Merge remote-tracking branch 'origin/farm/ceed3a6a/compiled-appserver-extension-exit'
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed compiled appserver startup deadlocking before socket creation when any user extension was present ([#5568](https://github.com/can1357/oh-my-pi/issues/5568)).
|
||||
- Fixed Bash internal URLs remaining unresolved when used as unquoted arguments inside command substitutions ([#5535](https://github.com/can1357/oh-my-pi/issues/5535)).
|
||||
- Fixed `--tools` silently dropping hidden tool names (`xdev`, `yield`, ...); hidden built-ins are now addressable per the `hidden` tool contract.
|
||||
- Fixed the built-in `fd` printing `fd: Broken pipe (os error 32)` when a downstream pipeline reader exited early (e.g. `fd … | head`); it now exits silently with 141 (128+SIGPIPE), matching real fd.
|
||||
|
||||
@@ -166,19 +166,22 @@ export async function collectBundledPiEntries(): Promise<BundledPiEntry[]> {
|
||||
return entries;
|
||||
}
|
||||
|
||||
function renderVirtualModule(entries: readonly BundledPiEntry[]): string {
|
||||
const imports = entries.map(entry => `import * as ${entry.binding} from ${JSON.stringify(entry.importSpecifier)};`);
|
||||
/** Render the lazy loader registry; exported so tests can execute the generated module. */
|
||||
export function __renderLegacyPiVirtualModule(entries: readonly BundledPiEntry[]): string {
|
||||
const loaders = entries.map(
|
||||
entry => `const ${entry.binding} = () => import(${JSON.stringify(entry.importSpecifier)});`,
|
||||
);
|
||||
const modules = entries.map(entry => `\t${JSON.stringify(entry.key)}: ${entry.binding},`);
|
||||
return [...imports, "", "export const BUNDLED_PI_MODULES = {", ...modules, "};", ""].join("\n");
|
||||
return [...loaders, "", "export const BUNDLED_PI_MODULE_LOADERS = {", ...modules, "};", ""].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build plugin that materializes the legacy Pi module graph entirely in
|
||||
* memory. Bun still needs static import edges at compile time, but no generated
|
||||
* source or key-list file is written to the repository.
|
||||
* Build plugin that materializes lazy legacy Pi module loaders entirely in
|
||||
* memory. Literal dynamic imports retain every compile-time edge without
|
||||
* evaluating unrelated host modules during extension bootstrap.
|
||||
*/
|
||||
export async function createLegacyPiVirtualModulePlugin(): Promise<Bun.BunPlugin> {
|
||||
const source = renderVirtualModule(await collectBundledPiEntries());
|
||||
const source = __renderLegacyPiVirtualModule(await collectBundledPiEntries());
|
||||
return {
|
||||
name: "omp:legacy-pi-modules",
|
||||
setup(build) {
|
||||
|
||||
@@ -16,43 +16,52 @@ const IS_COMPILED_BINARY = isCompiledBinary();
|
||||
// embedded entries. Bun.plugin `onResolve` also no longer fires for transitive
|
||||
// imports inside runtime-loaded extensions.
|
||||
//
|
||||
// Compiled builds therefore keep live JS-heap references to the host packages
|
||||
// and serve requested surfaces through `omp-legacy-pi-bundled:<key>` synthetic
|
||||
// modules. `scripts/legacy-pi-virtual-module.ts` derives the static import edges
|
||||
// Compiled builds retain lazy loaders for host packages and serve requested
|
||||
// surfaces through `omp-legacy-pi-bundled:<key>` synthetic modules.
|
||||
// `scripts/legacy-pi-virtual-module.ts` derives literal dynamic-import edges
|
||||
// from current package exports inside a Bun build plugin: no generated source
|
||||
// or duplicate key list exists on disk. Runtime extension loading stays lazy —
|
||||
// the virtual module is evaluated only when an extension requests a host
|
||||
// package — but the compiler still sees every possible edge at build time.
|
||||
// or duplicate key list exists on disk. Deferring each host module evaluation
|
||||
// avoids cycles with an extension-loading command that is itself in the
|
||||
// retained package graph.
|
||||
const BUNDLED_VIRTUAL_SCHEME = "omp-legacy-pi-bundled:";
|
||||
const BUNDLED_VIRTUAL_NAMESPACE = "omp-legacy-pi-bundled";
|
||||
const BUNDLED_MODULES_GLOBAL = "__ompLegacyPiBundledModules";
|
||||
const TYPEBOX_BUNDLED_MODULE_KEY = "typebox";
|
||||
|
||||
type BundledModules = Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
||||
type BundledModule = Readonly<Record<string, unknown>>;
|
||||
type BundledModules = Readonly<Record<string, BundledModule>>;
|
||||
type BundledModuleLoaders = Readonly<Record<string, () => Promise<BundledModule>>>;
|
||||
|
||||
let bundledModulesPromise: Promise<BundledModules> | null = null;
|
||||
const loadedBundledModules: Record<string, BundledModule> = {};
|
||||
let bundledModuleLoadersPromise: Promise<BundledModuleLoaders> | null = null;
|
||||
|
||||
/**
|
||||
* Lazy-load the build-supplied host modules and stash them on `globalThis` for
|
||||
* the synthetic module source emitted by `synthesizeBundledModuleSource`.
|
||||
* Load the build-supplied module registry without evaluating its host modules.
|
||||
*
|
||||
* `globalThis` is the bridge: each `omp-legacy-pi-bundled:<key>` source string
|
||||
* becomes a separate ES module and cannot close over this file's lexical scope.
|
||||
* The dynamic import is intentional conditional build code. Dev/test runs
|
||||
* never execute it; binary builds resolve the literal through the in-memory
|
||||
* plugin in `scripts/legacy-pi-virtual-module.ts`.
|
||||
* `globalThis` bridges the synthetic ES modules, which cannot close over this
|
||||
* file's lexical scope. Dev/test runs never execute the conditional import;
|
||||
* binary builds resolve it through the in-memory build plugin.
|
||||
*/
|
||||
function ensureBundledModulesLoaded(): Promise<BundledModules> {
|
||||
function ensureBundledModuleLoadersLoaded(): Promise<BundledModuleLoaders> {
|
||||
if (!IS_COMPILED_BINARY) {
|
||||
return Promise.reject(new Error("omp:legacy-pi-shim: bundled modules are only available in compiled mode"));
|
||||
}
|
||||
if (!bundledModulesPromise) {
|
||||
bundledModulesPromise = import("omp-legacy-pi-modules").then(module => {
|
||||
Reflect.set(globalThis, BUNDLED_MODULES_GLOBAL, module.BUNDLED_PI_MODULES);
|
||||
return module.BUNDLED_PI_MODULES;
|
||||
if (!bundledModuleLoadersPromise) {
|
||||
bundledModuleLoadersPromise = import("omp-legacy-pi-modules").then(module => {
|
||||
Reflect.set(globalThis, BUNDLED_MODULES_GLOBAL, loadedBundledModules);
|
||||
return module.BUNDLED_PI_MODULE_LOADERS;
|
||||
});
|
||||
}
|
||||
return bundledModulesPromise;
|
||||
return bundledModuleLoadersPromise;
|
||||
}
|
||||
|
||||
async function loadBundledModule(moduleKey: string): Promise<void> {
|
||||
const loaders = await ensureBundledModuleLoadersLoaded();
|
||||
const loader = loaders[moduleKey];
|
||||
if (!loader) {
|
||||
throw new Error(`omp:legacy-pi-shim: no bundled module registered for ${moduleKey}`);
|
||||
}
|
||||
loadedBundledModules[moduleKey] = await loader();
|
||||
}
|
||||
|
||||
function bundledModuleVirtualSpecifier(moduleKey: string): string {
|
||||
@@ -95,8 +104,8 @@ function synthesizeBundledModuleSourceFromModules(moduleKey: string, modules: Bu
|
||||
* `omp-legacy-pi-bundled:<key>` import.
|
||||
*/
|
||||
async function synthesizeBundledModuleSource(moduleKey: string): Promise<string> {
|
||||
const modules = await ensureBundledModulesLoaded();
|
||||
return synthesizeBundledModuleSourceFromModules(moduleKey, modules);
|
||||
await loadBundledModule(moduleKey);
|
||||
return synthesizeBundledModuleSourceFromModules(moduleKey, loadedBundledModules);
|
||||
}
|
||||
|
||||
/** Test seam for the virtual module's named/default export forwarding. */
|
||||
@@ -369,14 +378,14 @@ export function __buildLegacyPiPackageRootOverrides(
|
||||
let legacyPiPackageRootOverrides = __buildLegacyPiPackageRootOverrides(IS_COMPILED_BINARY);
|
||||
let legacyPiOverridesReadyPromise: Promise<void> | null = null;
|
||||
|
||||
/** Complete compiled-mode overrides once from the lazily evaluated host modules. */
|
||||
/** Complete compiled-mode overrides from the lazy host-module registry. */
|
||||
function ensureLegacyPiOverridesReady(): Promise<void> {
|
||||
if (!IS_COMPILED_BINARY) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!legacyPiOverridesReadyPromise) {
|
||||
legacyPiOverridesReadyPromise = ensureBundledModulesLoaded().then(modules => {
|
||||
legacyPiPackageRootOverrides = __buildLegacyPiPackageRootOverrides(true, Object.keys(modules));
|
||||
legacyPiOverridesReadyPromise = ensureBundledModuleLoadersLoaded().then(loaders => {
|
||||
legacyPiPackageRootOverrides = __buildLegacyPiPackageRootOverrides(true, Object.keys(loaders));
|
||||
});
|
||||
}
|
||||
return legacyPiOverridesReadyPromise;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
declare module "omp-legacy-pi-modules" {
|
||||
/** Host package namespaces retained by the compiled binary for legacy extensions. */
|
||||
export const BUNDLED_PI_MODULES: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
||||
/** Lazy host package namespace loaders retained for compiled legacy extensions. */
|
||||
export const BUNDLED_PI_MODULE_LOADERS: Readonly<Record<string, () => Promise<Readonly<Record<string, unknown>>>>>;
|
||||
}
|
||||
|
||||
+48
-1
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
import { __buildLegacyPiPackageRootOverrides } from "@oh-my-pi/pi-coding-agent/extensibility/plugins/legacy-pi-compat";
|
||||
import { collectBundledPiEntries } from "../../scripts/legacy-pi-virtual-module";
|
||||
import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
import { __renderLegacyPiVirtualModule, collectBundledPiEntries } from "../../scripts/legacy-pi-virtual-module";
|
||||
|
||||
const bundledModuleKeys = new Set((await collectBundledPiEntries()).map(entry => entry.key));
|
||||
|
||||
@@ -14,6 +17,50 @@ const bundledModuleKeys = new Set((await collectBundledPiEntries()).map(entry =>
|
||||
// the same `omp-legacy-pi-bundled:` virtual namespace as package roots without
|
||||
// a generated registry or duplicate key list.
|
||||
describe("legacy pi compat compiled-mode subpath overrides (issue #3442)", () => {
|
||||
it("does not evaluate unrelated host modules while loading the registry", async () => {
|
||||
using tempDir = TempDir.createSync("@omp-legacy-pi-loaders-");
|
||||
const alphaPath = path.join(tempDir.path(), "alpha.ts");
|
||||
const betaPath = path.join(tempDir.path(), "beta.ts");
|
||||
const registryPath = path.join(tempDir.path(), "registry.ts");
|
||||
await Bun.write(alphaPath, 'Reflect.set(globalThis, "__alphaLoads", 1);\nexport const value = "alpha";\n');
|
||||
await Bun.write(betaPath, 'Reflect.set(globalThis, "__betaLoads", 1);\nexport const value = "beta";\n');
|
||||
const registry = __renderLegacyPiVirtualModule([
|
||||
{ key: "alpha", binding: "bundledAlpha", importSpecifier: url.pathToFileURL(alphaPath).href },
|
||||
{ key: "beta", binding: "bundledBeta", importSpecifier: url.pathToFileURL(betaPath).href },
|
||||
]);
|
||||
await Bun.write(
|
||||
registryPath,
|
||||
`${registry}
|
||||
const beforeAlpha = Reflect.get(globalThis, "__alphaLoads") ?? 0;
|
||||
const beforeBeta = Reflect.get(globalThis, "__betaLoads") ?? 0;
|
||||
await BUNDLED_PI_MODULE_LOADERS.alpha();
|
||||
const afterAlpha = Reflect.get(globalThis, "__alphaLoads") ?? 0;
|
||||
const betaAfterAlpha = Reflect.get(globalThis, "__betaLoads") ?? 0;
|
||||
await BUNDLED_PI_MODULE_LOADERS.beta();
|
||||
process.stdout.write(JSON.stringify([
|
||||
beforeAlpha,
|
||||
beforeBeta,
|
||||
afterAlpha,
|
||||
betaAfterAlpha,
|
||||
Reflect.get(globalThis, "__alphaLoads") ?? 0,
|
||||
Reflect.get(globalThis, "__betaLoads") ?? 0,
|
||||
]));
|
||||
`,
|
||||
);
|
||||
const proc = Bun.spawn([process.execPath, registryPath], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
const [exitCode, stdout, stderr] = await Promise.all([
|
||||
proc.exited,
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(JSON.parse(stdout)).toEqual([0, 0, 1, 0, 1, 1]);
|
||||
});
|
||||
|
||||
it("serves @oh-my-pi/pi-ai/oauth through the bundled virtual namespace in compiled mode", () => {
|
||||
const overrides = __buildLegacyPiPackageRootOverrides(true, bundledModuleKeys);
|
||||
expect(overrides["@oh-my-pi/pi-ai/oauth"]).toBe("omp-legacy-pi-bundled:@oh-my-pi/pi-ai/oauth");
|
||||
|
||||
Reference in New Issue
Block a user