Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion scripts/runtime-provider-env-patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function inspect(runtime: string) {
const match = new RegExp(`^${escapePattern(options)}\\.env\\[(${identifier})\\]\\?\\.trim\\(\\)(\\|\\|void 0)?$`, "u").exec(source);
if (!match) incompatible();
return { name: binding.id.name, key: match[1]!, init: binding.init, source,
original: `${options}.env[${match[1]}]?.trim()`,
replacement: `${options}.env[${match[1]}]?.trim()||void 0` };
});
const builtin = bindings[0]!;
Expand Down Expand Up @@ -74,7 +75,7 @@ function inspect(runtime: string) {
const inserted = runtime.slice(initial.end, initial.end + recovery.length) === recovery;
const patched = inserted && bindings.every(binding => binding.source === binding.replacement) && bundledSource === replacement;
if (runtime.slice(prepare.start, prepare.end).includes(fallbackName) && !patched) incompatible();
return { patched, bindings, initial, bundled: bundled.init, replacement, recovery };
return { patched, bindings, initial, bundled: bundled.init, original, replacement, recovery };
}

export function hasRuntimeBuiltinProviderEnvRecovery(runtime: string): boolean {
Expand All @@ -94,3 +95,18 @@ export function patchRuntimeBuiltinProviderEnvRecovery(runtime: string): string
}
return runtime;
}

