mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-08-26 17:12:21 +00:00
184 lines
6.7 KiB
JavaScript
184 lines
6.7 KiB
JavaScript
#!/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([
|
|
"@silvia-odwyer/photon-node",
|
|
"jiti",
|
|
// Optional native accelerators. Their callers fall back to JavaScript when absent.
|
|
"bufferutil",
|
|
"utf-8-validate",
|
|
// Optional debug output coloring.
|
|
"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",
|
|
}));
|
|
},
|
|
};
|
|
|
|
function commonBuildOptions() {
|
|
return {
|
|
absWorkingDir: repoRoot,
|
|
banner,
|
|
bundle: true,
|
|
define: { PI_BUNDLED_NODE: "true" },
|
|
external: ["@silvia-odwyer/photon-node"],
|
|
format: "esm",
|
|
legalComments: "none",
|
|
logLevel: "warning",
|
|
metafile: true,
|
|
minifySyntax: true,
|
|
minifyWhitespace: true,
|
|
platform: "node",
|
|
// The source uses jiti/static so Bun embeds its Babel transform. The Node
|
|
// package replaces it with a synchronous lazy require so jiti loads only
|
|
// when importing an extension; Babel remains deferred until a cache miss
|
|
// needs transformation.
|
|
plugins: [lazyJitiPlugin],
|
|
sourcemap: false,
|
|
target: "node22.19",
|
|
// Do not apply the monorepo's source-oriented path aliases while bundling
|
|
// compiled output. Release builds must resolve the same package entries as
|
|
// an installed npm package.
|
|
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, "client", "index.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"),
|
|
client: join(codingAgentDistDir, "client", "index.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");
|
|
}
|
|
|
|
// These implementations are reached through variable-specifier imports or a
|
|
// worker URL, so the main bundle cannot follow them. Emit one self-contained
|
|
// file per implementation beside the code that resolves it.
|
|
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)`);
|