87 lines
4.8 KiB
JavaScript
87 lines
4.8 KiB
JavaScript
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, /<slot\b[^>]*\bname="([^"]+)"/g),
|
|
hasDefaultSlot: /<slot(?:\s|>)/.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,
|
|
}),
|
|
);
|