/*
RFC-0501: ratgeber article status review migrator — sets existing published
ratgeber articles to status: review-required so they don't fail the publication gate
until their prose bodies are updated with the mandatory 10-section structure.
Idempotent: running twice produces no changes on already-migrated files.
- Do not validate article records — that is ratgeber.article.validate.
- Do not modify prose files — only article frontmatter status field.
- RFC-0501: initial migrator — set existing published ratgeber articles to review-required.
*/
import path from "node:path";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
import type { Migrator, SternsystemData, MigrationContext } from "./types.ts";
import { MigrationError } from "./types.ts";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
export const RFC_0501_MIGRATOR_ID = "rfc-0501";
export const rfc0501Migrator: Migrator = {
id: RFC_0501_MIGRATOR_ID,
fromVersion: "4.12.0",
toVersion: "4.13.0",
description:
"Set existing published ratgeber articles to review-required — their prose bodies don't have the 10-section structure yet. Advances migratorCursor for RFC-0501 (ratgeber article types, mandatory structure, and publication gate).",
transform: async (
data: SternsystemData,
ctx: MigrationContext,
io: WorkspaceIO = createDefaultIO().io,
) => {
const articlesBaseDir = path.join(data.rootPath, "src", "content", "surface", "articles");
let langDirs: string[];
try {
langDirs = (await io.readdir(articlesBaseDir)).map((e) => e.name);
} catch {
ctx.logger.info(`[rfc-0501] no articles directory at ${articlesBaseDir} — skipping`);
return data;
}
for (const langDir of langDirs) {
const langPath = path.join(articlesBaseDir, langDir);
const stat = await io.stat(langPath).catch(() => null);
if (!stat?.isDirectory) continue;
let files: string[];
try {
files = (await io.readdir(langPath)).map((e) => e.name);
} catch {
continue;
}
for (const file of files) {
if (!file.endsWith(".md")) continue;
const filePath = path.join(langPath, file);
const raw = await io.readFile(filePath);
const frontmatterMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
if (!frontmatterMatch) continue;
const fmYaml = frontmatterMatch[1]!;
const fmData = parseYaml(fmYaml) as Record | null;
if (!fmData) continue;
if (fmData.status !== "published") continue;
fmData.status = "review-required";
const updatedFmYaml = stringifyYaml(fmData).trim();
const updatedRaw = `---\n${updatedFmYaml}\n---${raw.slice(frontmatterMatch[0].length)}`;
try {
await io.writeFile(filePath, updatedRaw);
ctx.logger.info(`[rfc-0501] set ${file} to review-required`);
} catch (err) {
throw new MigrationError(
RFC_0501_MIGRATOR_ID,
filePath,
"status",
`Failed to write updated frontmatter: ${(err as Error).message}`,
);
}
}
}
return data;
},
};