| #!/usr/bin/env node |
|
|
| import { chmodSync, existsSync, mkdirSync, rmSync } from "node:fs"; |
| import { isBuiltin } from "node:module"; |
| import { dirname, join, relative, resolve } from "node:path"; |
| import { fileURLToPath } from "node:url"; |
| import { build } from "esbuild"; |
|
|
| const scriptDir = dirname(fileURLToPath(import.meta.url)); |
| const repoRoot = resolve(scriptDir, ".."); |
| const codingAgentDir = join(repoRoot, "packages", "coding-agent"); |
| const aiDistDir = join(repoRoot, "packages", "ai", "dist"); |
| const codingAgentDistDir = join(codingAgentDir, "dist"); |
| const bundleDir = join(codingAgentDistDir, "bundle"); |
| const banner = { |
| js: 'import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);', |
| }; |
| const allowedExternalPackages = new Set([ |
| "@earendil-works/chord", |
| "@earendil-works/chord/bundler", |
| "@earendil-works/chord/context", |
| "@earendil-works/chord/delta", |
| "@earendil-works/chord/node", |
| "@silvia-odwyer/photon-node", |
| "jiti", |
| |
| "bufferutil", |
| "utf-8-validate", |
| |
| "kerberos", |
| |
| "supports-color", |
| ]); |
|
|
| const lazyJitiPlugin = { |
| name: "lazy-jiti-transform", |
| setup(build) { |
| build.onResolve({ filter: /^jiti\/static$/ }, () => ({ |
| namespace: "lazy-jiti", |
| path: "jiti/static", |
| })); |
| build.onLoad({ filter: /.*/, namespace: "lazy-jiti" }, () => ({ |
| contents: ` |
| import { createRequire } from "node:module"; |
| |
| const require = createRequire(import.meta.url); |
| let createJitiImpl; |
| |
| export function createJiti(...args) { |
| createJitiImpl ??= require("jiti").createJiti; |
| return createJitiImpl(...args); |
| } |
| `, |
| loader: "js", |
| })); |
| }, |
| }; |
|
|
| const httpsProxyAgentNamedExportPlugin = { |
| name: "https-proxy-agent-named-export", |
| setup(build) { |
| build.onResolve({ filter: /^https-proxy-agent$/ }, (args) => { |
| if (args.kind !== "dynamic-import") return undefined; |
| return { |
| namespace: "https-proxy-agent-named-export", |
| path: args.path, |
| }; |
| }); |
| build.onLoad( |
| { |
| filter: /^https-proxy-agent$/, |
| namespace: "https-proxy-agent-named-export", |
| }, |
| () => ({ |
| contents: 'export { HttpsProxyAgent } from "https-proxy-agent";', |
| loader: "js", |
| resolveDir: repoRoot, |
| }), |
| ); |
| }, |
| }; |
|
|
| function commonBuildOptions() { |
| return { |
| absWorkingDir: repoRoot, |
| banner, |
| bundle: true, |
| define: { PI_BUNDLED_NODE: "true" }, |
| external: ["@earendil-works/chord", "@silvia-odwyer/photon-node"], |
| format: "esm", |
| legalComments: "none", |
| logLevel: "warning", |
| metafile: true, |
| minifySyntax: true, |
| minifyWhitespace: true, |
| platform: "node", |
| |
| |
| |
| |
| plugins: [lazyJitiPlugin, httpsProxyAgentNamedExportPlugin], |
| sourcemap: false, |
| target: "node22.19", |
| |
| |
| |
| tsconfigRaw: { compilerOptions: {} }, |
| }; |
| } |
|
|
| function validateExternalImports(metafiles) { |
| const unexpected = new Set(); |
| for (const metafile of metafiles) { |
| for (const input of Object.values(metafile.inputs)) { |
| for (const imported of input.imports) { |
| if (!imported.external || isBuiltin(imported.path) || allowedExternalPackages.has(imported.path)) { |
| continue; |
| } |
| unexpected.add(imported.path); |
| } |
| } |
| } |
| if (unexpected.size > 0) { |
| throw new Error(`Bundle left unexpected external imports: ${Array.from(unexpected).sort().join(", ")}`); |
| } |
| } |
|
|
| function findContainingOutput(metafile, inputSuffix) { |
| const normalizedSuffix = inputSuffix.replaceAll("\\", "/"); |
| for (const [outputPath, output] of Object.entries(metafile.outputs)) { |
| if (Object.keys(output.inputs).some((inputPath) => inputPath.replaceAll("\\", "/").endsWith(normalizedSuffix))) { |
| return resolve(repoRoot, outputPath); |
| } |
| } |
| throw new Error(`Could not locate bundled output containing ${inputSuffix}`); |
| } |
|
|
| function outputBytes(metafiles) { |
| return metafiles.reduce( |
| (total, metafile) => total + Object.values(metafile.outputs).reduce((subtotal, output) => subtotal + output.bytes, 0), |
| 0, |
| ); |
| } |
|
|
| for (const entry of [ |
| join(codingAgentDistDir, "cli.js"), |
| join(codingAgentDistDir, "index.js"), |
| join(codingAgentDistDir, "rpc-entry.js"), |
| join(codingAgentDistDir, "utils", "image-resize-worker.js"), |
| join(aiDistDir, "api", "bedrock-converse-stream.js"), |
| join(aiDistDir, "auth", "oauth", "anthropic.js"), |
| ]) { |
| if (!existsSync(entry)) { |
| throw new Error(`Bundle input is missing: ${relative(repoRoot, entry)}. Build the workspace packages first.`); |
| } |
| } |
|
|
| rmSync(bundleDir, { force: true, recursive: true }); |
| mkdirSync(bundleDir, { recursive: true }); |
|
|
| const mainResult = await build({ |
| ...commonBuildOptions(), |
| entryNames: "[name]", |
| entryPoints: { |
| cli: join(codingAgentDistDir, "cli.js"), |
| index: join(codingAgentDistDir, "index.js"), |
| "rpc-entry": join(codingAgentDistDir, "rpc-entry.js"), |
| }, |
| outdir: bundleDir, |
| chunkNames: "chunks/[name]-[hash]", |
| splitting: true, |
| }); |
|
|
| const bedrockLoaderOutput = findContainingOutput(mainResult.metafile, "packages/ai/dist/api/bedrock-converse-stream.lazy.js"); |
| const oauthLoaderOutput = findContainingOutput(mainResult.metafile, "packages/ai/dist/auth/oauth/load.js"); |
| const imageResizeOutput = findContainingOutput(mainResult.metafile, "packages/coding-agent/dist/utils/image-resize.js"); |
| if (dirname(bedrockLoaderOutput) !== dirname(oauthLoaderOutput)) { |
| throw new Error("Bedrock and OAuth lazy loaders were emitted into different directories"); |
| } |
|
|
| |
| |
| |
| const lazyResult = await build({ |
| ...commonBuildOptions(), |
| entryNames: "[name]", |
| entryPoints: { |
| anthropic: join(aiDistDir, "auth", "oauth", "anthropic.js"), |
| "bedrock-converse-stream": join(aiDistDir, "api", "bedrock-converse-stream.js"), |
| "github-copilot": join(aiDistDir, "auth", "oauth", "github-copilot.js"), |
| "image-resize-worker": join(codingAgentDistDir, "utils", "image-resize-worker.js"), |
| "kimi-coding": join(aiDistDir, "auth", "oauth", "kimi-coding.js"), |
| "openai-codex": join(aiDistDir, "auth", "oauth", "openai-codex.js"), |
| openrouter: join(aiDistDir, "auth", "oauth", "openrouter.js"), |
| radius: join(aiDistDir, "auth", "oauth", "radius.js"), |
| xai: join(aiDistDir, "auth", "oauth", "xai.js"), |
| }, |
| outdir: dirname(bedrockLoaderOutput), |
| splitting: false, |
| }); |
|
|
| const imageResizeWorkerOutput = resolve(dirname(bedrockLoaderOutput), "image-resize-worker.js"); |
| if (dirname(imageResizeOutput) !== dirname(imageResizeWorkerOutput)) { |
| throw new Error("Image resize implementation and worker were emitted into different directories"); |
| } |
|
|
| validateExternalImports([mainResult.metafile, lazyResult.metafile]); |
| chmodSync(join(bundleDir, "cli.js"), 0o755); |
| chmodSync(join(bundleDir, "rpc-entry.js"), 0o755); |
|
|
| const files = new Set([...Object.keys(mainResult.metafile.outputs), ...Object.keys(lazyResult.metafile.outputs)]).size; |
| const mib = outputBytes([mainResult.metafile, lazyResult.metafile]) / (1024 * 1024); |
| console.log(`Built ${relative(repoRoot, bundleDir)} (${files} files, ${mib.toFixed(1)} MiB)`); |
|
|