import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import type { PrismaClient } from "@prisma/client"; /** * Importer für das VDA-ISA-2027-Vorlagenpaket (§6 der Spezifikation). * Liest die echten .md-Dateien, mapping.json, variables.schema.json, * Technische-Sicherheits-Baseline.md und Nachweisregister_zentral.md in die * Policy-Tabellen. Idempotent pro Mandant (löscht + legt neu an). */ type DocType = "LEITLINIE" | "RICHTLINIE" | "VERFAHREN" | "REGISTER"; function docCodeFromFile(file: string): string { return file.split("_")[0]; // "R08_…​.md" → "R08", "VA-03_…​.md" → "VA-03" } function titleOf(md: string): string { const m = md.match(/^#\s+(.+?)\s*$/m); return m ? m[1].trim() : "(ohne Titel)"; } /** Umsetzungstext je Anforderung aus den ``-Ankern extrahieren. */ function extractImpls(raw: string, into: Map) { const lines = raw.split("\n"); for (let i = 0; i < lines.length; i++) { const m = lines[i].match(/^/); if (!m) continue; const buf: string[] = []; for (let j = i + 1; j < lines.length; j++) { const l = lines[j]; const tr = l.trim(); if (tr === "" || tr.startsWith("/); const fulfills = fh ? fh[1].split(",").map((s) => s.trim()).filter(Boolean) : []; const policyCode = fh ? fh[2].trim() : null; for (const req of fulfills) { if (!reqToVas.has(req)) reqToVas.set(req, new Set()); reqToVas.get(req)!.add(code); } const orderIdx = 100 + parseInt(code.replace(/\D/g, ""), 10); docs.push({ code, type: "VERFAHREN", title: titleOf(raw), policyCode, fulfills, raw, orderIdx }); } // Register-Dokumente (Baseline + Nachweisregister) mitführen const baselineRaw = readFileSync(join(seedDir, "Technische-Sicherheits-Baseline.md"), "utf-8"); const nachweisRaw = readFileSync(join(seedDir, "Nachweisregister_zentral.md"), "utf-8"); docs.push({ code: "BASELINE", type: "REGISTER", title: titleOf(baselineRaw), policyCode: null, fulfills: [], raw: baselineRaw, orderIdx: 900 }); docs.push({ code: "NACHWEIS", type: "REGISTER", title: titleOf(nachweisRaw), policyCode: null, fulfills: [], raw: nachweisRaw, orderIdx: 901 }); for (const d of docs) { await prisma.policyDocument.create({ data: { tenantId, code: d.code, type: d.type, title: d.title, version: "1.0", status: "FREIGEGEBEN", policyCode: d.policyCode, fulfills: d.fulfills, rawMarkdown: d.raw, orderIdx: d.orderIdx, }, }); } // ── 3. Anforderungen (mapping.json) + vaCodes ──────────────────────────── for (const a of mapping.anforderungen) { await prisma.policyRequirement.create({ data: { tenantId, reqId: a.id, policyCode: a.policy, control: a.control, obligation: a.type, condition: a.condition, requirement: a.requirement, implementation: implMap.get(a.id) ?? a.implementation ?? "", vaCodes: [...(reqToVas.get(a.id) ?? [])].sort(), nachweisLink: a.nachweis_link ?? null, }, }); } // ── 4. Variablen + Feature-Flags (variables.schema.json) ───────────────── const schema = JSON.parse(readFileSync(join(seedDir, "variables.schema.json"), "utf-8")) as { properties: Record; required?: string[]; }; const required = new Set(schema.required ?? []); let vi = 0; for (const [key, prop] of Object.entries(schema.properties)) { const kind = prop.type === "boolean" ? "boolean" : "string"; const value = prop.default !== undefined ? String(prop.default) : prop.example !== undefined ? String(prop.example) : kind === "boolean" ? "false" : ""; await prisma.policyVariable.create({ data: { tenantId, key, title: prop.title ?? key, kind, groupName: varGroup(key), value, required: required.has(key), orderIdx: vi++ }, }); } // ── 5. Baseline-Parameter (Technische-Sicherheits-Baseline.md) ─────────── let section = ""; let bi = 0; for (const line of baselineRaw.split("\n")) { const h = line.match(/^##\s+(.+?)\s*$/); if (h) { section = h[1].trim(); continue; } const row = line.match(/^\|\s*(BL-[A-Z]+-\d+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*$/); if (row) { await prisma.policyBaselineParam.create({ data: { tenantId, blId: row[1], section, name: row[2].trim(), vorgabe: row[3].trim(), orderIdx: bi++ }, }); } } // ── 6. Nachweisregister (Nachweisregister_zentral.md) ──────────────────── for (const line of nachweisRaw.split("\n")) { const row = line.match(/^\|\s*(\d+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*$/); if (!row) continue; const policyMatch = row[2].match(/\{\{LINK:([^}]+)\}\}/); await prisma.policyEvidence.create({ data: { tenantId, nr: parseInt(row[1], 10), policyCode: policyMatch ? policyMatch[1] : row[2].trim(), nachweis: row[3].trim(), quelle: row[4].trim(), verantwortlich: row[5].trim(), turnus: row[6].trim(), }, }); } const counts = { documents: docs.length, requirements: mapping.anforderungen.length, variables: Object.keys(schema.properties).length, baseline: bi, }; return counts; }