import { cp, mkdir, rm, writeFile, chmod } from "node:fs/promises";import { existsSync } from "node:fs";import { createRequire } from "node:module";import { resolve, join, dirname } from "node:path";import { fileURLToPath, pathToFileURL } from "node:url";import { parseArgs } from "node:util";import { $ } from "bun"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");const cliRoot = resolve(repoRoot, "apps/cli");const webRoot = resolve(repoRoot, "apps/local");const distDir = resolve(cliRoot, "dist");const ONEPASSWORD_CORE_WASM_FILENAME = "onepassword-core_bg.wasm";const WORKERD_VERSION = "1.20260708.1";const WORKER_BUNDLER_DIRNAME = "worker-bundler"; const resolveQuickJsWasmPath = (): string => { const req = createRequire(join(repoRoot, "packages/kernel/runtime-quickjs/package.json")); const quickJsPkg = req.resolve("quickjs-emscripten/package.json"); const wasmPath = resolve( dirname(quickJsPkg), "../@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.wasm", ); if (!existsSync(wasmPath)) throw new Error(`QuickJS WASM not found at ${wasmPath}`); return wasmPath;}; const resolveOnePasswordCoreWasmPath = (): string => { const req = createRequire(join(repoRoot, "packages/plugins/onepassword/package.json")); const sdkPkg = req.resolve("@1password/sdk/package.json"); const sdkReq = createRequire(sdkPkg); const sdkCorePkg = sdkReq.resolve("@1password/sdk-core/package.json"); const wasmPath = join(dirname(sdkCorePkg), "nodejs/core_bg.wasm"); if (!existsSync(wasmPath)) throw new Error(`1Password SDK core WASM not found at ${wasmPath}`); return wasmPath;}; const resolveWorkerBundlerDistPath = (): string => { const req = createRequire(join(repoRoot, "apps/cli/package.json")); const pkgJson = req.resolve("@cloudflare/worker-bundler/package.json"); const distPath = join(dirname(pkgJson), "dist"); if (!existsSync(join(distPath, "index.js")) || !existsSync(join(distPath, "esbuild.wasm"))) { throw new Error(`@cloudflare/worker-bundler dist files not found at ${distPath}`); } return distPath;}; const createPackedWorkerBundlerSource = async (distPath: string): Promise<string> => { const esbuildPath = join(repoRoot, "node_modules/.bun/node_modules/esbuild/lib/main.js"); if (!existsSync(esbuildPath)) throw new Error(`esbuild not found at ${esbuildPath}`); const { build } = await import(pathToFileURL(esbuildPath).href); const result = await build({ entryPoints: [join(distPath, "index.js")], bundle: true, format: "esm", platform: "browser", external: ["./esbuild.wasm"], logLevel: "silent", write: false, }); const source = result.outputFiles[0]?.text; if (source === undefined) throw new Error("failed to bundle @cloudflare/worker-bundler"); return source;}; // ---------------------------------------------------------------------------// Metadata// --------------------------------------------------------------------------- const readMetadata = async () => { const rootPkg = await Bun.file(join(repoRoot, "package.json")).json(); const cliPkg = await Bun.file(join(cliRoot, "package.json")).json(); return { name: "executor", version: process.env.EXECUTOR_VERSION ?? cliPkg.version ?? rootPkg.version ?? "0.0.0", description: rootPkg.description ?? "Local AI executor with a CLI, local API server, and web UI.", keywords: rootPkg.keywords ?? [], homepage: rootPkg.homepage, bugs: rootPkg.bugs, repository: rootPkg.repository, license: rootPkg.license ?? "MIT", };}; // ---------------------------------------------------------------------------// Targets// --------------------------------------------------------------------------- type Target = { os: "linux" | "darwin" | "win32"; arch: "x64" | "arm64"; abi?: "musl";}; const ALL_TARGETS: Target[] = [ { os: "linux", arch: "x64" }, { os: "linux", arch: "arm64" }, { os: "linux", arch: "x64", abi: "musl" }, { os: "linux", arch: "arm64", abi: "musl" }, { os: "darwin", arch: "x64" }, { os: "darwin", arch: "arm64" }, { os: "win32", arch: "x64" }, { os: "win32", arch: "arm64" },]; const platformName = (t: Target) => (t.os === "win32" ? "windows" : t.os); /** Per-platform suffix used in dist directory names, npm dist-tags, and the * semver prerelease segment of variant package versions. e.g. "linux-x64", * "linux-x64-musl", "darwin-arm64". */const platformTag = (t: Target) => [platformName(t), t.arch, t.abi].filter(Boolean).join("-"); /** Dist directory name (e.g. dist/executor-linux-x64). Only used as a build * artifact convention; the actual npm package name inside is `executor`. */const targetPackageName = (t: Target) => `executor-${platformTag(t)}`; const bunTargetKeys = [ "linux-x64", "linux-arm64", "linux-x64-musl", "linux-arm64-musl", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64",] as const;type BunTargetKey = (typeof bunTargetKeys)[number]; const bunTargets = { "linux-x64": "bun-linux-x64", "linux-arm64": "bun-linux-arm64", "linux-x64-musl": "bun-linux-x64-musl", "linux-arm64-musl": "bun-linux-arm64-musl", "darwin-x64": "bun-darwin-x64", "darwin-arm64": "bun-darwin-arm64", "win32-x64": "bun-windows-x64", "win32-arm64": "bun-windows-arm64",} satisfies Record<BunTargetKey, Bun.Build.CompileTarget>; const isBunTargetKey = (key: string): key is BunTargetKey => bunTargetKeys.includes(key as BunTargetKey); const bunTarget = (t: Target): Bun.Build.CompileTarget => { const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); if (!isBunTargetKey(key)) throw new Error(`Unsupported Bun compile target: ${key}`); const target = bunTargets[key]; return target;}; const binaryName = (t: Target) => (t.os === "win32" ? "executor.exe" : "executor");const workerdBinaryName = (t: Target) => (t.os === "win32" ? "workerd.exe" : "workerd"); const isCurrentPlatform = (t: Target) => t.os === process.platform && t.arch === process.arch && !t.abi; /** * Resolve the platform-specific @napi-rs/keyring native binding for a target. * * `bun build --compile` doesn't include `.node` modules in bunfs, so the * keyring loader's dynamic `require('@napi-rs/keyring-<plat>-<arch>')` fails * at runtime. We copy the `.node` file next to the executor and main.ts sets * `NAPI_RS_NATIVE_LIBRARY_PATH` so the loader's env-var escape hatch picks it * up instead of trying to walk node_modules. */const resolveKeyringNative = (t: Target): string | null => { const platformMap: Record<string, { pkg: string; node: string }> = { "darwin-arm64": { pkg: "@napi-rs/keyring-darwin-arm64", node: "keyring.darwin-arm64.node" }, "darwin-x64": { pkg: "@napi-rs/keyring-darwin-x64", node: "keyring.darwin-x64.node" }, "linux-arm64": { pkg: "@napi-rs/keyring-linux-arm64-gnu", node: "keyring.linux-arm64-gnu.node", }, "linux-x64": { pkg: "@napi-rs/keyring-linux-x64-gnu", node: "keyring.linux-x64-gnu.node" }, "linux-arm64-musl": { pkg: "@napi-rs/keyring-linux-arm64-musl", node: "keyring.linux-arm64-musl.node", }, "linux-x64-musl": { pkg: "@napi-rs/keyring-linux-x64-musl", node: "keyring.linux-x64-musl.node", }, "win32-arm64": { pkg: "@napi-rs/keyring-win32-arm64-msvc", node: "keyring.win32-arm64-msvc.node", }, "win32-x64": { pkg: "@napi-rs/keyring-win32-x64-msvc", node: "keyring.win32-x64-msvc.node" }, }; const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); const entry = platformMap[key]; if (!entry) return null; try { const req = createRequire(join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json")); const pkgJson = req.resolve(`${entry.pkg}/package.json`); return join(dirname(pkgJson), entry.node); } catch { const bunPath = join( repoRoot, `node_modules/.bun/${entry.pkg.replace("/", "+")}@1.2.0/node_modules/${entry.pkg}/${entry.node}`, ); if (existsSync(bunPath)) return bunPath; return null; }}; /** * Resolve the platform-specific `@libsql/<target>` native binding for a target. * * The local server's SQLite driver (libSQL) loads its `.node` via a dynamic * `require('@libsql/<target>')`, which `bun build --compile` can't bundle into * bunfs (same limitation as keyring). We copy the right `.node` next to the * executor as `libsql.node`; main.ts redirects the bare require to it. */const LIBSQL_NATIVE_VERSION = "0.5.29";const resolveLibsqlNative = (t: Target): string | null => { const platformMap: Record<string, string> = { "darwin-arm64": "darwin-arm64", "darwin-x64": "darwin-x64", // The compiled binary runs on Bun, which libSQL's loader treats as glibc // (its musl->gnu workaround), so non-musl linux targets need the -gnu binding. "linux-arm64": "linux-arm64-gnu", "linux-x64": "linux-x64-gnu", "linux-arm64-musl": "linux-arm64-musl", "linux-x64-musl": "linux-x64-musl", "win32-arm64": "win32-arm64-msvc", "win32-x64": "win32-x64-msvc", }; const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); const target = platformMap[key]; if (!target) return null; const pkg = `@libsql/${target}`; try { const req = createRequire(join(repoRoot, "apps/local", "package.json")); const pkgJson = req.resolve(`${pkg}/package.json`); return join(dirname(pkgJson), "index.node"); } catch { const bunPath = join( repoRoot, `node_modules/.bun/${pkg.replace("/", "+")}@${LIBSQL_NATIVE_VERSION}/node_modules/${pkg}/index.node`, ); return existsSync(bunPath) ? bunPath : null; }}; const resolveWorkerdBinary = (t: Target): string | null => { const platformMap: Record<string, string> = { "darwin-arm64": "@cloudflare/workerd-darwin-arm64", "darwin-x64": "@cloudflare/workerd-darwin-64", "linux-arm64": "@cloudflare/workerd-linux-arm64", "linux-x64": "@cloudflare/workerd-linux-64", "win32-x64": "@cloudflare/workerd-windows-64", }; const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); const pkg = platformMap[key]; if (!pkg) return null; const binary = workerdBinaryName(t); try { const req = createRequire( join(repoRoot, "packages/kernel/runtime-workerd-subprocess/package.json"), ); const pkgJson = req.resolve(`${pkg}/package.json`); for (const candidate of [ join(dirname(pkgJson), "bin", binary), join(dirname(pkgJson), binary), ]) { if (existsSync(candidate)) return candidate; } } catch { const packageRoot = join( repoRoot, `node_modules/.bun/${pkg.replace("/", "+")}@${WORKERD_VERSION}/node_modules/${pkg}`, ); for (const candidate of [join(packageRoot, "bin", binary), join(packageRoot, binary)]) { if (existsSync(candidate)) return candidate; } } return null;}; // ---------------------------------------------------------------------------// Build mode// --------------------------------------------------------------------------- type BuildMode = "production" | "development"; // ---------------------------------------------------------------------------// Build web app// --------------------------------------------------------------------------- const buildWeb = async (mode: BuildMode) => { const webDist = join(webRoot, "dist"); await rm(webDist, { recursive: true, force: true }); console.log(`Building web app (${mode})...`); const proc = Bun.spawn(["bun", "run", "build", "--mode", mode], { cwd: webRoot, stdio: ["ignore", "inherit", "inherit"], env: { ...process.env, NODE_ENV: mode }, }); if ((await proc.exited) !== 0) throw new Error("Web build failed"); return webDist;}; // ---------------------------------------------------------------------------// Embedded web UI — generates a virtual module that imports all web assets// using `with { type: "file" }` so Bun bakes them into the compiled binary.// --------------------------------------------------------------------------- const createEmbeddedWebUISource = async (mode: BuildMode) => { const webDist = await buildWeb(mode); const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: webDist }))) .map((f) => f.replaceAll("\\", "/")) .sort(); const imports = files.map((file, i) => { const spec = join(webDist, file).replaceAll("\\", "/"); return `import file_${i} from ${JSON.stringify(spec)} with { type: "file" };`; }); const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`); return [ "// Auto-generated — maps web UI paths to embedded file references", ...imports, "export default {", ...entries, "} as Record<string, string>;", ].join("\n");}; // ---------------------------------------------------------------------------// Embedded legacy v1 drizzle migrations — inlined as text imports so drizzle's// `migrate()` (which reads a folder from disk) can be given a tmpdir// populated from the inlined contents at runtime. The v1→v2 data migration// replays this chain to bring an older v1 database up to v1-final before// reading it (v2's own schema is created from FumaDB DDL, not this folder).// --------------------------------------------------------------------------- const createEmbeddedMigrationsSource = async () => { const migrationsDir = resolve(webRoot, "drizzle-legacy-v1"); const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: migrationsDir }))) .map((f) => f.replaceAll("\\", "/")) .sort(); const imports = files.map((file, i) => { const spec = join(migrationsDir, file).replaceAll("\\", "/"); return `import file_${i} from ${JSON.stringify(spec)} with { type: "text" };`; }); const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`); return [ "// Auto-generated — maps migration paths to inlined file contents", ...imports, "export default {", ...entries, "} as Record<string, string>;", ].join("\n");}; // ---------------------------------------------------------------------------// Build platform binaries// --------------------------------------------------------------------------- const EMBEDDED_WEB_UI_STUB = `const files: Record<string, string> | null = null;\n\nexport default files;\n`;const EMBEDDED_MIGRATIONS_STUB = `const migrations: Record<string, string> | null = null;\n\nexport default migrations;\n`; const buildBinaries = async (targets: Target[], mode: BuildMode) => { const meta = await readMetadata(); const binaries: Record<string, string> = {}; const embeddedWebUIPath = join(cliRoot, "src/embedded-web-ui.gen.ts"); const embeddedMigrationsPath = join(webRoot, "src/db/embedded-migrations.gen.ts"); await rm(distDir, { recursive: true, force: true }); // Cross-platform builds need every target's optional native packages // (e.g. @napi-rs/keyring-darwin-arm64) so we can copy the right .node next // to each target's executor. `bun install --frozen-lockfile --cpu=* --os=*` // extracts them all without modifying the lockfile. const needsCrossPlatform = targets.some((t) => !isCurrentPlatform(t)); if (needsCrossPlatform) { console.log("Installing optional native deps for all platforms..."); const proc = Bun.spawn(["bun", "install", "--frozen-lockfile", "--cpu=*", "--os=*"], { cwd: repoRoot, stdio: ["ignore", "inherit", "inherit"], }); if ((await proc.exited) !== 0) { throw new Error("bun install --cpu=* --os=* failed"); } } console.log(`Generating embedded web UI bundle (${mode})...`); const embeddedWebUI = await createEmbeddedWebUISource(mode); await writeFile(embeddedWebUIPath, `${embeddedWebUI}\n`); console.log("Generating embedded drizzle migrations..."); const embeddedMigrations = await createEmbeddedMigrationsSource(); await writeFile(embeddedMigrationsPath, `${embeddedMigrations}\n`); const quickJsWasmPath = resolveQuickJsWasmPath(); const onePasswordCoreWasmPath = resolveOnePasswordCoreWasmPath(); const workerBundlerDistPath = resolveWorkerBundlerDistPath(); const packedWorkerBundlerSource = await createPackedWorkerBundlerSource(workerBundlerDistPath); try { for (const target of targets) { const name = targetPackageName(target); const outDir = join(distDir, name); const binDir = join(outDir, "bin"); await mkdir(binDir, { recursive: true }); console.log(`Building ${name}...`); await Bun.build({ entrypoints: [join(cliRoot, "src/main.ts")], minify: mode === "production", compile: { target: bunTarget(target), outfile: join(binDir, binaryName(target)), }, }); // Copy QuickJS WASM next to binary — loaded at runtime by the server await cp(quickJsWasmPath, join(binDir, "emscripten-module.wasm")); // Copy 1Password's sdk-core WASM next to the binary. The patched // sdk-core loader falls back to this sibling file when bun --compile // bakes a build-machine node_modules path into __dirname. await cp(onePasswordCoreWasmPath, join(binDir, ONEPASSWORD_CORE_WASM_FILENAME)); // Copy @napi-rs/keyring native binding next to executor — bun --compile // doesn't bundle .node files, so the loader needs to find it on disk // via NAPI_RS_NATIVE_LIBRARY_PATH (set in main.ts). const keyringNative = resolveKeyringNative(target); if (keyringNative && existsSync(keyringNative)) { await cp(keyringNative, join(binDir, "keyring.node")); } // Copy the libSQL native binding next to executor — same bunfs limitation // as keyring; main.ts redirects `require('@libsql/<plat>')` to it. const libsqlNative = resolveLibsqlNative(target); if (libsqlNative && existsSync(libsqlNative)) { await cp(libsqlNative, join(binDir, "libsql.node")); } const workerdBinary = resolveWorkerdBinary(target); if (workerdBinary && existsSync(workerdBinary)) { await cp(workerdBinary, join(binDir, workerdBinaryName(target))); if (target.os !== "win32") await chmod(join(binDir, workerdBinaryName(target)), 0o755); } await cp(workerBundlerDistPath, join(binDir, WORKER_BUNDLER_DIRNAME, "dist"), { recursive: true, }); await writeFile( join(binDir, WORKER_BUNDLER_DIRNAME, "dist", "index.bundled.js"), packedWorkerBundlerSource, ); // Smoke test on current platform if (isCurrentPlatform(target)) { const bin = join(binDir, binaryName(target)); console.log(` Smoke test: ${bin} --version`); const version = await $`${bin} --version`.text(); console.log(` OK: ${version.trim()}`); } // Variant package.json. All variants publish to the SAME npm package // name (`executor`) under platform-tagged versions (e.g. // `1.4.14-linux-x64`). The wrapper references each variant via an // `npm:` alias in its optionalDependencies. This mirrors codex's // pattern and avoids ever having to claim a new npm package name when // a new platform is added — a single trusted-publishing config on the // `executor` package covers everything. // // No `bin` field on purpose — the wrapper's launcher resolves the // platform binary via require.resolve on the alias name and execs it. const tag = platformTag(target); const variantVersion = `${meta.version}-${tag}`; await writeFile( join(outDir, "package.json"), JSON.stringify( { name: meta.name, version: variantVersion, description: `${meta.description} (${tag})`, os: [target.os], cpu: [target.arch], homepage: meta.homepage, bugs: meta.bugs, repository: meta.repository, license: meta.license, }, null, 2, ) + "\n", ); // The local alias name (`executor-linux-x64`, ...) is what the // wrapper's launcher passes to require.resolve at runtime. It'sLines 1–500 of 1084. Load more
import { cp, mkdir, rm, writeFile, chmod } from "node:fs/promises"; import { existsSync } from "node:fs"; import { createRequire } from "node:module"; import { resolve, join, dirname } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { parseArgs } from "node:util"; import { $ } from "bun"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); const cliRoot = resolve(repoRoot, "apps/cli"); const webRoot = resolve(repoRoot, "apps/local"); const distDir = resolve(cliRoot, "dist"); const ONEPASSWORD_CORE_WASM_FILENAME = "onepassword-core_bg.wasm"; const WORKERD_VERSION = "1.20260708.1"; const WORKER_BUNDLER_DIRNAME = "worker-bundler"; const resolveQuickJsWasmPath = (): string => { const req = createRequire(join(repoRoot, "packages/kernel/runtime-quickjs/package.json")); const quickJsPkg = req.resolve("quickjs-emscripten/package.json"); const wasmPath = resolve( dirname(quickJsPkg), "../@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.wasm", ); if (!existsSync(wasmPath)) throw new Error(`QuickJS WASM not found at ${wasmPath}`); return wasmPath; }; const resolveOnePasswordCoreWasmPath = (): string => { const req = createRequire(join(repoRoot, "packages/plugins/onepassword/package.json")); const sdkPkg = req.resolve("@1password/sdk/package.json"); const sdkReq = createRequire(sdkPkg); const sdkCorePkg = sdkReq.resolve("@1password/sdk-core/package.json"); const wasmPath = join(dirname(sdkCorePkg), "nodejs/core_bg.wasm"); if (!existsSync(wasmPath)) throw new Error(`1Password SDK core WASM not found at ${wasmPath}`); return wasmPath; }; const resolveWorkerBundlerDistPath = (): string => { const req = createRequire(join(repoRoot, "apps/cli/package.json")); const pkgJson = req.resolve("@cloudflare/worker-bundler/package.json"); const distPath = join(dirname(pkgJson), "dist"); if (!existsSync(join(distPath, "index.js")) || !existsSync(join(distPath, "esbuild.wasm"))) { throw new Error(`@cloudflare/worker-bundler dist files not found at ${distPath}`); } return distPath; }; const createPackedWorkerBundlerSource = async (distPath: string): Promise<string> => { const esbuildPath = join(repoRoot, "node_modules/.bun/node_modules/esbuild/lib/main.js"); if (!existsSync(esbuildPath)) throw new Error(`esbuild not found at ${esbuildPath}`); const { build } = await import(pathToFileURL(esbuildPath).href); const result = await build({ entryPoints: [join(distPath, "index.js")], bundle: true, format: "esm", platform: "browser", external: ["./esbuild.wasm"], logLevel: "silent", write: false, }); const source = result.outputFiles[0]?.text; if (source === undefined) throw new Error("failed to bundle @cloudflare/worker-bundler"); return source; }; // --------------------------------------------------------------------------- // Metadata // --------------------------------------------------------------------------- const readMetadata = async () => { const rootPkg = await Bun.file(join(repoRoot, "package.json")).json(); const cliPkg = await Bun.file(join(cliRoot, "package.json")).json(); return { name: "executor", version: process.env.EXECUTOR_VERSION ?? cliPkg.version ?? rootPkg.version ?? "0.0.0", description: rootPkg.description ?? "Local AI executor with a CLI, local API server, and web UI.", keywords: rootPkg.keywords ?? [], homepage: rootPkg.homepage, bugs: rootPkg.bugs, repository: rootPkg.repository, license: rootPkg.license ?? "MIT", }; }; // --------------------------------------------------------------------------- // Targets // --------------------------------------------------------------------------- type Target = { os: "linux" | "darwin" | "win32"; arch: "x64" | "arm64"; abi?: "musl"; }; const ALL_TARGETS: Target[] = [ { os: "linux", arch: "x64" }, { os: "linux", arch: "arm64" }, { os: "linux", arch: "x64", abi: "musl" }, { os: "linux", arch: "arm64", abi: "musl" }, { os: "darwin", arch: "x64" }, { os: "darwin", arch: "arm64" }, { os: "win32", arch: "x64" }, { os: "win32", arch: "arm64" }, ]; const platformName = (t: Target) => (t.os === "win32" ? "windows" : t.os); /** Per-platform suffix used in dist directory names, npm dist-tags, and the * semver prerelease segment of variant package versions. e.g. "linux-x64", * "linux-x64-musl", "darwin-arm64". */ const platformTag = (t: Target) => [platformName(t), t.arch, t.abi].filter(Boolean).join("-"); /** Dist directory name (e.g. dist/executor-linux-x64). Only used as a build * artifact convention; the actual npm package name inside is `executor`. */ const targetPackageName = (t: Target) => `executor-${platformTag(t)}`; const bunTargetKeys = [ "linux-x64", "linux-arm64", "linux-x64-musl", "linux-arm64-musl", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64", ] as const; type BunTargetKey = (typeof bunTargetKeys)[number]; const bunTargets = { "linux-x64": "bun-linux-x64", "linux-arm64": "bun-linux-arm64", "linux-x64-musl": "bun-linux-x64-musl", "linux-arm64-musl": "bun-linux-arm64-musl", "darwin-x64": "bun-darwin-x64", "darwin-arm64": "bun-darwin-arm64", "win32-x64": "bun-windows-x64", "win32-arm64": "bun-windows-arm64", } satisfies Record<BunTargetKey, Bun.Build.CompileTarget>; const isBunTargetKey = (key: string): key is BunTargetKey => bunTargetKeys.includes(key as BunTargetKey); const bunTarget = (t: Target): Bun.Build.CompileTarget => { const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); if (!isBunTargetKey(key)) throw new Error(`Unsupported Bun compile target: ${key}`); const target = bunTargets[key]; return target; }; const binaryName = (t: Target) => (t.os === "win32" ? "executor.exe" : "executor"); const workerdBinaryName = (t: Target) => (t.os === "win32" ? "workerd.exe" : "workerd"); const isCurrentPlatform = (t: Target) => t.os === process.platform && t.arch === process.arch && !t.abi; /** * Resolve the platform-specific @napi-rs/keyring native binding for a target. * * `bun build --compile` doesn't include `.node` modules in bunfs, so the * keyring loader's dynamic `require('@napi-rs/keyring-<plat>-<arch>')` fails * at runtime. We copy the `.node` file next to the executor and main.ts sets * `NAPI_RS_NATIVE_LIBRARY_PATH` so the loader's env-var escape hatch picks it * up instead of trying to walk node_modules. */ const resolveKeyringNative = (t: Target): string | null => { const platformMap: Record<string, { pkg: string; node: string }> = { "darwin-arm64": { pkg: "@napi-rs/keyring-darwin-arm64", node: "keyring.darwin-arm64.node" }, "darwin-x64": { pkg: "@napi-rs/keyring-darwin-x64", node: "keyring.darwin-x64.node" }, "linux-arm64": { pkg: "@napi-rs/keyring-linux-arm64-gnu", node: "keyring.linux-arm64-gnu.node", }, "linux-x64": { pkg: "@napi-rs/keyring-linux-x64-gnu", node: "keyring.linux-x64-gnu.node" }, "linux-arm64-musl": { pkg: "@napi-rs/keyring-linux-arm64-musl", node: "keyring.linux-arm64-musl.node", }, "linux-x64-musl": { pkg: "@napi-rs/keyring-linux-x64-musl", node: "keyring.linux-x64-musl.node", }, "win32-arm64": { pkg: "@napi-rs/keyring-win32-arm64-msvc", node: "keyring.win32-arm64-msvc.node", }, "win32-x64": { pkg: "@napi-rs/keyring-win32-x64-msvc", node: "keyring.win32-x64-msvc.node" }, }; const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); const entry = platformMap[key]; if (!entry) return null; try { const req = createRequire(join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json")); const pkgJson = req.resolve(`${entry.pkg}/package.json`); return join(dirname(pkgJson), entry.node); } catch { const bunPath = join( repoRoot, `node_modules/.bun/${entry.pkg.replace("/", "+")}@1.2.0/node_modules/${entry.pkg}/${entry.node}`, ); if (existsSync(bunPath)) return bunPath; return null; } }; /** * Resolve the platform-specific `@libsql/<target>` native binding for a target. * * The local server's SQLite driver (libSQL) loads its `.node` via a dynamic * `require('@libsql/<target>')`, which `bun build --compile` can't bundle into * bunfs (same limitation as keyring). We copy the right `.node` next to the * executor as `libsql.node`; main.ts redirects the bare require to it. */ const LIBSQL_NATIVE_VERSION = "0.5.29"; const resolveLibsqlNative = (t: Target): string | null => { const platformMap: Record<string, string> = { "darwin-arm64": "darwin-arm64", "darwin-x64": "darwin-x64", // The compiled binary runs on Bun, which libSQL's loader treats as glibc // (its musl->gnu workaround), so non-musl linux targets need the -gnu binding. "linux-arm64": "linux-arm64-gnu", "linux-x64": "linux-x64-gnu", "linux-arm64-musl": "linux-arm64-musl", "linux-x64-musl": "linux-x64-musl", "win32-arm64": "win32-arm64-msvc", "win32-x64": "win32-x64-msvc", }; const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); const target = platformMap[key]; if (!target) return null; const pkg = `@libsql/${target}`; try { const req = createRequire(join(repoRoot, "apps/local", "package.json")); const pkgJson = req.resolve(`${pkg}/package.json`); return join(dirname(pkgJson), "index.node"); } catch { const bunPath = join( repoRoot, `node_modules/.bun/${pkg.replace("/", "+")}@${LIBSQL_NATIVE_VERSION}/node_modules/${pkg}/index.node`, ); return existsSync(bunPath) ? bunPath : null; } }; const resolveWorkerdBinary = (t: Target): string | null => { const platformMap: Record<string, string> = { "darwin-arm64": "@cloudflare/workerd-darwin-arm64", "darwin-x64": "@cloudflare/workerd-darwin-64", "linux-arm64": "@cloudflare/workerd-linux-arm64", "linux-x64": "@cloudflare/workerd-linux-64", "win32-x64": "@cloudflare/workerd-windows-64", }; const key = [t.os, t.arch, t.abi].filter(Boolean).join("-"); const pkg = platformMap[key]; if (!pkg) return null; const binary = workerdBinaryName(t); try { const req = createRequire( join(repoRoot, "packages/kernel/runtime-workerd-subprocess/package.json"), ); const pkgJson = req.resolve(`${pkg}/package.json`); for (const candidate of [ join(dirname(pkgJson), "bin", binary), join(dirname(pkgJson), binary), ]) { if (existsSync(candidate)) return candidate; } } catch { const packageRoot = join( repoRoot, `node_modules/.bun/${pkg.replace("/", "+")}@${WORKERD_VERSION}/node_modules/${pkg}`, ); for (const candidate of [join(packageRoot, "bin", binary), join(packageRoot, binary)]) { if (existsSync(candidate)) return candidate; } } return null; }; // --------------------------------------------------------------------------- // Build mode // --------------------------------------------------------------------------- type BuildMode = "production" | "development"; // --------------------------------------------------------------------------- // Build web app // --------------------------------------------------------------------------- const buildWeb = async (mode: BuildMode) => { const webDist = join(webRoot, "dist"); await rm(webDist, { recursive: true, force: true }); console.log(`Building web app (${mode})...`); const proc = Bun.spawn(["bun", "run", "build", "--mode", mode], { cwd: webRoot, stdio: ["ignore", "inherit", "inherit"], env: { ...process.env, NODE_ENV: mode }, }); if ((await proc.exited) !== 0) throw new Error("Web build failed"); return webDist; }; // --------------------------------------------------------------------------- // Embedded web UI — generates a virtual module that imports all web assets // using `with { type: "file" }` so Bun bakes them into the compiled binary. // --------------------------------------------------------------------------- const createEmbeddedWebUISource = async (mode: BuildMode) => { const webDist = await buildWeb(mode); const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: webDist }))) .map((f) => f.replaceAll("\\", "/")) .sort(); const imports = files.map((file, i) => { const spec = join(webDist, file).replaceAll("\\", "/"); return `import file_${i} from ${JSON.stringify(spec)} with { type: "file" };`; }); const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`); return [ "// Auto-generated — maps web UI paths to embedded file references", ...imports, "export default {", ...entries, "} as Record<string, string>;", ].join("\n"); }; // --------------------------------------------------------------------------- // Embedded legacy v1 drizzle migrations — inlined as text imports so drizzle's // `migrate()` (which reads a folder from disk) can be given a tmpdir // populated from the inlined contents at runtime. The v1→v2 data migration // replays this chain to bring an older v1 database up to v1-final before // reading it (v2's own schema is created from FumaDB DDL, not this folder). // --------------------------------------------------------------------------- const createEmbeddedMigrationsSource = async () => { const migrationsDir = resolve(webRoot, "drizzle-legacy-v1"); const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: migrationsDir }))) .map((f) => f.replaceAll("\\", "/")) .sort(); const imports = files.map((file, i) => { const spec = join(migrationsDir, file).replaceAll("\\", "/"); return `import file_${i} from ${JSON.stringify(spec)} with { type: "text" };`; }); const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`); return [ "// Auto-generated — maps migration paths to inlined file contents", ...imports, "export default {", ...entries, "} as Record<string, string>;", ].join("\n"); }; // --------------------------------------------------------------------------- // Build platform binaries // --------------------------------------------------------------------------- const EMBEDDED_WEB_UI_STUB = `const files: Record<string, string> | null = null;\n\nexport default files;\n`; const EMBEDDED_MIGRATIONS_STUB = `const migrations: Record<string, string> | null = null;\n\nexport default migrations;\n`; const buildBinaries = async (targets: Target[], mode: BuildMode) => { const meta = await readMetadata(); const binaries: Record<string, string> = {}; const embeddedWebUIPath = join(cliRoot, "src/embedded-web-ui.gen.ts"); const embeddedMigrationsPath = join(webRoot, "src/db/embedded-migrations.gen.ts"); await rm(distDir, { recursive: true, force: true }); // Cross-platform builds need every target's optional native packages // (e.g. @napi-rs/keyring-darwin-arm64) so we can copy the right .node next // to each target's executor. `bun install --frozen-lockfile --cpu=* --os=*` // extracts them all without modifying the lockfile. const needsCrossPlatform = targets.some((t) => !isCurrentPlatform(t)); if (needsCrossPlatform) { console.log("Installing optional native deps for all platforms..."); const proc = Bun.spawn(["bun", "install", "--frozen-lockfile", "--cpu=*", "--os=*"], { cwd: repoRoot, stdio: ["ignore", "inherit", "inherit"], }); if ((await proc.exited) !== 0) { throw new Error("bun install --cpu=* --os=* failed"); } } console.log(`Generating embedded web UI bundle (${mode})...`); const embeddedWebUI = await createEmbeddedWebUISource(mode); await writeFile(embeddedWebUIPath, `${embeddedWebUI}\n`); console.log("Generating embedded drizzle migrations..."); const embeddedMigrations = await createEmbeddedMigrationsSource(); await writeFile(embeddedMigrationsPath, `${embeddedMigrations}\n`); const quickJsWasmPath = resolveQuickJsWasmPath(); const onePasswordCoreWasmPath = resolveOnePasswordCoreWasmPath(); const workerBundlerDistPath = resolveWorkerBundlerDistPath(); const packedWorkerBundlerSource = await createPackedWorkerBundlerSource(workerBundlerDistPath); try { for (const target of targets) { const name = targetPackageName(target); const outDir = join(distDir, name); const binDir = join(outDir, "bin"); await mkdir(binDir, { recursive: true }); console.log(`Building ${name}...`); await Bun.build({ entrypoints: [join(cliRoot, "src/main.ts")], minify: mode === "production", compile: { target: bunTarget(target), outfile: join(binDir, binaryName(target)), }, }); // Copy QuickJS WASM next to binary — loaded at runtime by the server await cp(quickJsWasmPath, join(binDir, "emscripten-module.wasm")); // Copy 1Password's sdk-core WASM next to the binary. The patched // sdk-core loader falls back to this sibling file when bun --compile // bakes a build-machine node_modules path into __dirname. await cp(onePasswordCoreWasmPath, join(binDir, ONEPASSWORD_CORE_WASM_FILENAME)); // Copy @napi-rs/keyring native binding next to executor — bun --compile // doesn't bundle .node files, so the loader needs to find it on disk // via NAPI_RS_NATIVE_LIBRARY_PATH (set in main.ts). const keyringNative = resolveKeyringNative(target); if (keyringNative && existsSync(keyringNative)) { await cp(keyringNative, join(binDir, "keyring.node")); } // Copy the libSQL native binding next to executor — same bunfs limitation // as keyring; main.ts redirects `require('@libsql/<plat>')` to it. const libsqlNative = resolveLibsqlNative(target); if (libsqlNative && existsSync(libsqlNative)) { await cp(libsqlNative, join(binDir, "libsql.node")); } const workerdBinary = resolveWorkerdBinary(target); if (workerdBinary && existsSync(workerdBinary)) { await cp(workerdBinary, join(binDir, workerdBinaryName(target))); if (target.os !== "win32") await chmod(join(binDir, workerdBinaryName(target)), 0o755); } await cp(workerBundlerDistPath, join(binDir, WORKER_BUNDLER_DIRNAME, "dist"), { recursive: true, }); await writeFile( join(binDir, WORKER_BUNDLER_DIRNAME, "dist", "index.bundled.js"), packedWorkerBundlerSource, ); // Smoke test on current platform if (isCurrentPlatform(target)) { const bin = join(binDir, binaryName(target)); console.log(` Smoke test: ${bin} --version`); const version = await $`${bin} --version`.text(); console.log(` OK: ${version.trim()}`); } // Variant package.json. All variants publish to the SAME npm package // name (`executor`) under platform-tagged versions (e.g. // `1.4.14-linux-x64`). The wrapper references each variant via an // `npm:` alias in its optionalDependencies. This mirrors codex's // pattern and avoids ever having to claim a new npm package name when // a new platform is added — a single trusted-publishing config on the // `executor` package covers everything. // // No `bin` field on purpose — the wrapper's launcher resolves the // platform binary via require.resolve on the alias name and execs it. const tag = platformTag(target); const variantVersion = `${meta.version}-${tag}`; await writeFile( join(outDir, "package.json"), JSON.stringify( { name: meta.name, version: variantVersion, description: `${meta.description} (${tag})`, os: [target.os], cpu: [target.arch], homepage: meta.homepage, bugs: meta.bugs, repository: meta.repository, license: meta.license, }, null, 2, ) + "\n", ); // The local alias name (`executor-linux-x64`, ...) is what the // wrapper's launcher passes to require.resolve at runtime. It's