94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
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<string>();
|
|
const discoveredPaths = new Set(discovered.map((page) => page.legacyPath));
|
|
const entriesByPath = new Map<string, MigrationEntry[]>();
|
|
|
|
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));
|
|
}
|