import * as fs from "node:fs"; import * as path from "node:path"; import type { MigrationContext } from "../types"; function discoverFonts(ctx: MigrationContext): string[] { // Check public/fonts (post-move) const fontsDir = path.join(ctx.sourceDir, "public", "fonts"); if (fs.existsSync(fontsDir)) return scanFontDir(fontsDir); // Check static/fonts (pre-move) const staticFonts = path.join(ctx.sourceDir, "static", "fonts"); if (fs.existsSync(staticFonts)) return scanFontDir(staticFonts); // Check static-*/fonts/ (multi-brand sites) try { const entries = fs.readdirSync(ctx.sourceDir, { withFileTypes: true }); for (const e of entries) { if (e.isDirectory() && e.name.startsWith("static-")) { const brandFonts = path.join(ctx.sourceDir, e.name, "fonts"); if (fs.existsSync(brandFonts)) return scanFontDir(brandFonts); } } } catch { /* ignore */ } return []; } function scanFontDir(dir: string): string[] { try { const allFonts = fs.readdirSync(dir) .filter((f) => /\.(woff2?|ttf|otf|eot)$/i.test(f)); // Only preload the most critical fonts (Regular + Bold of the primary family). // All fonts are still available via @font-face in CSS — this just controls // which ones get for faster rendering. const critical = allFonts.filter((f) => /[-_](Regular|Bold)\.(woff2?|ttf|otf)$/i.test(f) && !/Italic/i.test(f) ); // If we found critical weights, use those; otherwise take first 2 const toPreload = critical.length > 0 ? critical.slice(0, 4) // max 4 preloads : allFonts.slice(0, 2); return toPreload.map((f) => `/fonts/${f}`); } catch { return []; } } function hasMatchers(ctx: MigrationContext): boolean { const matchersDir = path.join(ctx.sourceDir, "matchers"); if (fs.existsSync(matchersDir)) return true; const srcMatchers = path.join(ctx.sourceDir, "src", "matchers"); return fs.existsSync(srcMatchers); } export function generateSetup(ctx: MigrationContext): string { const isVtex = ctx.platform === "vtex"; const siteName = ctx.siteName; const fonts = discoverFonts(ctx); const hasLocationMatcher = hasMatchers(ctx); // Build productionOrigins from known domain patterns const origins: string[] = []; // Check if source has productionOrigins in existing setup files const possibleDomains = [ `www.${siteName}.com.br`, `${siteName}.com.br`, ]; for (const domain of possibleDomains) { origins.push(`"https://${domain}"`); } const fontEntries = fonts.length > 0 ? fonts.map((f) => `"${f}"`).join(", ") : ""; return `/** * Site setup — orchestrator that wires framework, commerce, and sections. * * Actual logic lives in focused modules: * setup/commerce-loaders.ts — COMMERCE_LOADERS map (VTEX + site data fetchers) * setup/commerce-init.ts — server-only registration of COMMERCE_LOADERS + * invoke. Imported by worker-entry.ts, NOT here, * so the loader/action module graph (and any * credential in it) never enters the client bundle. * setup/section-loaders.ts — registerSectionLoaders (per-section prop enrichment) * * This file IS imported by router.tsx (client) for section registration/hydration, * so it must stay free of server-only imports (COMMERCE_LOADERS, invoke, secrets). * * Section metadata (eager, sync, layout, cache, LoadingFallback) is declared * in each section file and auto-extracted by generate-sections.ts. */ import "./cache-config"; import { applySectionConventions } from "@decocms/blocks/cms"; import { createSiteSetup } from "@decocms/blocks/setup";${isVtex ? ` import { createInstrumentedFetch } from "@decocms/blocks/sdk/instrumentedFetch"; import { initVtexFromBlocks, setVtexFetch } from "@decocms/apps-vtex";` : ""}${hasLocationMatcher ? ` import { registerLocationMatcher } from "./matchers/location";` : ""} import { blocks as generatedBlocks } from "../.deco/blocks.gen"; import { sectionMeta, syncComponents, loadingFallbacks, renderJsons } from "../.deco/sections.gen"; import { PreviewProviders } from "@decocms/tanstack"; // @ts-ignore Vite ?url import import appCss from "./styles/app.css?url"; import "./setup/section-loaders"; // -- Framework setup -- createSiteSetup({ sections: import.meta.glob("./sections/**/*.tsx") as Record Promise>, blocks: generatedBlocks, meta: () => import("../.deco/meta.gen.json").then((m) => m.default), css: appCss, fonts: [${fontEntries}], productionOrigins: [ ${origins.join(",\n ")}, ], previewWrapper: PreviewProviders,${hasLocationMatcher ? ` customMatchers: [registerLocationMatcher],` : ""}${isVtex ? ` initPlatform: (blocks) => initVtexFromBlocks(blocks),` : ""} onResolveError: (error, resolveType, context) => { console.error(\`[CMS-DEBUG] \${context} "\${resolveType}" failed:\`, error); }, onDanglingReference: (resolveType) => { console.warn(\`[CMS-DEBUG] Dangling reference: \${resolveType}\`); return null; }, }); ${isVtex ? ` // -- VTEX wiring -- setVtexFetch(createInstrumentedFetch("vtex")); ` : ""} // -- Convention-driven section registration -- applySectionConventions({ meta: sectionMeta, syncComponents, loadingFallbacks, renderJsons, sectionGlob: import.meta.glob("./sections/**/*.tsx") as Record Promise>, }); // -- Commerce + invoke -- // Registration lives in setup/commerce-init.ts, imported ONLY by // src/worker-entry.ts (server). Importing it here would drag COMMERCE_LOADERS — // and every site loader/action module it references — into the client bundle. `; }