Files
oh-my-pi/packages/coding-agent/scripts/build-binary.ts
T
roboomp aa29b79642 fix(plugins): route bundled pi-* subpath imports through the virtual registry
Compiled-binary extension validation rewrote @(scope)/pi-ai/oauth →
@oh-my-pi/pi-ai/oauth, but LEGACY_PI_PACKAGE_ROOT_OVERRIDES only
covered bare package roots. resolveCanonicalPiSpecifier therefore
fell through to Bun.resolveSync, which fails inside bunfs on Bun
1.3.14+, then the rewriteLegacyPiImports catch left the original
specifier alone. Bun's native resolver then failed because most
plugins (e.g. @charmland/pi-hyper-provider) declare @(scope)/pi-ai
as a peerDependency only and never materialize a real install.

A new scripts/generate-legacy-pi-bundled-registry.ts reads every
bundled pi-* package's non-wildcard exports field and emits both
the heavy legacy-pi-bundled-registry.ts (static imports + map) and
a light legacy-pi-bundled-keys.ts. legacy-pi-compat.ts statically
imports the keys file to seed the override map without paying the
legacy-pi-coding-agent-shim → ../index → export/html/... cascade,
so subpath imports now route to the same omp-legacy-pi-bundled:
virtual namespace that already serves the roots.
scripts/build-binary.ts runs the generator before bun build
--compile so new pi-* subpaths added under packages/*/package.json
ship without manual regeneration; --check verifies the committed
output stays in sync.

Fixes #3442
2026-06-25 05:52:38 +00:00

125 lines
5.1 KiB
TypeScript

#!/usr/bin/env bun
import { createRequire } from "node:module";
import * as path from "node:path";
const packageDir = path.join(import.meta.dir, "..");
const repoRoot = path.join(packageDir, "..", "..");
// Optional cross-compile target, e.g. CROSS_TARGET=linux-arm64 → bun build
// --target=bun-linux-arm64, embeds the matching native, outputs dist/omp-<target>.
const crossTarget = Bun.env.CROSS_TARGET || null;
const [crossPlatform, crossArch] = crossTarget ? crossTarget.split("-") : [null, null];
// x64 uses the baseline bun runtime so it runs under Rosetta / pre-AVX2 CPUs
// (the modern bun-linux-x64 target SIGILLs under Apple-Silicon Rosetta).
const bunTarget = crossTarget ? (crossTarget === "linux-x64" ? "bun-linux-x64-baseline" : `bun-${crossTarget}`) : null;
const outName = crossTarget ? `omp-${crossTarget}` : "omp";
const outputPath = path.join(packageDir, "dist", outName);
// Transformers.js is an optional, native-heavy dependency that is never bundled
// into the binary; the tiny-model worker `bun install`s it into a runtime cache
// on first use. The `catalog:` spec cannot be resolved from inside the compiled
// bunfs (issue #1763), so embed the concrete installed version here for the
// worker to pin its runtime install against.
const transformersVersion = (
createRequire(import.meta.url)("@huggingface/transformers/package.json") as { version: string }
).version;
function shouldAdhocSignDarwinBinary(): boolean {
return process.platform === "darwin" && !crossTarget;
}
async function runCommand(
command: string[],
env: NodeJS.ProcessEnv = Bun.env,
cwd: string = packageDir,
): Promise<void> {
const proc = Bun.spawn(command, {
cwd,
env,
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(`Command failed with exit code ${exitCode}: ${command.join(" ")}`);
}
}
async function main(): Promise<void> {
// Generate inside the try so the finally always restores the empty checked-in
// placeholders (stats client archive, docs index) even on failure.
try {
await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]);
await runCommand(["bun", "scripts/generate-docs-index.ts", "--generate"]);
await runCommand(
["bun", "--cwd=../natives", "run", "embed:native"],
crossTarget
? { ...Bun.env, TARGET_PLATFORM: crossPlatform as string, TARGET_ARCH: crossArch as string }
: Bun.env,
);
await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--generate"]);
// Regenerate the bundled-pi registry + key set before the compile so any
// new pi-* subpath export added under `packages/*/package.json` is served
// from the host's in-process copy. Without this, `bun build --compile`
// would freeze whatever the committed registry happened to enumerate at
// the time of the last manual `--generate`, and a new subpath added
// since then would crash extension validation with `Cannot find module`
// (issue #3442). The generator also normalizes formatting, so the diff
// against the committed copy stays clean.
await runCommand(["bun", "scripts/generate-legacy-pi-bundled-registry.ts", "--generate"]);
try {
const buildEnv = shouldAdhocSignDarwinBinary() ? { ...Bun.env, BUN_NO_CODESIGN_MACHO_BINARY: "1" } : Bun.env;
await runCommand(
[
"bun",
"build",
"--compile",
...(bunTarget ? ["--target", bunTarget] : []),
"--no-compile-autoload-bunfig",
"--no-compile-autoload-dotenv",
"--no-compile-autoload-tsconfig",
"--no-compile-autoload-package-json",
"--keep-names",
"--define",
'process.env.PI_COMPILED="true"',
"--define",
`process.env.PI_TINY_TRANSFORMERS_VERSION=${JSON.stringify(transformersVersion)}`,
"--external",
"fastembed",
"--external",
"onnxruntime-node",
"--root",
".",
"./packages/coding-agent/src/cli.ts",
// Legacy pi-* extension compat surfaces (host packages + shims)
// were previously listed as explicit `--compile` entries so the
// rewrite path could emit `/$bunfs/root/...` URLs against them.
// Bun 1.3.14 made bunfs files unreachable at runtime (issue
// #3423), so `legacy-pi-compat.ts` now serves them through a
// virtual namespace backed by `legacy-pi-bundled-registry.ts`,
// which static-imports each surface — the bundler already
// includes them via the main module graph, so no `--compile`
// extras are required.
"--outfile",
`packages/coding-agent/dist/${outName}`,
],
buildEnv,
repoRoot,
);
// Bun 1.3.12 emits a truncated Mach-O signature on darwin builds.
if (shouldAdhocSignDarwinBinary()) {
await runCommand(["codesign", "--force", "--sign", "-", outputPath]);
}
} finally {
await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--reset"]);
await runCommand(["bun", "--cwd=../natives", "run", "embed:native", "--reset"]);
}
} finally {
await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--reset"]);
await runCommand(["bun", "scripts/generate-docs-index.ts", "--reset"]);
}
}
await main();