/* RFC-0514: contact form structured fields migrator — transforms existing send-message page blocks in pages//*.md by adding emailField with enabled: true and removing the deprecated contactRequirementMessage prop. Idempotent: running twice produces the same result. Do not add phoneField — it is optional and site-specific. Do not modify the section manifest or generated types — those are platform files. RFC-0514: initial migrator — add emailField to send-message blocks, remove 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_0514_MIGRATOR_ID = "rfc-0514"; 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; if (!/emailField:/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} emailField:\n${indent} enabled: true\n${indent} required: true\n`, ); } else { newBody = ` props:\n emailField:\n enabled: true\n required: true\n` + newBody; } modified = true; } newBody = newBody.replace(/\n\s*contactRequirementMessage:\s*[^\n]+/g, ""); if (newBody !== blockBody) { modified = true; } return prefix + newBody; }); if (modified) { try { await io.writeFile(filePath, updated); ctx.logger.info(`[rfc-0514] migrated send-message block in ${filePath}`); } catch (err) { throw new MigrationError( RFC_0514_MIGRATOR_ID, filePath, "", `Failed to update send-message block: ${(err as Error).message}`, ); } } } export const rfc0514Migrator: Migrator = { id: RFC_0514_MIGRATOR_ID, fromVersion: "4.18.0", toVersion: "4.19.0", description: "Add emailField with enabled: true to send-message page blocks and remove deprecated contactRequirementMessage. Advances migratorCursor for RFC-0514 (contact form structured fields).", 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-0514] 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; }, };