release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
interface ExplainRoute {
|
||||
kind: "page" | "api" | "realtime";
|
||||
path: string;
|
||||
source: string;
|
||||
execution: string;
|
||||
canPrerender: boolean;
|
||||
needsClientRuntime: boolean;
|
||||
needsServerRuntime: boolean;
|
||||
hydrationStrategy: string | null;
|
||||
reasons?: string[];
|
||||
cachePolicy?: Record<string, string>;
|
||||
requiredPermission?: string | null;
|
||||
}
|
||||
|
||||
interface ExplainReport {
|
||||
frameworkVersion: string;
|
||||
adapter: string;
|
||||
routes: ExplainRoute[];
|
||||
assets: Array<{ file: string; bytes: number }>;
|
||||
measurements: Record<string, number>;
|
||||
budgetViolations: Array<{ metric: string; budget: number; actual: number }>;
|
||||
}
|
||||
|
||||
export interface Explanation {
|
||||
target: string;
|
||||
subject: string;
|
||||
summary: string;
|
||||
reasons: string[];
|
||||
evidence: Record<string, unknown>;
|
||||
}
|
||||
|
||||
function loadReport(root: string): ExplainReport {
|
||||
const path = join(resolve(root), "dist", "build-report.json");
|
||||
if (!existsSync(path)) {
|
||||
throw new Error(
|
||||
"WRN-EXPLAIN-NO-BUILD: run `wrnexus build` before requesting build explanations.",
|
||||
);
|
||||
}
|
||||
return JSON.parse(readFileSync(path, "utf8")) as ExplainReport;
|
||||
}
|
||||
|
||||
function routeMatch(routes: ExplainRoute[], subject: string): ExplainRoute | undefined {
|
||||
const normalized = subject.startsWith("/") ? subject : `/${subject}`;
|
||||
return routes.find((route) => route.path === normalized || route.source.includes(subject));
|
||||
}
|
||||
|
||||
export function explainBuildDecision(root: string, target: string, subject = ""): Explanation {
|
||||
const report = loadReport(root);
|
||||
if (
|
||||
target === "route" ||
|
||||
target === "hydration" ||
|
||||
target === "cache" ||
|
||||
target === "permission"
|
||||
) {
|
||||
const route =
|
||||
target === "permission"
|
||||
? report.routes.find((item) => item.requiredPermission === subject)
|
||||
: routeMatch(report.routes, subject);
|
||||
if (!route) throw new Error(`WRN-EXPLAIN-NOT-FOUND: no route or source matches '${subject}'.`);
|
||||
if (target === "cache") {
|
||||
const policy = route.cachePolicy ?? {};
|
||||
const entries = Object.entries(policy);
|
||||
return {
|
||||
target,
|
||||
subject: route.path,
|
||||
summary: entries.length
|
||||
? `Route cache uses '${policy.strategy ?? "framework-default"}' strategy.`
|
||||
: "Route has no explicit cache policy and uses safe framework defaults.",
|
||||
reasons: entries.length
|
||||
? entries.map(([name, value]) => `${name} = ${value}`)
|
||||
: ["responses remain private/revalidated unless an explicit safe policy enables reuse"],
|
||||
evidence: { source: route.source, cachePolicy: policy, execution: route.execution },
|
||||
};
|
||||
}
|
||||
if (target === "permission") {
|
||||
const requested = subject.startsWith("/") ? undefined : subject;
|
||||
const matches = report.routes.filter((item) =>
|
||||
requested ? item.requiredPermission === requested : item.path === route.path,
|
||||
);
|
||||
return {
|
||||
target,
|
||||
subject: requested ?? route.path,
|
||||
summary: matches.length
|
||||
? `${matches.length} route(s) require this permission.`
|
||||
: "No built route declares this permission.",
|
||||
reasons: matches.length
|
||||
? matches.map((item) => `${item.path} declares security.permission in ${item.source}`)
|
||||
: ["authorization may still be enforced programmatically; inspect authz policies"],
|
||||
evidence: { routes: matches },
|
||||
};
|
||||
}
|
||||
const reasons = route.reasons?.length ? route.reasons : ["no dynamic requirement was detected"];
|
||||
return {
|
||||
target,
|
||||
subject: route.path,
|
||||
summary:
|
||||
target === "hydration"
|
||||
? route.needsClientRuntime
|
||||
? `Hydration uses '${route.hydrationStrategy ?? "load"}' because client runtime is required.`
|
||||
: "Hydration is omitted because no client runtime is required."
|
||||
: `Route execution is '${route.execution}'${route.canPrerender ? " and can prerender" : " and cannot prerender"}.`,
|
||||
reasons,
|
||||
evidence: { ...route },
|
||||
};
|
||||
}
|
||||
if (target === "bundle") {
|
||||
const assets = [...report.assets].sort((a, b) => b.bytes - a.bytes);
|
||||
return {
|
||||
target,
|
||||
subject: subject || "production bundle",
|
||||
summary: `${assets.length} emitted assets; largest is ${assets[0]?.file ?? "none"}.`,
|
||||
reasons: assets.slice(0, 10).map((asset) => `${asset.file}: ${asset.bytes} bytes`),
|
||||
evidence: { measurements: report.measurements, largestAssets: assets.slice(0, 10) },
|
||||
};
|
||||
}
|
||||
if (target === "build") {
|
||||
return {
|
||||
target,
|
||||
subject: "production build",
|
||||
summary: `${report.routes.length} routes target the ${report.adapter} adapter.`,
|
||||
reasons: report.budgetViolations.length
|
||||
? report.budgetViolations.map(
|
||||
(item) => `${item.metric} exceeds ${item.budget} with ${item.actual}`,
|
||||
)
|
||||
: ["all configured performance budgets pass"],
|
||||
evidence: {
|
||||
frameworkVersion: report.frameworkVersion,
|
||||
adapter: report.adapter,
|
||||
measurements: report.measurements,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`WRN-EXPLAIN-TARGET: unsupported target '${target}'.`);
|
||||
}
|
||||
|
||||
export function runExplain(root: string, target: string, subject: string, args: string[]): void {
|
||||
const explanation = explainBuildDecision(root, target, subject);
|
||||
if (args.includes("--json")) {
|
||||
console.log(JSON.stringify(explanation, null, 2));
|
||||
return;
|
||||
}
|
||||
console.log(explanation.summary);
|
||||
explanation.reasons.forEach((reason, index) => console.log(` ${index + 1}. ${reason}`));
|
||||
}
|
||||
Reference in New Issue
Block a user