import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; const root = process.cwd(); const contract = JSON.parse( fs.readFileSync(path.join(root, 'docs/contracts/foundation-styles-r11.json'), 'utf8'), ); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); const failures = []; function normalizeSelector(selector) { return selector.replace(/\s+/g, '').trim(); } function normalizeDeclarations(declarations) { return declarations .split(';') .map((declaration) => declaration.trim()) .filter(Boolean) .join(';'); } function extractFlatRules(source) { const withoutComments = source.replace(/\/\*[\s\S]*?\*\//g, ''); return [...withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((match) => ({ selector: normalizeSelector(match[1]), declarations: normalizeDeclarations(match[2]), })); } const entry = read(contract.entry); let previousImportIndex = -1; for (const importPath of contract.importOrder) { const statement = `import '${importPath}';`; const importIndex = entry.indexOf(statement); if (importIndex < 0) { failures.push(`${contract.entry} is missing ${statement}`); } else if (importIndex <= previousImportIndex) { failures.push(`${importPath} is not loaded in the required foundation-to-component order`); } previousImportIndex = importIndex; } const applicationImportIndex = entry.indexOf(contract.applicationImport); if (applicationImportIndex < 0) { failures.push(`${contract.entry} is missing the application route import`); } else if (applicationImportIndex <= previousImportIndex) { failures.push('AppRoutes must enter the dependency graph after the ordered foundation/component styles'); } const tokens = read(contract.tokensFile); const tokenNames = [...tokens.matchAll(/^\s*(--[a-z0-9-]+)\s*:/gim)].map((match) => match[1]); if (tokenNames.length < contract.minimumTokenCount) { failures.push(`${contract.tokensFile} has ${tokenNames.length} tokens; expected at least ${contract.minimumTokenCount}`); } for (const token of contract.requiredTokens) { if (!tokenNames.includes(token)) failures.push(`${contract.tokensFile} is missing ${token}`); } const tokenRules = extractFlatRules(tokens); if (tokenRules.length !== 1 || tokenRules[0].selector !== ':root') { failures.push(`${contract.tokensFile} must remain a single :root design-token scope`); } const reset = read(contract.resetFile); const resetRules = extractFlatRules(reset); const expectedSelectors = Object.keys(contract.resetRuleHashes); const actualSelectors = resetRules.map((rule) => rule.selector); for (const selector of expectedSelectors) { const matches = resetRules.filter((rule) => rule.selector === selector); if (matches.length !== 1) { failures.push(`${contract.resetFile} must contain exactly one ${selector} rule`); continue; } const actualHash = crypto.createHash('sha256').update(matches[0].declarations).digest('hex'); if (actualHash !== contract.resetRuleHashes[selector]) { failures.push(`${contract.resetFile} changed declarations for ${selector}`); } } for (const selector of actualSelectors) { if (!expectedSelectors.includes(selector)) { failures.push(`${contract.resetFile} contains non-foundation selector ${selector}`); } } const sourceRules = extractFlatRules(read(contract.sourceFile)); const remainingSelectors = new Set(sourceRules.map((rule) => rule.selector)); for (const selector of expectedSelectors) { if (remainingSelectors.has(selector)) { failures.push(`${contract.sourceFile} still owns reset selector ${selector}`); } } if (failures.length > 0) { console.error(`R11 foundation styles verification failed:\n- ${failures.join('\n- ')}`); process.exit(1); } console.log( `R11 foundation styles verified: ${tokenNames.length} design tokens, ` + `${resetRules.length} reset/base rules and deterministic import order preserved.`, );