80 lines
2.7 KiB
JavaScript
80 lines
2.7 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import crypto from 'node:crypto';
|
|
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
|
const required = [
|
|
'09_DEVELOPER_HANDOFF_v1.0.md',
|
|
'DECISION_LOG_v9.0.md',
|
|
'DEFINITION_OF_DONE_v1.0.md',
|
|
'OPEN_ISSUE_REGISTER_v1.0.csv',
|
|
'tokens/design-tokens_v1.0.json',
|
|
'tokens/semantic-tokens_v1.0.css',
|
|
'components/COMPONENT_CONTRACTS_v1.0.md',
|
|
'localization/ROUTE_MANIFEST_v1.0.json',
|
|
'localization/locales/en/homepage.json',
|
|
'localization/locales/fr/homepage.json',
|
|
'localization/locales/ar/homepage.json',
|
|
'interaction/theme-bootstrap_v1.0.js',
|
|
'qa/QA_MATRIX_v1.0.csv',
|
|
'qa/ACCESSIBILITY_CHECKLIST_v1.0.csv',
|
|
'qa/PERFORMANCE_BUDGETS_v1.0.json',
|
|
];
|
|
const errors = [];
|
|
let checks = 0;
|
|
for (const file of required) {
|
|
checks++;
|
|
if (!fs.existsSync(path.join(root, file))) errors.push(`Missing ${file}`);
|
|
}
|
|
const readJson = (f) => JSON.parse(fs.readFileSync(path.join(root, f), 'utf8'));
|
|
for (const file of [
|
|
'tokens/design-tokens_v1.0.json',
|
|
'localization/ROUTE_MANIFEST_v1.0.json',
|
|
'qa/PERFORMANCE_BUDGETS_v1.0.json',
|
|
'qa/visual-regression-scenarios_v1.0.json',
|
|
]) {
|
|
try {
|
|
readJson(file);
|
|
checks++;
|
|
} catch (e) {
|
|
errors.push(`${file}: ${e.message}`);
|
|
}
|
|
}
|
|
function shape(value) {
|
|
if (Array.isArray(value)) return value.map(shape);
|
|
if (value && typeof value === 'object')
|
|
return Object.fromEntries(
|
|
Object.keys(value)
|
|
.sort()
|
|
.map((k) => [k, shape(value[k])]),
|
|
);
|
|
return typeof value;
|
|
}
|
|
const locales = ['en', 'fr', 'ar'].map((l) => readJson(`localization/locales/${l}/homepage.json`));
|
|
const base = JSON.stringify(shape(locales[0]));
|
|
for (let i = 1; i < locales.length; i++) {
|
|
checks++;
|
|
if (JSON.stringify(shape(locales[i])) !== base)
|
|
errors.push(`Locale shape mismatch: ${['en', 'fr', 'ar'][i]}`);
|
|
}
|
|
const routes = readJson('localization/ROUTE_MANIFEST_v1.0.json');
|
|
for (const route of routes.routes) {
|
|
for (const locale of routes.locales) {
|
|
checks++;
|
|
if (!(locale in route.slugs)) errors.push(`Route ${route.id} missing ${locale}`);
|
|
}
|
|
}
|
|
const issueCsv = fs.readFileSync(path.join(root, 'OPEN_ISSUE_REGISTER_v1.0.csv'), 'utf8');
|
|
checks++;
|
|
if (/,(Critical|High),[^\n]*,,/.test(issueCsv))
|
|
errors.push('Critical/High issue missing ownership data');
|
|
const css = fs.readFileSync(path.join(root, 'tokens/semantic-tokens_v1.0.css'), 'utf8');
|
|
checks++;
|
|
if (!css.includes('--color-action-conversion')) errors.push('Conversion semantic token missing');
|
|
if (errors.length) {
|
|
console.error(JSON.stringify({ status: 'FAILED', checks, errors }, null, 2));
|
|
process.exit(1);
|
|
}
|
|
console.log(
|
|
JSON.stringify({ status: 'PASSED', checks, files: fs.readdirSync(root).length }, null, 2),
|
|
);
|