RustyCMS: file-based headless CMS — API, Admin UI (content, types, assets), Docker/Caddy, image transform; only demo type and demo content in version control

Made-with: Cursor
This commit is contained in:
Peter Meier
2026-03-12 14:21:49 +01:00
parent aad93d145f
commit 7795a238e1
278 changed files with 15551 additions and 4072 deletions

View File

@@ -0,0 +1,713 @@
#!/usr/bin/env node
/**
* Migriert Contentful-Export (contentful-export.json) nach rustycms content/de/.
* Nur deutsche Locale (en kann später ergänzt werden).
*
* Aufruf: node scripts/contentful-to-rustycms.mjs [Pfad-zum-Export] [--html-only] [--only=quote|iframe|image|image_gallery]
* Default Export-Pfad: ../www.windwiderstand.de/contentful-export.json
* --html-only: nur HTML migrieren
* --only=X: nur Typ X migrieren (mehrfach möglich), X = html|quote|iframe|image|image_gallery|youtube_video
*/
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
const CONTENT_DE = path.join(ROOT, "content", "de");
const argv = process.argv.slice(2);
const ONLY = new Set();
if (argv.includes("--html-only")) ONLY.add("html");
argv.forEach((a) => {
const m = a.match(/^--only=(.+)$/);
if (m) ONLY.add(m[1]);
});
const EXPORT_PATH = argv.filter((a) => a !== "--html-only" && !a.startsWith("--only="))[0] || path.join(ROOT, "..", "www.windwiderstand.de", "contentful-export.json");
// ─── Slug-Normalisierung ─────────────────────────────────────────────────
function slugify(value) {
if (value == null || value === "") return "";
let s = String(value)
.replace(/^\//, "")
.replace(/\//g, "-")
.replace(/[^a-zA-Z0-9_-]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
return s || "untitled";
}
function safeSlug(entry, type) {
const f = entry?.fields || {};
if (type === "page" && f.slug != null) return f.slug === "/" ? "home" : slugify(f.slug);
if (f.id != null) return slugify(f.id);
if (type === "link" && f.slug != null) return slugify(f.slug);
if (entry?.sys?.id) return slugify(entry.sys.id);
return "untitled";
}
// ─── Rekursive Sammlung: alle Entries und Assets ──────────────────────────
function collectNodes(obj, entries, assets) {
if (!obj || typeof obj !== "object") return;
if (Array.isArray(obj)) {
obj.forEach((item) => collectNodes(item, entries, assets));
return;
}
const sys = obj.sys;
const fields = obj.fields;
if (sys?.type === "Asset" && fields?.file) {
const id = sys.id;
if (!assets.has(id)) assets.set(id, { sys, fields });
return;
}
const contentTypeId = sys?.contentType?.sys?.id || sys?.contentType?.id;
if (sys?.type === "Entry" && contentTypeId) {
const id = sys.id;
if (!entries.has(id)) {
entries.set(id, { sys: { ...sys, contentType: { id: contentTypeId } }, fields: fields || {} });
if (fields && typeof fields === "object") {
Object.values(fields).forEach((v) => collectNodes(v, entries, assets));
}
}
return;
}
Object.values(obj).forEach((v) => collectNodes(v, entries, assets));
}
// ─── JSON5-ähnlich ausgeben (gültiges JSON, 2 Leerzeichen) ────────────────
function writeJson5(filePath, obj) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const str = JSON.stringify(obj, null, 2);
fs.writeFileSync(filePath, str + "\n", "utf8");
}
// ─── Main ───────────────────────────────────────────────────────────────
function main() {
if (!fs.existsSync(EXPORT_PATH)) {
console.error("Export-Datei nicht gefunden:", EXPORT_PATH);
process.exit(1);
}
console.log("Lese Export:", EXPORT_PATH);
const data = JSON.parse(fs.readFileSync(EXPORT_PATH, "utf8"));
const byType = data.byType || {};
const entries = new Map();
const assets = new Map();
Object.values(byType).forEach((group) => {
if (group?.items) group.items.forEach((item) => collectNodes(item, entries, assets));
});
console.log("Einträge:", entries.size, "Assets:", assets.size);
const idToSlug = new Map();
const SUPPORTED_ROW_COMPONENTS = new Set([
"markdown", "html", "componentLinkList", "fullwidthBanner", "componentPostOverview", "componentSearchableText",
"quoteComponent", "iframe", "image", "imageGallery", "youtubeVideo",
]);
function getSlug(entryOrId, contentType) {
const entry = typeof entryOrId === "string" ? entries.get(entryOrId) : entryOrId;
if (!entry) return null;
const id = entry.sys?.id;
const type = contentType || entry.sys?.contentType?.id || entry.sys?.contentType?.sys?.id;
if (idToSlug.has(id)) return idToSlug.get(id);
// Assets: bereits in idToSlug aus Schritt 1
if (entry.sys?.type === "Asset") return idToSlug.get(id) || slugify(entry.fields?.title || id);
const slug = safeSlug(entry, type);
let finalSlug = slug;
let n = 0;
const used = new Set(idToSlug.values());
while (used.has(finalSlug)) {
n++;
finalSlug = slug + "-" + n;
}
idToSlug.set(id, finalSlug);
return finalSlug;
}
function refToSlug(ref) {
if (ref == null) return null;
if (typeof ref === "string") return idToSlug.get(ref) || ref;
if (ref.sys?.type === "Entry") return getSlug(ref, ref.sys?.contentType?.id);
if (ref.sys?.type === "Asset") return getSlug(ref, "img");
return null;
}
const run = (key) => !ONLY.size || ONLY.has(key);
// Bei --only=image/image_gallery: nur Asset-Slugs in idToSlug eintragen (für refToSlug), ohne img-Dateien zu schreiben
if (ONLY.size && (ONLY.has("image") || ONLY.has("image_gallery"))) {
assets.forEach((asset, id) => {
const slug = slugify(asset.fields?.title || id);
const uniq = Array.from(idToSlug.values()).includes(slug) ? slug + "-" + id.slice(0, 6) : slug;
idToSlug.set(id, uniq);
});
}
if (run("img")) {
// ─── 1) Assets → img ───────────────────────────────────────────────────
assets.forEach((asset, id) => {
const slug = slugify(asset.fields?.title || id);
const uniq = Array.from(idToSlug.values()).includes(slug) ? slug + "-" + id.slice(0, 6) : slug;
idToSlug.set(id, uniq);
const file = asset.fields?.file || {};
let url = file.url || "";
if (url && !url.startsWith("http")) url = "https:" + url;
const out = {
_slug: uniq,
title: asset.fields?.title ?? "",
description: asset.fields?.description ?? "",
file: {
url,
fileName: file.fileName,
contentType: file.contentType,
details: file.details,
},
};
writeJson5(path.join(CONTENT_DE, "img", uniq + ".json5"), out);
});
console.log("img:", assets.size);
}
if (run("tag")) {
// ─── 2) Tag ────────────────────────────────────────────────────────────
(byType.tag?.items || []).forEach((entry) => {
const slug = getSlug(entry, "tag");
const f = entry.fields || {};
writeJson5(path.join(CONTENT_DE, "tag", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
});
});
console.log("tag:", (byType.tag?.items || []).length);
}
if (run("link")) {
// ─── 3) Link ───────────────────────────────────────────────────────────
(byType.link?.items || []).forEach((entry) => {
const slug = getSlug(entry, "link");
const f = entry.fields || {};
writeJson5(path.join(CONTENT_DE, "link", slug + ".json5"), {
_slug: slug,
name: f.linkName ?? slug,
internal: slug,
linkName: f.linkName ?? "",
url: f.url ?? "",
newTab: f.newTab ?? false,
external: f.externalLink ?? false,
description: f.description ?? "",
alt: f.alt ?? "",
showText: f.showText !== false,
author: f.author ?? "",
date: f.date ?? "",
source: f.source ?? "",
});
});
console.log("link:", (byType.link?.items || []).length);
}
if (run("markdown")) {
// ─── 4) Markdown ────────────────────────────────────────────────────────
function extractMarkdown(entry) {
const slug = getSlug(entry, "markdown");
const f = entry.fields || {};
const layout = f.layout?.fields || {};
writeJson5(path.join(CONTENT_DE, "markdown", slug + ".json5"), {
_slug: slug,
name: f.id ?? slug,
content: f.content ?? "",
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
alignment: f.alignment ?? "left",
});
return slug;
}
function ensureMarkdown(entry) {
if (!entry || entry.sys?.contentType?.id !== "markdown") return null;
if (!idToSlug.has(entry.sys.id)) extractMarkdown(entry);
return idToSlug.get(entry.sys.id);
}
(byType.markdown?.items || []).forEach(extractMarkdown);
entries.forEach((entry) => {
if (entry.sys?.contentType?.id === "markdown" && !idToSlug.has(entry.sys.id)) extractMarkdown(entry);
});
console.log("markdown:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "markdown").length);
}
// ─── 4b) HTML (html) ────────────────────────────────────────────────────
if (run("html")) {
function extractHtml(entry) {
const slug = getSlug(entry, "html");
const f = entry.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "html", slug + ".json5"), {
_slug: slug,
name: f.id ?? slug,
html: f.html ?? "",
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
});
return slug;
}
(byType.html?.items || []).forEach(extractHtml);
entries.forEach((entry) => {
if (entry.sys?.contentType?.id === "html" && !idToSlug.has(entry.sys.id)) extractHtml(entry);
});
console.log("html:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "html").length);
}
// ─── 4c) Quote (quoteComponent) ─────────────────────────────────────────
if (run("quote")) {
(byType.quoteComponent?.items || []).forEach((entry) => {
const slug = getSlug(entry, "quoteComponent");
const f = entry.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "quote", slug + ".json5"), {
_slug: slug,
quote: f.quote ?? "",
author: f.author ?? "",
variant: f.variant ?? "left",
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
});
});
entries.forEach((e) => {
if (e.sys?.contentType?.id === "quoteComponent" && !idToSlug.has(e.sys.id)) {
const slug = getSlug(e, "quoteComponent");
const f = e.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "quote", slug + ".json5"), {
_slug: slug,
quote: f.quote ?? "",
author: f.author ?? "",
variant: f.variant ?? "left",
layout: { mobile: layout.mobile ?? "12", tablet: layout.tablet, desktop: layout.desktop, spaceBottom: layout.spaceBottom ?? 0 },
});
}
});
console.log("quote:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "quoteComponent").length);
}
// ─── 4d) Iframe ────────────────────────────────────────────────────────
if (run("iframe")) {
(byType.iframe?.items || []).forEach((entry) => {
const slug = getSlug(entry, "iframe");
const f = entry.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "iframe", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
content: f.content ?? "",
iframe: f.iframe ?? "",
overlayImage: refToSlug(f.overlayImage) ?? undefined,
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
});
});
entries.forEach((e) => {
if (e.sys?.contentType?.id === "iframe" && !idToSlug.has(e.sys.id)) {
const slug = getSlug(e, "iframe");
const f = e.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "iframe", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
content: f.content ?? "",
iframe: f.iframe ?? "",
overlayImage: refToSlug(f.overlayImage) ?? undefined,
layout: { mobile: layout.mobile ?? "12", tablet: layout.tablet, desktop: layout.desktop, spaceBottom: layout.spaceBottom ?? 0 },
});
}
});
console.log("iframe:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "iframe").length);
}
// ─── 4e) Image ───────────────────────────────────────────────────────────
if (run("image")) {
(byType.image?.items || []).forEach((entry) => {
const slug = getSlug(entry, "image");
const f = entry.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "image", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
image: refToSlug(f.image) ?? "",
caption: f.caption ?? "",
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
...(f.maxWidth != null && { maxWidth: f.maxWidth }),
...(f.aspectRatio != null && { aspectRatio: f.aspectRatio }),
});
});
entries.forEach((e) => {
if (e.sys?.contentType?.id === "image" && !idToSlug.has(e.sys.id)) {
const slug = getSlug(e, "image");
const f = e.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "image", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
image: refToSlug(f.image) ?? "",
caption: f.caption ?? "",
layout: { mobile: layout.mobile ?? "12", tablet: layout.tablet, desktop: layout.desktop, spaceBottom: layout.spaceBottom ?? 0 },
...(f.maxWidth != null && { maxWidth: f.maxWidth }),
...(f.aspectRatio != null && { aspectRatio: f.aspectRatio }),
});
}
});
console.log("image:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "image").length);
}
// ─── 4f) ImageGallery ───────────────────────────────────────────────────
if (run("image_gallery")) {
(byType.imageGallery?.items || []).forEach((entry) => {
const slug = getSlug(entry, "imageGallery");
const f = entry.fields || {};
const layout = f.layout?.fields || f.layout || {};
const imageSlugs = (f.images || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "image_gallery", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
images: imageSlugs,
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
...(f.description != null && f.description !== "" && { description: f.description }),
});
});
entries.forEach((e) => {
if ((e.sys?.contentType?.id === "imageGallery" || e.sys?.contentType?.id === "imgGallery") && !idToSlug.has(e.sys.id)) {
const slug = getSlug(e, "imageGallery");
const f = e.fields || {};
const layout = f.layout?.fields || f.layout || {};
const imageSlugs = (f.images || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "image_gallery", slug + ".json5"), {
_slug: slug,
name: f.name ?? slug,
images: imageSlugs,
layout: { mobile: layout.mobile ?? "12", tablet: layout.tablet, desktop: layout.desktop, spaceBottom: layout.spaceBottom ?? 0 },
...(f.description != null && f.description !== "" && { description: f.description }),
});
}
});
console.log("image_gallery:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "imageGallery" || e.sys?.contentType?.id === "imgGallery").length);
}
// ─── 4g) YoutubeVideo ───────────────────────────────────────────────────
if (run("youtube_video")) {
(byType.youtubeVideo?.items || []).forEach((entry) => {
const slug = getSlug(entry, "youtubeVideo");
const f = entry.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "youtube_video", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
youtubeId: f.youtubeId ?? "",
...(f.params != null && f.params !== "" && { params: f.params }),
...(f.title != null && f.title !== "" && { title: f.title }),
...(f.description != null && f.description !== "" && { description: f.description }),
layout: {
mobile: layout.mobile ?? "12",
tablet: layout.tablet,
desktop: layout.desktop,
spaceBottom: layout.spaceBottom ?? 0,
},
});
});
entries.forEach((e) => {
if (e.sys?.contentType?.id === "youtubeVideo" && !idToSlug.has(e.sys.id)) {
const slug = getSlug(e, "youtubeVideo");
const f = e.fields || {};
const layout = f.layout?.fields || f.layout || {};
writeJson5(path.join(CONTENT_DE, "youtube_video", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
youtubeId: f.youtubeId ?? "",
...(f.params != null && f.params !== "" && { params: f.params }),
...(f.title != null && f.title !== "" && { title: f.title }),
...(f.description != null && f.description !== "" && { description: f.description }),
layout: { mobile: layout.mobile ?? "12", tablet: layout.tablet, desktop: layout.desktop, spaceBottom: layout.spaceBottom ?? 0 },
});
}
});
console.log("youtube_video:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "youtubeVideo").length);
}
if (ONLY.size) {
console.log("Fertig. Nur migriert:", [...ONLY].sort().join(", "));
return;
}
// ─── 5) Link-List (componentLinkList) ───────────────────────────────────
function writeLinkList(entry) {
const slug = getSlug(entry, "componentLinkList");
const f = entry.fields || {};
const linkSlugs = (f.links || []).map((l) => refToSlug(l)).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "link_list", slug + ".json5"), {
_slug: slug,
headline: f.headline ?? "",
links: linkSlugs,
});
}
(byType.componentLinkList?.items || []).forEach(writeLinkList);
entries.forEach((e) => {
if (e.sys?.contentType?.id === "componentLinkList" && !idToSlug.has(e.sys.id)) writeLinkList(e);
});
console.log("link_list:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "componentLinkList").length);
// ─── 6) FullwidthBanner ────────────────────────────────────────────────
function extractFullwidthBanner(entry) {
const slug = getSlug(entry, "fullwidthBanner");
const f = entry.fields || {};
const img = f.img;
let image = [];
if (img?.fields?.file?.url) {
let u = img.fields.file.url;
if (!u.startsWith("http")) u = "https:" + u;
image = [u];
}
writeJson5(path.join(CONTENT_DE, "fullwidth_banner", slug + ".json5"), {
_slug: slug,
name: f.id ?? slug,
variant: f.variant ?? "light",
headline: f.headline ?? "",
subheadline: f.subheadline ?? "",
text: f.text ?? "",
image,
});
return slug;
}
(byType.fullwidthBanner?.items || []).forEach(extractFullwidthBanner);
entries.forEach((e) => {
if (e.sys?.contentType?.id === "fullwidthBanner" && !idToSlug.has(e.sys.id)) extractFullwidthBanner(e);
});
console.log("fullwidth_banner:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "fullwidthBanner").length);
// ─── 7) PostOverview (componentPostOverview) ────────────────────────────
(byType.componentPostOverview?.items || []).forEach((entry) => {
const slug = getSlug(entry, "componentPostOverview");
const f = entry.fields || {};
const tagSlugs = (f.filterByTag || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "post_overview", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
headline: f.headline ?? "",
allPosts: f.allPosts ?? true,
filterByTag: tagSlugs,
});
});
entries.forEach((e) => {
if (e.sys?.contentType?.id === "componentPostOverview" && !idToSlug.has(e.sys.id)) {
const slug = getSlug(e, "componentPostOverview");
const f = e.fields || {};
const tagSlugs = (f.filterByTag || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "post_overview", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
headline: f.headline ?? "",
allPosts: f.allPosts ?? true,
filterByTag: tagSlugs,
});
}
});
console.log("post_overview:", Array.from(entries.values()).filter((e) => e.sys?.contentType?.id === "componentPostOverview").length);
// ─── 8) TextFragment + SearchableText (optional, falls verwendet) ────────
(byType.textFragment?.items || []).forEach((entry) => {
const slug = getSlug(entry, "textFragment");
const f = entry.fields || {};
const tagSlugs = (f.tags || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "text_fragment", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
title: f.title ?? "",
text: f.text ?? "",
tags: tagSlugs,
});
});
(byType.componentSearchableText?.items || []).forEach((entry) => {
const slug = getSlug(entry, "componentSearchableText");
const f = entry.fields || {};
const fragSlugs = (f.textFragments || []).map(refToSlug).filter(Boolean);
const tagSlugs = (f.tagWhitelist || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "searchable_text", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
textFragments: fragSlugs,
tagWhitelist: tagSlugs,
title: f.title,
description: f.description,
});
});
// ─── 9) Post ───────────────────────────────────────────────────────────
(byType.post?.items || []).forEach((entry) => {
const slug = getSlug(entry, "post");
const f = entry.fields || {};
const row1Content = (f.row1Content || []).map(refToSlug).filter(Boolean);
const obj = {
_slug: slug,
slug: f.slug ?? "/" + slug,
linkName: f.linkName ?? "",
headline: f.headline ?? "",
subheadline: f.subheadline ?? "",
excerpt: f.excerpt ?? "",
...(entry.sys?.createdAt && { created: entry.sys.createdAt }),
postImage: refToSlug(f.postImage) || null,
postTag: (f.postTag || []).map(refToSlug).filter(Boolean),
important: f.important ?? false,
content: f.content ?? "",
showCommentSection: f.showCommentSection !== false,
row1JustifyContent: f.row1JustifyContent ?? "start",
row1AlignItems: f.row1AlignItems ?? "start",
seoTitle: f.seoTitle ?? "",
seoDescription: f.seoDescription ?? "",
seoMetaRobots: f.seoMetaRobots ?? "index, follow",
};
if (obj.postImage == null) delete obj.postImage;
if (row1Content.length) obj.row1Content = row1Content;
writeJson5(path.join(CONTENT_DE, "post", slug + ".json5"), obj);
});
console.log("post:", (byType.post?.items || []).length);
// ─── 10) Page (alle Page-Einträge aus entries, damit auch nur in Nav referenzierte) ───
const pageEntries = Array.from(entries.values()).filter((e) => (e.sys?.contentType?.id || e.sys?.contentType?.sys?.id) === "page");
pageEntries.forEach((entry) => {
const slug = getSlug(entry, "page");
const f = entry.fields || {};
const row1Content = (f.row1Content || [])
.filter((ref) => ref?.sys?.contentType && SUPPORTED_ROW_COMPONENTS.has(ref.sys.contentType.sys?.id || ref.sys.contentType.id))
.map(refToSlug)
.filter(Boolean);
const name = slug === "home" || f.slug === "/" ? "home" : slug;
const pageSlug = f.slug ?? (name === "home" ? "/" : "/" + name);
writeJson5(path.join(CONTENT_DE, "page", slug + ".json5"), {
_slug: slug,
slug: pageSlug,
name,
linkName: f.linkName ?? "",
headline: f.headline ?? "",
subheadline: f.subheadline ?? "",
seoTitle: f.seoTitle ?? "",
seoDescription: f.seoDescription ?? "",
seoMetaRobots: f.seoMetaRobots ?? "index, follow",
row1JustifyContent: f.row1JustifyContent ?? "start",
row1AlignItems: f.row1AlignItems ?? "start",
row1Content,
...(refToSlug(f.topFullwidthBanner) ? { topFullwidthBanner: refToSlug(f.topFullwidthBanner) } : {}),
});
});
console.log("page:", pageEntries.length);
// ─── 11) Footer ────────────────────────────────────────────────────────
(byType.footer?.items || []).forEach((entry) => {
const slug = getSlug(entry, "footer");
const f = entry.fields || {};
const row1Content = (f.row1Content || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "footer", slug + ".json5"), {
_slug: slug,
id: f.id ?? slug,
row1JustifyContent: f.row1JustifyContent ?? "start",
row1AlignItems: f.row1AlignItems ?? "start",
row1Content,
});
});
// ─── 12) Navigation ─────────────────────────────────────────────────────
(byType.navigation?.items || []).forEach((entry) => {
const slug = getSlug(entry, "navigation");
const f = entry.fields || {};
const links = (f.links || []).map((p) => refToSlug(p)).filter(Boolean);
const internal = f.id ?? (slug === "navigation-header" ? "navigation-header" : slug);
writeJson5(path.join(CONTENT_DE, "navigation", (f.id || slug) + ".json5"), {
_slug: f.id || slug,
name: f.id ?? slug,
internal,
links,
});
});
// ─── 13) PageConfig ─────────────────────────────────────────────────────
const pageConfigItems = byType.pageConfig?.items || [];
const defaultFooterText = "© Bürgerinitiative Vachdorf. Alle Rechte vorbehalten.";
pageConfigItems.forEach((entry) => {
const slug = "default";
const f = entry.fields || {};
const logoRef = f.logo;
const logoSlug = logoRef ? (logoRef.sys?.type === "Asset" ? idToSlug.get(logoRef.sys?.id) : null) : null;
writeJson5(path.join(CONTENT_DE, "page_config", slug + ".json5"), {
_slug: slug,
logo: logoSlug ?? "logo",
footerText1: defaultFooterText,
seoTitle: f.seoTitle ?? "Bürgerinitiative Vachdorf / $1",
seoDescription: f.seoDescription ?? "$1 - Bürgerinitiative Vachdorf",
website: f.website ?? "https://www.windwiderstand.de",
});
});
if (pageConfigItems.length === 0) {
writeJson5(path.join(CONTENT_DE, "page_config", "default.json5"), {
_slug: "default",
logo: "logo",
footerText1: defaultFooterText,
seoTitle: "Bürgerinitiative Vachdorf / $1",
seoDescription: "$1 - Bürgerinitiative Vachdorf",
website: "https://www.windwiderstand.de",
});
}
// ─── 14) Campaign ───────────────────────────────────────────────────────
(byType.campaign?.items || []).forEach((entry) => {
const slug = getSlug(entry, "campaign");
const f = entry.fields || {};
writeJson5(path.join(CONTENT_DE, "campaign", slug + ".json5"), {
_slug: slug,
campaignName: f.campaingName ?? f.campaignName ?? slug,
urlPattern: f.urlPatter ?? f.urlPattern ?? "/",
selector: f.selector ?? "body",
insertHtml: f.insertHtml ?? "beforeend",
timeUntil: f.timeUntil ?? "",
html: f.html ?? "",
javascript: f.javascript ?? "",
css: f.css ?? "",
});
});
// ─── 15) Campaigns ─────────────────────────────────────────────────────
(byType.campaigns?.items || []).forEach((entry) => {
const slug = getSlug(entry, "campaigns");
const f = entry.fields || {};
const campaignSlugs = (f.campaings || f.campaigns || []).map(refToSlug).filter(Boolean);
writeJson5(path.join(CONTENT_DE, "campaigns", (f.id || slug) + ".json5"), {
_slug: f.id || slug,
id: f.id ?? slug,
campaigns: campaignSlugs,
enable: f.enable !== false,
});
});
console.log("Fertig. Content in", CONTENT_DE);
}
main();