fix(production): preserve emitted plugin runtimes

This commit is contained in:
2026-08-13 18:00:30 +05:30
parent 94a6b436f5
commit 5106256401
6 changed files with 110 additions and 4 deletions
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/cli",
"version": "0.8.31",
"version": "0.8.32",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -10,6 +10,9 @@
"bin": {
"wrnexus": "src/index.ts"
},
"files": [
"src"
],
"dependencies": {
"@wrnexus/core": "workspace:*",
"@wrnexus/router": "workspace:*",
+5 -1
View File
@@ -961,7 +961,11 @@ async function emitPluginAssets(
runtime: true,
immutable: true,
});
runtimes.push({ ...runtime, entry: undefined, source: undefined, publicPath });
// The emitted runtime is served from its content-addressed public path.
// Keep an empty source marker so @wrnexus/plugin can normalize this
// already-emitted descriptor during production HTML rendering without
// trying to resolve the original build-time source on the deploy host.
runtimes.push({ ...runtime, entry: undefined, source: "", publicPath });
}
for (const asset of contributions.assets) {
@@ -0,0 +1,88 @@
import { afterAll, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { runBuild } from "../src/build.ts";
const scratchRoot = join(import.meta.dir, ".tmp-plugin-runtime-production");
mkdirSync(scratchRoot, { recursive: true });
afterAll(() => rmSync(scratchRoot, { recursive: true, force: true }));
test("production renders and serves a content-addressed plugin runtime", async () => {
const root = mkdtempSync(join(scratchRoot, "app-"));
mkdirSync(join(root, "app", "pages"), { recursive: true });
mkdirSync(join(root, "app", "components"), { recursive: true });
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "plugin-runtime-fixture" }));
writeFileSync(
join(root, "wrnexus.config.ts"),
`import { definePlugin } from "@wrnexus/plugin";
export default {
plugins: [definePlugin({
name: "runtime-fixture",
clientRuntimes: [{
id: "runtime-fixture",
source: "window.__runtimeFixture = true;",
type: "script",
bundle: false,
}],
})],
};
`,
);
writeFileSync(
join(root, "app", "components", "RuntimeProbe.wrn"),
`component RuntimeProbe { view { <main {...attrs} data-wrnexus-runtime="runtime-fixture">Runtime fixture</main> } }\n`,
);
writeFileSync(
join(root, "app", "pages", "index.wrn"),
`import RuntimeProbe from "../components/RuntimeProbe.wrn"\npage Home { view { <RuntimeProbe /> } }\n`,
);
await runBuild(root);
const proc = Bun.spawn({
cmd: ["bun", join(root, "dist", "server.js")],
env: { ...process.env, PORT: "0" },
stdout: "pipe",
stderr: "pipe",
cwd: root,
});
let port: number | undefined;
try {
const reader = proc.stdout.getReader();
const decoder = new TextDecoder();
let output = "";
const giveUp = setTimeout(() => proc.kill(), 20_000);
try {
while (port === undefined) {
const { value, done } = await reader.read();
if (done) break;
output += decoder.decode(value, { stream: true });
const match = /listening on http:\/\/[^:]+:(\d+)/.exec(output);
if (match) port = Number(match[1]);
}
} finally {
clearTimeout(giveUp);
reader.releaseLock();
}
if (port === undefined) {
throw new Error(`production server failed to start: ${await new Response(proc.stderr).text()}`);
}
const page = await fetch(`http://127.0.0.1:${port}/`);
expect(page.status).toBe(200);
const html = await page.text();
const assetPath = html.match(/(\/__wrnexus\/assets\/runtime-fixture\.[a-f0-9]+\.js)/)?.[1];
expect(assetPath).toBeTruthy();
const asset = await fetch(`http://127.0.0.1:${port}${assetPath}`);
expect(asset.status).toBe(200);
expect(await asset.text()).toContain("window.__runtimeFixture = true");
} finally {
proc.kill();
await proc.exited;
}
}, 30_000);