import type { LegacyPage, MigrationEntry } from "./types"; const prohibitedPlaceholder = /\b(?:TBD|TODO|FIXME|UNCLASSIFIED)\b/i; function hasProhibitedPlaceholder(value: unknown): boolean { if (typeof value === "string") return prohibitedPlaceholder.test(value); if (Array.isArray(value)) return value.some(hasProhibitedPlaceholder); if (value && typeof value === "object") return Object.values(value).some(hasProhibitedPlaceholder); return false; } export function validateMigrationEntries( discovered: readonly LegacyPage[], entries: readonly MigrationEntry[], ): string[] { const issues = new Set(); const discoveredPaths = new Set(discovered.map((page) => page.legacyPath)); const entriesByPath = new Map(); for (const entry of entries) { const matchingEntries = entriesByPath.get(entry.legacyPath) ?? []; matchingEntries.push(entry); entriesByPath.set(entry.legacyPath, matchingEntries); if (!discoveredPaths.has(entry.legacyPath)) { issues.add(`unknown legacyPath: ${entry.legacyPath}`); } } for (const page of discovered) { const matchingEntries = entriesByPath.get(page.legacyPath) ?? []; if (matchingEntries.length === 0) { issues.add(`missing legacyPath: ${page.legacyPath}`); continue; } if (matchingEntries.length > 1) { issues.add(`duplicate legacyPath: ${page.legacyPath}`); } } for (const entry of entries) { const { legacyPath } = entry; if (entry.targetPath === null && entry.decision !== "REMOVE") { issues.add(`${entry.decision} requires targetPath: ${legacyPath}`); } if ( entry.targetPath !== null && entry.decision !== "REMOVE" && !entry.targetPath.startsWith("/admin/") ) { issues.add( `non-REMOVE targetPath must start with /admin/: ${legacyPath}`, ); } if ( (entry.decision === "MERGE" || entry.decision === "REBUILD") && entry.requiredTests.length === 0 ) { issues.add(`${entry.decision} requires requiredTests: ${legacyPath}`); } if (entry.dependencies.mutations.length > 0) { if (entry.capabilities.mutate.length === 0) { issues.add( `mutation dependencies require mutate capabilities: ${legacyPath}`, ); } if (entry.auditRequirement === "NONE") { issues.add( `mutation dependencies require auditRequirement: ${legacyPath}`, ); } } if (entry.status === "VERIFIED" && entry.parityEvidence.length === 0) { issues.add(`VERIFIED requires parityEvidence: ${legacyPath}`); } if (hasProhibitedPlaceholder(entry)) { issues.add(`prohibited placeholder: ${legacyPath}`); } } return [...issues].sort((a, b) => a.localeCompare(b)); }