167 lines
4.0 KiB
JavaScript
167 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const root = process.cwd();
|
|
const appSourceRoots = ['apps/web/src'];
|
|
const strictVisualRoots = ['apps/web/src/ui', 'docs/page-templates'];
|
|
const allowedLegacyFiles = new Set([
|
|
'apps/web/src/workbench/WorkbenchShell.tsx',
|
|
]);
|
|
|
|
const bannedImports = [
|
|
{
|
|
pattern: /from\s+['"]tdesign-react['"]/,
|
|
message: 'New app UI must use @/ui instead of tdesign-react.',
|
|
},
|
|
{
|
|
pattern: /from\s+['"]tdesign-icons-react['"]/,
|
|
message: 'New icons must use lucide-react instead of tdesign-icons-react.',
|
|
},
|
|
];
|
|
|
|
const bannedBluePrimary = [
|
|
/#1677ff/i,
|
|
/#1890ff/i,
|
|
/#2563eb/i,
|
|
/#3b82f6/i,
|
|
/rgb\(\s*37\s*,\s*99\s*,\s*235\s*\)/i,
|
|
];
|
|
|
|
const uiRequiredDirs = [
|
|
'apps/web/src/ui',
|
|
'docs/page-templates',
|
|
];
|
|
|
|
const requiredFiles = [
|
|
{
|
|
path: 'docs/TRUEGROWTH_GOLDEN_IMAGE_PAGE_UI.md',
|
|
includes: [
|
|
'/image',
|
|
'golden page',
|
|
'Browser QA Requirement',
|
|
'light and dark',
|
|
],
|
|
},
|
|
{
|
|
path: 'docs/TRUEGROWTH_UI_SYSTEM.md',
|
|
includes: ['TRUEGROWTH_GOLDEN_IMAGE_PAGE_UI.md', '/image'],
|
|
},
|
|
{
|
|
path: 'docs/TRUEGROWTH_GOLDEN_PAGE_MIGRATION_AUDIT.md',
|
|
includes: ['/video', '/avatar', '/assets', 'Browser Evidence'],
|
|
},
|
|
{
|
|
path: 'AGENTS.md',
|
|
includes: ['TRUEGROWTH_GOLDEN_IMAGE_PAGE_UI.md', '/image'],
|
|
},
|
|
{
|
|
path: 'docs/page-templates/generator-page.tsx',
|
|
includes: ['tg-golden-generator'],
|
|
},
|
|
{
|
|
path: 'docs/page-templates/media-library-page.tsx',
|
|
includes: ['tg-golden-library'],
|
|
},
|
|
{
|
|
path: 'docs/page-templates/status-page.tsx',
|
|
includes: ['tg-golden-status'],
|
|
},
|
|
];
|
|
|
|
const errors = [];
|
|
|
|
for (const dir of appSourceRoots) {
|
|
walk(path.join(root, dir), (file) => {
|
|
const relative = path.relative(root, file).replace(/\\/g, '/');
|
|
if (!/\.(tsx?)$/.test(relative)) {
|
|
return;
|
|
}
|
|
const content = fs.readFileSync(file, 'utf8');
|
|
if (!allowedLegacyFiles.has(relative)) {
|
|
for (const rule of bannedImports) {
|
|
if (rule.pattern.test(content)) {
|
|
errors.push(`${relative}: ${rule.message}`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const dir of strictVisualRoots) {
|
|
walk(path.join(root, dir), (file) => {
|
|
const relative = path.relative(root, file).replace(/\\/g, '/');
|
|
if (!/\.(tsx?|scss|css)$/.test(relative)) {
|
|
return;
|
|
}
|
|
const content = fs.readFileSync(file, 'utf8');
|
|
for (const rule of bannedImports) {
|
|
if (rule.pattern.test(content)) {
|
|
errors.push(`${relative}: ${rule.message}`);
|
|
}
|
|
}
|
|
for (const pattern of bannedBluePrimary) {
|
|
if (pattern.test(content)) {
|
|
errors.push(
|
|
`${relative}: Blue primary/focus colors are not allowed in the UI system. Use TrueGrowth orange tokens.`
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const dir of uiRequiredDirs) {
|
|
if (!fs.existsSync(path.join(root, dir))) {
|
|
errors.push(`${dir}: required UI system directory is missing.`);
|
|
}
|
|
}
|
|
|
|
for (const file of requiredFiles) {
|
|
const absolute = path.join(root, file.path);
|
|
if (!fs.existsSync(absolute)) {
|
|
errors.push(`${file.path}: required golden UI standard file is missing.`);
|
|
continue;
|
|
}
|
|
|
|
const content = fs.readFileSync(absolute, 'utf8');
|
|
for (const text of file.includes) {
|
|
if (!content.includes(text)) {
|
|
errors.push(`${file.path}: missing required golden UI reference "${text}".`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
console.error('TrueGrowth UI system check failed:');
|
|
for (const error of errors) {
|
|
console.error(`- ${error}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('TrueGrowth UI system check passed.');
|
|
|
|
function walk(dir, visit) {
|
|
if (!fs.existsSync(dir)) {
|
|
return;
|
|
}
|
|
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
if (
|
|
entry.name === 'node_modules' ||
|
|
entry.name === 'dist' ||
|
|
entry.name === '.nx'
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
walk(fullPath, visit);
|
|
} else if (entry.isFile()) {
|
|
visit(fullPath);
|
|
}
|
|
}
|
|
}
|