mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-23 02:31:31 +00:00
Emit CommonJS for every package
The CJS pass ran only where a manifest named a require entry, so an ESM-only package could drift into the tree and a package marked commonjs could carry ESM files. Now every package gets all three emits from tsgo; a .cjs.ts sibling answers for the one file that needs import.meta, and the CommonJS output is parsed as a script so a module that cannot load there fails the build by name. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
name: Build image
|
||||
|
||||
# Builds + pushes ghcr.io/hanzoai/gui (the gui.hanzo.ai site, served on
|
||||
# :3000 by the repo's existing Dockerfile) on a v* semver tag via the
|
||||
# canonical reusable workflow, natively on the self-hosted arcd fleet
|
||||
# (NO GitHub-hosted runners). Semver-only tags. Deployable on DOKS.
|
||||
#
|
||||
# This is the CONTAINER-image path. The repo's publish-gui*.yml workflows
|
||||
# publish the npm library packages and are unaffected.
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
uses: hanzoai/.github/.github/workflows/docker-build.yml@main
|
||||
with:
|
||||
image: ghcr.io/hanzoai/gui
|
||||
secrets: inherit
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
# use node base with bun installed - avoids vite+bun+docker hanging issues
|
||||
# see: https://github.com/vitejs/vite/discussions/16030
|
||||
FROM node:22
|
||||
|
||||
# install bun (pinned to avoid breaking changes from unpinned latest)
|
||||
RUN npm install -g [email protected]
|
||||
|
||||
ARG CF_API_KEY
|
||||
ARG CF_EMAIL
|
||||
ARG CF_ZONE_ID
|
||||
ARG DISCORD_BOT_TOKEN
|
||||
ARG DISCORD_ID
|
||||
ARG DISCORD_OAUTH2
|
||||
ARG DISCORD_PUB_KEY
|
||||
ARG GITHUB_ADMIN_TOKEN
|
||||
ARG GITHUB_APP_CLIENT_ID
|
||||
ARG GITHUB_APP_SECRET
|
||||
ARG GITHUB_SPONSOR_WEBHOOK_SECRET
|
||||
ARG GITHUB_TOKEN
|
||||
ARG IS_GUI_DEV
|
||||
ARG NEXT_PUBLIC_GITHUB_APP_ID
|
||||
ARG NEXT_PUBLIC_GITHUB_AUTH_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_IS_GUI_DEV
|
||||
ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
ARG NEXT_PUBLIC_SUPABASE_URL
|
||||
ARG NEXT_PUBLIC_URL
|
||||
ARG POSTMARK_SERVER_TOKEN
|
||||
ARG CLOUDFLARE_TURNSTILE_SECRET
|
||||
ARG SHOULD_UNLOCK_GIT_CRYPT
|
||||
ARG STRIPE_SECRET_KEY
|
||||
ARG STRIPE_SIGNING_SIGNATURE_SECRET
|
||||
ARG STUDIO_JWT_SECRET
|
||||
ARG SUPABASE_SERVICE_ROLE_KEY
|
||||
ARG TAKEOUT_RENEWAL_COUPON_ID
|
||||
ARG URL
|
||||
ARG ONE_SERVER_URL
|
||||
ARG APP_NAME
|
||||
ARG GUI_PRO_SECRET
|
||||
ARG DEEPSEEK_API_KEY
|
||||
ARG BENTO_GITHUB_TOKEN
|
||||
ARG BENTO_BRANCH
|
||||
|
||||
# install dependencies (sharp needs libvips for image processing)
|
||||
RUN apt-get update && apt-get install -y git bsdmainutils vim-common gh libvips-dev
|
||||
|
||||
WORKDIR /root/hanzogui
|
||||
COPY . .
|
||||
|
||||
# init git (allow empty commit if nothing to commit)
|
||||
RUN git config --global user.email "[email protected]" && git config --global user.name "Docker Build" && git init . && git add -A && (git commit -m 'add' > /dev/null || true)
|
||||
|
||||
# Clone bento repository as sibling directory (optional)
|
||||
# Use BENTO_BRANCH env var if set, otherwise default to main
|
||||
WORKDIR /root
|
||||
RUN if [ -n "$BENTO_GITHUB_TOKEN" ]; then \
|
||||
BRANCH="${BENTO_BRANCH:-main}"; \
|
||||
echo "Cloning bento repository (branch: $BRANCH)..."; \
|
||||
unset GITHUB_TOKEN && \
|
||||
echo "$BENTO_GITHUB_TOKEN" | gh auth login --with-token && \
|
||||
gh repo clone hanzogui/bento -- --branch "$BRANCH" && \
|
||||
gh auth logout --hostname github.com && \
|
||||
echo "✅ Bento repository cloned (branch: $BRANCH)" && \
|
||||
echo "REQUIRE_BENTO=true" > /tmp/bento_status; \
|
||||
else \
|
||||
echo "⚠️ BENTO_GITHUB_TOKEN not provided - bento features will not be available" && \
|
||||
echo "REQUIRE_BENTO=false" > /tmp/bento_status; \
|
||||
fi
|
||||
|
||||
WORKDIR /root/hanzogui
|
||||
|
||||
# Set REQUIRE_BENTO based on whether bento was cloned
|
||||
RUN export $(cat /tmp/bento_status)
|
||||
|
||||
# First install without bento deps
|
||||
RUN bun install
|
||||
|
||||
# Merge bento dependencies into root package.json and reinstall (only if bento was cloned)
|
||||
RUN if [ -d "../bento" ]; then node scripts/with-bento.mjs && bun install; fi
|
||||
RUN bun run build
|
||||
RUN bun run build:app
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["bash", "-c", "bun run docker:serve"]
|
||||
@@ -1,68 +0,0 @@
|
||||
# local build dockerfile - uses bento from ../bento
|
||||
# build from PARENT directory: docker build -f hanzogui5/Dockerfile.local -t hanzogui-site .
|
||||
FROM node:22
|
||||
|
||||
# install bun
|
||||
RUN npm install -g bun
|
||||
|
||||
ARG CF_API_KEY
|
||||
ARG CF_EMAIL
|
||||
ARG CF_ZONE_ID
|
||||
ARG DISCORD_BOT_TOKEN
|
||||
ARG DISCORD_ID
|
||||
ARG DISCORD_OAUTH2
|
||||
ARG DISCORD_PUB_KEY
|
||||
ARG GITHUB_ADMIN_TOKEN
|
||||
ARG GITHUB_APP_CLIENT_ID
|
||||
ARG GITHUB_APP_SECRET
|
||||
ARG GITHUB_SPONSOR_WEBHOOK_SECRET
|
||||
ARG GITHUB_TOKEN
|
||||
ARG IS_GUI_DEV
|
||||
ARG NEXT_PUBLIC_GITHUB_APP_ID
|
||||
ARG NEXT_PUBLIC_GITHUB_AUTH_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_IS_GUI_DEV
|
||||
ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
ARG NEXT_PUBLIC_SUPABASE_URL
|
||||
ARG NEXT_PUBLIC_URL
|
||||
ARG POSTMARK_SERVER_TOKEN
|
||||
ARG CLOUDFLARE_TURNSTILE_SECRET
|
||||
ARG SHOULD_UNLOCK_GIT_CRYPT
|
||||
ARG STRIPE_SECRET_KEY
|
||||
ARG STRIPE_SIGNING_SIGNATURE_SECRET
|
||||
ARG STUDIO_JWT_SECRET
|
||||
ARG SUPABASE_SERVICE_ROLE_KEY
|
||||
ARG TAKEOUT_RENEWAL_COUPON_ID
|
||||
ARG URL
|
||||
ARG ONE_SERVER_URL
|
||||
ARG APP_NAME
|
||||
ARG GUI_PRO_SECRET
|
||||
ARG DEEPSEEK_API_KEY
|
||||
|
||||
# install dependencies (sharp needs libvips for image processing)
|
||||
RUN apt-get update && apt-get install -y git bsdmainutils vim-common libvips-dev
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
# copy both repos
|
||||
COPY bento /root/bento
|
||||
COPY hanzogui5 /root/hanzogui
|
||||
|
||||
WORKDIR /root/hanzogui
|
||||
|
||||
# init git (allow empty commit if nothing to commit)
|
||||
RUN git config --global user.email "[email protected]" && git config --global user.name "Docker Build" && git init . && git add -A && (git commit -m 'add' > /dev/null || true)
|
||||
|
||||
# install hanzogui deps first
|
||||
RUN bun install
|
||||
|
||||
# setup bento - merge deps and symlink node_modules
|
||||
RUN node scripts/with-bento.mjs && bun install
|
||||
|
||||
# build
|
||||
RUN bun run build
|
||||
RUN bun run build:app
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["bun", "run", "docker:serve"]
|
||||
+20
-15
@@ -1,6 +1,6 @@
|
||||
## @hanzogui/build
|
||||
|
||||
One package, built by the TypeScript compiler alone.
|
||||
One package, built by the TypeScript compiler alone: `tsgo`, three passes.
|
||||
|
||||
```
|
||||
hanzogui-build # ESM + declarations, CommonJS, native
|
||||
@@ -9,24 +9,29 @@ hanzogui-build --watch # the ESM emit, continuously
|
||||
hanzogui-build clean # remove dist and types (clean:build is the same)
|
||||
```
|
||||
|
||||
Three project files in the package describe the three emits, and the compiler
|
||||
reads exactly one answer per target:
|
||||
Every package ships its source and three emits. Three project files describe
|
||||
them, and the compiler reads exactly one answer per target:
|
||||
|
||||
| project | writes |
|
||||
| ---------------------- | --------------------------------------------- |
|
||||
| `tsconfig.esm.json` | `dist/esm/*.js` and `types/*.d.ts` |
|
||||
| `tsconfig.cjs.json` | `dist/cjs/*.js`, marked commonjs by its own `package.json` |
|
||||
| `tsconfig.native.json` | `dist/native/*.js`, where a `.native` file takes its sibling's name |
|
||||
| project | writes |
|
||||
| ---------------------- | --------------------------------------------------------------- |
|
||||
| `tsconfig.esm.json` | `dist/esm/*.js` and `types/*.d.ts`; the one pass that typechecks |
|
||||
| `tsconfig.cjs.json` | `dist/cjs/*.js`, marked commonjs by its own `package.json` |
|
||||
| `tsconfig.native.json` | `dist/native/*.js` |
|
||||
|
||||
Source names every relative import with its extension and the compiler rewrites
|
||||
it on emit, so what lands in `dist` is loadable by Node as written. Nothing else
|
||||
runs over the code.
|
||||
|
||||
A package is CommonJS only where its manifest states a `require` entry; one that
|
||||
uses `import.meta` or a top-level `await` has no CommonJS form and states none.
|
||||
The native pass runs only where a `tsconfig.native.json` exists, which is where
|
||||
`.native.*` sources exist; on that platform the sibling's callers resolve to the
|
||||
native file through the same relative import.
|
||||
A sibling answers for its file on one target and reaches no other emit:
|
||||
`x.cjs.ts` takes x's name in the CommonJS output, `x.native.ts` in the native
|
||||
one. A module that needs its own location says `import.meta.url` in `here.ts`
|
||||
and `__filename` in `here.cjs.ts`, and everything else imports `url` from
|
||||
`./here.ts`. The CommonJS output is parsed as a script afterwards; a file only a
|
||||
module could load fails the build by name.
|
||||
|
||||
The `exports` map points at what the emits produce: `dist/esm/x.js`,
|
||||
`dist/cjs/x.js`, `dist/native/x.js`, `types/x.d.ts`.
|
||||
Each emit answers `process.env.GUI_TARGET` with its own literal, `"web"` or
|
||||
`"native"`, so a bundler folds the other platform away.
|
||||
|
||||
The `exports` map points at what the emits produce: `types/x.d.ts`,
|
||||
`dist/esm/x.js`, `dist/cjs/x.js`, and `dist/native/x.js` where the package
|
||||
builds native.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const set = () => {
|
||||
process.env.GUI_TARGET = 'web'
|
||||
}
|
||||
export const key = 'process.env.GUI_TARGET'
|
||||
export const on = process.env.GUI_TARGET
|
||||
@@ -0,0 +1 @@
|
||||
export const url = `file://${__filename}`
|
||||
@@ -0,0 +1 @@
|
||||
export const url = import.meta.url
|
||||
@@ -61,6 +61,28 @@ describe('hanzogui-build', () => {
|
||||
expect(read(dir, 'dist/esm/platform.js')).not.toContain('process.env')
|
||||
})
|
||||
|
||||
it('answers only a read; an assignment and a string stay as written', () => {
|
||||
const js = read(dir, 'dist/esm/env.js')
|
||||
expect(js).toContain("process.env.GUI_TARGET = 'web'")
|
||||
expect(js).toContain("'process.env.GUI_TARGET'")
|
||||
expect(js).toContain('const on = "web"')
|
||||
})
|
||||
|
||||
it('lets a .cjs sibling answer for its file in the CommonJS emit alone', () => {
|
||||
expect(read(dir, 'dist/esm/here.js')).toContain('import.meta.url')
|
||||
expect(read(dir, 'dist/cjs/here.js')).toContain('__filename')
|
||||
expect(read(dir, 'dist/cjs/here.js')).not.toContain('import.meta')
|
||||
for (const p of ['dist/esm/here.cjs.js', 'dist/cjs/here.cjs.js', 'dist/native/here.cjs.js', 'types/here.cjs.d.ts']) {
|
||||
expect(existsSync(join(dir, p))).toBe(false)
|
||||
}
|
||||
expect(existsSync(join(dir, 'types/here.d.ts'))).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps each sibling out of the emits it does not answer for', () => {
|
||||
expect(existsSync(join(dir, 'dist/esm/platform.native.js'))).toBe(false)
|
||||
expect(existsSync(join(dir, 'dist/cjs/platform.native.js'))).toBe(false)
|
||||
})
|
||||
|
||||
it('answers each platform through the manifest', async () => {
|
||||
const web = await import(join(dir, 'dist/esm/index.js'))
|
||||
expect(web.greet('a')).toBe('Hello, a, from web')
|
||||
@@ -90,17 +112,24 @@ describe('hanzogui-build --skip-native', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('an ESM-only package', () => {
|
||||
it('states no require entry and gets no CommonJS', () => {
|
||||
describe('a package that states no require entry', () => {
|
||||
it('still gets CommonJS, so no manifest decides what a build emits', () => {
|
||||
const dir = copy()
|
||||
const manifest = JSON.parse(read(dir, 'package.json'))
|
||||
delete manifest.main
|
||||
delete manifest.exports['.'].require
|
||||
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
|
||||
writeFileSync(join(dir, 'src/index.ts'), "export const here: string = import.meta.url\n")
|
||||
build(dir)
|
||||
expect(existsSync(join(dir, 'dist/esm/index.js'))).toBe(true)
|
||||
expect(existsSync(join(dir, 'dist/cjs'))).toBe(false)
|
||||
expect(existsSync(join(dir, 'dist/cjs/index.js'))).toBe(true)
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('a module only a module could load', () => {
|
||||
it('fails the CommonJS emit by name instead of shipping it', () => {
|
||||
const dir = copy()
|
||||
writeFileSync(join(dir, 'src/index.ts'), 'export const here: string = import.meta.url\n')
|
||||
expect(() => build(dir)).toThrow(/index\.js is not CommonJS/)
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,14 +13,7 @@
|
||||
"test:web": "bun run test"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": "7.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"babel-plugin-react-compiler": ">=1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
}
|
||||
"typescript": "7.0.2",
|
||||
"oxc-parser": "0.147.0"
|
||||
}
|
||||
}
|
||||
|
||||
+95
-41
@@ -2,14 +2,19 @@
|
||||
/**
|
||||
* hanzogui-build — one package, built by the TypeScript compiler alone.
|
||||
*
|
||||
* Three emits from one source tree, each its own project file so the compiler
|
||||
* reads exactly one answer per target:
|
||||
* A package ships its source and three emits, each from its own project file so
|
||||
* the compiler reads exactly one answer per target:
|
||||
*
|
||||
* tsconfig.esm.json dist/esm/*.js + types/*.d.ts
|
||||
* tsconfig.cjs.json dist/cjs/*.js (marked commonjs by its own package.json)
|
||||
* tsconfig.native.json dist/native/*.js, where a `.native` sibling replaces its
|
||||
* web file, so the same relative import reaches the
|
||||
* native implementation without a bundler choosing.
|
||||
* tsconfig.esm.json dist/esm/*.js + types/*.d.ts the one pass that typechecks
|
||||
* tsconfig.cjs.json dist/cjs/*.js marked commonjs by its own package.json
|
||||
* tsconfig.native.json dist/native/*.js
|
||||
*
|
||||
* A sibling answers for its file on one target and reaches no other emit:
|
||||
* `x.cjs.ts` takes x's name in the CommonJS output, `x.native.ts` in the native
|
||||
* one. That is how a module says `import.meta.url` once and `__filename` once
|
||||
* without either syntax leaking into the emit that cannot parse it. The CommonJS
|
||||
* output is parsed as a script afterwards, and a file only a module could load
|
||||
* fails the build by name.
|
||||
*
|
||||
* Source names its imports with their extension and the compiler rewrites them
|
||||
* on emit, so the output is loadable by Node as written. Nothing else runs over
|
||||
@@ -21,8 +26,9 @@
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { parseSync } from 'oxc-parser'
|
||||
|
||||
const need = createRequire(import.meta.url)
|
||||
const tsc = need(join(dirname(need.resolve('typescript/package.json')), 'lib/getExePath.js')).default()
|
||||
@@ -35,22 +41,78 @@ const emit = (project, extra = []) => {
|
||||
if (run.status !== 0) process.exit(run.status ?? 1)
|
||||
}
|
||||
|
||||
const files = (dir, out = []) => {
|
||||
if (existsSync(dir))
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, entry.name)
|
||||
entry.isDirectory() ? files(p, out) : out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// The platform is a property of the emit, not of the host: the runtime asks
|
||||
// process.env.GUI_TARGET which one it is on, and each output directory answers
|
||||
// with its own literal, so a bundler folds the other platform away and a
|
||||
// browser is never asked for a process it does not have.
|
||||
// The reads of process.env.GUI_TARGET in one file, by span. Only a read: an
|
||||
// assignment to it stays a runtime assignment, and the words inside a string
|
||||
// are a bundler's define key, not ours.
|
||||
const reads = (file, text) => {
|
||||
const { program, errors } = parseSync(file, text, { sourceType: 'unambiguous' })
|
||||
if (errors.length) throw new Error(`${file}: ${errors[0].message}`)
|
||||
const spans = []
|
||||
const walk = (node, parent) => {
|
||||
if (Array.isArray(node)) return node.forEach((n) => walk(n, parent))
|
||||
if (!node || typeof node.type !== 'string') return
|
||||
if (
|
||||
node.type === 'MemberExpression' && !node.computed && node.property.name === 'GUI_TARGET' &&
|
||||
node.object.type === 'MemberExpression' && !node.object.computed && node.object.property.name === 'env' &&
|
||||
node.object.object.type === 'Identifier' && node.object.object.name === 'process' &&
|
||||
!(parent?.type === 'AssignmentExpression' && parent.left === node)
|
||||
) spans.push([node.start, node.end])
|
||||
for (const key in node) if (key !== 'type' && typeof node[key] === 'object') walk(node[key], node)
|
||||
}
|
||||
walk(program, null)
|
||||
return spans
|
||||
}
|
||||
|
||||
const answer = (dir, platform) => {
|
||||
if (!existsSync(dir)) return
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, entry.name)
|
||||
if (entry.isDirectory()) answer(p, platform)
|
||||
else if (entry.name.endsWith('.js')) {
|
||||
const text = readFileSync(p, 'utf8')
|
||||
if (text.includes('process.env.GUI_TARGET')) writeFileSync(p, text.replaceAll('process.env.GUI_TARGET', JSON.stringify(platform)))
|
||||
for (const p of files(dir)) {
|
||||
if (!p.endsWith('.js')) continue
|
||||
let text = readFileSync(p, 'utf8')
|
||||
if (!text.includes('process.env.GUI_TARGET')) continue
|
||||
for (const [start, end] of reads(p, text).reverse()) text = text.slice(0, start) + JSON.stringify(platform) + text.slice(end)
|
||||
writeFileSync(p, text)
|
||||
}
|
||||
}
|
||||
|
||||
// `x.<tag>.js` takes x's name, so the import x's callers already make resolves to it.
|
||||
const adopt = (dir, tag) => {
|
||||
for (const p of files(dir)) {
|
||||
if (p.endsWith(`.${tag}.js.map`)) renameSync(p, p.replace(`.${tag}.js.map`, '.js.map'))
|
||||
else if (p.endsWith(`.${tag}.js`)) {
|
||||
const map = new RegExp(`sourceMappingURL=(.+)\\.${tag}\\.js\\.map`)
|
||||
writeFileSync(p.replace(`.${tag}.js`, '.js'), readFileSync(p, 'utf8').replace(map, 'sourceMappingURL=$1.js.map'))
|
||||
rmSync(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A sibling for another target has no place in this emit.
|
||||
const drop = (dir, tags, ext = '.js') => {
|
||||
for (const p of files(dir)) if (tags.some((tag) => p.endsWith(`.${tag}${ext}`) || p.endsWith(`.${tag}${ext}.map`))) rmSync(p)
|
||||
}
|
||||
|
||||
// CommonJS is a script. What only a module could load is named here, by file,
|
||||
// rather than found by whoever requires it.
|
||||
const script = (dir) => {
|
||||
for (const p of files(dir)) {
|
||||
if (!p.endsWith('.js')) continue
|
||||
const { errors } = parseSync(p, readFileSync(p, 'utf8'), { sourceType: 'script' })
|
||||
if (errors.length) throw new Error(`${p} is not CommonJS: ${errors[0].message}. Give its source a .cjs.ts sibling.`)
|
||||
}
|
||||
}
|
||||
|
||||
for (const dir of ['dist', 'types']) rmSync(join(cwd, dir), { recursive: true, force: true })
|
||||
|
||||
// `clean` and `clean:build` remove the outputs and stop: the workspace runs them
|
||||
@@ -61,44 +123,36 @@ if (args.has('clean') || args.has('clean:build')) process.exit(0)
|
||||
if (args.has('--watch')) {
|
||||
emit('tsconfig.esm.json', ['--watch'])
|
||||
} else {
|
||||
|
||||
emit('tsconfig.esm.json')
|
||||
answer(join(cwd, 'dist/esm'), 'web')
|
||||
drop(join(cwd, 'dist/esm'), ['cjs', 'native'])
|
||||
drop(join(cwd, 'types'), ['cjs'], '.d.ts')
|
||||
|
||||
// A package is CommonJS only where its manifest says a `require` reaches it.
|
||||
// One that uses import.meta or a top-level await is ESM by nature and states
|
||||
// no such entry, so there is nothing to emit for CommonJS.
|
||||
//
|
||||
// A package that asks for isolatedDeclarations cannot emit without
|
||||
// declarations, so the CJS and native passes write theirs beside their
|
||||
// output and those are dropped: the ESM pass already published the one copy.
|
||||
if (/"require"|dist\/cjs/.test(JSON.stringify(manifest.exports ?? {}) + (manifest.main ?? ''))) {
|
||||
emit('tsconfig.cjs.json')
|
||||
// The ESM pass checked the source; the other two only translate it. A package
|
||||
// that asks for isolatedDeclarations cannot emit without declarations, so
|
||||
// those passes write theirs beside their output and the copies are dropped.
|
||||
if (existsSync(join(cwd, 'tsconfig.cjs.json'))) {
|
||||
emit('tsconfig.cjs.json', ['--noCheck'])
|
||||
answer(join(cwd, 'dist/cjs'), 'web')
|
||||
rmSync(join(cwd, 'dist/cjs/.types'), { recursive: true, force: true })
|
||||
adopt(join(cwd, 'dist/cjs'), 'cjs')
|
||||
drop(join(cwd, 'dist/cjs'), ['native'])
|
||||
mkdirSync(join(cwd, 'dist/cjs'), { recursive: true })
|
||||
writeFileSync(join(cwd, 'dist/cjs/package.json'), '{ "type": "commonjs" }\n')
|
||||
script(join(cwd, 'dist/cjs'))
|
||||
}
|
||||
|
||||
if (!args.has('--skip-native') && existsSync(join(cwd, 'tsconfig.native.json'))) {
|
||||
emit('tsconfig.native.json')
|
||||
emit('tsconfig.native.json', ['--noCheck'])
|
||||
answer(join(cwd, 'dist/native'), 'native')
|
||||
rmSync(join(cwd, 'dist/native/.types'), { recursive: true, force: true })
|
||||
// A `.native` file was written to answer for its sibling on that platform;
|
||||
// here it takes the sibling's name, so the import the sibling's callers
|
||||
// already make resolves to it.
|
||||
const walk = (dir) => {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, entry.name)
|
||||
if (entry.isDirectory()) walk(p)
|
||||
else if (entry.name.endsWith('.native.js.map')) renameSync(p, p.replace('.native.js.map', '.js.map'))
|
||||
else if (entry.name.endsWith('.native.js')) {
|
||||
const named = p.replace('.native.js', '.js')
|
||||
writeFileSync(named, readFileSync(p, 'utf8').replace(/sourceMappingURL=(.+)\.native\.js\.map/, 'sourceMappingURL=$1.js.map'))
|
||||
rmSync(p)
|
||||
}
|
||||
}
|
||||
adopt(join(cwd, 'dist/native'), 'native')
|
||||
drop(join(cwd, 'dist/native'), ['cjs'])
|
||||
}
|
||||
|
||||
if (manifest.bin) {
|
||||
for (const bin of typeof manifest.bin === 'string' ? [manifest.bin] : Object.values(manifest.bin)) {
|
||||
if (existsSync(join(cwd, bin))) chmodSync(join(cwd, bin), 0o755)
|
||||
}
|
||||
walk(join(cwd, 'dist/native'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require('./dist/cjs')
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "@hanzogui/vite-plugin-cjs",
|
||||
"version": "8.3.1",
|
||||
"license": "MIT",
|
||||
"main": "./index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cp -r ../vite-plugin/dist . || true"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzogui/vite-plugin": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/legacy.cjs'))
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const command = require.resolve('create-gui')
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
try {
|
||||
require('node:child_process').execSync(`node ${command} ${args.join(' ')}`, {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
} catch (err) {
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
if (typeof globalThis['__DEV__'] === 'undefined') {
|
||||
globalThis['__DEV__'] = process.env.NODE_ENV === 'development'
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const View = require('react-native').View
|
||||
module.exports = new Proxy(
|
||||
{
|
||||
get default() {
|
||||
return View
|
||||
},
|
||||
},
|
||||
{
|
||||
get(_, key) {
|
||||
if (key === 'createAnimatedComponent') {
|
||||
return (x) => x
|
||||
}
|
||||
return View
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -1,45 +0,0 @@
|
||||
module.exports = worm(true)
|
||||
|
||||
function worm(root = false) {
|
||||
const obj = root
|
||||
? {
|
||||
get default() {
|
||||
return worm()
|
||||
},
|
||||
}
|
||||
: function () {
|
||||
// dev warning in debug mode
|
||||
if (
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
process.env.DEBUG?.startsWith('hanzogui')
|
||||
) {
|
||||
console.warn(`
|
||||
|
||||
This has been excluded via Gui!
|
||||
Check "excludeReactNativeWebExports" setting and include it to fix.
|
||||
|
||||
${new Error().stack}
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
return worm()
|
||||
}
|
||||
|
||||
obj.displayName = `ProxyWorm - Check excludeReactNativeWebExports`
|
||||
obj._isProxyWorm = true
|
||||
|
||||
// reanimated tries to find component like things
|
||||
obj.prototype = obj.prototype || {}
|
||||
obj.prototype.isReactComponent = true
|
||||
|
||||
return new Proxy(obj, {
|
||||
get(_, key) {
|
||||
if (Reflect.has(obj, key)) return Reflect.get(obj, key)
|
||||
return worm()
|
||||
},
|
||||
apply() {
|
||||
return worm()
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/v4.cjs'))
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export * from './types/v4'
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/v5.cjs'))
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export * from './types/v5'
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/inject-styles.cjs'))
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/animations.motion.cjs'))
|
||||
@@ -1 +0,0 @@
|
||||
export default (_) => _
|
||||
@@ -1,77 +0,0 @@
|
||||
const React = require('react')
|
||||
|
||||
// Create mock RN components that render as simple divs/spans for testing
|
||||
// react-test-renderer will serialize these properly
|
||||
const createMockComponent = (name) => {
|
||||
const Component = React.forwardRef((props, ref) => {
|
||||
const { children, style, ...rest } = props
|
||||
// Return a "View" element that react-test-renderer understands
|
||||
return React.createElement(name, { ...rest, style, ref }, children)
|
||||
})
|
||||
Component.displayName = name
|
||||
return Component
|
||||
}
|
||||
|
||||
// For components that don't need to render (like Image)
|
||||
const emtpyComponent = () => null
|
||||
|
||||
// Mock usePressability for testing - returns empty event handlers
|
||||
const usePressabilityMock = () => ({})
|
||||
|
||||
function proxyWorm() {
|
||||
return new Proxy(
|
||||
{
|
||||
StyleSheet: {
|
||||
create() {},
|
||||
},
|
||||
Platform: {
|
||||
OS: 'web',
|
||||
},
|
||||
Image: emtpyComponent,
|
||||
View: createMockComponent('View'),
|
||||
Text: createMockComponent('Text'),
|
||||
TextInput: createMockComponent('TextInput'),
|
||||
ScrollView: createMockComponent('ScrollView'),
|
||||
Dimensions: {
|
||||
get: () => ({ width: 1024, height: 768 }),
|
||||
addEventListener: () => ({ remove: () => {} }),
|
||||
},
|
||||
Appearance: {
|
||||
getColorScheme: () => 'light',
|
||||
addChangeListener: () => {},
|
||||
removeChangeListener: () => {},
|
||||
},
|
||||
addPoolingTo() {},
|
||||
// Libraries/Pressability/usePressability mock
|
||||
Libraries: {
|
||||
Pressability: {
|
||||
usePressability: {
|
||||
default: usePressabilityMock,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
get(target, key) {
|
||||
return Reflect.get(target, key) || proxyWorm()
|
||||
},
|
||||
apply() {
|
||||
return proxyWorm()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const proxy = proxyWorm()
|
||||
|
||||
module.exports = proxy
|
||||
module.exports.default = proxy
|
||||
module.exports.Platform = proxy.Platform
|
||||
module.exports.StyleSheet = proxy.StyleSheet
|
||||
module.exports.Image = proxy.Image
|
||||
module.exports.View = proxy.View
|
||||
module.exports.Text = proxy.Text
|
||||
module.exports.TextInput = proxy.TextInput
|
||||
module.exports.ScrollView = proxy.ScrollView
|
||||
module.exports.Dimensions = proxy.Dimensions
|
||||
module.exports.Appearance = proxy.Appearance
|
||||
@@ -1,57 +0,0 @@
|
||||
const emtpyComponent = () => null
|
||||
|
||||
function proxyWorm() {
|
||||
return new Proxy(
|
||||
{
|
||||
StyleSheet: {
|
||||
create() {},
|
||||
},
|
||||
Platform: {
|
||||
OS: 'web',
|
||||
},
|
||||
Image: emtpyComponent,
|
||||
View: emtpyComponent,
|
||||
Text: emtpyComponent,
|
||||
TextInput: emtpyComponent,
|
||||
ScrollView: emtpyComponent,
|
||||
Dimensions: {
|
||||
addEventListener(cb) {},
|
||||
},
|
||||
Appearance: {
|
||||
getColorScheme: () => 'light',
|
||||
addChangeListener: () => {},
|
||||
removeChangeListener: () => {},
|
||||
},
|
||||
addPoolingTo() {},
|
||||
},
|
||||
{
|
||||
get(target, key) {
|
||||
return Reflect.get(target, key) || proxyWorm()
|
||||
},
|
||||
apply() {
|
||||
return proxyWorm()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const proxy = proxyWorm()
|
||||
|
||||
// Named exports that can be tree-shaken
|
||||
export const Platform = proxy.Platform
|
||||
export const StyleSheet = proxy.StyleSheet
|
||||
export const Image = proxy.Image
|
||||
export const View = proxy.View
|
||||
export const Text = proxy.Text
|
||||
export const TextInput = proxy.TextInput
|
||||
export const ScrollView = proxy.ScrollView
|
||||
export const Dimensions = proxy.Dimensions
|
||||
export const Pressable = proxy.Pressable
|
||||
export const Animated = proxy.Animated
|
||||
export const Easing = proxy.Easing
|
||||
export const Appearance = proxy.Appearance
|
||||
export const findNodeHandle = proxy.findNodeHandle
|
||||
export const unstable_batchedUpdates = proxy.unstable_batchedUpdates
|
||||
|
||||
// Default export
|
||||
export default proxy
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import { rm } from 'node:fs/promises'
|
||||
|
||||
await rm('./dist', { recursive: true, force: true })
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: ['./src/cli.ts'],
|
||||
outdir: './dist',
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
splitting: false,
|
||||
external: [],
|
||||
minify: false,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
for (const m of result.logs) console.error(m)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const cli = './dist/cli.js'
|
||||
const text = await Bun.file(cli).text()
|
||||
if (!text.startsWith('#!')) {
|
||||
await Bun.write(cli, '#!/usr/bin/env node\n' + text)
|
||||
}
|
||||
await Bun.$`chmod +x ${cli}`
|
||||
|
||||
const size = (await Bun.file(cli).text()).length
|
||||
console.log(`built @hanzogui/get (${(size / 1024).toFixed(1)} KB)`)
|
||||
@@ -1,31 +0,0 @@
|
||||
import { build, context } from 'esbuild'
|
||||
|
||||
const isWatch = process.argv.includes('--watch')
|
||||
|
||||
const config = {
|
||||
entryPoints: ['src/index.ts', 'src/cli.ts'],
|
||||
bundle: false,
|
||||
outdir: 'dist',
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: 'es2020',
|
||||
}
|
||||
|
||||
async function runBuild() {
|
||||
if (isWatch) {
|
||||
const ctx = await context({
|
||||
...config,
|
||||
logLevel: 'info',
|
||||
})
|
||||
await ctx.watch()
|
||||
console.info('Watching for changes...')
|
||||
} else {
|
||||
await build(config)
|
||||
console.info('Build complete')
|
||||
}
|
||||
}
|
||||
|
||||
runBuild().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Vendored
-43
@@ -1,43 +0,0 @@
|
||||
import { createElement, forwardRef } from 'react'
|
||||
|
||||
// react-native props that have no DOM spelling. react-native-svg's own web
|
||||
// build runs props through react-native-web's createDOMProps, which performs
|
||||
// this translation; without it these land on the svg element verbatim and
|
||||
// React warns ("React does not recognize the testID prop on a DOM element").
|
||||
const toDOMProps = (props) => {
|
||||
const { testID, accessibilityLabel, ...rest } = props
|
||||
if (testID !== undefined) rest['data-testid'] = testID
|
||||
if (accessibilityLabel !== undefined) rest['aria-label'] = accessibilityLabel
|
||||
return rest
|
||||
}
|
||||
|
||||
const svgElement = (tag) =>
|
||||
forwardRef((props, ref) => createElement(tag, { ...toDOMProps(props), ref }))
|
||||
|
||||
export const Circle = svgElement('circle')
|
||||
export const ClipPath = svgElement('clipPath')
|
||||
export const Defs = svgElement('defs')
|
||||
export const Ellipse = svgElement('ellipse')
|
||||
export const ForeignObject = svgElement('foreignObject')
|
||||
export const G = svgElement('g')
|
||||
export const Image = svgElement('image')
|
||||
export const Line = svgElement('line')
|
||||
export const LinearGradient = svgElement('linearGradient')
|
||||
export const Marker = svgElement('marker')
|
||||
export const Mask = svgElement('mask')
|
||||
export const Path = svgElement('path')
|
||||
export const Pattern = svgElement('pattern')
|
||||
export const Polygon = svgElement('polygon')
|
||||
export const Polyline = svgElement('polyline')
|
||||
export const RadialGradient = svgElement('radialGradient')
|
||||
export const Rect = svgElement('rect')
|
||||
export const Shape = svgElement('shape')
|
||||
export const Stop = svgElement('stop')
|
||||
export const Svg = svgElement('svg')
|
||||
export const Symbol = svgElement('symbol')
|
||||
export const Text = svgElement('text')
|
||||
export const TextPath = svgElement('clipPath')
|
||||
export const TSpan = svgElement('tSpan')
|
||||
export const Use = svgElement('use')
|
||||
|
||||
export default Svg
|
||||
@@ -1,3 +0,0 @@
|
||||
// re-export react-native-svg for native platforms
|
||||
export * from 'react-native-svg'
|
||||
export { default } from 'react-native-svg'
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { Glob } from 'bun'
|
||||
|
||||
await rm('./dist', { recursive: true, force: true })
|
||||
|
||||
const entrypoints = Array.from(new Glob('src/**/*.ts').scanSync('.'))
|
||||
.filter((p) => !p.endsWith('.test.ts'))
|
||||
.map((p) => `./${p}`)
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints,
|
||||
outdir: './dist',
|
||||
root: './src',
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
splitting: false,
|
||||
external: [],
|
||||
minify: false,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
for (const m of result.logs) console.error(m)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`built @hanzogui/recipes (${result.outputs.length} files)`)
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/cjs/linear-gradient.cjs'))
|
||||
-1
@@ -1 +0,0 @@
|
||||
export * from '../src/linear-gradient'
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/cjs/linear-gradient.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/cjs/linear-gradient.native.js'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/cjs/linear-gradient.native.js'))
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/test'))
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export * from './types'
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/test.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/test.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/test.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/test.cjs'))
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/native'))
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export * from './types'
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/native.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/native.cjs'))
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/react-native-web.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/cjs/react-native-web.cjs'))
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/cjs/react-native-web.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/cjs/react-native-web.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/cjs/react-native-web.cjs'))
|
||||
@@ -1 +0,0 @@
|
||||
Object.assign(module.exports, require('./dist/cjs/index.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with hanzogui static compiler
|
||||
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
|
||||
@@ -1,2 +0,0 @@
|
||||
// compat with metro without package exports
|
||||
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
app="$APP_NAME"
|
||||
echo "Serving $app"
|
||||
|
||||
if [ "$app" = "takeout" ]; then
|
||||
cd code/takeout && bun run serve:host
|
||||
elif [ "$app" = "docs" ]; then
|
||||
cd code/one-docs && bun run serve:host
|
||||
else
|
||||
cd apps/gui.hanzo.ai && bun run serve:host
|
||||
fi
|
||||
Reference in New Issue
Block a user