Files
WRNexusJS/packages/cli/src/explain.ts
T
Clintchiz 586a6db8ff
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.0
2026-08-02 23:18:51 +05:30

148 lines
5.4 KiB
TypeScript

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}`));
}