#!/usr/bin/env node // 🤖 Generates the NATIVE seed catalogue — appCatalogReport() for every // built-in app, as the JSON the iOS/Mac apps bundle // (ios/LChatCore/Sources/LChatCore/Seed/app-catalog.json, decoded by // AppRegistry). The seed is what lets native know every built-in app's // manifest facts (title, glyph, places, presentation, commands) with ZERO // per-app native code before any webview has reported the live catalogue; // the runtime appCatalog() pull supersedes it per space (custom + member // apps only exist there). Run after adding or changing an app: // // node tools/gen-app-catalog.mjs --write # regenerate the bundled seed // node tools/gen-app-catalog.mjs # print to stdout // // test/appcatalog-seed.test.mjs pins the committed seed against this // generator's output, so a drifted seed fails `npm test` — the same // drift-gate shape as the schema copies (test/schema-engine.test.mjs). import { readdirSync, writeFileSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import path from "node:path"; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const appsDir = path.join(root, "web", "static", "apps"); export const seedPath = path.join( root, "ios", "LChatCore", "Sources", "LChatCore", "Seed", "app-catalog.json"); // appstudio.js needs a real DOM at module scope (it builds its editor // eagerly), so it can't load headless. It registers no places and no // commands — nothing a native picker or router reads — so the seed simply // omits it; spaces that install it still report it via the live pull. const SKIP = new Set(["appstudio.js"]); export async function collect() { const files = readdirSync(appsDir) .filter((f) => f.endsWith(".js") && !SKIP.has(f)) .sort(); for (const f of files) await import(path.join(appsDir, f)); const { appCatalogReport } = await import(path.join(appsDir, "..", "apps.js")); // Sorted by name so regenerating is a stable diff. return appCatalogReport().sort((a, b) => a.name.localeCompare(b.name)); } const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); if (isMain) { const json = JSON.stringify(await collect(), null, 2) + "\n"; if (process.argv.includes("--write")) { const before = (() => { try { return readFileSync(seedPath, "utf8"); } catch { return ""; } })(); writeFileSync(seedPath, json); console.log(before === json ? `unchanged: ${seedPath}` : `wrote ${seedPath}`); } else { process.stdout.write(json); } }