1115 lines · 43.5 KB · TypeScript
RawDownload
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;}; // ---------------------------------------------------------------------------// MCP-Apps shell (mcp-app.html)//// The MCP host serves the shell as the `ui://executor/shell.html` resource, and// `@executor-js/mcp-apps-shell` reads it from disk at runtime. That runtime// `fs.readFile` is invisible to `bun build --compile`, so without this the// packaged binary would fail every artifact resource read. Build it if missing// and copy it next to the executable, where the loader finds it via// `process.execPath` — the same colocation trick used for `libsql.node` /// `emscripten-module.wasm`.// --------------------------------------------------------------------------- const mcpAppsShellDir = resolve(repoRoot, "packages/hosts/mcp-apps-shell");const mcpAppsShellHtml = join(mcpAppsShellDir, "dist/mcp-app.html"); const ensureMcpAppsShell = async (): Promise<string> => {  if (!existsSync(mcpAppsShellHtml)) {    console.log("Building MCP-Apps shell (mcp-app.html)...");    await $`bun run --cwd ${mcpAppsShellDir} build:shell`.quiet();  }  if (!existsSync(mcpAppsShellHtml)) {    throw new Error(`MCP-Apps shell missing at ${mcpAppsShellHtml} after build:shell`);  }  return mcpAppsShellHtml;}; 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 mcpAppsShellPath = await ensureMcpAppsShell();  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 the MCP-Apps shell next to the binary. Platform-independent HTML,      // read from execDir at runtime by @executor-js/mcp-apps-shell.      await cp(mcpAppsShellPath, join(binDir, "mcp-app.html"));       // 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.

Lines 1–500 of 1115.