75 lines
2.4 KiB
JavaScript
75 lines
2.4 KiB
JavaScript
import { readFile } from 'node:fs/promises';
|
|
import { access } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
|
|
const SOURCE_COUNT = 29;
|
|
const DOCS_ROOT = 'src/content/docs/';
|
|
|
|
export async function loadManifest(path) {
|
|
return JSON.parse(await readFile(path, 'utf8'));
|
|
}
|
|
|
|
export function validateManifest(manifest) {
|
|
if (!Array.isArray(manifest?.entries)) {
|
|
throw new TypeError('Migration manifest entries must be an array.');
|
|
}
|
|
|
|
if (manifest.entries.length !== SOURCE_COUNT) {
|
|
throw new Error(`Migration manifest must contain exactly ${SOURCE_COUNT} entries.`);
|
|
}
|
|
|
|
const sources = new Set();
|
|
const destinations = new Set();
|
|
|
|
for (const entry of manifest.entries) {
|
|
if (!entry || typeof entry !== 'object') {
|
|
throw new TypeError('Each migration manifest entry must be an object.');
|
|
}
|
|
|
|
const { source, destination, mode } = entry;
|
|
if (typeof source !== 'string' || typeof destination !== 'string') {
|
|
throw new TypeError('Each migration manifest entry needs string source and destination paths.');
|
|
}
|
|
if (!['page', 'merge'].includes(mode)) {
|
|
throw new TypeError('Each migration manifest entry mode must be page or merge.');
|
|
}
|
|
if (source.startsWith('pages/wiki/')) {
|
|
throw new Error(`Generic DokuWiki source is excluded: ${source}`);
|
|
}
|
|
if (!(source === 'pages/start.txt' || source === 'pages/study.txt' || source.startsWith('pages/study/'))) {
|
|
throw new Error(`Source is outside the selected migration scope: ${source}`);
|
|
}
|
|
if (!destination.startsWith(DOCS_ROOT)) {
|
|
throw new Error(`Destination is outside ${DOCS_ROOT}: ${destination}`);
|
|
}
|
|
if (sources.has(source)) {
|
|
throw new Error(`Duplicate migration source: ${source}`);
|
|
}
|
|
if (destinations.has(destination) && mode !== 'merge') {
|
|
throw new Error(`Duplicate destination requires a later merge entry: ${destination}`);
|
|
}
|
|
|
|
sources.add(source);
|
|
destinations.add(destination);
|
|
}
|
|
|
|
return { entries: manifest.entries };
|
|
}
|
|
|
|
export async function checkDestinations(manifest, root) {
|
|
const destinations = [...new Set(manifest.entries.map(({ destination }) => destination))];
|
|
const missing = [];
|
|
|
|
for (const destination of destinations) {
|
|
try {
|
|
await access(join(root, destination));
|
|
} catch {
|
|
missing.push(destination);
|
|
}
|
|
}
|
|
|
|
return {
|
|
total: manifest.entries.length,
|
|
missing,
|
|
};
|
|
}
|