Files
tech-log-frontend/scripts/check-optional-recipe-fixtures.ts

205 lines
6.6 KiB
TypeScript

import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { measureOptionalRecipeBundle } from "./lib/optional-recipe-bundle.ts";
import { optionalRecipeFixturesArtifactSchema } from "./contracts/release-artifacts.ts";
import {
scanOptionalRecipeSources,
scanProductionBundle,
validateRecipeCatalog,
} from "./lib/optional-recipes.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
type ReferenceRuntimeRecipe = Readonly<{
id: string;
referenceRuntime: Readonly<{
sourceRoots: readonly string[];
}>;
}>;
const catalog = JSON.parse(
await readFile("config/recipes/frontend-capability-recipes.json", "utf8"),
);
const packageDocument = JSON.parse(await readFile("package.json", "utf8"));
const catalogRecipes: unknown[] = Array.isArray(catalog.recipes)
? (catalog.recipes as unknown[])
: [];
const referenceRuntimeRecipes = catalogRecipes.filter(
(recipe: unknown): recipe is ReferenceRuntimeRecipe =>
Boolean(
recipe &&
typeof recipe === "object" &&
typeof (recipe as Record<string, unknown>).id === "string" &&
(recipe as Record<string, unknown>).referenceRuntime &&
typeof (recipe as Record<string, unknown>).referenceRuntime ===
"object" &&
Array.isArray(
(
(recipe as Record<string, unknown>)
.referenceRuntime as Record<string, unknown>
).sourceRoots,
),
),
);
const bundleBudgetFixtures = await Promise.all(
referenceRuntimeRecipes.map(async (recipe) => {
const measurement = await measureOptionalRecipeBundle({
recipeId: recipe.id,
sourceRoots: recipe.referenceRuntime.sourceRoots,
bundleBudgetGzipBytes: 1,
});
return Object.freeze({
recipeId: recipe.id,
gzipBytes: measurement.gzipBytes,
fixtureBudgetGzipBytes: measurement.bundleBudgetGzipBytes,
rejected: !measurement.passed,
});
}),
);
const cleanupCatalog = structuredClone(catalog);
cleanupCatalog.recipes.find(
(recipe: { id: string }) => recipe.id === "realtime",
).lifecycleMethods = [];
const dependencyCatalog = structuredClone(catalog);
dependencyCatalog.productionRuntimeDependencies = ["zustand"];
const workflowCatalog = structuredClone(catalog);
workflowCatalog.recipes.find(
(recipe: { id: string }) => recipe.id === "client-workflow",
).serverStatePolicy = "copied-server-state";
const sourceViolations = await scanOptionalRecipeSources(
"tests/fixtures/optional-recipes/forbidden",
{ scanProductionBoundary: false },
);
const productionViolations = await scanOptionalRecipeSources(
"tests/fixtures/optional-recipes/forbidden/production-import",
{ scanProductionBoundary: true },
);
const runtimeCompositionViolations = await scanOptionalRecipeSources(
"tests/fixtures/optional-recipes/forbidden/runtime-composition",
{ scanProductionBoundary: true },
);
const bundleFixtureRoot = ".tmp/optional-recipe-runtime-bundle";
await rm(bundleFixtureRoot, { recursive: true, force: true });
await mkdir(`${bundleFixtureRoot}/assets`, { recursive: true });
await writeFile(
`${bundleFixtureRoot}/assets/runtime.js`,
'throw new TypeError("OPFS runtime policy is invalid.");\n',
);
const bundleViolations = await scanProductionBundle(bundleFixtureRoot);
await rm(bundleFixtureRoot, { recursive: true, force: true });
const inventoryFixtureRoot =
".tmp/optional-recipe-runtime-module-inventory";
await rm(inventoryFixtureRoot, { recursive: true, force: true });
await mkdir(`${inventoryFixtureRoot}/.vite`, { recursive: true });
await writeFile(`${inventoryFixtureRoot}/.vite/manifest.json`, "{}\n");
await writeFile(
`${inventoryFixtureRoot}/.vite/module-inventory.json`,
`${JSON.stringify({
schemaVersion: 1,
chunks: [
{
fileName: "assets/application.js",
modules: [
"src/adapters/browser-files/browser-file-vault.ts",
],
},
],
})}\n`,
);
const inventoryViolations =
await scanProductionBundle(inventoryFixtureRoot);
await rm(inventoryFixtureRoot, { recursive: true, force: true });
sourceViolations.push(...productionViolations);
sourceViolations.push(...runtimeCompositionViolations);
const ruleIds = new Set(sourceViolations.map(({ ruleId }) => ruleId));
const results = [
{
id: "cleanup-omission",
passed: validateRecipeCatalog(cleanupCatalog, packageDocument).some(
(violation) => violation === "realtime:CLEANUP_CONTRACT_MISSING",
),
},
{
id: "unselected-runtime-dependency",
passed: validateRecipeCatalog(dependencyCatalog, packageDocument).includes(
"UNSELECTED_RUNTIME_DEPENDENCY",
),
},
{
id: "server-state-policy",
passed: validateRecipeCatalog(workflowCatalog, packageDocument).includes(
"client-workflow:SERVER_STATE_DUPLICATION_POLICY",
),
},
{
id: "vendor-direct-import",
passed: ruleIds.has("VENDOR_IMPORT_OUTSIDE_ADAPTER"),
},
{
id: "credential-leak",
passed: ruleIds.has("CREDENTIAL_LEAK_PATH"),
},
{
id: "server-state-source-duplication",
passed: ruleIds.has("CLIENT_STORE_DUPLICATES_SERVER_STATE"),
},
{
id: "production-imports-recipe",
passed: ruleIds.has("PRODUCTION_IMPORTS_RECIPE"),
},
{
id: "reference-runtime-not-composed",
passed: ruleIds.has("REFERENCE_RUNTIME_COMPOSED_WITHOUT_SELECTION"),
},
{
id: "reference-runtime-not-bundled",
passed: bundleViolations.some((file) => file.endsWith("runtime.js")),
},
{
id: "reference-runtime-module-not-bundled",
passed: inventoryViolations.some((violation) =>
violation.includes(
"src/adapters/browser-files/browser-file-vault.ts",
),
),
},
{
id: "reference-runtime-bundle-over-budget",
passed:
bundleBudgetFixtures.length === referenceRuntimeRecipes.length &&
bundleBudgetFixtures.length > 0 &&
bundleBudgetFixtures.every(
(fixture) =>
fixture.gzipBytes > fixture.fixtureBudgetGzipBytes &&
fixture.rejected,
),
},
];
const report = optionalRecipeFixturesArtifactSchema.parse({
schemaVersion: 1,
results,
bundleBudgetFixtures,
passed: results.every(({ passed }) => passed),
});
await mkdir("artifacts/quality", { recursive: true });
await writeValidatedJsonArtifact({
path: "artifacts/quality/optional-recipe-fixtures.json",
schema: optionalRecipeFixturesArtifactSchema,
value: report,
});
if (!report.passed) {
process.stderr.write(
`Optional recipe negative fixtures failed: ${results
.filter(({ passed }) => !passed)
.map(({ id }) => id)
.join(", ")}\n`,
);
process.exit(1);
}
process.stdout.write(
`Optional recipe negative fixtures: PASS (${results.length} forbidden cases rejected)\n`,
);