refactor(coding-agent): restructured plugin system and build logic

- Migrated legacy static bundled-module registry to a dynamic Bun-based in-memory plugin system.
- Removed over 4,000 lines of manually maintained registry files to reduce maintenance overhead.
- Implemented CJS absolute path rewriting and native-addon load hooks to improve FFI dependency resolution.
- Standardized binary build processes by centralizing compilation logic into shared utilities.
This commit is contained in:
can1357
2026-07-11 07:24:47 +02:00
parent ea632a518b
commit 33c161d9dd
20 changed files with 761 additions and 5332 deletions
+56 -73
View File
@@ -2,17 +2,41 @@
import { createRequire } from "node:module";
import * as path from "node:path";
import { compileCodingAgent } from "./compile-binary";
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";
interface CrossBuild {
readonly id: string;
readonly platform: string;
readonly arch: string;
readonly target: Bun.Build.CompileTarget;
}
function resolveCrossBuild(value: string | undefined): CrossBuild | null {
switch (value) {
case undefined:
case "":
return null;
case "darwin-arm64":
return { id: value, platform: "darwin", arch: "arm64", target: "bun-darwin-arm64" };
case "darwin-x64":
return { id: value, platform: "darwin", arch: "x64", target: "bun-darwin-x64" };
case "linux-arm64":
return { id: value, platform: "linux", arch: "arm64", target: "bun-linux-arm64" };
case "linux-x64":
return { id: value, platform: "linux", arch: "x64", target: "bun-linux-x64-baseline" };
case "win32-x64":
case "windows-x64":
return { id: value, platform: "win32", arch: "x64", target: "bun-windows-x64-modern" };
default:
throw new Error(`Unsupported CROSS_TARGET: ${value}`);
}
}
const crossBuild = resolveCrossBuild(Bun.env.CROSS_TARGET);
const outName = crossBuild ? `omp-${crossBuild.id}` : "omp";
const outputPath = path.join(packageDir, "dist", outName);
// Transformers.js is an optional, native-heavy dependency that is never bundled
@@ -20,12 +44,19 @@ const outputPath = path.join(packageDir, "dist", outName);
// 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;
const transformersManifest: unknown = createRequire(import.meta.url)("@huggingface/transformers/package.json");
if (
typeof transformersManifest !== "object" ||
transformersManifest === null ||
!("version" in transformersManifest) ||
typeof transformersManifest.version !== "string"
) {
throw new Error("@huggingface/transformers package manifest has no string version");
}
const transformersVersion = transformersManifest.version;
function shouldAdhocSignDarwinBinary(): boolean {
return process.platform === "darwin" && !crossTarget;
return process.platform === "darwin" && !crossBuild;
}
async function runCommand(
@@ -50,73 +81,26 @@ async function main(): Promise<void> {
// placeholders (stats client archive, docs index) even on failure.
try {
await runCommand(["bun", "--cwd=../stats", "run", "gen:stats"]);
await runCommand(["bun", "run", "gen:docs"]);
// `legacy-pi-bundled-registry.ts` static-imports
// `@oh-my-pi/pi-coding-agent/export/html` (one of pi-coding-agent's
// named subpath exports, see scripts/generate-legacy-pi-bundled-registry.ts),
// whose source pulls in `tool-views.generated.js`. The root
// `package.json` "prepare" lifecycle hook builds that file on
// `bun install`, but a clean binary build that skips install hooks
// would `bun build --compile` against the registry entry and fail
// resolving the missing generated bundle. Rebuilding the tool views
// here makes the compile self-contained and matches what `prepack`
// does for the npm bundle.
// The in-memory legacy Pi virtual module reaches the coding-agent
// `export/html` subpath, whose source imports `tool-views.generated.js`.
// Rebuild it before compilation so clean checkouts that skipped install
// hooks still contain that generated bundle.
await runCommand(["bun", "--cwd=../collab-web", "run", "gen:tool-views"]);
await runCommand(
["bun", "--cwd=../natives", "run", "gen:native"],
crossTarget
? { ...Bun.env, TARGET_PLATFORM: crossPlatform as string, TARGET_ARCH: crossArch as string }
: Bun.env,
crossBuild ? { ...Bun.env, TARGET_PLATFORM: crossBuild.platform, TARGET_ARCH: crossBuild.arch } : Bun.env,
);
await runCommand(["bun", "run", "gen:mupdf"]);
// 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,
await compileCodingAgent({
repoRoot,
);
entrypoint: path.join(packageDir, "src", "cli.ts"),
outfile: outputPath,
transformersVersion,
target: crossBuild?.target,
external: ["fastembed", "onnxruntime-node"],
skipBuiltinCodesign: shouldAdhocSignDarwinBinary(),
});
// Bun 1.3.12 emits a truncated Mach-O signature on darwin builds.
if (shouldAdhocSignDarwinBinary()) {
@@ -128,7 +112,6 @@ async function main(): Promise<void> {
}
} finally {
await runCommand(["bun", "--cwd=../stats", "run", "gen:stats:reset"]);
await runCommand(["bun", "run", "gen:docs:reset"]);
}
}