// Bundles each harness-server entry point into a self-contained ESM file: the // packaged app ships no node_modules. Entry points keep their exact relative // paths under dist-server, where the server locates each proxy by path. import { build } from "esbuild"; import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { serverLocalePlugin } from "./server-locale-plugin.mjs"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const server = join(root, "server"); // yaml's Node export is CommonJS with dynamic requires that cannot run once // inlined into ESM; its browser export is the same parser without them. const yamlEsmPlugin = { name: "yaml-esm", setup(build) { build.onResolve({ filter: /^yaml$/ }, () => ({ path: join(root, "node_modules", "yaml", "browser", "index.js"), })); }, }; // ws is CommonJS and requires Node's own modules, which an ESM bundle reaches // only through a require of its own (esbuild otherwise throws "Dynamic require // of "events" is not supported" when it loads). const NODE_REQUIRE = { js: 'import { createRequire as __ombCreateRequire } from "node:module";\nconst require = __ombCreateRequire(import.meta.url);', }; // Every file run as its own process. const ENTRY_POINTS = [ "index.ts", "message-search.worker.ts", // the `openmausbot` command "openmausbot.ts", "pair-cli.ts", "workspace-backup.worker.ts", // the OMB Cloud Pro home image's entry point "cloud-home-start.ts", // the container image's entry point "server-launcher.ts", // Imported directly by the packaged smoke probes. An explicit root sibling, // because tsc may emit its own copy under dist-server/server/. "proxy-paths.ts", "local-computer.ts", "local-computer-proxy.ts", // the hook helper Claude Code runs at PostToolUse/PreCompact/SessionStart/Stop "hooks/omb-hook.ts", "container-mcp.ts", "vps-container-mcp.ts", "permission-proxy.ts", "connector-proxy.ts", "mcp-gate.ts", "mcp-remote-proxy.ts", "harness-mcp-proxy.ts", "drivers/agents-proxy.ts", "drivers/dweb-proxy.ts", "drivers/phone-proxy.ts", "drivers/chatgpt-plan-token-command.ts", // the bot box (server/bot-box): the in-box bridge, the probe the self-check runs inside a box, and the check by hand "bot-box/box-bridge.ts", "bot-box/box-probe.ts", "bot-box/box-check.ts", // the meeting bot, copied to a cloud computer and run there with its own Node "meetings/meetbot.ts", ]; await build({ entryPoints: ENTRY_POINTS.map((entry) => join(server, entry)), bundle: true, platform: "node", target: "node20", format: "esm", outbase: server, outdir: join(root, "dist-server"), // Function names stay in stack traces. Node prints a whole source line with an uncaught error, so no line may be long. minifyWhitespace: true, minifySyntax: true, lineLimit: 500, // Written after tsc, replacing its output for these entry points. allowOverwrite: true, logLevel: "info", plugins: [yamlEsmPlugin, serverLocalePlugin], banner: NODE_REQUIRE, }); // External MCP clients launch this as an independent stdio process, so release // users need no repository, TypeScript, pnpm or node_modules. await build({ entryPoints: [join(root, "scripts", "mcp-server.ts")], bundle: true, platform: "node", target: "node20", format: "esm", outfile: join(root, "dist-server", "mcp-server.js"), allowOverwrite: true, logLevel: "info", plugins: [serverLocalePlugin], }); // `openmausbot serve --tunnel` spawns the connector guardian as its own // process, so it must exist as a file beside the server. await build({ entryPoints: [join(root, "electron", "managed-companion-guardian-main.mjs")], bundle: true, platform: "node", target: "node20", format: "esm", outfile: join(root, "dist-server", "tunnel-guardian.js"), allowOverwrite: true, logLevel: "info", }); // Runs itself when executed directly, so it must never be inlined into another // entry. await build({ entryPoints: [join(root, "scripts", "prepare-cloudflared.mjs")], bundle: true, platform: "node", target: "node20", format: "esm", outfile: join(root, "dist-server", "prepare-cloudflared.js"), allowOverwrite: true, logLevel: "info", }); // The enterprise layer (enterprise/LICENSE; delete the folder for pure OSS). // Shipped bundled to dist-server/enterprise/server/index.js, where the server // finds it; its own license travels with it. if (existsSync(join(root, "enterprise", "server", "index.ts"))) { await build({ entryPoints: [join(root, "enterprise", "server", "index.ts")], bundle: true, platform: "node", target: "node20", format: "esm", outfile: join(root, "dist-server", "enterprise", "server", "index.js"), allowOverwrite: true, logLevel: "info", plugins: [serverLocalePlugin], }); copyFileSync(join(root, "enterprise", "LICENSE"), join(root, "dist-server", "enterprise", "LICENSE")); } // The packaged desktop and the container launcher run this bootstrap to turn on Node's compile cache. // Copied, not bundled: esbuild would inline index.js into it. copyFileSync(join(root, "scripts", "desktop-server-entry.mjs"), join(root, "dist-server", "desktop-entry.mjs")); // The model catalog snapshot (server/model-catalog/catalog.ts) is read from // disk, not inlined: 1.5 MB of JSON has no place in index.js. The bundle looks // for it under model-catalog/ beside itself. Its MIT notice is inside the file. const catalogSnapshot = join(root, "dist-server", "model-catalog", "models-dev.snapshot.json"); mkdirSync(dirname(catalogSnapshot), { recursive: true }); copyFileSync(join(server, "model-catalog", "models-dev.snapshot.json"), catalogSnapshot); // Pi loads this through its own jiti and supplies TypeBox. Inline our local // policy module so the extension also works without the source checkout; // keep the Pi-owned dependency external and retain the .ts loading contract. const piMcpExtSrc = join(server, "drivers", "pi-mcp-extension.ts"); const piMcpExtDest = join(root, "dist-server", "drivers", "pi-mcp-extension.ts"); mkdirSync(dirname(piMcpExtDest), { recursive: true }); await build({ entryPoints: [piMcpExtSrc], outfile: piMcpExtDest, bundle: true, platform: "node", target: "node24", format: "esm", external: ["typebox"], }); // Desktop resources live outside app.asar, so its package.json cannot mark // these .js bundles as ESM. Do not inherit a user's CommonJS package scope. // The version rides along: an image ships dist-server without the root // package.json, and the server reports this one (server/environment.ts). const { version } = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); writeFileSync(join(root, "dist-server", "package.json"), `${JSON.stringify({ type: "module", version })}\n`); // tsc copies the language packs server/server-lines.ts imports (13 MB) beside its own output. The bundles above carry // the server's keys, so nothing reads the copies. rmSync(join(root, "dist-server", "src", "locales"), { recursive: true, force: true });