#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const REQUIRED_PACKAGE_MANAGER = "pnpm@10.33.0"; const REQUIRED_SCRIPTS = ["typecheck", "lint", "test", "test:packages", "test:mobile", "build", "build:ci"]; const REQUIRED_FILES = ["package.json", "pnpm-lock.yaml", ".eslintrc"]; const REQUIRED_SCRIPT_BODIES = { build: "pnpm --filter @toolctrl/db prisma:generate && pnpm --filter @toolctrl/db build && pnpm --filter @toolctrl/web build", "build:ci": "pnpm run build", "verify:i18n": "tsx scripts/i18n-check.ts && node scripts/verify-hardcoded-strings.mjs && eslint --max-warnings=0 --ext .tsx,.ts packages/ui-app/src packages/ui-subframe/src packages/ui-mock/src", "verify:ui-hot-swap": "tsx scripts/verify-ui-hot-swap.ts", "test:hot-swap": "pnpm exec vitest run scripts/verify-ui-hot-swap.test.ts && pnpm run verify:ui-hot-swap", "verify:test-coverage": "node scripts/verify-test-coverage.mjs", "verify:sidebar-routes": "pnpm exec vitest run apps/web/app/application/sidebar/AdminSidebar.test.tsx apps/web/app/application/sidebar/AppSidebar.test.tsx apps/web/app/components/layouts/sidebars/shadcn/app-sidebar.test.tsx apps/web/app/routes/admin/admin-route-targets.test.ts apps/web/app/routes.test.ts", "verify:a11y-coverage": "node scripts/verify-a11y-coverage.mjs", "test:a11y": "pnpm exec vitest run apps/web/app/components/ui/button.a11y.test.tsx apps/web/app/components/ui/errors/ErrorPage.a11y.test.tsx apps/web/app/components/ui/*.axe.test.tsx apps/web/app/components/core/SkipToContent.test.tsx apps/web/app/components/aceternity/HoverEffect.a11y.test.tsx apps/web/app/components/ui/lists/ViewToggle.a11y.test.tsx apps/web/app/components/ui/lists/ViewToggleWithUrl.a11y.test.tsx apps/web/app/components/ui/tabs/TabsWithIcons.a11y.test.tsx scripts/verify-color-contrast.test.mjs", "test:mobile": "pnpm --filter @toolctrl/mobile test", }; const PHASE_C_UI_GLOBS = ["packages/ui-app/src", "packages/ui-subframe/src", "packages/ui-mock/src"]; const PHASE_C_I18N_SELECTOR_SNIPPETS = [ "Property[key.name=/^(title|label|placeholder|ariaLabel|alt|description|message|heading|subject)$/] > Literal", "Property[key.value=/^(title|label|placeholder|aria-label|ariaLabel|alt|description|message|heading|subject)$/] > Literal", "Property[key.name=/^(title|label|placeholder|ariaLabel|alt|description|message|heading|subject)$/] > TemplateLiteral", "Property[key.value=/^(title|label|placeholder|aria-label|ariaLabel|alt|description|message|heading|subject)$/] > TemplateLiteral", "JSXText", "JSXAttribute[name.name=/^(aria-label|title|placeholder)$/] > Literal", ]; function readJsonFile(rootDir, relativePath) { return JSON.parse(fs.readFileSync(path.join(rootDir, relativePath), "utf8")); } function checkFileExists(rootDir, relativePath) { return fs.existsSync(path.join(rootDir, relativePath)); } function hasScript(manifest, scriptName) { return typeof manifest.scripts?.[scriptName] === "string" && manifest.scripts[scriptName].trim().length > 0; } function isWarnRule(ruleValue) { if (ruleValue === "warn" || ruleValue === 1) { return true; } return Array.isArray(ruleValue) && (ruleValue[0] === "warn" || ruleValue[0] === 1); } export function checkRequiredFiles(rootDir) { const passes = []; const failures = []; for (const relativePath of REQUIRED_FILES) { if (checkFileExists(rootDir, relativePath)) { passes.push(`${relativePath}: exists`); } else { failures.push(`${relativePath}: missing required CI baseline file`); } } return { passes, failures, observations: [] }; } export function checkPackageManagerAndScripts(rootDir) { const relativePath = "package.json"; const passes = []; const failures = []; if (!checkFileExists(rootDir, relativePath)) { return { passes, failures: [`${relativePath}: missing required CI baseline file`], observations: [] }; } let manifest; try { manifest = readJsonFile(rootDir, relativePath); } catch (error) { return { passes, failures: [`${relativePath}: failed to read manifest JSON: ${error.message}`], observations: [] }; } if (manifest.packageManager === REQUIRED_PACKAGE_MANAGER) { passes.push(`${relativePath}: packageManager is ${REQUIRED_PACKAGE_MANAGER}`); } else { failures.push(`${relativePath}: packageManager must be exactly ${REQUIRED_PACKAGE_MANAGER}`); } for (const scriptName of REQUIRED_SCRIPTS) { if (hasScript(manifest, scriptName)) { passes.push(`${relativePath}: script "${scriptName}" is defined`); } else { failures.push(`${relativePath}: missing required script "${scriptName}"`); } } for (const [scriptName, expectedCommand] of Object.entries(REQUIRED_SCRIPT_BODIES)) { const actualCommand = manifest.scripts?.[scriptName]; if (actualCommand === expectedCommand) { passes.push(`${relativePath}: script "${scriptName}" matches the required CI command`); } else if (typeof actualCommand === "string" && actualCommand.trim().length > 0) { failures.push(`${relativePath}: script "${scriptName}" must be exactly "${expectedCommand}"`); } else { failures.push(`${relativePath}: missing required script "${scriptName}"`); } } if (hasScript(manifest, "lint")) { const lintCommand = manifest.scripts.lint; if (/\beslint\b/.test(lintCommand)) { passes.push(`${relativePath}: script "lint" invokes ESLint`); } else { failures.push(`${relativePath}: script "lint" must invoke ESLint`); } if (/\s--max-warnings=0(?:\s|$)/.test(` ${lintCommand} `)) { passes.push(`${relativePath}: script "lint" fails on ESLint warnings`); } else { failures.push(`${relativePath}: script "lint" must include --max-warnings=0`); } } if (hasScript(manifest, "verify:i18n")) { const i18nCommand = manifest.scripts["verify:i18n"]; if (/\beslint\b/.test(i18nCommand) && PHASE_C_UI_GLOBS.every((uiGlob) => i18nCommand.includes(uiGlob))) { passes.push(`${relativePath}: script "verify:i18n" runs ESLint over Phase C UI packages`); } else { failures.push(`${relativePath}: script "verify:i18n" must run ESLint over Phase C UI packages`); } if (i18nCommand.includes("--no-error-on-unmatched-pattern")) { failures.push(`${relativePath}: script "verify:i18n" must not include --no-error-on-unmatched-pattern`); } else { passes.push(`${relativePath}: script "verify:i18n" fails when configured UI lint roots disappear`); } if (/\s--max-warnings=0(?:\s|$)/.test(` ${i18nCommand} `)) { passes.push(`${relativePath}: script "verify:i18n" fails on ESLint warnings`); } else { failures.push(`${relativePath}: script "verify:i18n" must include --max-warnings=0`); } } else { failures.push(`${relativePath}: missing required script "verify:i18n"`); } return { passes, failures, observations: [] }; } export function checkPhaseCUiLintRoots(rootDir) { const passes = []; const failures = []; for (const uiRoot of PHASE_C_UI_GLOBS) { const absoluteRoot = path.join(rootDir, uiRoot); if (!fs.existsSync(absoluteRoot) || !fs.statSync(absoluteRoot).isDirectory()) { failures.push(`${uiRoot}: missing Phase C UI lint root`); continue; } const lintableFiles = listLintableFiles(absoluteRoot); if (lintableFiles.length === 0) { failures.push(`${uiRoot}: Phase C UI lint root contains no TypeScript files`); } else { passes.push(`${uiRoot}: Phase C UI lint root contains ${lintableFiles.length} TypeScript file(s)`); } } return { passes, failures, observations: [] }; } function listLintableFiles(absoluteRoot) { const files = []; for (const entry of fs.readdirSync(absoluteRoot, { withFileTypes: true })) { const absolutePath = path.join(absoluteRoot, entry.name); if (entry.isDirectory()) { files.push(...listLintableFiles(absolutePath)); } else if (/\.(?:ts|tsx)$/.test(entry.name)) { files.push(absolutePath); } } return files; } export function checkEslintBaseline(rootDir) { const relativePath = ".eslintrc"; const passes = []; const failures = []; if (!checkFileExists(rootDir, relativePath)) { return { passes, failures: [`${relativePath}: missing required CI baseline file`], observations: [] }; } let eslintConfig; try { eslintConfig = readJsonFile(rootDir, relativePath); } catch (error) { return { passes, failures: [`${relativePath}: failed to read ESLint config JSON: ${error.message}`], observations: [] }; } const rules = eslintConfig.rules ?? {}; const requiredWarnRules = ["no-console", "@typescript-eslint/no-unused-vars"]; for (const ruleName of requiredWarnRules) { if (isWarnRule(rules[ruleName])) { passes.push(`${relativePath}: rule "${ruleName}" is warn`); } else { failures.push(`${relativePath}: rule "${ruleName}" must be warn for the current CI baseline`); } } const restrictedSyntaxSelectors = getPhaseCRestrictedSyntaxSelectors(eslintConfig); for (const selectorSnippet of PHASE_C_I18N_SELECTOR_SNIPPETS) { if (restrictedSyntaxSelectors.some((selector) => selector.includes(selectorSnippet))) { passes.push(`${relativePath}: Phase C i18n ESLint selector "${selectorSnippet}" is enforced`); } else { failures.push(`${relativePath}: missing Phase C i18n ESLint selector "${selectorSnippet}"`); } } return { passes, failures, observations: [] }; } function getPhaseCRestrictedSyntaxSelectors(eslintConfig) { const selectors = []; for (const override of eslintConfig.overrides ?? []) { const files = Array.isArray(override.files) ? override.files : [override.files].filter(Boolean); const targetsPhaseCUi = PHASE_C_UI_GLOBS.every((uiGlob) => files.some((filePattern) => String(filePattern).includes(uiGlob))); const rule = override.rules?.["no-restricted-syntax"]; if (!targetsPhaseCUi || !Array.isArray(rule) || rule[0] !== "error") { continue; } for (const entry of rule.slice(1)) { if (entry && typeof entry.selector === "string") { selectors.push(entry.selector); } } } return selectors; } export function verifyCiBaseline(rootDir = process.cwd()) { const checks = [checkRequiredFiles(rootDir), checkPackageManagerAndScripts(rootDir), checkEslintBaseline(rootDir), checkPhaseCUiLintRoots(rootDir)]; return checks.reduce( (result, check) => ({ passes: [...result.passes, ...check.passes], failures: [...result.failures, ...check.failures], observations: [...result.observations, ...check.observations], }), { passes: [], failures: [], observations: [] }, ); } export function formatCiBaselineResult(result) { const status = result.failures.length > 0 ? "FAIL" : "PASS"; return [ `${status} CI baseline verification`, ...result.passes.map((pass) => `PASS ${pass}`), ...result.observations.map((observation) => `OBSERVE ${observation}`), ...result.failures.map((failure) => `FAIL ${failure}`), `Summary: ${result.passes.length} passed, ${result.observations.length} observed, ${result.failures.length} failed`, "", ].join("\n"); } const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); if (isCli) { const result = verifyCiBaseline(process.cwd()); const output = formatCiBaselineResult(result); if (result.failures.length > 0) { process.stderr.write(output); process.exitCode = 1; } else { process.stdout.write(output); } }