#!/usr/bin/env node /* eslint-disable @typescript-eslint/explicit-function-return-type */ import fs from 'node:fs' import path from 'node:path' const root = path.resolve(process.cwd()) const sourceSpecs = [ path.join(root, 'apps/web/e2e/critical-flows.spec.ts'), path.join(root, 'apps/web/e2e/critical-flows-extended.spec.ts'), ] const fullstackDir = path.join(root, 'apps/web/e2e/full-stack') /** * @param {string} filePath * @returns {string} */ function read(filePath) { return fs.readFileSync(filePath, 'utf8') } /** * @param {string} content * @returns {string[]} */ function extractTestNames(content) { const names = [] const regex = /\btest\s*\(\s*(['"])(.*?)\1/g let match while ((match = regex.exec(content)) !== null) { names.push(match[2]) } return names } /** * @param {string} dir * @returns {string[]} */ function listFullstackSpecs(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }) return entries .filter((entry) => entry.isFile() && entry.name.endsWith('.spec.ts')) .map((entry) => path.join(dir, entry.name)) } const sourceNames = new Set() for (const spec of sourceSpecs) { for (const name of extractTestNames(read(spec))) { sourceNames.add(name) } } const fullstackNames = new Set() for (const spec of listFullstackSpecs(fullstackDir)) { for (const name of extractTestNames(read(spec))) { fullstackNames.add(name) } } const missing = [...sourceNames] .filter((name) => !fullstackNames.has(name)) .sort((a, b) => a.localeCompare(b)) if (missing.length > 0) { console.error('Full-stack parity check failed. Missing scenarios:') for (const name of missing) { console.error(`- ${name}`) } process.exit(1) } process.stdout.write(`Full-stack parity check passed (${sourceNames.size} source scenarios covered).\n`)