From 4422743cf7233c418f15dc299e3e13ef17fe832c Mon Sep 17 00:00:00 2001 From: Phototonic <35551999+Phototonic@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:38:26 +1100 Subject: [PATCH 1/2] test: isolate bundle builds from the Bun test runner --- tests/plugin-bundle-boundary.test.ts | 45 +++++++++++----------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/tests/plugin-bundle-boundary.test.ts b/tests/plugin-bundle-boundary.test.ts index 2e536b55..b85c488c 100644 --- a/tests/plugin-bundle-boundary.test.ts +++ b/tests/plugin-bundle-boundary.test.ts @@ -2,9 +2,21 @@ import { afterEach, describe, expect, it } from "bun:test"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { pathToFileURL } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; const tempDirs: string[] = []; +const projectRoot = fileURLToPath(new URL("../", import.meta.url)); + +function buildBundle(entrypoint: string): string { + // Keep bundling outside the test runner's resolver cache (oven-sh/bun#22941). + // Use the current Bun executable and retain package bundling for these guards. + const result = Bun.spawnSync( + [process.execPath, "build", entrypoint, "--target=bun", "--packages=bundle"], + { cwd: projectRoot } + ); + expect(result.exitCode, result.stderr.toString()).toBe(0); + return result.stdout.toString(); +} afterEach(() => { for (const dir of tempDirs.splice(0)) { @@ -13,15 +25,8 @@ afterEach(() => { }); describe("OpenCode plugin loader bundle boundary", () => { - it("does not pull local embedding transformer internals into the plugin-loader bundle", async () => { - const result = await Bun.build({ - entrypoints: ["./dist/plugin.js"], - target: "bun", - packages: "bundle", - }); - - expect(result.success).toBe(true); - const output = await result.outputs[0]!.text(); + it("does not pull local embedding transformer internals into the plugin-loader bundle", () => { + const output = buildBundle("./dist/plugin.js"); expect(output).not.toContain("node_modules/@huggingface/transformers"); expect(output).not.toContain("@huggingface/transformers/src"); expect(output).not.toContain("@huggingface/transformers/dist"); @@ -29,15 +34,8 @@ describe("OpenCode plugin loader bundle boundary", () => { expect(output).not.toContain("node_modules/@xenova/transformers"); }, 30_000); - it("does not pull opencode SDK or OIDC internals into the plugin-loader bundle", async () => { - const result = await Bun.build({ - entrypoints: ["./dist/plugin.js"], - target: "bun", - packages: "bundle", - }); - - expect(result.success).toBe(true); - const output = await result.outputs[0]!.text(); + it("does not pull opencode SDK or OIDC internals into the plugin-loader bundle", () => { + const output = buildBundle("./dist/plugin.js"); expect(output).not.toContain("@opencode-ai/sdk/v2/client"); expect(output).not.toContain("node_modules/@opencode-ai/sdk"); expect(output).not.toContain("@vercel/oidc"); @@ -45,14 +43,7 @@ describe("OpenCode plugin loader bundle boundary", () => { }, 30_000); it("resolves the provider module from a single-file bundled lazy loader", async () => { - const result = await Bun.build({ - entrypoints: ["./dist/services/ai/opencode-provider-loader.js"], - target: "bun", - packages: "bundle", - }); - - expect(result.success).toBe(true); - const output = await result.outputs[0]!.text(); + const output = buildBundle("./dist/services/ai/opencode-provider-loader.js"); expect(output).not.toContain("@opencode-ai/sdk/v2/client"); expect(output).not.toContain("@vercel/oidc"); From 91c74977db16f76851c590a9fee123bb1856473c Mon Sep 17 00:00:00 2001 From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:42:53 +0100 Subject: [PATCH 2/2] test: harden subprocess bundle build helper Read the CLI bundle from a temp --outfile instead of stdout, pipe stdio explicitly, reject empty output, and cache per entrypoint so the two plugin.js assertions share one subprocess build. --- tests/plugin-bundle-boundary.test.ts | 41 ++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/tests/plugin-bundle-boundary.test.ts b/tests/plugin-bundle-boundary.test.ts index b85c488c..89f6fd91 100644 --- a/tests/plugin-bundle-boundary.test.ts +++ b/tests/plugin-bundle-boundary.test.ts @@ -1,21 +1,44 @@ import { afterEach, describe, expect, it } from "bun:test"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; const tempDirs: string[] = []; const projectRoot = fileURLToPath(new URL("../", import.meta.url)); +const bundleCache = new Map(); function buildBundle(entrypoint: string): string { - // Keep bundling outside the test runner's resolver cache (oven-sh/bun#22941). - // Use the current Bun executable and retain package bundling for these guards. - const result = Bun.spawnSync( - [process.execPath, "build", entrypoint, "--target=bun", "--packages=bundle"], - { cwd: projectRoot } - ); - expect(result.exitCode, result.stderr.toString()).toBe(0); - return result.stdout.toString(); + const cached = bundleCache.get(entrypoint); + if (cached !== undefined) { + return cached; + } + + // Isolate from the test-runner resolver cache (oven-sh/bun#22941) via a fresh + // Bun process. Prefer --outfile over stdout (stdout is the status line then). + const dir = mkdtempSync(join(tmpdir(), "opencode-mem-bundle-")); + tempDirs.push(dir); + const outfile = join(dir, "bundle.js"); + const result = Bun.spawnSync({ + cmd: [ + process.execPath, + "build", + entrypoint, + "--target=bun", + "--packages=bundle", + `--outfile=${outfile}`, + ], + cwd: projectRoot, + stdout: "pipe", + stderr: "pipe", + }); + const stderr = Buffer.from(result.stderr).toString("utf8"); + expect(result.exitCode, stderr).toBe(0); + + const output = readFileSync(outfile, "utf8"); + expect(output.length).toBeGreaterThan(0); + bundleCache.set(entrypoint, output); + return output; } afterEach(() => {