import { spawnSync } from "node:child_process"; import { cp, mkdir, readFile, readdir, rm, symlink, writeFile, } from "node:fs/promises"; import path from "node:path"; const fixtureRoot = path.resolve(".tmp/reference-feature-removal"); const pnpmCli = /** @type {string} */ (process.env.npm_execpath); const featureSource = "src/features/reference-feature"; const featureTests = "tests/features/reference-feature"; const featureOwnedPaths = [ featureSource, featureTests, "tests/e2e/reference-form.spec.js", "tests/e2e/reference-route.spec.js", "tests/mocks", ]; const copyTargets = [ "src", "tests", "recipes", "scripts", "config", "public", "index.html", "package.json", "tsconfig.base.json", "tsconfig.json", "tsconfig.app.json", "tsconfig.node.json", "tsconfig.test.json", "tsconfig.recipes.json", "vite.config.js", "vitest.config.js", "playwright.config.js", "eslint.config.js", ".dependency-cruiser.cjs", ]; const emptyContracts = `import { PLATFORM_ROUTE_RUNTIME_CONTRACT } from "../contracts/route-runtime-contract.js"; import { PLATFORM_ROUTE_REGISTRY } from "../contracts/routes.js"; import { PLATFORM_SCHEMA_REGISTRY } from "../contracts/schema-registry.js"; export const INSTALLED_FEATURE_CONTRACTS = /** @type {readonly unknown[]} */ (Object.freeze([])); export const ROUTE_REGISTRY = PLATFORM_ROUTE_REGISTRY; export const ROUTE_RUNTIME_CONTRACT = PLATFORM_ROUTE_RUNTIME_CONTRACT; export const API_OPERATIONS = Object.freeze({}); export const QUERY_REGISTRY = Object.freeze({}); export const SCHEMA_REGISTRY = PLATFORM_SCHEMA_REGISTRY; export const NAVIGATION_ROUTES = Object.freeze( Object.values(ROUTE_REGISTRY) .filter((definition) => definition.navigationOrder !== null) .sort( (left, right) => /** @type {number} */ (left.navigationOrder) - /** @type {number} */ (right.navigationOrder), ), ); /** @param {string} routeId */ export function getRoute(routeId) { const registry = /** @type {Readonly>} */ ( ROUTE_REGISTRY ); const selected = registry[routeId]; if (!selected) throw new Error(\`Unregistered route: \${routeId}\`); return selected; } /** @param {string} routeId */ export function routePath(routeId) { return getRoute(routeId).path; } `; const emptyRuntimes = `import { PLATFORM_ROUTE_CODECS } from "../presentation/routes/platform-route-codecs.js"; import { PLATFORM_ROUTE_RUNTIME } from "../presentation/routes/route-runtime.js"; export const ROUTE_CODECS = PLATFORM_ROUTE_CODECS; export const ROUTE_RUNTIME = PLATFORM_ROUTE_RUNTIME; `; const emptyAdapters = `type FeatureContext = Readonly<{ createHttpClient(contract: Readonly>): unknown; }>; export function createInstalledFeatureInputs(_context: FeatureContext) { void _context; return Object.freeze({}); } `; const emptyMessages = `export const INSTALLED_MESSAGE_CATALOGS = Object.freeze({ "ko-KR": Object.freeze({}), "en-US": Object.freeze({}), }); `; /** @param {string} directory @returns {Promise} */ async function filesBelow(directory) { const entries = await readdir(directory, { withFileTypes: true }); const groups = await Promise.all( entries.map((entry) => { const target = path.join(directory, entry.name); return entry.isDirectory() ? filesBelow(target) : [target]; }), ); return groups.flat(); } /** @param {string} script @param {string[]} [extra] */ function runPnpm(script, extra = []) { const result = spawnSync(process.execPath, [pnpmCli, script, ...extra], { cwd: fixtureRoot, stdio: "inherit", }); return result.status === 0; } await rm(fixtureRoot, { recursive: true, force: true }); await mkdir(fixtureRoot, { recursive: true }); for (const target of copyTargets) { await cp(target, path.join(fixtureRoot, target), { recursive: true }); } await symlink(path.resolve("node_modules"), path.join(fixtureRoot, "node_modules"), "dir"); for (const ownedPath of featureOwnedPaths) { await rm(path.join(fixtureRoot, ownedPath), { recursive: true, force: true, }); } await writeFile( path.join(fixtureRoot, "src/features/installed-feature-contracts.js"), emptyContracts, ); await writeFile( path.join(fixtureRoot, "src/features/installed-feature-runtimes.tsx"), emptyRuntimes, ); await writeFile( path.join(fixtureRoot, "src/features/installed-feature-adapters.ts"), emptyAdapters, ); await writeFile( path.join(fixtureRoot, "src/features/installed-feature-messages.js"), emptyMessages, ); const governanceFile = path.join( fixtureRoot, "config/contracts/registry-governance.json", ); const removalGovernance = JSON.parse(await readFile(governanceFile, "utf8")); removalGovernance.registries = removalGovernance.registries.map( /** @param {Record} registry */ (registry) => ({ ...registry, ...(Array.isArray(registry.consumers) ? { consumers: registry.consumers.filter( /** @param {{path?: string}} consumer */ (consumer) => !consumer.path?.includes("features/reference-feature"), ), } : {}), ...(Array.isArray(registry.consumerDirectories) ? { consumerDirectories: registry.consumerDirectories.filter( /** @param {string} directory */ (directory) => !directory.includes("features/reference-feature"), ), } : {}), }), ); await writeFile( governanceFile, `${JSON.stringify(removalGovernance, null, 2)}\n`, ); /** @type {string[]} */ const residue = []; for (const root of ["src", "tests"]) { for (const file of await filesBelow(path.join(fixtureRoot, root))) { const relative = path.relative(fixtureRoot, file); const content = await readFile(file, "utf8"); if ( /REFERENCE_RESOURCE|reference-feature|reference-resource/i.test( `${relative}\n${content}`, ) ) { residue.push(relative); } } } const checks = [ ["typecheck", runPnpm("check:types")], ["architecture", runPnpm("check:architecture")], ["registry-structure", runPnpm("check:registries:structure")], ["unit-integration", runPnpm("test:all")], [ "home-smoke", runPnpm("exec", [ "vitest", "run", "tests/component/router.test.jsx", "--reporter=default", ]), ], ["build", runPnpm("build")], ]; /** @type {string[]} */ const builtResidue = []; for (const file of await filesBelow(path.join(fixtureRoot, "dist"))) { if (!/\.(?:js|css|html|json)$/.test(file)) continue; const content = await readFile(file, "utf8"); if ( /REFERENCE_RESOURCE|reference-feature|reference-resource/i.test(content) ) { builtResidue.push(path.relative(fixtureRoot, file)); } } const routeCatalog = await import( `${new URL( "../src/features/installed-feature-contracts.js", `file://${fixtureRoot}/scripts/`, ).href}?removed=${Date.now()}` ); const routeIds = Object.keys(routeCatalog.ROUTE_REGISTRY); const routeAbsent = routeIds.every((routeId) => !routeId.startsWith("REFERENCE_")); checks.push(["route-absent", routeAbsent]); checks.push(["fixture-id-residue", residue.length === 0]); checks.push(["built-fixture-id-residue", builtResidue.length === 0]); const passed = checks.every(([, result]) => result); await mkdir("artifacts/tests", { recursive: true }); await writeFile( "artifacts/tests/sample-removal.xml", `\n` + `` + checks .map( ([name, result]) => `${result ? "" : `${[...residue, ...builtResidue].join(", ")}`}`, ) .join("") + `\n`, ); await rm(fixtureRoot, { recursive: true, force: true }); if (!passed) { const failures = checks .filter(([, result]) => !result) .map(([name]) => name); process.stderr.write( `Reference feature removal failed: ${failures.join(", ")}; residue: ${[...residue, ...builtResidue].join(", ")}\n`, ); process.exit(1); } process.stdout.write( `Reference feature removal: PASS (${checks.length} checks, no fixture IDs)\n`, );