hermes-agent/ui-opentui/scripts/build.mjs
alt-glitch ae11a636dc feat(tui): run on Node 26 (one runtime), finalize copy UX, rename to ui-opentui
Ports the engine off the second JS runtime onto Node 26.3 (node:ffi) so the
repo ships a single JavaScript runtime: child_process for the gateway, vitest
for tests, an esbuild + Solid build step. Mouse selection copies the rendered
text you highlight, and the clipboard path is crash-proofed (a broken copy
pipe no longer quits the UI). Renames the engine dir ui-tui-opentui-v2/ ->
ui-opentui/ and updates the launcher/installer/Docker references.
2026-06-09 16:16:48 +00:00

75 lines
3.1 KiB
JavaScript

/**
* Build the OpenTUI v2 Solid app for Node 26 (no Bun).
*
* Mirrors OpenTUI's own Node recipe (`~/github/opentui/.../run-node26.mjs` +
* `packages/solid/scripts/solid-transform.ts`): apply babel-preset-solid in
* `generate:"universal"` mode with `moduleName:"@opentui/solid"` to every app
* .tsx/.jsx, and force solid-js to its CLIENT/universal build (the package's
* `node` export condition points at the SSR `server.js`, which lacks the
* reactive primitives the universal renderer needs).
*
* `@opentui/core` stays EXTERNAL: it resolves its per-arch native `libopentui.so`
* (and the tree-sitter worker) from its own package dir via `import.meta.url`;
* bundling it would break those paths.
*
* Run with the Node that will launch the app:
* node scripts/build.mjs # → dist/main.js (app entry)
* node scripts/build.mjs <entry.tsx> <outdir> # build an arbitrary entry (smokes/spikes)
* Launch:
* node --experimental-ffi --no-warnings dist/main.js
*/
import { readFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { transformAsync } from '@babel/core'
import tsPreset from '@babel/preset-typescript'
import solidPreset from 'babel-preset-solid'
import * as esbuild from 'esbuild'
const require = createRequire(import.meta.url)
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
/** esbuild plugin that reproduces @opentui/solid's transform + solid-js resolution. */
const opentuiSolid = {
name: 'opentui-solid',
setup(build) {
// App JSX (.tsx/.jsx, never node_modules) → babel-preset-solid (universal).
build.onLoad({ filter: /\.[cm]?[jt]sx$/ }, async args => {
if (args.path.includes('/node_modules/')) return null
const code = await readFile(args.path, 'utf8')
const out = await transformAsync(code, {
filename: args.path,
configFile: false,
babelrc: false,
presets: [[solidPreset, { moduleName: '@opentui/solid', generate: 'universal' }], [tsPreset]]
})
return { contents: out?.code ?? '', loader: 'js' }
})
// Force the universal/client solid-js build (node condition → server.js otherwise).
build.onResolve({ filter: /^solid-js$/ }, () => ({ path: require.resolve('solid-js/dist/solid.js') }))
build.onResolve({ filter: /^solid-js\/store$/ }, () => ({ path: require.resolve('solid-js/store/dist/store.js') }))
}
}
const [, , entryArg, outdirArg] = process.argv
const entry = entryArg ? resolve(process.cwd(), entryArg) : resolve(root, 'src/entry/main.tsx')
const outdir = outdirArg ? resolve(process.cwd(), outdirArg) : resolve(root, 'dist')
await esbuild.build({
entryPoints: [entry],
outdir,
bundle: true,
format: 'esm',
platform: 'node',
target: 'node26',
splitting: true,
sourcemap: true,
logLevel: 'info',
// Native blob + tree-sitter worker resolve from @opentui/core's own dir at runtime.
external: ['@opentui/core', '@opentui/core/*'],
plugins: [opentuiSolid],
define: { 'process.env.OPENTUI_BUN_ONLY_EXAMPLES': '"false"' }
})