#!/usr/bin/env node /* * scripts/mirror-ee.js — runs at predev/prebuild. * * Next.js Pages Router only routes files physically inside pages/. Premium pages/APIs * live in ee/pages/** so they can be stripped from the public build, but they must be * COPIED into pages/** for Next to see them during dev/build in the private repo. * * This script copies ee/pages/** → pages/** and records every file it wrote in * pages/.ee-mirror-manifest.json. It also writes a generated pages/.gitignore listing * every mirrored path — git reads nested .gitignore files natively, so the copies are * never tracked in either repo and pages/ stays clean in git. Source of truth is ee/pages. * * When ee/ is absent (public build), this is a no-op. See docs/OPEN_CORE.md. * * Everything else in ee/ (lib code) is imported in place via @/ee — NOT mirrored. */ const fs = require("fs"); const path = require("path"); const ROOT = path.resolve(__dirname, ".."); const EE_PAGES = path.join(ROOT, "ee", "pages"); const PAGES = path.join(ROOT, "pages"); const MANIFEST = path.join(PAGES, ".ee-mirror-manifest.json"); const NESTED_IGNORE = path.join(PAGES, ".gitignore"); function cleanPrevious() { // Remove files written by a previous mirror run so renames/deletions in ee/pages // don't leave stale routes behind. if (!fs.existsSync(MANIFEST)) return; try { const prev = JSON.parse(fs.readFileSync(MANIFEST, "utf8")); for (const rel of prev.files ?? []) { const p = path.join(PAGES, rel); if (fs.existsSync(p)) fs.rmSync(p); } fs.rmSync(MANIFEST); if (fs.existsSync(NESTED_IGNORE)) fs.rmSync(NESTED_IGNORE); } catch { /* ignore a corrupt manifest — fresh mirror below overwrites cleanly */ } } function walk(dir, base = dir) { const out = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const abs = path.join(dir, entry.name); if (entry.isDirectory()) out.push(...walk(abs, base)); else out.push(path.relative(base, abs)); } return out; } function main() { cleanPrevious(); if (!fs.existsSync(EE_PAGES)) { console.log("[mirror-ee] no ee/pages (public build or none yet) — nothing to mirror"); return; } const files = walk(EE_PAGES); for (const rel of files) { const src = path.join(EE_PAGES, rel); const dst = path.join(PAGES, rel); fs.mkdirSync(path.dirname(dst), { recursive: true }); fs.copyFileSync(src, dst); } fs.writeFileSync( MANIFEST, JSON.stringify({ generatedAt: new Date().toISOString(), files }, null, 2) ); // Nested .gitignore so the mirrored copies are never tracked. Git reads this natively. // Paths are POSIX-style and relative to pages/. The manifest + this file ignore themselves. // IMPORTANT: gitignore treats [ ] * ? as glob metacharacters, so Next's dynamic-route // segments like [replyId] must be backslash-escaped or the pattern won't match the literal // directory (leaving the mirrored copy tracked). Escape them. const esc = (p) => p.replace(/([[\]*?])/g, "\\$1"); const ignoreBody = [ "# GENERATED by scripts/mirror-ee.js — do not edit. Lists premium pages mirrored", "# from ee/pages/. Source of truth is ee/pages/**. See docs/OPEN_CORE.md.", ".gitignore", ".ee-mirror-manifest.json", ...files.map((f) => "/" + esc(f.split(path.sep).join("/"))), "", ].join("\n"); fs.writeFileSync(NESTED_IGNORE, ignoreBody); console.log(`[mirror-ee] mirrored ${files.length} premium page(s) into pages/`); } main();