feat: 기능 추가 과정중
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
|
||||
import { measureOptionalRecipeBundle } from "./lib/optional-recipe-bundle.ts";
|
||||
import {
|
||||
scanOptionalRecipeSources,
|
||||
scanProductionBundle,
|
||||
validateRecipeCatalog,
|
||||
} from "./lib/optional-recipes.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 = {
|
||||
schemaVersion: 1,
|
||||
results,
|
||||
bundleBudgetFixtures,
|
||||
passed: results.every(({ passed }) => passed),
|
||||
};
|
||||
await mkdir("artifacts/quality", { recursive: true });
|
||||
await writeFile(
|
||||
"artifacts/quality/optional-recipe-fixtures.json",
|
||||
`${JSON.stringify(report, null, 2)}\n`,
|
||||
);
|
||||
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`,
|
||||
);
|
||||
Reference in New Issue
Block a user