import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import path from "node:path"; // @ts-expect-error Node 24 executes erasable TypeScript for this build-time gate. import { REQUIRED_COMPONENT_TOKENS, REQUIRED_PRIMITIVE_TOKENS, REQUIRED_SEMANTIC_TOKENS } from "../src/presentation/design-system/tokens/token-contract.ts"; const fixtureMode = process.argv.includes("--fixture"); const failures = []; const tokenFiles = { primitive: "src/presentation/design-system/tokens/primitive.css", semantic: "src/presentation/design-system/tokens/semantic.css", component: "src/presentation/design-system/tokens/component.css", }; /** @type {Array} */ const tokenLayers = [ ["primitive", tokenFiles.primitive, REQUIRED_PRIMITIVE_TOKENS], ["semantic", tokenFiles.semantic, REQUIRED_SEMANTIC_TOKENS], ["component", tokenFiles.component, REQUIRED_COMPONENT_TOKENS], ]; for (const [layer, file, tokens] of tokenLayers) { const source = await readFile(file, "utf8"); for (const token of tokens) { if (!source.includes(`${token}:`)) { failures.push(`${layer} token is missing: ${token}`); } } } const cssSources = await Promise.all( [...Object.values(tokenFiles), "src/presentation/styles/theme.css"].map( async (file) => ({ file, source: await readFile(file, "utf8") }), ), ); const definitions = new Set( cssSources.flatMap(({ source }) => [...source.matchAll(/(--[a-z0-9-]+)\s*:/g)].map((match) => match[1]), ), ); for (const { file, source } of cssSources) { for (const match of source.matchAll(/var\((--[a-z0-9-]+)/g)) { if (!definitions.has(match[1])) { failures.push(`${file} uses undefined token ${match[1]}`); } } } const semanticSource = await readFile(tokenFiles.semantic, "utf8"); const darkSource = semanticSource.match(/:root\[data-theme="dark"\]\s*\{([\s\S]*?)\n\}/)?.[1] ?? ""; for (const token of [ "--color-surface", "--color-surface-muted", "--color-surface-elevated", "--color-content", "--color-content-muted", "--color-content-inverse", "--color-border", "--color-border-strong", "--color-action", "--color-action-hover", "--color-action-pressed", "--color-danger", "--color-focus", "--color-disabled-content", "--color-disabled-surface", ]) { if (!darkSource.includes(`${token}:`)) { failures.push(`dark theme token is missing: ${token}`); } } const componentSource = await readFile(tokenFiles.component, "utf8"); if (!componentSource.includes("@media (forced-colors: active)")) { failures.push("forced-colors token fallback is missing"); } /** @param {string} directory @returns {Promise} */ async function listSourceFiles(directory) { const result = []; for (const entry of await readdir(directory, { withFileTypes: true })) { const target = path.join(directory, entry.name); if (entry.isDirectory()) result.push(...(await listSourceFiles(target))); else if (/\.(js|jsx|mjs|ts|tsx|mts)$/.test(entry.name)) result.push(target); } return result; } const sources = fixtureMode ? await listSourceFiles("tests/fixtures/design-system/forbidden") : await listSourceFiles("src"); for (const file of sources) { const source = await readFile(file, "utf8"); const vendorFacade = file === "src/presentation/design-system/icons/vendors/lucide.tsx"; if (!vendorFacade && /from\s+["']lucide-react["']/.test(source)) { failures.push(`direct icon vendor import in ${file}`); } if ( /from\s+["'](?:react-aria-components|@radix-ui\/[^"']+)["']/.test(source) ) { failures.push(`direct headless vendor import in ${file}`); } if ( !file.includes("src/presentation/design-system/") && /presentation\/design-system\/(?!index(?:\.js)?["'])/.test(source) ) { failures.push(`design-system deep import in ${file}`); } if ( !file.includes("src/presentation/design-system/tokens/") && /(?:#[0-9a-f]{3,8}\b|oklch\(|rgba?\()/i.test(source) ) { failures.push(`raw palette value in ${file}`); } if ( file.includes("tests/fixtures/design-system/forbidden") && source.includes("TOOLTIP_ONLY_REQUIRED_INFORMATION") ) { failures.push(`tooltip-only required information in ${file}`); } } const report = { schemaVersion: 1, mode: fixtureMode ? "negative-fixture" : "source", checkedTokenCount: REQUIRED_PRIMITIVE_TOKENS.length + REQUIRED_SEMANTIC_TOKENS.length + REQUIRED_COMPONENT_TOKENS.length, failures, passed: failures.length === 0, }; await mkdir("artifacts/quality", { recursive: true }); await writeFile( fixtureMode ? "artifacts/quality/design-system-fixture.json" : "artifacts/quality/design-system.json", `${JSON.stringify(report, null, 2)}\n`, ); if (failures.length > 0) { process.stderr.write(`Design system contract failed:\n${failures.join("\n")}\n`); process.exit(1); } process.stdout.write( `Design system contract: ${report.checkedTokenCount} tokens and vendor boundaries PASS\n`, );