release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+185 -8
View File
@@ -21,15 +21,19 @@ import {
statSync,
writeFileSync,
} from "node:fs";
import { basename, extname, join, resolve } from "node:path";
import { basename, dirname, extname, join, relative, resolve } from "node:path";
import { buildRouter, type Route } from "@wrnexus/router";
import { getReactiveRuntime } from "@wrnexus/csr";
import {
analyzeRuntimeImports,
analyzeRuntimeRequirements,
assertValidAst,
generate,
parse,
type RuntimeRequirements,
type DeploymentRuntime,
runtimeCapabilities,
resolveWrnImports,
} from "@wrnexus/compiler";
import {
loadAppConfig,
@@ -64,6 +68,38 @@ const INLINE_CSS_LIMIT_BYTES = 4096;
const fwd = (p: string) => p.replace(/\\/g, "/");
function deploymentRuntime(adapter: string | undefined): DeploymentRuntime | undefined {
if (!adapter) return "bun";
if (["bun", "node", "edge", "worker", "service-worker", "browser"].includes(adapter))
return adapter as DeploymentRuntime;
return undefined;
}
export function validateRuntimeCapabilities(appRoot: string, adapter?: string): void {
const runtime = deploymentRuntime(adapter);
if (!runtime || runtime === "bun" || runtime === "node") return;
const appDir = join(resolve(appRoot), "app");
if (!existsSync(appDir)) return;
const files: string[] = [];
const walk = (directory: string) => {
for (const name of readdirSync(directory)) {
const file = join(directory, name);
const stat = statSync(file);
if (stat.isDirectory()) walk(file);
else if (/\.(?:[cm]?[jt]s|wrn)$/.test(file)) files.push(file);
}
};
walk(appDir);
const diagnostics = files.flatMap((file) =>
analyzeRuntimeImports(readFileSync(file, "utf8"), runtime).map(
(diagnostic) =>
`${fwd(file.slice(resolve(appRoot).length + 1))}: ${diagnostic.code} ${diagnostic.message}`,
),
);
if (diagnostics.length)
throw new Error(`Runtime capability validation failed:\n${diagnostics.join("\n")}`);
}
export async function runBuild(appRoot: string): Promise<void> {
const root = resolve(appRoot);
const appDir = join(root, "app");
@@ -72,6 +108,8 @@ export async function runBuild(appRoot: string): Promise<void> {
const reactivePath = join(distDir, "reactive.js");
const publicDir = join(root, "public");
const distPublicDir = join(distDir, "public");
const config = await loadAppConfig(root);
validateRuntimeCapabilities(root, config.build?.adapter);
console.log(`Building ${appDir} -> ${distDir}`);
@@ -83,11 +121,14 @@ export async function runBuild(appRoot: string): Promise<void> {
console.log(`✓ Public: ${distPublicDir}`);
}
const config = await loadAppConfig(root);
const discoveredPlugins = await discoverPlugins(root, config.plugins, {
includeDevDependencies: true,
strict: true,
warn: (message) => console.warn(`[wrnexus:plugin] ${message}`),
runtime: deploymentRuntime(config.build?.adapter),
capabilities: [...runtimeCapabilities(deploymentRuntime(config.build?.adapter) ?? "bun")],
enforcePermissions: config.pluginPermissions?.enforce,
grantedPermissions: config.pluginPermissions?.grants,
});
const pluginRunner = createPluginRunner(discoveredPlugins, {
root,
@@ -100,8 +141,27 @@ export async function runBuild(appRoot: string): Promise<void> {
await pluginRunner.configure(config as Record<string, unknown>);
await pluginRunner.configResolved(config as Readonly<Record<string, unknown>>);
const pluginContributions = await pluginRunner.contributions();
const { generateApplicationTypes } = await import("./types.ts");
generateApplicationTypes(root, pluginContributions);
const componentDirs = [uiComponentsDir(), ...pluginContributions.componentDirs];
await pluginRunner.hook("buildStart");
const virtualModules = new Map<string, string>();
for (const [index, module] of pluginContributions.virtualModules.entries()) {
const output = join(compiledDir, `virtual-${index}.ts`);
writeFileSync(
output,
await module.load({
root,
mode: "production",
command: "build",
profile: process.env.WRNEXUS_PROFILE,
metadata: new Map(),
warn: (message) => console.warn(`[wrnexus:plugin] ${message}`),
}),
"utf8",
);
virtualModules.set(module.id, output);
}
// `.wrn` route files are compiled once into deterministic intermediate modules.
// Plugin AST/code transforms run only when configured, so existing applications
@@ -109,12 +169,18 @@ export async function runBuild(appRoot: string): Promise<void> {
let compiledCount = 0;
const compiledFiles = new Map<string, string>();
const runtimeAnalysis = new Map<string, RuntimeRequirements>();
const partialStaticFiles = new Set<string>();
const compileWrn = async (file: string): Promise<void> => {
if (!file.endsWith(".wrn") || compiledFiles.has(file)) return;
const out = join(compiledDir, `route${compiledCount++}.ts`);
// Reserve the artifact before resolving imports so cycles terminate and
// mutually dependent generated modules can point at deterministic paths.
compiledFiles.set(file, out);
const source = readFileSync(file, "utf8");
let ast = parse(source);
assertValidAst(ast, { file, accessibility: true });
ast = await pluginRunner.transformAst(ast, file);
if (ast.renderMode === "partial-static") partialStaticFiles.add(file);
runtimeAnalysis.set(file, analyzeRuntimeRequirements(ast));
const pluginDiagnostics = await pluginRunner.diagnostics(ast, file);
const errors = pluginDiagnostics.filter((diagnostic) => diagnostic.severity === "error");
@@ -126,11 +192,46 @@ export async function runBuild(appRoot: string): Promise<void> {
errors.map((diagnostic) => `[${diagnostic.code}] ${diagnostic.message}`).join("\n"),
);
}
let code = `// compiled from .wrn\n${generate(ast)}`;
code = await pluginRunner.transformCode(code, file);
const out = join(compiledDir, `route${compiledCount++}.ts`);
writeFileSync(out, code, "utf8");
compiledFiles.set(file, out);
try {
let code = `// compiled from .wrn\n${generate(ast)}`;
code = await pluginRunner.transformCode(code, file);
for (const [id, target] of virtualModules) {
const sourcePattern = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const relativeTarget = relative(dirname(out), target).replace(/\\/g, "/");
const specifier = relativeTarget.startsWith(".") ? relativeTarget : `./${relativeTarget}`;
code = code.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(specifier),
);
}
const resolvedImports = resolveWrnImports(ast.structuredImports, file, {
appRoot: root,
mode: config.imports?.mode ?? "compatible",
aliases: config.imports?.aliases,
});
for (const imported of resolvedImports) {
if (imported.diagnostic?.severity === "error") {
throw new Error(`${imported.diagnostic.code}: ${imported.diagnostic.message}`);
}
if (!imported.resolved || !imported.declaration.source.startsWith(".")) continue;
let target = imported.resolved;
if (target.endsWith(".wrn")) {
await compileWrn(target);
target = compiledFiles.get(target)!;
}
const sourcePattern = imported.declaration.source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const relativeTarget = relative(dirname(out), target).replace(/\\/g, "/");
const specifier = relativeTarget.startsWith(".") ? relativeTarget : `./${relativeTarget}`;
code = code.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(specifier),
);
}
writeFileSync(out, code, "utf8");
} catch (error) {
compiledFiles.delete(file);
throw error;
}
};
const importPathFor = (file: string): string => fwd(compiledFiles.get(file) ?? file);
// Regenerate typed DB queries (app/db/queries/*.sql → queries.gen.ts) first, so
@@ -235,6 +336,37 @@ export async function runBuild(appRoot: string): Promise<void> {
...router.layouts.map((layout) => layout.file),
]);
for (const file of wrnFiles) await compileWrn(file);
const partialShells = new Map<string, { shell: string; regions: number }>();
const partialPages = router.pages.filter((route) => partialStaticFiles.has(route.file));
if (partialPages.length > 0) {
const { precomputePartialStaticShell } = await import("@wrnexus/dev-server");
const moduleCache = new Map<string, Record<string, unknown>>();
const loadCompiled = async (file: string): Promise<Record<string, unknown>> => {
const existing = moduleCache.get(file);
if (existing) return existing;
const output = compiledFiles.get(file);
if (!output) throw new Error(`WRN-PARTIAL-STATIC-MODULE: ${file} was not compiled`);
const loaded = (await import(pathToFileURL(output).href)) as Record<string, unknown>;
moduleCache.set(file, loaded);
return loaded;
};
const components = await Promise.all(
router.components.map(async (component) => ({
name: component.name,
mod: await loadCompiled(component.file),
})),
);
for (const route of partialPages) {
const result = await precomputePartialStaticShell(await loadCompiled(route.file), components);
partialShells.set(route.raw, result);
}
writeFileSync(
join(distDir, "partial-shells.json"),
JSON.stringify(Object.fromEntries(partialShells), null, 2) + "\n",
"utf8",
);
console.log(`✓ Partial shells: ${partialShells.size} build-time route shell(s)`);
}
const assetHash = createHash("sha256");
const emittedPluginAssets = await emitPluginAssets(
pluginContributions,
@@ -339,7 +471,8 @@ export async function runBuild(appRoot: string): Promise<void> {
const parts = routes.map((r) => {
const v = `m${counter++}`;
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(r.file))};`);
return ` { raw: ${JSON.stringify(r.raw)}, mod: ${v} },`;
const partial = partialShells.get(r.raw);
return ` { raw: ${JSON.stringify(r.raw)}, mod: ${v}${partial ? `, staticShell: ${JSON.stringify(partial.shell)}` : ""} },`;
});
return parts.length ? `\n${parts.join("\n")}\n ` : "";
};
@@ -428,6 +561,7 @@ await createProductionServer(
observability: ${JSON.stringify(config.observability ?? {})},
tenancy: ${JSON.stringify(config.tenancy ?? {})},
navigation: ${JSON.stringify(config.navigation ?? {})},
developmentRuntime: process.env.WRNEXUS_PRODUCTION_DEV === "1",
},
);
`;
@@ -485,12 +619,32 @@ await createProductionServer(
source: migration.entry ?? "inline",
}));
report.componentDirs = componentDirs.map(fwd);
report.partialStaticShells = [...partialShells].map(([route, value]) => ({
route,
regions: value.regions,
bytes: Buffer.byteLength(value.shell, "utf8"),
}));
const violations = checkPerformanceBudgets(
config.performance?.budgets ?? {},
report.measurements,
);
report.budgetViolations = violations;
writeFileSync(join(distDir, "build-report.json"), JSON.stringify(report, null, 2) + "\n", "utf8");
const contributedAdapter = pluginContributions.deploymentAdapters.find(
(adapter) => adapter.name === config.build?.adapter,
);
if (contributedAdapter)
await contributedAdapter.build(
{ distDir, report },
{
root,
mode: "production",
command: "build",
profile: process.env.WRNEXUS_PROFILE,
metadata: new Map(),
warn: (message) => console.warn(`[wrnexus:plugin] ${message}`),
},
);
await pluginRunner.hook("buildEnd", report);
console.log(`✓ Server: ${join(distDir, "server.js")}`);
@@ -636,6 +790,7 @@ interface BuildReport {
clientRuntimes?: Array<{ id: string; publicPath: string; type: string; load: string }>;
migrations?: Array<{ id: string; database: string; source: string }>;
componentDirs?: string[];
partialStaticShells?: Array<{ route: string; regions: number; bytes: number }>;
generatedAt: string;
root: string;
adapter: string;
@@ -650,6 +805,10 @@ interface BuildReport {
needsClientRuntime: boolean;
needsServerRuntime: boolean;
hydrationStrategy: string | null;
reasons: string[];
optimization: RuntimeRequirements["optimization"];
cachePolicy: Record<string, string>;
requiredPermission: string | null;
}>;
assets: Array<{ file: string; bytes: number }>;
measurements: { routeJsBytes: number; routeCssBytes: number; imageBytes: number };
@@ -709,6 +868,24 @@ function createBuildReport(input: {
needsClientRuntime: input.runtimeAnalysis.get(route.file)?.needsClientRuntime ?? true,
needsServerRuntime: input.runtimeAnalysis.get(route.file)?.needsServerRuntime ?? true,
hydrationStrategy: input.runtimeAnalysis.get(route.file)?.hydrationStrategy ?? null,
reasons: input.runtimeAnalysis.get(route.file)?.reasons ?? [
"runtime requirements unavailable",
],
optimization: input.runtimeAnalysis.get(route.file)?.optimization ?? {
staticNodes: 0,
reactiveRegions: 0,
eliminatedBranches: 0,
unusedState: [],
unusedHandlers: [],
constantProps: [],
unusedLocalCssClasses: [],
batchableStateUpdates: 0,
memoizableComponents: [],
preloadDependencies: [],
serverOnlyModules: [],
},
cachePolicy: input.runtimeAnalysis.get(route.file)?.cachePolicy ?? {},
requiredPermission: input.runtimeAnalysis.get(route.file)?.requiredPermission ?? null,
})),
assets,
measurements: {