import { readFileSync, readdirSync, mkdirSync, writeFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { createHash } from "node:crypto"; import console from "node:console"; import { COMPONENT_CONTRACTS } from "../packages/design-system/src/contracts.ts"; // Source inventory only: these observations do not certify rendered behavior. const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const read = (path) => readFileSync(resolve(root, path), "utf8"); const names = (source, expression) => [...new Set([...source.matchAll(expression)].map((m) => m[1]))].sort(); const reference = JSON.parse(read("packages/ui/component-reference.json")); const catalog = JSON.parse(read("packages/ui/component-catalog.json")); const components = readdirSync(resolve(root, "packages/ui/components")) .filter((f) => f.endsWith(".wrn")) .sort() .map((file) => { const path = `packages/ui/components/${file}`; const source = read(path); const name = /^\s*component\s+(\w+)/m.exec(source)?.[1]; if (!name) throw new Error(`Missing component declaration: ${path}`); const entry = reference.components.find((c) => c.name === name); return { name, source: path, sha256: createHash("sha256").update(source).digest("hex"), category: catalog.components.find((c) => c.name === name)?.category ?? "uncatalogued", inReference: Boolean(entry), inCatalog: catalog.components.some((c) => c.name === name), props: entry?.props?.map((p) => p.name) ?? [], tokenReferences: names(source, /var\(\s*--wrn-([\w-]+)/g), dataAttributes: names(source, /\b(data-[\w-]+)\s*=/g), slots: names(source, /]*\bname="([^"]+)"/g), hasDefaultSlot: /)/.test(source), hasLocalStyle: /\bstyle\s*\{/.test(source), hasContractPartHooks: /\bdata-wrn-part\s*=/.test(source), literalGeometryDeclarations: ( source.match( /\b(?:border-radius|padding(?:-inline|-block)?|box-shadow|transform)\s*:\s*(?!var\()[^;{}]+;/g, ) ?? [] ).length, status: COMPONENT_CONTRACTS[name]?.source === path ? "recipe-supported" : "legacy-uncontracted", contractVersion: COMPONENT_CONTRACTS[name]?.contractVersion ?? null, regions: COMPONENT_CONTRACTS[name]?.regions ?? {}, }; }); const actual = new Set(components.map((c) => c.name)); const report = { schemaVersion: 1, methodology: "Static observations from current working tree, including uncommitted files. Props copied from generated reference; not a parser or visual/behavior certification. Literal declaration count is a heuristic and includes legitimate structural values.", componentCount: components.length, referenceCount: reference.components.length, catalogCount: catalog.components.length, referenceOnly: reference.components.filter((c) => !actual.has(c.name)).map((c) => c.name), catalogOnly: catalog.components.filter((c) => !actual.has(c.name)).map((c) => c.name), components, }; const directory = resolve(root, "docs/design-system"); mkdirSync(directory, { recursive: true }); writeFileSync( resolve(directory, "component-coverage.json"), `${JSON.stringify(report, null, 2)}\n`, ); const rows = components.map( (c) => `| ${c.name} | ${c.category} | ${c.tokenReferences.length} | ${c.hasLocalStyle ? "yes" : "no"} | ${c.hasContractPartHooks ? "yes" : "no"} | ${c.inReference && c.inCatalog ? "matched" : "gap"} | ${c.status} |`, ); writeFileSync( resolve(directory, "component-coverage.md"), `# Design-system source coverage\n\nRegenerate with \`node scripts/audit-design-system.mjs\`. This inventories the working tree, including uncommitted components. It does not certify visual, accessibility or runtime behavior.\n\nSources: **${components.length}**; reference entries: **${report.referenceCount}**; catalog entries: **${report.catalogCount}**. Versioned contracts cover all 104 components. Provider, native and content-owned regions retain explicit styling limits in the JSON report. Status reports hook/recipe support, not accessibility certification. Token counts are source-local references, not inherited/global coverage. Part hooks refer specifically to the versioned \`data-wrn-part\` convention.\n\n| Component | Category | Tokens | Local style | Part hooks | Metadata | Status |\n| --- | --- | ---: | --- | --- | --- | --- |\n${rows.join("\n")}\n`, ); console.log( JSON.stringify({ components: report.componentCount, reference: report.referenceCount, catalog: report.catalogCount, metadataGaps: components.filter((c) => !c.inReference || !c.inCatalog).map((c) => c.name), localStyles: components.filter((c) => c.hasLocalStyle).length, partHooks: components.filter((c) => c.hasContractPartHooks).length, referenceOnly: report.referenceOnly, catalogOnly: report.catalogOnly, }), );