fix(production): preserve emitted plugin runtimes
This commit is contained in:
@@ -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:*",
|
||||
|
||||
@@ -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);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.8.11",
|
||||
"version": "0.8.12",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -188,6 +188,7 @@ export function generateQueriesFile(
|
||||
): string {
|
||||
const byTable = new Map(models.map((m) => [m.model.name, m]));
|
||||
const usedModels = new Set<string>();
|
||||
let usesExecResult = false;
|
||||
const blocks: string[] = [];
|
||||
|
||||
for (const q of queries) {
|
||||
@@ -202,6 +203,7 @@ export function generateQueriesFile(
|
||||
const sig = argFields.length ? `db: Db, args: { ${argFields.join("; ")} }` : "db: Db";
|
||||
|
||||
if (q.kind === "exec") {
|
||||
usesExecResult = true;
|
||||
blocks.push(
|
||||
`export async function ${q.name}(${sig}): Promise<ExecResult> {\n return db.exec(${sqlLit}, ${positional});\n}`,
|
||||
);
|
||||
@@ -224,7 +226,9 @@ export function generateQueriesFile(
|
||||
);
|
||||
}
|
||||
|
||||
const imports = [`import type { Db, ExecResult } from "@wrnexus/db";`];
|
||||
const imports = [
|
||||
`import type { Db${usesExecResult ? ", ExecResult" : ""} } from "@wrnexus/db";`,
|
||||
];
|
||||
if (usedModels.size > 0) {
|
||||
imports.push(`import { ${[...usedModels].sort().join(", ")} } from "./schema.ts";`);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,13 @@ test("query generator infers params and result types", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("query generator omits ExecResult when there are no exec queries", () => {
|
||||
const queries = parseQueries("-- name: ListUsers :many\nSELECT * FROM users;");
|
||||
const code = generateQueriesFile(queries, [{ varName: "users", model: users }], "sqlite");
|
||||
expect(code).toContain('import type { Db } from "@wrnexus/db";');
|
||||
expect(code).not.toContain("ExecResult");
|
||||
});
|
||||
|
||||
test("paginate returns a page window with correct metadata", async () => {
|
||||
const { paginate } = await import("../src/index.ts");
|
||||
const db = createDb(sqlite());
|
||||
|
||||
Reference in New Issue
Block a user