import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { afterEach, describe, it } from "vitest"; import { formatCiBaselineResult, verifyCiBaseline } from "./verify-ci-baseline.mjs"; const VALID_PACKAGE = { private: true, packageManager: "pnpm@10.33.0", scripts: { build: "pnpm --filter @toolctrl/db prisma:generate && pnpm --filter @toolctrl/db build && pnpm --filter @toolctrl/web build", "build:ci": "pnpm run build", lint: "eslint --max-warnings=0 --ext .tsx,.ts .", test: "pnpm --filter @toolctrl/web test", "test:packages": "node scripts/verify-package-test-scripts.mjs && pnpm --filter './packages/*' --if-present test", "test:mobile": "pnpm --filter @toolctrl/mobile test", typecheck: "tsc -b", "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", }, }; const PHASE_C_DESCRIPTOR_SELECTORS = [ "Property[key.name=/^(title|label|placeholder|ariaLabel|alt|description|message|heading|subject)$/] > Literal[value=/[A-Za-zÄÖÜäöü]/]", "Property[key.value=/^(title|label|placeholder|aria-label|ariaLabel|alt|description|message|heading|subject)$/] > Literal[value=/[A-Za-zÄÖÜäöü]/]", "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", ]; const VALID_ESLINTRC = { root: true, rules: { "no-console": "warn", "@typescript-eslint/no-unused-vars": "warn", }, overrides: [ { files: ["packages/ui-app/src/**/*.{ts,tsx}", "packages/ui-subframe/src/**/*.{ts,tsx}", "packages/ui-mock/src/**/*.{ts,tsx}"], rules: { "no-restricted-syntax": [ "error", ...PHASE_C_DESCRIPTOR_SELECTORS.map((selector) => ({ selector, message: "Use i18n key fields instead of hardcoded user-facing strings in Phase C UI package descriptors.", })), ], }, }, { files: ["packages/ui-app/src/**/*.{tsx}", "packages/ui-subframe/src/**/*.{tsx}", "packages/ui-mock/src/**/*.{tsx}"], rules: { "no-restricted-syntax": [ "error", ...PHASE_C_DESCRIPTOR_SELECTORS.map((selector) => ({ selector, message: "Use i18n key fields instead of hardcoded user-facing strings in Phase C UI package descriptors.", })), { selector: "JSXText[value=/\\S/]", message: "Use i18n label keys or translated values instead of hardcoded JSX text in Phase C UI packages.", }, { selector: "JSXAttribute[name.name=/^(aria-label|title|placeholder)$/] > Literal[value=/[A-Za-zÄÖÜäöü]/]", message: "Use i18n label keys or translated values instead of hardcoded JSX accessibility/title text in Phase C UI packages.", }, ], }, }, ], }; const fixtures = []; function writeFile(rootDir, relativePath, content) { const absolutePath = path.join(rootDir, relativePath); fs.mkdirSync(path.dirname(absolutePath), { recursive: true }); fs.writeFileSync(absolutePath, content); } function writeJson(rootDir, relativePath, value) { writeFile(rootDir, relativePath, `${JSON.stringify(value, null, 2)}\n`); } function createFixture(overrides = {}) { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-baseline-")); fixtures.push(rootDir); writeJson(rootDir, "package.json", overrides.rootPackage ?? VALID_PACKAGE); writeFile(rootDir, "pnpm-lock.yaml", overrides.lockfile ?? "lockfileVersion: '9.0'\n"); writeJson(rootDir, ".eslintrc", overrides.eslintrc ?? VALID_ESLINTRC); writeFile(rootDir, "packages/ui-app/src/index.ts", "export const uiApp = true;\n"); writeFile(rootDir, "packages/ui-subframe/src/index.ts", "export const uiSubframe = true;\n"); writeFile(rootDir, "packages/ui-mock/src/index.ts", "export const uiMock = true;\n"); if (overrides.remove) { for (const relativePath of overrides.remove) { fs.rmSync(path.join(rootDir, relativePath), { recursive: true, force: true }); } } return rootDir; } function mutatePackage(mutator) { const rootPackage = structuredClone(VALID_PACKAGE); mutator(rootPackage); return rootPackage; } afterEach(() => { while (fixtures.length > 0) { fs.rmSync(fixtures.pop(), { recursive: true, force: true }); } }); describe("verify-ci-baseline", () => { it("passes for a repository with required CI prerequisites", () => { const rootDir = createFixture(); const result = verifyCiBaseline(rootDir); const output = formatCiBaselineResult(result); assert.deepEqual(result.failures, []); assert.ok(result.passes.some((pass) => pass === "package.json: packageManager is pnpm@10.33.0")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"typecheck\" is defined")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"test:mobile\" is defined")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"build:ci\" is defined")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"build:ci\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"lint\" invokes ESLint")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"lint\" fails on ESLint warnings")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"verify:i18n\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"verify:ui-hot-swap\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"test:hot-swap\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"verify:test-coverage\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"verify:sidebar-routes\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"verify:a11y-coverage\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"test:a11y\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"test:mobile\" matches the required CI command")); assert.ok(result.passes.some((pass) => pass === "package.json: script \"verify:i18n\" runs ESLint over Phase C UI packages")); assert.ok(result.passes.some((pass) => pass === "pnpm-lock.yaml: exists")); assert.ok(result.passes.some((pass) => pass === ".eslintrc: rule \"no-console\" is warn")); assert.ok(result.passes.some((pass) => pass === ".eslintrc: rule \"@typescript-eslint/no-unused-vars\" is warn")); assert.ok(result.passes.some((pass) => pass.includes(".eslintrc: Phase C i18n ESLint selector \"JSXText"))); assert.match(output, /^PASS CI baseline verification/); assert.match(output, /Summary: \d+ passed, 0 observed, 0 failed/); }); it("fails when the lockfile is missing", () => { const rootDir = createFixture({ remove: ["pnpm-lock.yaml"] }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /pnpm-lock\.yaml: missing required CI baseline file/); }); it("fails when the lint script is missing", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { delete rootPackage.scripts.lint; }), }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /package\.json: missing required script "lint"/); }); it("fails when the lint script does not invoke ESLint", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { rootPackage.scripts.lint = "prettier --check ."; }), }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /package\.json: script "lint" must invoke ESLint/); }); it("fails when the lint script allows ESLint warnings", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { rootPackage.scripts.lint = "eslint --ext .tsx,.ts ."; }), }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /package\.json: script "lint" must include --max-warnings=0/); }); it("fails when ESLint config is missing", () => { const rootDir = createFixture({ remove: [".eslintrc"] }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /\.eslintrc: missing required CI baseline file/); }); it("fails when required ESLint warn baselines are not explicit", () => { const rootDir = createFixture({ eslintrc: { root: true, rules: { "no-console": "error", "@typescript-eslint/no-unused-vars": "off", }, }, }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /\.eslintrc: rule "no-console" must be warn for the current CI baseline/); assert.match(result.failures.join("\n"), /\.eslintrc: rule "@typescript-eslint\/no-unused-vars" must be warn for the current CI baseline/); }); it("fails when verify:i18n does not run ESLint over Phase C UI packages", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { rootPackage.scripts["verify:i18n"] = "tsx scripts/i18n-check.ts"; }), }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /package\.json: script "verify:i18n" must run ESLint over Phase C UI packages/); }); it("fails when critical verification script bodies are softened", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { rootPackage.scripts["verify:i18n"] = "eslint --ext .tsx,.ts packages/ui-app/src packages/ui-subframe/src packages/ui-mock/src"; rootPackage.scripts["verify:ui-hot-swap"] = "echo skipped"; rootPackage.scripts["test:hot-swap"] = "pnpm run verify:ui-hot-swap || true"; rootPackage.scripts["verify:test-coverage"] = "echo skipped"; rootPackage.scripts["verify:sidebar-routes"] = "echo skipped"; rootPackage.scripts["verify:a11y-coverage"] = "echo skipped"; rootPackage.scripts["test:a11y"] = "echo skipped"; }), }); const result = verifyCiBaseline(rootDir); const failures = result.failures.join("\n"); assert.match(failures, /package\.json: script "verify:i18n" must be exactly "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"/); assert.match(failures, /package\.json: script "verify:ui-hot-swap" must be exactly "tsx scripts\/verify-ui-hot-swap\.ts"/); assert.match(failures, /package\.json: script "test:hot-swap" must be exactly "pnpm exec vitest run scripts\/verify-ui-hot-swap\.test\.ts && pnpm run verify:ui-hot-swap"/); assert.match(failures, /package\.json: script "verify:test-coverage" must be exactly "node scripts\/verify-test-coverage\.mjs"/); assert.match(failures, /package\.json: script "verify:sidebar-routes" must be exactly "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"/); assert.match(failures, /package\.json: script "verify:a11y-coverage" must be exactly "node scripts\/verify-a11y-coverage\.mjs"/); assert.match(failures, /package\.json: script "test:a11y" must be exactly "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"/); }); it("fails when build omits Prisma generation", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { rootPackage.scripts.build = "pnpm --filter @toolctrl/db build && pnpm --filter @toolctrl/web build"; }), }); const result = verifyCiBaseline(rootDir); assert.match( result.failures.join("\n"), /package\.json: script "build" must be exactly "pnpm --filter @toolctrl\/db prisma:generate && pnpm --filter @toolctrl\/db build && pnpm --filter @toolctrl\/web build"/, ); }); it("fails when verify:i18n suppresses unmatched lint roots", () => { const rootDir = createFixture({ rootPackage: mutatePackage((rootPackage) => { rootPackage.scripts["verify:i18n"] = "eslint --no-error-on-unmatched-pattern --ext .tsx,.ts packages/ui-app/src packages/ui-subframe/src packages/ui-mock/src"; }), }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /package\.json: script "verify:i18n" must not include --no-error-on-unmatched-pattern/); }); it("fails when a Phase C UI lint root is missing", () => { const rootDir = createFixture({ remove: ["packages/ui-mock/src"] }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /packages\/ui-mock\/src: missing Phase C UI lint root/); }); it("fails when Phase C i18n JSX ESLint selectors are missing", () => { const rootDir = createFixture({ eslintrc: { ...VALID_ESLINTRC, overrides: [VALID_ESLINTRC.overrides[0]], }, }); const result = verifyCiBaseline(rootDir); assert.match(result.failures.join("\n"), /\.eslintrc: missing Phase C i18n ESLint selector "JSXText/); assert.match(result.failures.join("\n"), /\.eslintrc: missing Phase C i18n ESLint selector "JSXAttribute/); }); it("CLI exits non-zero and prints path-specific output for a missing prerequisite", () => { const rootDir = createFixture({ remove: ["pnpm-lock.yaml"] }); const scriptPath = path.resolve("scripts/verify-ci-baseline.mjs"); assert.throws( () => execFileSync(process.execPath, [scriptPath], { cwd: rootDir, encoding: "utf8", stdio: "pipe" }), (error) => { assert.equal(error.status, 1); assert.match(error.stderr, /^FAIL CI baseline verification/); assert.match(error.stderr, /FAIL pnpm-lock\.yaml: missing required CI baseline file/); return true; }, ); }); });