#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, ".."); const sourceFile = path.join(root, "src", "content", "home-page.json"); const defaultEndpoint = "http://localhost:1337/graphql"; const endpoint = process.env.STRAPI_GRAPHQL_URL ?? process.env.PUBLIC_GRAPHQL_URL ?? defaultEndpoint; const token = process.env.STRAPI_TOKEN ?? process.env.STRAPI_API_TOKEN ?? process.env.AUTH_TOKEN; const isDryRun = process.argv.includes("--dry-run"); function readSource() { if (!fs.existsSync(sourceFile)) { throw new Error(`Missing source file: ${path.relative(root, sourceFile)}`); } return JSON.parse(fs.readFileSync(sourceFile, "utf8")); } // Drop null/undefined keys so optional Strapi fields (e.g. anchor) are omitted // rather than sent as explicit nulls. function compact(object) { return Object.fromEntries( Object.entries(object).filter(([, value]) => value !== null && value !== undefined) ); } // Maps a source section ("type") to its Strapi dynamic-zone component payload. // resolveCategory turns a category slug into the documentId Strapi expects for // the card -> category relation. const SECTION_BUILDERS = { hero: (section) => compact({ __component: "sections.home-hero", title: section.title, description: section.description, anchor: section.anchor, badge: section.badge, stats: (section.stats ?? []).map((stat) => ({ key: stat.key, value: stat.value, })), }), services: (section, resolveCategory) => compact({ __component: "sections.services", title: section.title, description: section.description, anchor: section.anchor, cards: (section.cards ?? []).map((card) => compact({ title: card.title, description: card.description, icon: { value: card.icon }, color: { value: card.color }, category: resolveCategory(card.category), }) ), }), solutions: (section, resolveCategory) => compact({ __component: "sections.solutions", title: section.title, description: section.description, anchor: section.anchor, cards: (section.cards ?? []).map((card) => compact({ title: card.title, description: card.description, footnote: card.footnote, icon: { value: card.icon }, category: resolveCategory(card.category), }) ), }), cases: (section) => compact({ __component: "sections.cases", title: section.title, description: section.description, anchor: section.anchor, cards: (section.cards ?? []).map((card) => compact({ title: card.title, description: card.description, pretitle: card.pretitle, badge: card.badge, footnote: card.footnote, color: { value: card.color }, icon: { value: card.icon }, }) ), }), about: (section) => compact({ __component: "sections.about", title: section.title, description: section.description, anchor: section.anchor, partners: (section.partners ?? []).map((value) => ({ value })), cards: (section.cards ?? []).map((card) => compact({ title: card.title, description: card.description, color: { value: card.color }, icon: { value: card.icon }, }) ), }), }; async function gql(query, variables = {}) { const headers = { "Content-Type": "application/json", Accept: "application/graphql-response+json, application/json", }; if (token) headers.Authorization = `Bearer ${token}`; const response = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify({ query, variables }), }); if (!response.ok) { throw new Error(`GraphQL HTTP ${response.status}: ${response.statusText}`); } const payload = await response.json(); if (payload.errors?.length) { const messages = payload.errors.map((err) => err.message).join("; "); throw new Error(`GraphQL error: ${messages}`); } return payload.data; } async function fetchCategoryDocumentIds() { const query = ` query CategoryIds { categories(pagination: { limit: 500 }) { documentId slug } } `; const data = await gql(query); const map = new Map(); for (const category of data?.categories ?? []) { if (!category?.slug || !category?.documentId) continue; map.set(category.slug, category.documentId); } return map; } function buildSections(source, categoryIdBySlug) { const sections = Array.isArray(source.sections) ? source.sections : []; const resolveCategory = (slug) => { const documentId = categoryIdBySlug.get(slug); if (!documentId) { throw new Error( `Category "${slug}" is missing in Strapi. Seed categories before seeding the home page.` ); } return documentId; }; return sections.map((section) => { const build = SECTION_BUILDERS[section.type]; if (!build) { throw new Error(`Unknown home-page section type "${String(section.type)}".`); } return build(section, resolveCategory); }); } async function updateHomePage(sections) { const mutation = ` mutation UpdateHomePage($data: HomePageInput!) { updateHomePage(data: $data) { sections { __typename } } } `; const data = await gql(mutation, { data: { sections } }); return data?.updateHomePage; } async function main() { console.log(`Seeding home page to ${endpoint}${isDryRun ? " (dry-run)" : ""}`); if (!token) { console.warn("No STRAPI token provided. If endpoint is protected, set STRAPI_TOKEN."); } const source = readSource(); if (isDryRun && process.argv.includes("--offline")) { // Allow inspecting the payload shape without touching Strapi. const sections = buildSections(source, new Map( (source.sections ?? []) .flatMap((section) => section.cards ?? []) .map((card) => card.category) .filter(Boolean) .map((slug) => [slug, `dryrun-${slug}`]) )); console.log(JSON.stringify(sections, null, 2)); return; } const categoryIdBySlug = await fetchCategoryDocumentIds(); const sections = buildSections(source, categoryIdBySlug); if (isDryRun) { console.log(JSON.stringify(sections, null, 2)); console.log(`DRY-RUN UPDATE homePage (${sections.length} sections)`); return; } const updated = await updateHomePage(sections); console.log(`UPDATE homePage (${updated.sections.length} sections)`); } main().catch((error) => { console.error(error); process.exit(1); });