/** Restore the upstream bootstrap in test fixtures, preserving all other runtime patches. */
export function unpatchRuntimeBuiltinProviderEnvRecovery(runtime: string): string {
const inspected = inspect(runtime);
if (!inspected.patched) return runtime;
const edits = [
...inspected.bindings.map(binding => ({ start: binding.init.start, end: binding.init.end, text: binding.original })),
{ start: inspected.initial.end, end: inspected.initial.end + inspected.recovery.length, text: "" },
{ start: inspected.bundled.start, end: inspected.bundled.end, text: inspected.original }
];
for (const edit of edits.sort((first, second) => second.start - first.start)) {
runtime = runtime.slice(0, edit.start) + edit.text + runtime.slice(edit.end);
}
return runtime;
}
8 changes: 5 additions & 3 deletions src/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ export async function main(args: string[]): Promise<number> {
process.once("SIGINT", onPluginSigint);
process.once("SIGTERM", onPluginSigterm);
if (process.platform !== "win32") process.once("SIGHUP", onPluginSighup);
const builtinProviderWarnings = new Set<string>();
let pluginCommand: number | undefined;
try {
pluginCommand = await runPluginCommand(args, {
Expand All @@ -541,9 +542,10 @@ export async function main(args: string[]): Promise<number> {
params,
onStderr: stderr => {
for (const line of stderr.split(/\r?\n/u)) {
if (line.startsWith("ZCode Built-in: ignoring unavailable ZCODE_BUILTIN_PROVIDER_CONFIG_FILE ")) {
process.stderr.write(`${line}\n`);
}
if (!line.startsWith("ZCode Built-in: ignoring unavailable ZCODE_BUILTIN_PROVIDER_CONFIG_FILE ")
|| builtinProviderWarnings.has(line)) continue;
builtinProviderWarnings.add(line);
process.stderr.write(`${line}\n`);
}
},
signal: signal ?? pluginAbortController.signal,
Expand Down
13 changes: 12 additions & 1 deletion test/runtime-provider-env-patches.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { hasRuntimeBuiltinProviderEnvRecovery, patchRuntimeBuiltinProviderEnvRecovery } from "../scripts/runtime-provider-env-patches.ts";
import { hasRuntimeBuiltinProviderEnvRecovery, patchRuntimeBuiltinProviderEnvRecovery, unpatchRuntimeBuiltinProviderEnvRecovery } from "../scripts/runtime-provider-env-patches.ts";

const source = [
'var active="ZCODE_BUILTIN_PROVIDER_CONFIG_FILE",bundled="ZCODE_BUILTIN_PROVIDER_BUNDLED_CONFIG_FILE",personal="ZCODE_PERSONAL_PROVIDER_CONFIG_FILE";',
Expand Down Expand Up @@ -40,6 +40,16 @@ describe("built-in provider environment recovery", () => {
expect(patchRuntimeBuiltinProviderEnvRecovery(patched)).toBe(patched);
});

test("restores the unpatched bootstrap without removing unrelated runtime changes", () => {
const baseline = `${source};const unrelatedPatch=true;`;
const patched = patchRuntimeBuiltinProviderEnvRecovery(baseline);
const restored = unpatchRuntimeBuiltinProviderEnvRecovery(patched);
expect(restored).toBe(baseline);
expect(hasRuntimeBuiltinProviderEnvRecovery(restored)).toBe(false);
expect(unpatchRuntimeBuiltinProviderEnvRecovery(restored)).toBe(restored);
expect(patchRuntimeBuiltinProviderEnvRecovery(restored)).toBe(patched);
});

test.each([undefined, "", " \t "])("discovers bundled config for an unset or blank override (%j)", async value => {
const host = fixture();
const env: Record<string, string> = value === undefined ? {} : { ZCODE_BUILTIN_PROVIDER_CONFIG_FILE: value };
Expand Down Expand Up @@ -122,5 +132,6 @@ describe("built-in provider environment recovery", () => {
const partial = patchRuntimeBuiltinProviderEnvRecovery(source).replace("using bundled catalog", "partial recovery");
expect(hasRuntimeBuiltinProviderEnvRecovery(partial)).toBe(false);
expect(() => patchRuntimeBuiltinProviderEnvRecovery(partial)).toThrow("incompatible");
expect(() => unpatchRuntimeBuiltinProviderEnvRecovery(partial)).toThrow("incompatible");
});
});
62 changes: 50 additions & 12 deletions test/runtime/builtin-provider-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import { mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from "node:
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { hasRuntimeBuiltinProviderEnvRecovery, patchRuntimeBuiltinProviderEnvRecovery } from "../../scripts/runtime-provider-env-patches.ts";
import { hasRuntimeBuiltinProviderEnvRecovery, patchRuntimeBuiltinProviderEnvRecovery, unpatchRuntimeBuiltinProviderEnvRecovery } from "../../scripts/runtime-provider-env-patches.ts";
import { runtimeTestEnv } from "../fixtures/runtime-env.ts";

const root = fileURLToPath(new URL("../..", import.meta.url));
const node = Bun.which("node");
const endpoint = "http://127.0.0.1:9";
let directory = "", packageRoot = "", builtinContent = "";
let baselineUnpatched = false;

beforeAll(async () => {
directory = await mkdtemp(join(tmpdir(), "zcode-builtin-env-"));
Expand All @@ -25,10 +24,15 @@ beforeAll(async () => {
}
}
const original = await readFile(join(root, "vendor/zcode.cjs"), "utf8");
baselineUnpatched = !hasRuntimeBuiltinProviderEnvRecovery(original);
const baseline = unpatchRuntimeBuiltinProviderEnvRecovery(original);
const patched = patchRuntimeBuiltinProviderEnvRecovery(original);
expect(hasRuntimeBuiltinProviderEnvRecovery(baseline)).toBe(false);
if (patchRuntimeBuiltinProviderEnvRecovery(baseline) !== patched) {
throw new Error("Unpatched runtime fixture did not preserve the other runtime patches.");
}
builtinContent = await readFile(join(root, "vendor/provider/zcode-builtin.json"), "utf8");
await writeFile(join(vendor, "zcode.cjs"), patchRuntimeBuiltinProviderEnvRecovery(original));
await writeFile(join(vendor, "unpatched.cjs"), original);
await writeFile(join(vendor, "zcode.cjs"), patched);
await writeFile(join(vendor, "unpatched.cjs"), baseline);
await writeFile(join(vendor, "provider/zcode-builtin.json"), builtinContent);
await writeFile(join(packageRoot, "package.json"), JSON.stringify({ type: "module", version: "0.0.0-test" }));
const build = await Bun.build({ entrypoints: [join(root, "bin/zcode.ts")], outdir: join(packageRoot, "bin"), target: "node", format: "esm" });
Expand Down Expand Up @@ -145,18 +149,52 @@ test("fails with a diagnostic when neither override nor bundled fallback is usab
expect(result.stderr).toContain("no valid bundled fallback");
}, 10000);

test("recovers the real launcher plugin-validation path, reproducing failure when an unpatched runtime is available", async () => {
test("reproduces an unavailable builtin override failure with the unpatched runtime", async () => {
const { home, env } = await homeEnv(false);
env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE = join(home, "stale.json");
const control = await run(home, env, [join(packageRoot, "vendor/unpatched.cjs"), "app-server"]);
expect(control.code).toBe(1);
expect(control.stderr).toContain("均不可用");
}, 10000);

test("recovers the real launcher plugin-validation source path with one warning", async () => {
const { home, env } = await homeEnv(false);
env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE = join(home, "stale.json");
if (baselineUnpatched) {
const control = await run(home, env, [join(packageRoot, "vendor/unpatched.cjs"), "app-server"]);
expect(control.code).toBe(1);
expect(control.stderr).toContain("均不可用");
}
const marketplace = join(home, "marketplace.json");
await writeFile(marketplace, JSON.stringify({ name: "builtin-env-fixture", plugins: [] }));
const fixed = await run(home, env, [join(packageRoot, "bin/zcode.js"), "plugins", "validate", "--source", marketplace], false);
expect(fixed.code).toBe(0);
expect(fixed.stdout).toContain("Plugin validation completed.");
expect(fixed.stderr).toContain("using bundled catalog");
expect(fixed.stderr.match(/using bundled catalog/gu)).toHaveLength(1);
}, 15000);

test("warns once per command when plugin-coordinate validation starts multiple app-servers", async () => {
const { home, env } = await homeEnv(false);
const marketplace = join(home, "marketplace");
const plugin = join(marketplace, "plugin");
await mkdir(join(plugin, ".zcode-plugin"), { recursive: true });
await mkdir(join(plugin, "skills", "probe"), { recursive: true });
await writeFile(join(marketplace, "marketplace.json"), JSON.stringify({
name: "builtin-env-fixture", pluginRoot: ".",
plugins: [{ name: "probe", source: "./plugin", version: "1.0.0", description: "Validation fixture" }]
}));
await writeFile(join(plugin, ".zcode-plugin", "plugin.json"), JSON.stringify({
name: "probe", version: "1.0.0", description: "Validation fixture", skills: "skills"
}));
await writeFile(join(plugin, "skills", "probe", "SKILL.md"),
"---\nname: probe\ndescription: Validation fixture\n---\nValidation fixture.\n");
const launcher = join(packageRoot, "bin/zcode.js");
const added = await run(home, env, [launcher, "plugins", "marketplace", "add", marketplace, "--yes"], false);
expect(added.code).toBe(0);
expect(added.stderr).not.toContain("using bundled catalog");

env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE = join(home, "stale.json");
for (const json of [false, true]) {
const result = await run(home, env,
[launcher, "plugins", "validate", "probe@builtin-env-fixture", ...json ? ["--json"] : []], false);
expect(result.code).toBe(0);
if (json) expect(JSON.parse(result.stdout)).toMatchObject({ diagnostics: [] });
else expect(result.stdout).toContain("Plugin validation completed.");
expect(result.stderr.match(/using bundled catalog/gu)).toHaveLength(1);
}
}, 30000);
Loading