import fs from 'node:fs'; import path from 'node:path'; import postcss from 'postcss'; const root = process.cwd(); const contract = JSON.parse( fs.readFileSync(path.join(root, 'docs/contracts/shared-components-r11.json'), 'utf8'), ); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); const failures = []; function mediaOf(rule) { let parent = rule.parent; while (parent) { if (parent.type === 'atrule' && parent.name === 'media') return parent.params; parent = parent.parent; } return undefined; } function normalizedDeclarations(rule) { return rule.nodes .filter((node) => node.type === 'decl') .map((node) => `${node.prop}:${node.value}${node.important ? ' !important' : ''}`) .join(';'); } function selectorClasses(selector) { return (selector.match(/\.([a-zA-Z0-9_-]+)/g) ?? []).map((value) => value.slice(1)); } function isPureSharedSelector(selector) { const classes = selectorClasses(selector); const generic = new Set(contract.genericClasses); const states = new Set(contract.allowedStateClasses); const ownsSharedClass = classes.some((className) => className.startsWith('ui-') || generic.has(className)); return classes.length > 0 && ownsSharedClass && classes.every((className) => className.startsWith('ui-') || generic.has(className) || states.has(className)); } const entry = read(contract.entry); const globalImport = entry.indexOf(`import '@/styles/global.css';`); const componentImport = entry.indexOf(`import '@/styles/components.css';`); if (globalImport < 0 || componentImport < 0 || globalImport >= componentImport) { failures.push(`${contract.componentsFile} must load after ${contract.legacyFile}`); } const componentsSource = read(contract.componentsFile); const markerIndexes = [ componentsSource.indexOf(contract.compatibilityMarker), componentsSource.indexOf(contract.canonicalMarker), componentsSource.indexOf(contract.responsiveMarker), ]; if (markerIndexes.some((index) => index < 0) || !(markerIndexes[0] < markerIndexes[1] && markerIndexes[1] < markerIndexes[2])) { failures.push('compatibility, canonical and responsive component sections are missing or out of order'); } const componentsRoot = postcss.parse(componentsSource, { from: contract.componentsFile }); const componentRules = []; componentsRoot.walkRules((rule) => componentRules.push(rule)); const selectorCount = componentRules.reduce((total, rule) => total + rule.selectors.length, 0); if (componentRules.length < contract.minimumRuleCount) { failures.push(`${contract.componentsFile} has ${componentRules.length} rules; expected at least ${contract.minimumRuleCount}`); } if (selectorCount < contract.minimumSelectorCount) { failures.push(`${contract.componentsFile} has ${selectorCount} selectors; expected at least ${contract.minimumSelectorCount}`); } for (const requirement of contract.requiredRuleFragments) { const matches = componentRules.filter( (rule) => rule.selectors.includes(requirement.selector) && mediaOf(rule) === requirement.media, ); if (matches.length === 0) { failures.push( `${contract.componentsFile} is missing ${requirement.selector}` + `${requirement.media ? ` in @media ${requirement.media}` : ' outside media queries'}`, ); continue; } const declarations = matches.map(normalizedDeclarations).join(';'); for (const fragment of requirement.includes) { if (!declarations.includes(fragment)) { failures.push(`${requirement.selector} is missing declaration ${fragment}`); } } } const legacyRoot = postcss.parse(read(contract.legacyFile), { from: contract.legacyFile }); legacyRoot.walkRules((rule) => { for (const selector of rule.selectors) { if (isPureSharedSelector(selector)) { failures.push(`${contract.legacyFile} still owns shared component selector ${selector}`); } } }); for (const [file, classNames] of Object.entries(contract.requiredComponentBindings)) { const source = read(file); for (const className of classNames) { if (!source.includes(className)) failures.push(`${file} is missing component class ${className}`); } } if (failures.length > 0) { console.error(`R11 shared components verification failed:\n- ${[...new Set(failures)].join('\n- ')}`); process.exit(1); } console.log( `R11 shared components verified: ${componentRules.length} rules, ${selectorCount} selectors, ` + `${contract.genericClasses.length} generic class families and desktop/mobile ownership are isolated.`, );