#!/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); } } }