import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; import { measureOptionalRecipeBundle, type OptionalRecipeBundleMeasurement, } from "./lib/optional-recipe-bundle.ts"; import { scanOptionalRecipeSources, scanProductionBundle, validateRecipeCatalog, } from "./lib/optional-recipes.ts"; import { assertMatchesJsonSchema } from "./lib/json-schema.ts"; type GateViolation = Readonly<{ ruleId: string; path: string; detail?: string; }>; type ReferenceRuntimeBundleInput = Readonly<{ recipeId: string; sourceRoots: readonly string[]; bundleBudgetGzipBytes: number; }>; const argument = (name: string, fallback: string): string => { const index = process.argv.indexOf(name); return index === -1 ? fallback : (process.argv[index + 1] ?? fallback); }; const catalogPath = argument( "--catalog", "config/recipes/frontend-capability-recipes.json", ); const sourceRoot = argument("--source-root", "src"); const distRoot = argument("--dist-root", "dist"); const artifactPath = argument( "--artifact", "artifacts/quality/optional-recipes.json", ); const requireDist = process.argv.includes("--require-dist"); const catalog = JSON.parse( await readFile(catalogPath, "utf8"), ) as Record; const recipes = Array.isArray(catalog.recipes) ? catalog.recipes : []; const packageDocument = JSON.parse( await readFile("package.json", "utf8"), ) as Record; const catalogSchemaViolations: string[] = []; try { assertMatchesJsonSchema( JSON.parse( await readFile( "schemas/config/frontend-capability-recipes.schema.json", "utf8", ), ), catalog, "optional recipe catalog", ); } catch { catalogSchemaViolations.push("CATALOG_JSON_SCHEMA_INVALID"); } const catalogViolations = [ ...catalogSchemaViolations, ...validateRecipeCatalog(catalog, packageDocument), ]; const runtimeSourceViolations = ( await Promise.all( recipes.flatMap((recipe: unknown) => { if (!recipe || typeof recipe !== "object") return []; const row = recipe as Record; const runtime = row.referenceRuntime; if (!runtime || typeof runtime !== "object") return []; const sourceRoots = (runtime as Record).sourceRoots; if (!Array.isArray(sourceRoots)) return []; return sourceRoots .filter( (sourceRoot): sourceRoot is string => typeof sourceRoot === "string", ) .map(async (sourceRoot) => ({ sourceRoot, exists: await stat(sourceRoot) .then(() => true) .catch(() => false), })); }), ) ).filter(({ exists }) => !exists); const { inputs: referenceRuntimeBundleInputs, violations: referenceRuntimeBundleConfigurationViolations, } = referenceRuntimeBundleInputsFrom(recipes); const referenceRuntimeBundles: OptionalRecipeBundleMeasurement[] = []; const referenceRuntimeBundleMeasurementViolations: GateViolation[] = []; for (const bundleInput of referenceRuntimeBundleInputs) { try { const measurement = await measureOptionalRecipeBundle(bundleInput); referenceRuntimeBundles.push(measurement); if (!measurement.passed) { referenceRuntimeBundleMeasurementViolations.push({ ruleId: "REFERENCE_RUNTIME_BUNDLE_BUDGET_EXCEEDED", path: measurement.recipeId, detail: `${measurement.gzipBytes} > ` + `${measurement.bundleBudgetGzipBytes} gzip bytes`, }); } } catch (error) { referenceRuntimeBundleMeasurementViolations.push({ ruleId: "REFERENCE_RUNTIME_BUNDLE_MEASUREMENT_FAILED", path: bundleInput.recipeId, detail: safeErrorSummary(error), }); } } referenceRuntimeBundles.sort((left, right) => compareText(left.recipeId, right.recipeId), ); const sourceViolations = await scanOptionalRecipeSources(sourceRoot); const bundlePresent = await stat(`${distRoot}/.vite/manifest.json`) .then(() => true) .catch(() => false); const bundleViolations = await scanProductionBundle(distRoot); const violations: GateViolation[] = [ ...catalogViolations.map((ruleId) => ({ ruleId, path: catalogPath })), ...runtimeSourceViolations.map(({ sourceRoot }) => ({ ruleId: "REFERENCE_RUNTIME_SOURCE_MISSING", path: sourceRoot, })), ...sourceViolations, ...bundleViolations.map((path) => ({ ruleId: "UNSELECTED_RECIPE_IN_PRODUCTION_BUNDLE", path, })), ...(requireDist && !bundlePresent ? [{ ruleId: "PRODUCTION_BUNDLE_MISSING", path: distRoot }] : []), ...referenceRuntimeBundleConfigurationViolations, ...referenceRuntimeBundleMeasurementViolations, ]; const report = { schemaVersion: 1, decisionId: "VD-10", selectedCapabilities: [], referenceRuntimes: recipes .filter( (recipe: unknown): recipe is Record => Boolean( recipe && typeof recipe === "object" && (recipe as Record).referenceRuntime, ), ) .map((recipe: Record) => ({ id: recipe.id, referenceRuntime: recipe.referenceRuntime, })), recipeCount: recipes.length, productionRuntimeDependencies: catalog.productionRuntimeDependencies ?? null, referenceRuntimeBundleBudgets: referenceRuntimeBundles, bundleStatus: bundlePresent ? bundleViolations.length === 0 ? "PASS" : "FAIL" : "NOT_BUILT", violations, passed: violations.length === 0, }; await mkdir("artifacts/quality", { recursive: true }); await writeFile(artifactPath, `${JSON.stringify(report, null, 2)}\n`); if (violations.length > 0) { process.stderr.write( `Optional recipe contract failed:\n${violations .map( (violation) => `${violation.ruleId}: ${violation.path}` + (violation.detail ? ` (${violation.detail})` : ""), ) .join("\n")}\n`, ); process.exit(1); } const referenceRuntimeBudgetStatus = referenceRuntimeBundles.length > 0 ? `, budgets=${referenceRuntimeBundles .map( (measurement) => `${measurement.recipeId}:${measurement.gzipBytes}/` + measurement.bundleBudgetGzipBytes, ) .join(",")} gzip bytes` : ""; process.stdout.write( `Optional recipes: PASS (${report.recipeCount} optional capabilities, ${report.referenceRuntimes.length} uncomposed reference runtimes, bundle=${report.bundleStatus}${referenceRuntimeBudgetStatus})\n`, ); function referenceRuntimeBundleInputsFrom( recipeRows: readonly unknown[], ): Readonly<{ inputs: readonly ReferenceRuntimeBundleInput[]; violations: readonly GateViolation[]; }> { const inputs: ReferenceRuntimeBundleInput[] = []; const violations: GateViolation[] = []; for (const recipe of recipeRows) { if (!recipe || typeof recipe !== "object") continue; const row = recipe as Record; if (row.referenceRuntime === undefined) continue; const recipeId = typeof row.id === "string" ? row.id : "unknown-reference-runtime"; const runtime = row.referenceRuntime; const sourceRoots = runtime && typeof runtime === "object" ? (runtime as Record).sourceRoots : null; if ( !Array.isArray(sourceRoots) || !sourceRoots.every( (sourceRoot): sourceRoot is string => typeof sourceRoot === "string", ) || !Number.isSafeInteger(row.bundleBudgetGzipBytes) || (row.bundleBudgetGzipBytes as number) < 1 ) { violations.push({ ruleId: "REFERENCE_RUNTIME_BUNDLE_CONFIGURATION_INVALID", path: recipeId, }); continue; } inputs.push( Object.freeze({ recipeId, sourceRoots: Object.freeze([...sourceRoots]), bundleBudgetGzipBytes: row.bundleBudgetGzipBytes as number, }), ); } return Object.freeze({ inputs: Object.freeze( inputs.sort((left, right) => compareText(left.recipeId, right.recipeId), ), ), violations: Object.freeze(violations), }); } function safeErrorSummary(error: unknown): string { const message = error instanceof Error ? error.message : "unknown measurement failure"; return message .split(/\r?\n/, 1)[0] ?.replaceAll(process.cwd(), ".") .slice(0, 512) ?? "unknown measurement failure"; } function compareText(left: string, right: string): number { return left < right ? -1 : left > right ? 1 : 0; }