refactor: migrate legacy wire namespace to wrn
This commit is contained in:
@@ -6,7 +6,7 @@ Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web fr
|
||||
|
||||
## Overview
|
||||
|
||||
This package is the server runtime that powers a WrNexus app in both development and production. A single **request runtime** (`createHandlers`) owns HTTP/WebSocket dispatch and SSR document assembly; it knows nothing about _how_ modules and assets are produced, so the dev and prod entry points wire in different backends: dev uses dynamic module loading plus on-the-fly bundling and injects a live-reload client; prod uses a static, pre-built manifest with cache-immutable assets. The package also ships a multi-app **gateway** (route several apps by `Host` header behind one port) and a portable `node:http` adapter for WinterCG hosts. It is entirely server-side and Bun-native (`Bun.serve`, `Bun.file`, `Bun.gzipSync`).
|
||||
This package is the server runtime that powers a WrNexus app in both development and production. A single **request runtime** (`createHandlers`) owns HTTP/WebSocket dispatch and SSR document assembly; it knows nothing about _how_ modules and assets are produced, so the dev and prod entry points wrn in different backends: dev uses dynamic module loading plus on-the-fly bundling and injects a live-reload client; prod uses a static, pre-built manifest with cache-immutable assets. The package also ships a multi-app **gateway** (route several apps by `Host` header behind one port) and a portable `node:http` adapter for WinterCG hosts. It is entirely server-side and Bun-native (`Bun.serve`, `Bun.file`, `Bun.gzipSync`).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -21,16 +21,16 @@ bun add @wrnexus/dev-server
|
||||
|
||||
### Main entry (`@wrnexus/dev-server`)
|
||||
|
||||
| Export | Kind | Purpose |
|
||||
| --------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `startServer(opts: ServeOptions)` | `Promise<RunningServer>` | Start the dev server on `Bun.serve`: builds the router, connects/migrates databases, wires assets + HMR, and starts the file watcher. |
|
||||
| `createHandlers(deps: RuntimeDeps)` | `Handlers` | The shared request runtime (fetch + websocket handlers). Re-exported from `runtime.ts`. |
|
||||
| `createProductionServer(manifest, opts)` | `Bun.Server` | Start the production server from a precompiled manifest. |
|
||||
| `createProductionHandlers(manifest, opts)` | `Handlers` | Build the portable prod fetch/websocket handlers with no server bound (the deployment-adapter seam). |
|
||||
| `startGateway(opts: GatewayOptions)` | `Promise<RunningGateway>` | Boot multiple apps as child processes and route by `Host`. |
|
||||
| `toRequest`, `writeResponse`, `nodeListener`, `serveNode` | functions | `node:http` ↔ WinterCG `Request`/`Response` adapter. |
|
||||
| `RESTART_EXIT_CODE` | `number` (`97`) | Exit code the dev child uses to ask the supervisor for a fresh process. |
|
||||
| `STYLES_HREF`, `HMR_CLIENT_JS` | constants | The global stylesheet URL and the inline HMR client script. |
|
||||
| Export | Kind | Purpose |
|
||||
| --------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `startServer(opts: ServeOptions)` | `Promise<RunningServer>` | Start the dev server on `Bun.serve`: builds the router, connects/migrates databases, connects assets + HMR, and starts the file watcher. |
|
||||
| `createHandlers(deps: RuntimeDeps)` | `Handlers` | The shared request runtime (fetch + websocket handlers). Re-exported from `runtime.ts`. |
|
||||
| `createProductionServer(manifest, opts)` | `Bun.Server` | Start the production server from a precompiled manifest. |
|
||||
| `createProductionHandlers(manifest, opts)` | `Handlers` | Build the portable prod fetch/websocket handlers with no server bound (the deployment-adapter seam). |
|
||||
| `startGateway(opts: GatewayOptions)` | `Promise<RunningGateway>` | Boot multiple apps as child processes and route by `Host`. |
|
||||
| `toRequest`, `writeResponse`, `nodeListener`, `serveNode` | functions | `node:http` ↔ WinterCG `Request`/`Response` adapter. |
|
||||
| `RESTART_EXIT_CODE` | `number` (`97`) | Exit code the dev child uses to ask the supervisor for a fresh process. |
|
||||
| `STYLES_HREF`, `HMR_CLIENT_JS` | constants | The global stylesheet URL and the inline HMR client script. |
|
||||
|
||||
Exported types: `ServeOptions`, `RunningServer`, `RuntimeDeps`, `AssetServer`, `WsData`, `GatewayApp`, `GatewayOptions`, `GatewayAuth`, `GatewaySecurity`, `RunningGateway`, `FetchHandler`.
|
||||
|
||||
@@ -85,7 +85,7 @@ interface RuntimeDeps {
|
||||
getMiddleware(): Promise<Middleware[]>;
|
||||
assets: AssetServer; // serves /__wrnexus/* (islands, reactive, hmr)
|
||||
hasStyles?: boolean; // inject the global stylesheet link
|
||||
hasUi?: boolean; // inject the Wire UI stylesheet (/__wrnexus/ui.css)
|
||||
hasUi?: boolean; // inject the WrNexus UI stylesheet (/__wrnexus/ui.css)
|
||||
theme?: ResolvedTheme; // enables /__wrnexus/theme.css + <html data-theme>
|
||||
i18n?: ResolvedI18n; // enables ctx.t, <html lang>, {t:key} markers
|
||||
inlineStyles?: string; // inline small prod stylesheets into <head>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.8.15",
|
||||
"version": "0.8.16",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -80,7 +80,7 @@ export function createDevAssetServer(
|
||||
pluginAssets: readonly ServedPluginAsset[] = [],
|
||||
): DevAssetServer {
|
||||
let cssCache: string | null = null;
|
||||
let schemasCode = schemasJs ?? "window.__wireSchemas={};";
|
||||
let schemasCode = schemasJs ?? "window.__wrnSchemas={};";
|
||||
|
||||
return {
|
||||
invalidateCss() {
|
||||
|
||||
@@ -567,7 +567,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
if (origin && origin !== url.origin) return false;
|
||||
const cookieHeader = request.headers.get("cookie") ?? "";
|
||||
const cookieToken =
|
||||
/(?:^|;\s*)wire-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
|
||||
/(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
|
||||
/(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1];
|
||||
const headerToken =
|
||||
request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf") ?? "";
|
||||
|
||||
@@ -160,7 +160,7 @@ function rewriteArtifactImports(
|
||||
if (entry.resolved.endsWith(".wrn")) {
|
||||
const dependencySource = readFileSync(entry.resolved, "utf8");
|
||||
const isStore = /\b(?:global|page)\s+store\s+[A-Za-z_$][\w$]*\s*\{/.test(dependencySource);
|
||||
const dependency = compileWireArtifacts(
|
||||
const dependency = compileWrnArtifacts(
|
||||
entry.resolved,
|
||||
moduleVersions.get(entry.resolved) ?? 0,
|
||||
);
|
||||
@@ -230,7 +230,7 @@ async function rewriteArtifactImportsAsync(
|
||||
if (replacement.endsWith(".wrn")) {
|
||||
const dependencySource = readFileSync(replacement, "utf8");
|
||||
const isStore = /\b(?:global|page)\s+store\s+[A-Za-z_$][\w$]*\s*\{/.test(dependencySource);
|
||||
const dependency = await compileWireArtifactsAsync(
|
||||
const dependency = await compileWrnArtifactsAsync(
|
||||
replacement,
|
||||
moduleVersions.get(replacement) ?? 0,
|
||||
);
|
||||
@@ -266,7 +266,7 @@ export async function loadModule(file: string): Promise<Record<string, unknown>>
|
||||
const version = moduleVersions.get(file) ?? 0;
|
||||
// `.wrn` files are compiled to TypeScript first, then imported.
|
||||
let target = file.endsWith(".wrn")
|
||||
? (await compileWireArtifactsAsync(file, version)).main
|
||||
? (await compileWrnArtifactsAsync(file, version)).main
|
||||
: file;
|
||||
let temporary = false;
|
||||
// Bun intentionally caches local TS/JS modules by filesystem path and ignores
|
||||
@@ -441,13 +441,13 @@ async function bundleBrowserArtifact(
|
||||
}
|
||||
}
|
||||
|
||||
export function compileWireArtifactsAsync(file: string, version = 0): Promise<WrnCompileArtifacts> {
|
||||
export function compileWrnArtifactsAsync(file: string, version = 0): Promise<WrnCompileArtifacts> {
|
||||
const key = `${file}:${version}`;
|
||||
const active = asyncCompileInProgress.get(key);
|
||||
if (active) return active;
|
||||
const task = (async () => {
|
||||
if (!devCompilerPipeline) {
|
||||
const artifacts = compileWireArtifacts(file, version);
|
||||
const artifacts = compileWrnArtifacts(file, version);
|
||||
const code = readFileSync(artifacts.browser, "utf8");
|
||||
const bundled = await bundleBrowserArtifact(
|
||||
code,
|
||||
@@ -525,7 +525,7 @@ export function resetWrnCompileMetrics(): void {
|
||||
});
|
||||
}
|
||||
|
||||
export function compileWireArtifacts(file: string, version = 0): WrnCompileArtifacts {
|
||||
export function compileWrnArtifacts(file: string, version = 0): WrnCompileArtifacts {
|
||||
const active = compileInProgress.get(file);
|
||||
if (active) return active;
|
||||
const cacheDir = compileCacheDir ?? join(dirname(file), ".wrnexus");
|
||||
@@ -598,22 +598,22 @@ export function compileWireArtifacts(file: string, version = 0): WrnCompileArtif
|
||||
|
||||
export async function loadWrnServerModule(file: string): Promise<Record<string, unknown>> {
|
||||
const version = moduleVersions.get(file) ?? 0;
|
||||
const artifact = compileWireArtifacts(file, version).server;
|
||||
const artifact = compileWrnArtifacts(file, version).server;
|
||||
return import(pathToFileURL(artifact).href) as Promise<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export function wrnBrowserArtifact(file: string): string {
|
||||
return compileWireArtifacts(file, moduleVersions.get(file) ?? 0).browser;
|
||||
return compileWrnArtifacts(file, moduleVersions.get(file) ?? 0).browser;
|
||||
}
|
||||
|
||||
export function wrnBrowserArtifactUrl(file: string): string {
|
||||
const artifact = compileWireArtifacts(file, moduleVersions.get(file) ?? 0).browser;
|
||||
const artifact = compileWrnArtifacts(file, moduleVersions.get(file) ?? 0).browser;
|
||||
return `/__wrnexus/client/${basename(artifact).replace(/\.client\.mjs$/, ".mjs")}`;
|
||||
}
|
||||
|
||||
/** Async browser artifact URL used by HMR so imported client modules are bundled before delivery. */
|
||||
export async function wrnBrowserArtifactUrlAsync(file: string): Promise<string> {
|
||||
const artifact = (await compileWireArtifactsAsync(file, moduleVersions.get(file) ?? 0)).browser;
|
||||
const artifact = (await compileWrnArtifactsAsync(file, moduleVersions.get(file) ?? 0)).browser;
|
||||
return `/__wrnexus/client/${basename(artifact).replace(/\.client\.mjs$/, ".mjs")}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export interface ProdOptions {
|
||||
inlineStyles?: string;
|
||||
/** `stylesPath` contains theme + UI + app CSS in cascade order. */
|
||||
stylesIncludeFramework?: boolean;
|
||||
/** `stylesPath` already contains the shared Wire UI stylesheet. */
|
||||
/** `stylesPath` already contains the shared WrNexus UI stylesheet. */
|
||||
stylesIncludeUi?: boolean;
|
||||
/** Absolute path to the pre-built reactive runtime. */
|
||||
reactivePath?: string;
|
||||
@@ -107,11 +107,11 @@ export interface ProdOptions {
|
||||
themeJsPath?: string;
|
||||
/** Resolved theme config: enables `<html data-theme>` + `theme.css` link. */
|
||||
theme?: ResolvedTheme;
|
||||
/** Absolute path to the pre-built Wire UI stylesheet (`ui.css`). */
|
||||
/** Absolute path to the pre-built WrNexus UI stylesheet (`ui.css`). */
|
||||
uiCssPath?: string;
|
||||
/** Combined production theme + Wire UI stylesheet. */
|
||||
/** Combined production theme + WrNexus UI stylesheet. */
|
||||
frameworkCssPath?: string;
|
||||
/** Pre-built `window.__wireSchemas = {...}` script for client validation. */
|
||||
/** Pre-built `window.__wrnSchemas = {...}` script for client validation. */
|
||||
schemasJs?: string;
|
||||
/**
|
||||
* Authorization declarations discovered by `wrnexus build` from
|
||||
@@ -367,7 +367,7 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
|
||||
return (await serveStoredFile(pathname)) ?? new Response("Not Found", { status: 404 });
|
||||
}
|
||||
if (pathname === "/__wrnexus/schemas.js") {
|
||||
return new Response(opts.schemasJs ?? "window.__wireSchemas={};", { headers: JS_HEADERS });
|
||||
return new Response(opts.schemasJs ?? "window.__wrnSchemas={};", { headers: JS_HEADERS });
|
||||
}
|
||||
if (pathname === "/__wrnexus/theme.css") return serveFile(opts.themePath, CSS_HEADERS);
|
||||
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
|
||||
@@ -524,7 +524,7 @@ export function createProductionHandlers(
|
||||
/**
|
||||
* Apply migrations bundled into the build before the server accepts traffic, so
|
||||
* a fresh deploy always runs on the latest schema — exactly like the dev server
|
||||
* auto-migrates on startup. Applied migrations are tracked in `_wire_migrations`,
|
||||
* auto-migrates on startup. Applied migrations are tracked in `_wrn_migrations`,
|
||||
* so this is idempotent and safe to run on every boot. Opt out with
|
||||
* `autoMigrate: false` (e.g. multi-instance deploys that migrate in a release
|
||||
* step). A failed migration is logged but does not crash the server: each
|
||||
|
||||
@@ -4,8 +4,8 @@ import { brotliCompressSync, constants as zlibConstants } from "node:zlib";
|
||||
*
|
||||
* It owns the HTTP/WebSocket dispatch and the SSR document assembly, but knows
|
||||
* nothing about *how* modules or assets are produced — those come in via
|
||||
* `RuntimeDeps`. Dev wires in dynamic module loading + on-the-fly bundling;
|
||||
* prod wires in a static manifest + pre-built chunks on disk.
|
||||
* `RuntimeDeps`. Dev connects in dynamic module loading + on-the-fly bundling;
|
||||
* prod connects in a static manifest + pre-built chunks on disk.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -154,7 +154,7 @@ export interface RuntimeDeps {
|
||||
assets: AssetServer;
|
||||
/** When true, inject the global stylesheet link into every page head. */
|
||||
hasStyles?: boolean;
|
||||
/** When true, inject the Wire UI stylesheet link (`/__wrnexus/ui.css`). */
|
||||
/** When true, inject the WrNexus UI stylesheet link (`/__wrnexus/ui.css`). */
|
||||
hasUi?: boolean;
|
||||
/** Production build combined theme + UI stylesheet. */
|
||||
hasFrameworkStyles?: boolean;
|
||||
@@ -705,10 +705,10 @@ export const HMR_CLIENT_JS = `
|
||||
}
|
||||
|
||||
if (
|
||||
window.wireTheme &&
|
||||
typeof window.wireTheme.bind === "function"
|
||||
window.wrnTheme &&
|
||||
typeof window.wrnTheme.bind === "function"
|
||||
) {
|
||||
window.wireTheme.bind(document);
|
||||
window.wrnTheme.bind(document);
|
||||
}
|
||||
|
||||
window.dispatchEvent(
|
||||
@@ -938,7 +938,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
|
||||
// Global <head> additions, identical on every page (styles are global).
|
||||
// Theme tokens load first so global.css and component styles can override them.
|
||||
// Order: theme tokens, then Wire UI, then the app stylesheet — so the app's
|
||||
// Order: theme tokens, then WrNexus UI, then the app stylesheet — so the app's
|
||||
// own CSS (loaded last) can override both the tokens and the UI classes.
|
||||
const headParts: string[] = [];
|
||||
if (!deps.theme && deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
|
||||
|
||||
@@ -21,15 +21,15 @@ export function collectScripts(
|
||||
}
|
||||
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
|
||||
if (
|
||||
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
|
||||
/\bdata-wire-accent-(set|clear)\b/.test(body)
|
||||
/\bdata-wrn-theme-(toggle|set)\b/.test(body) ||
|
||||
/\bdata-wrn-accent-(set|clear)\b/.test(body)
|
||||
) {
|
||||
scripts.push(THEME_JS_HREF);
|
||||
}
|
||||
if (/\bdata-schema="[A-Za-z0-9_-]+"/.test(body)) {
|
||||
scripts.push("/__wrnexus/schemas.js", "/__wrnexus/validate.js");
|
||||
}
|
||||
if (/\bdata-wire-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wire-lang\b/.test(body)) {
|
||||
if (/\bdata-wrn-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wrn-lang\b/.test(body)) {
|
||||
scripts.push(I18N_JS_HREF);
|
||||
}
|
||||
if (/\bdata-room=/.test(body)) scripts.push("/__wrnexus/realtime.js");
|
||||
|
||||
@@ -54,7 +54,7 @@ test("server actions validate, enforce CSRF, invalidate, and progressively enhan
|
||||
expect(html).toContain('name="wrnexus-csrf"');
|
||||
expect(html).toContain("/__wrnexus/actions.js");
|
||||
const cookie = page!.headers.get("set-cookie")!;
|
||||
const token = /wire-csrf=([^;]+)/.exec(cookie)?.[1];
|
||||
const token = /wrn-csrf=([^;]+)/.exec(cookie)?.[1];
|
||||
if (!token) throw new Error("expected CSRF cookie");
|
||||
|
||||
const invalid = await handlers.fetch(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "nod
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
compileWireArtifactsAsync,
|
||||
compileWrnArtifactsAsync,
|
||||
setCompileCacheDir,
|
||||
setCompileImportOptions,
|
||||
} from "../src/pipeline.ts";
|
||||
@@ -27,7 +27,7 @@ page Settings {
|
||||
|
||||
try {
|
||||
setCompileCacheDir(cache);
|
||||
const artifacts = await compileWireArtifactsAsync(page);
|
||||
const artifacts = await compileWrnArtifactsAsync(page);
|
||||
const browser = readFileSync(artifacts.browser, "utf8");
|
||||
expect(browser).toContain("wrnexus-client-bundled");
|
||||
expect(browser).not.toContain("file://");
|
||||
@@ -65,7 +65,7 @@ page Settings {
|
||||
aliases: { "~": "./app" },
|
||||
autoImport: false,
|
||||
});
|
||||
const artifacts = await compileWireArtifactsAsync(page);
|
||||
const artifacts = await compileWrnArtifactsAsync(page);
|
||||
const browser = readFileSync(artifacts.browser, "utf8");
|
||||
expect(browser).toContain("wrnexus-client-bundled");
|
||||
expect(browser).not.toContain("~/client/storage.ts");
|
||||
@@ -81,7 +81,7 @@ page Settings {
|
||||
aliases: { "~": "./alternate" },
|
||||
autoImport: false,
|
||||
});
|
||||
const changed = await compileWireArtifactsAsync(page);
|
||||
const changed = await compileWrnArtifactsAsync(page);
|
||||
expect(changed.browser).not.toBe(artifacts.browser);
|
||||
} finally {
|
||||
setCompileCacheDir(null);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import {
|
||||
compileWireArtifacts,
|
||||
compileWrnArtifacts,
|
||||
setCompileCacheDir,
|
||||
setCompileImportOptions,
|
||||
} from "../src/pipeline.ts";
|
||||
@@ -31,24 +31,24 @@ function fixture(): { root: string; page: string } {
|
||||
test("legacy, compatible, and explicit import modes are enforced from app config", () => {
|
||||
const { root, page } = fixture();
|
||||
setCompileImportOptions(root, { mode: "legacy" });
|
||||
expect(() => compileWireArtifacts(page, 1)).not.toThrow();
|
||||
expect(() => compileWrnArtifacts(page, 1)).not.toThrow();
|
||||
|
||||
const warnings: string[] = [];
|
||||
const originalWarn = console.warn;
|
||||
console.warn = (...args) => warnings.push(args.join(" "));
|
||||
try {
|
||||
setCompileImportOptions(root, { mode: "compatible" });
|
||||
expect(() => compileWireArtifacts(page, 2)).not.toThrow();
|
||||
expect(() => compileWrnArtifacts(page, 2)).not.toThrow();
|
||||
expect(warnings.some((message) => message.includes("WRN-IMPORT-IMPLICIT"))).toBe(true);
|
||||
|
||||
setCompileImportOptions(root, { mode: "explicit" });
|
||||
expect(() => compileWireArtifacts(page, 3)).toThrow("WRN-IMPORT-IMPLICIT");
|
||||
expect(() => compileWrnArtifacts(page, 3)).toThrow("WRN-IMPORT-IMPLICIT");
|
||||
|
||||
writeFileSync(
|
||||
page,
|
||||
'import Card from "@/components/Card.wrn"\npage Home { view { <Card /> } }',
|
||||
);
|
||||
expect(() => compileWireArtifacts(page, 4)).not.toThrow();
|
||||
expect(() => compileWrnArtifacts(page, 4)).not.toThrow();
|
||||
} finally {
|
||||
console.warn = originalWarn;
|
||||
}
|
||||
@@ -68,5 +68,5 @@ test("compiler-native reactive elements do not require application imports", ()
|
||||
}`,
|
||||
);
|
||||
setCompileImportOptions(root, { mode: "explicit" });
|
||||
expect(() => compileWireArtifacts(page, 5)).not.toThrow();
|
||||
expect(() => compileWrnArtifacts(page, 5)).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -3,8 +3,8 @@ import { mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
compileWireArtifacts,
|
||||
compileWireArtifactsAsync,
|
||||
compileWrnArtifacts,
|
||||
compileWrnArtifactsAsync,
|
||||
getWrnCompileMetrics,
|
||||
invalidateModule,
|
||||
loadModule,
|
||||
@@ -32,7 +32,7 @@ test("development compilation awaits plugin AST and code transforms", async () =
|
||||
});
|
||||
try {
|
||||
writeFileSync(file, "page Home { view { <h1>Plugin</h1> } }\n");
|
||||
const artifact = await compileWireArtifactsAsync(file);
|
||||
const artifact = await compileWrnArtifactsAsync(file);
|
||||
expect(astTransformed).toBeTrue();
|
||||
expect((await import(artifact.main)).pluginTransformed).toBeTrue();
|
||||
} finally {
|
||||
@@ -48,10 +48,10 @@ test("WRN compilation exposes cache hit, miss, timing, and error metrics", () =>
|
||||
resetWrnCompileMetrics();
|
||||
try {
|
||||
writeFileSync(file, "page Home { view { <h1>Metrics</h1> } }\n");
|
||||
compileWireArtifacts(file);
|
||||
compileWireArtifacts(file);
|
||||
compileWrnArtifacts(file);
|
||||
compileWrnArtifacts(file);
|
||||
writeFileSync(file, "page Broken { view { <h1> } }\n");
|
||||
expect(() => compileWireArtifacts(file)).toThrow();
|
||||
expect(() => compileWrnArtifacts(file)).toThrow();
|
||||
expect(getWrnCompileMetrics()).toMatchObject({
|
||||
hits: 1,
|
||||
misses: 2,
|
||||
|
||||
@@ -2,13 +2,13 @@ import { expect, test } from "bun:test";
|
||||
import { HMR_CLIENT_JS, collectScripts } from "../src/runtime.ts";
|
||||
|
||||
test("accent controls load the shared theme runtime", () => {
|
||||
expect(collectScripts('<button data-wire-accent-set="rose">Rose</button>')).toContain(
|
||||
expect(collectScripts('<button data-wrn-accent-set="rose">Rose</button>')).toContain(
|
||||
"/__wrnexus/theme.js",
|
||||
);
|
||||
});
|
||||
|
||||
test("accent clear controls load the shared theme runtime", () => {
|
||||
expect(collectScripts("<button data-wire-accent-clear>Reset</button>")).toContain(
|
||||
expect(collectScripts("<button data-wrn-accent-clear>Reset</button>")).toContain(
|
||||
"/__wrnexus/theme.js",
|
||||
);
|
||||
});
|
||||
@@ -20,9 +20,9 @@ test("HMR preserves the active browser theme and accent", () => {
|
||||
|
||||
expect(HMR_CLIENT_JS).toMatch(/var nextLang\s*=\s*doc\.documentElement\.getAttribute\("lang"\)/);
|
||||
|
||||
expect(HMR_CLIENT_JS).toMatch(/typeof\s+window\.wireTheme\.bind\s*===\s*"function"/);
|
||||
expect(HMR_CLIENT_JS).toMatch(/typeof\s+window\.wrnTheme\.bind\s*===\s*"function"/);
|
||||
|
||||
expect(HMR_CLIENT_JS).toMatch(/window\.wireTheme\.bind\(document\)/);
|
||||
expect(HMR_CLIENT_JS).toMatch(/window\.wrnTheme\.bind\(document\)/);
|
||||
|
||||
expect(HMR_CLIENT_JS).toContain('new CustomEvent("wrnexus:hmr-updated"');
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect, test } from "bun:test";
|
||||
import { createDevAssetServer } from "../src/assets.ts";
|
||||
|
||||
test("UI CSS is read from its live source after an HMR change", async () => {
|
||||
let css = ".wire-card { color: red; }";
|
||||
let css = ".wrn-card { color: red; }";
|
||||
const assets = createDevAssetServer(
|
||||
process.cwd(),
|
||||
"development",
|
||||
@@ -14,7 +14,7 @@ test("UI CSS is read from its live source after an HMR change", async () => {
|
||||
const first = await assets.serve("/__wrnexus/ui.css");
|
||||
expect(await first?.text()).toContain("color: red");
|
||||
|
||||
css = ".wire-card { color: blue; }";
|
||||
css = ".wrn-card { color: blue; }";
|
||||
assets.invalidateCss();
|
||||
|
||||
const second = await assets.serve("/__wrnexus/ui.css");
|
||||
|
||||
Reference in New Issue
Block a user