Use @mariozechner/jiti fork with virtualModules for extension loading in Bun binary

- Switch from jiti to @mariozechner/jiti fork
- Use virtualModules option for bundled packages in compiled binary
- Disable tryNative so jiti handles all nested imports
- Lazy-load pi-coding-agent to avoid circular dependency
This commit is contained in:
Mario Zechner 2026-01-13 04:55:11 +01:00
parent cedd8fe306
commit 1919fd7c9c
4 changed files with 243 additions and 29 deletions

216
package-lock.json generated
View file

@ -1000,6 +1000,17 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@emnapi/core": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
"integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
@ -1010,6 +1021,16 @@
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
@ -1979,6 +2000,17 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@ -2245,6 +2277,19 @@
"node": ">= 10"
}
},
"node_modules/@mariozechner/jiti": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.2.tgz",
"integrity": "sha512-CcFowm/fDWcEMH/F47DQcdawpLQb0nw+WR+hZOv8mgAeACFJxE9uo3cXjUk/5Cl3j23t/oxvtxxUtlBCUIGeQg==",
"license": "MIT",
"dependencies": {
"std-env": "^3.10.0",
"yoctocolors": "^2.1.2"
},
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/@mariozechner/mini-lit": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@mariozechner/mini-lit/-/mini-lit-0.2.1.tgz",
@ -2583,6 +2628,18 @@
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
"integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -4314,6 +4371,16 @@
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@ -4879,6 +4946,13 @@
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT",
"optional": true
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@ -6460,15 +6534,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-tokens": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
@ -8039,6 +8104,16 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -8048,6 +8123,17 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"license": "MIT",
"optional": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@ -8059,7 +8145,6 @@
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"dev": true,
"license": "MIT"
},
"node_modules/string_decoder": {
@ -8338,6 +8423,32 @@
"node": ">=6"
}
},
"node_modules/terser": {
"version": "5.44.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT",
"optional": true
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -9052,6 +9163,18 @@
"node": ">=8"
}
},
"node_modules/yoctocolors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
"integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
@ -9157,6 +9280,7 @@
"license": "MIT",
"dependencies": {
"@mariozechner/clipboard": "^0.3.0",
"@mariozechner/jiti": "^2.6.2",
"@mariozechner/pi-agent-core": "^0.45.2",
"@mariozechner/pi-ai": "^0.45.2",
"@mariozechner/pi-tui": "^0.45.2",
@ -9165,7 +9289,6 @@
"diff": "^8.0.2",
"file-type": "^21.1.1",
"glob": "^11.0.3",
"jiti": "^2.6.1",
"marked": "^15.0.12",
"minimatch": "^10.1.1",
"proper-lockfile": "^4.1.2",
@ -9214,6 +9337,77 @@
"dev": true,
"license": "MIT"
},
"packages/jiti": {
"version": "2.6.1",
"extraneous": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
},
"devDependencies": {
"@babel/core": "^7.28.4",
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-simple-access": "^7.27.1",
"@babel/plugin-proposal-decorators": "^7.28.0",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-import-assertions": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/preset-typescript": "^7.27.1",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@rspack/cli": "^1.5.8",
"@rspack/core": "^1.5.8",
"@types/babel__core": "^7.20.5",
"@types/babel__helper-module-imports": "^7.18.3",
"@types/babel__helper-plugin-utils": "^7.10.3",
"@types/babel__template": "^7.4.4",
"@types/babel__traverse": "^7.28.0",
"@types/node": "^24.6.1",
"@vitest/coverage-v8": "^3.2.4",
"acorn": "^8.15.0",
"babel-plugin-parameter-decorator": "^1.0.16",
"changelogen": "^0.6.2",
"config": "^4.1.1",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.5",
"escape-string-regexp": "^5.0.0",
"eslint": "^9.36.0",
"eslint-config-unjs": "^0.5.0",
"estree-walker": "^3.0.3",
"etag": "^1.8.1",
"fast-glob": "^3.3.3",
"is-installed-globally": "^1.0.0",
"mime": "^4.1.0",
"mlly": "^1.8.0",
"moment-timezone": "^0.6.0",
"nano-jsx": "^0.2.0",
"pathe": "^2.0.3",
"pkg-types": "^2.3.0",
"preact": "^10.27.2",
"preact-render-to-string": "^6.6.2",
"prettier": "^3.6.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"reflect-metadata": "^0.2.2",
"solid-js": "^1.9.9",
"std-env": "^3.9.0",
"terser-webpack-plugin": "^5.3.14",
"tinyexec": "^1.0.1",
"ts-loader": "^9.5.4",
"typescript": "^5.9.3",
"vitest": "^3.2.4",
"vue": "^3.5.22",
"yoctocolors": "^2.1.2",
"zod": "^4.1.11"
}
},
"packages/mom": {
"name": "@mariozechner/pi-mom",
"version": "0.45.2",

View file

@ -6,7 +6,7 @@
### Fixed
- Extensions now load correctly in compiled Bun binary by using jiti for module resolution with proper alias handling
- Extensions now load correctly in compiled Bun binary using `@mariozechner/jiti` fork with `virtualModules` support. Bundled packages (`@sinclair/typebox`, `@mariozechner/pi-tui`, `@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`) are accessible to extensions without filesystem node_modules.
## [0.45.1] - 2026-01-13

View file

@ -39,6 +39,7 @@
},
"dependencies": {
"@mariozechner/clipboard": "^0.3.0",
"@mariozechner/jiti": "^2.6.2",
"@mariozechner/pi-agent-core": "^0.45.2",
"@mariozechner/pi-ai": "^0.45.2",
"@mariozechner/pi-tui": "^0.45.2",
@ -47,7 +48,6 @@
"diff": "^8.0.2",
"file-type": "^21.1.1",
"glob": "^11.0.3",
"jiti": "^2.6.1",
"marked": "^15.0.12",
"minimatch": "^10.1.1",
"proper-lockfile": "^4.1.2",

View file

@ -1,5 +1,7 @@
/**
* Extension loader - loads TypeScript extension modules using jiti.
*
* Uses @mariozechner/jiti fork with virtualModules support for compiled Bun binaries.
*/
import * as fs from "node:fs";
@ -7,9 +9,15 @@ import { createRequire } from "node:module";
import * as os from "node:os";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { createJiti } from "@mariozechner/jiti";
import * as _bundledPiAi from "@mariozechner/pi-ai";
import type { KeyId } from "@mariozechner/pi-tui";
import { createJiti } from "jiti";
import { getAgentDir } from "../../config.js";
import * as _bundledPiTui from "@mariozechner/pi-tui";
// Static imports of packages that extensions may use.
// These MUST be static so Bun bundles them into the compiled binary.
// The virtualModules option then makes them available to extensions.
import * as _bundledTypebox from "@sinclair/typebox";
import { getAgentDir, isBunBinary } from "../../config.js";
import { createEventBus, type EventBus } from "../event-bus.js";
import type { ExecOptions } from "../exec.js";
import { execCommand } from "../exec.js";
@ -24,8 +32,28 @@ import type {
ToolDefinition,
} from "./types.js";
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
let _lazyPiCodingAgent: unknown;
const VIRTUAL_MODULES: Record<string, unknown> = {
"@sinclair/typebox": _bundledTypebox,
"@mariozechner/pi-tui": _bundledPiTui,
"@mariozechner/pi-ai": _bundledPiAi,
// Lazy-loaded to avoid circular dependency (loader.ts is part of pi-coding-agent)
get "@mariozechner/pi-coding-agent"() {
if (!_lazyPiCodingAgent) {
// Dynamic require after module initialization completes
_lazyPiCodingAgent = require("../../index.js");
}
return _lazyPiCodingAgent;
},
};
const require = createRequire(import.meta.url);
/**
* Get aliases for jiti (used in Node.js/development mode).
* In Bun binary mode, virtualModules is used instead.
*/
let _aliases: Record<string, string> | null = null;
function getAliases(): Record<string, string> {
if (_aliases) return _aliases;
@ -33,27 +61,16 @@ function getAliases(): Record<string, string> {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageIndex = path.resolve(__dirname, "../..", "index.js");
// Debug: log what we're resolving
if (process.env.DEBUG_EXTENSIONS) {
console.error("[DEBUG] import.meta.url:", import.meta.url);
console.error("[DEBUG] __dirname:", __dirname);
}
const typeboxEntry = require.resolve("@sinclair/typebox");
const typeboxRoot = typeboxEntry.replace(/\/build\/cjs\/index\.js$/, "");
_aliases = {
"@mariozechner/pi-coding-agent": packageIndex,
"@mariozechner/pi-coding-agent/extensions": path.resolve(__dirname, "index.js"),
"@mariozechner/pi-tui": require.resolve("@mariozechner/pi-tui"),
"@mariozechner/pi-ai": require.resolve("@mariozechner/pi-ai"),
"@sinclair/typebox": typeboxRoot,
};
if (process.env.DEBUG_EXTENSIONS) {
console.error("[DEBUG] aliases:", JSON.stringify(_aliases, null, 2));
}
return _aliases;
}
@ -224,12 +241,15 @@ function createExtensionAPI(
return api;
}
async function loadExtensionModule(path: string) {
async function loadExtensionModule(extensionPath: string) {
const jiti = createJiti(import.meta.url, {
alias: getAliases(),
// In Bun binary: use virtualModules for bundled packages (no filesystem resolution)
// Also disable tryNative so jiti handles ALL imports (not just the entry point)
// In Node.js/dev: use aliases to resolve to node_modules paths
...(isBunBinary ? { virtualModules: VIRTUAL_MODULES, tryNative: false } : { alias: getAliases() }),
});
const module = await jiti.import(path, { default: true });
const module = await jiti.import(extensionPath, { default: true });
const factory = module as ExtensionFactory;
return typeof factory !== "function" ? undefined : factory;
}