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:
2026-09-04 21:07:03 -07:00
co-authored by Hanzo Dev
parent 7ae0c37771
commit fd796e3eb4
57 changed files with 158 additions and 671 deletions
-25
View File
@@ -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
View File
@@ -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"]
-68
View File
@@ -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
View File
@@ -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
+34 -5
View File
@@ -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 })
})
})
+2 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/cjs/legacy.cjs'))
-12
View File
@@ -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)
}
-3
View File
@@ -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
},
}
)
-45
View File
@@ -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
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/cjs/v4.cjs'))
-1
View File
@@ -1 +0,0 @@
export * from './types/v4'
-1
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/cjs/v5.cjs'))
-1
View File
@@ -1 +0,0 @@
export * from './types/v5'
-1
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/cjs/inject-styles.cjs'))
-1
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/cjs/animations.motion.cjs'))
-1
View File
@@ -1 +0,0 @@
export default (_) => _
-77
View File
@@ -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
-57
View File
@@ -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
-29
View File
@@ -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)`)
-31
View File
@@ -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)
})
-43
View File
@@ -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'
-27
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/test'))
-1
View File
@@ -1 +0,0 @@
export * from './types'
-2
View File
@@ -1,2 +0,0 @@
// compat with hanzogui static compiler
Object.assign(module.exports, require('../dist/test.cjs'))
-2
View File
@@ -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
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/native'))
-1
View File
@@ -1 +0,0 @@
export * from './types'
-2
View File
@@ -1,2 +0,0 @@
// compat with hanzogui static compiler
Object.assign(module.exports, require('../dist/native.cjs'))
-2
View File
@@ -1,2 +0,0 @@
// compat with metro without package exports
Object.assign(module.exports, require('../dist/native.cjs'))
-1
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
Object.assign(module.exports, require('./dist/cjs/index.cjs'))
-2
View File
@@ -1,2 +0,0 @@
// compat with hanzogui static compiler
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
-2
View File
@@ -1,2 +0,0 @@
// compat with metro without package exports
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
-2
View File
@@ -1,2 +0,0 @@
// compat with hanzogui static compiler
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
-2
View File
@@ -1,2 +0,0 @@
// compat with metro without package exports
Object.assign(module.exports, require('../dist/cjs/index.cjs'))
-12
View File
@@ -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