/*
RFC-0572: revert contact form to single-textarea migrator — transforms existing
send-message page blocks in pages//*.md by removing emailField and phoneField props
and re-adding contactRequirementMessage if not present. Idempotent: running twice
produces the same result. No-op on blocks that never had structured fields.
- Do not modify the section manifest or generated types — those are platform files.
- Do not remove the referrerField — it is preserved by RFC-0572.
- RFC-0572: initial migrator — remove emailField/phoneField from send-message blocks, re-add contactRequirementMessage.
*/
import path from "node:path";
import type { Migrator, SternsystemData, MigrationContext } from "./types.ts";
import { MigrationError } from "./types.ts";
import {
createDefaultIO,
type DirEntry,
type WorkspaceIO,
} from "@warpgogol/werkstatt-shared/kernel/workspace-io";
export const RFC_0572_MIGRATOR_ID = "rfc-0572";
async function scanDir(
dir: string,
lang: string,
ctx: MigrationContext,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
let entries: DirEntry[];
try {
entries = await io.readdir(dir);
} catch {
return;
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory) {
await scanDir(fullPath, lang, ctx);
} else if (entry.isFile && entry.name.endsWith(".md")) {
await migrateFile(fullPath, lang, ctx);
}
}
}
async function migrateFile(
filePath: string,
lang: string,
ctx: MigrationContext,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
let content: string;
try {
content = await io.readFile(filePath);
} catch {
return;
}
if (!content.includes("send-message")) return;
let modified = false;
let updated = content;
const blockRegex =
/(- id:\s*send-message(?:-section)?\n)([\s\S]*?)(?=\n- id:|\n---|\nsections:|\Z)/g;
updated = updated.replace(blockRegex, (_match, prefix: string, blockBody: string) => {
let newBody = blockBody;
const emailFieldMatch = /\n(\s*)emailField:\n[\s\S]*?(?=\n\s*\S|\n\s*-\s|\Z)/g;
const phoneFieldMatch = /\n(\s*)phoneField:\n[\s\S]*?(?=\n\s*\S|\n\s*-\s|\Z)/g;
if (emailFieldMatch.test(newBody)) {
newBody = newBody.replace(/\n\s*emailField:\n(\s+)[^\n]*\n/g, "");
newBody = newBody.replace(
/\n\s*emailField:\n\s+enabled:[^\n]*\n\s+required:[^\n]*\n(\s+(?:label|placeholder):[^\n]*\n)*/g,
"",
);
modified = true;
}
if (phoneFieldMatch.test(newBody)) {
newBody = newBody.replace(
/\n\s*phoneField:\n\s+enabled:[^\n]*\n\s+required:[^\n]*\n(\s+(?:label|placeholder):[^\n]*\n)*/g,
"",
);
modified = true;
}
if (!/contactRequirementMessage:/m.test(newBody)) {
const propsMatch = /^(\s*)props:\n/.exec(newBody);
if (propsMatch) {
const indent = propsMatch[1]!;
newBody = newBody.replace(
/^(\s*)props:\n/,
`${indent}props:\n${indent} contactRequirementMessage: "Bitte hinterlassen Sie Ihre E-Mail-Adresse oder Telefonnummer im Text."\n`,
);
modified = true;
}
}
return prefix + newBody;
});
if (modified) {
try {
await io.writeFile(filePath, updated);
ctx.logger.info(`[rfc-0572] migrated send-message block in ${filePath}`);
} catch (err) {
throw new MigrationError(
RFC_0572_MIGRATOR_ID,
filePath,
"",
`Failed to update send-message block: ${(err as Error).message}`,
);
}
}
}
export const rfc0572Migrator: Migrator = {
id: RFC_0572_MIGRATOR_ID,
fromVersion: "4.19.0",
toVersion: "4.20.0",
description:
"Remove emailField/phoneField from send-message page blocks and re-add contactRequirementMessage. Advances migratorCursor for RFC-0572 (revert to single-textarea contact form).",
transform: async (
data: SternsystemData,
ctx: MigrationContext,
io: WorkspaceIO = createDefaultIO().io,
) => {
const pagesBaseDir = path.join(data.rootPath, "src", "content", "pages");
let langDirs: string[];
try {
langDirs = (await io.readdir(pagesBaseDir)).map((e) => e.name);
} catch {
ctx.logger.info(`[rfc-0572] no pages directory at ${pagesBaseDir} — skipping`);
langDirs = [];
}
for (const lang of langDirs) {
const langPath = path.join(pagesBaseDir, lang);
const stat = await io.stat(langPath).catch(() => null);
if (!stat?.isDirectory) continue;
await scanDir(langPath, lang, ctx);
}
return data;
},
};