@fluid-app/fluid-cli-portal 0.1.57 → 0.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
1
+ import { N as __exportAll } from "./pull-BZqZAiSF.mjs";
2
2
  //#region src/vite-plugin/backend-dev-plugin.ts
3
3
  var backend_dev_plugin_exports = /* @__PURE__ */ __exportAll({ backendDevPlugin: () => backendDevPlugin });
4
4
  /**
@@ -61,4 +61,4 @@ function serve(res, contentType, body) {
61
61
  //#endregion
62
62
  export { backend_dev_plugin_exports as n, backendDevPlugin as t };
63
63
 
64
- //# sourceMappingURL=backend-dev-plugin-CY3yxgJ1.mjs.map
64
+ //# sourceMappingURL=backend-dev-plugin-CA3i3_ge.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend-dev-plugin-CY3yxgJ1.mjs","names":[],"sources":["../src/vite-plugin/backend-dev-plugin.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\n\nexport interface BackendDevPluginOptions {\n /** Entry module path relative to project root. @default \"/src/main.tsx\" */\n entry?: string;\n /** Production chunk filenames to stub as empty ES modules. */\n chunks?: string[];\n}\n\n/**\n * Serves dev-compatible responses at production asset paths so the Rails\n * backend can point `PORTAL_CDN_BASE` at `http://localhost:5173` and get\n * HMR + live source without any template changes.\n *\n * Handles:\n * - `/portal.js` → ESM shim that loads `@vite/client` + the real entry\n * - `/portal.css` → empty (Vite injects CSS via JS in dev)\n * - `/vendor.js`, `/query.js` → empty ES modules (no chunks in dev)\n */\nexport function backendDevPlugin(options?: BackendDevPluginOptions): Plugin {\n const entry = options?.entry ?? \"/src/main.tsx\";\n const chunks = options?.chunks ?? [\"vendor.js\", \"query.js\"];\n\n return {\n name: \"fluid-backend-dev\",\n apply: \"serve\",\n\n // Register BEFORE Vite's SPA fallback so these paths don't\n // get caught by the index.html catch-all.\n configureServer(server) {\n // Idempotency guard: the CLI injects this plugin and a vite.config may\n // also wire it — only register the middleware once.\n const flagged = server as unknown as { __fluidBackendDevWired?: boolean };\n if (flagged.__fluidBackendDevWired) return;\n flagged.__fluidBackendDevWired = true;\n\n server.middlewares.use((req, res, next) => {\n const raw = req.url ?? \"\";\n const qIndex = raw.indexOf(\"?\");\n const url = qIndex === -1 ? raw : raw.slice(0, qIndex);\n\n if (url === \"/portal.js\") {\n serve(\n res,\n \"application/javascript\",\n [\n `import RefreshRuntime from \"/@react-refresh\";`,\n `import \"/@vite/client\";`,\n `RefreshRuntime.injectIntoGlobalHook(window);`,\n `window.$RefreshReg$ = () => {};`,\n `window.$RefreshSig$ = () => (type) => type;`,\n `window.__vite_plugin_react_preamble_installed__ = true;`,\n `await import(\"${entry}\");`,\n ].join(\"\\n\"),\n );\n return;\n }\n\n if (url === \"/portal.css\") {\n serve(res, \"text/css\", \"/* dev: CSS injected via JS */\");\n return;\n }\n\n const chunkName = url.startsWith(\"/\") ? url.slice(1) : url;\n if (chunks.includes(chunkName)) {\n serve(res, \"application/javascript\", \"export {};\");\n return;\n }\n\n next();\n });\n },\n };\n}\n\nfunction serve(\n res: import(\"http\").ServerResponse,\n contentType: string,\n body: string,\n) {\n res.setHeader(\"Content-Type\", `${contentType}; charset=utf-8`);\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.statusCode = 200;\n res.end(body);\n}\n"],"mappings":";;;;;;;;;;;;;AAmBA,SAAgB,iBAAiB,SAA2C;CAC1E,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,SAAS,SAAS,UAAU,CAAC,aAAa,WAAW;AAE3D,QAAO;EACL,MAAM;EACN,OAAO;EAIP,gBAAgB,QAAQ;GAGtB,MAAM,UAAU;AAChB,OAAI,QAAQ,uBAAwB;AACpC,WAAQ,yBAAyB;AAEjC,UAAO,YAAY,KAAK,KAAK,KAAK,SAAS;IACzC,MAAM,MAAM,IAAI,OAAO;IACvB,MAAM,SAAS,IAAI,QAAQ,IAAI;IAC/B,MAAM,MAAM,WAAW,KAAK,MAAM,IAAI,MAAM,GAAG,OAAO;AAEtD,QAAI,QAAQ,cAAc;AACxB,WACE,KACA,0BACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA,iBAAiB,MAAM;MACxB,CAAC,KAAK,KAAK,CACb;AACD;;AAGF,QAAI,QAAQ,eAAe;AACzB,WAAM,KAAK,YAAY,iCAAiC;AACxD;;IAGF,MAAM,YAAY,IAAI,WAAW,IAAI,GAAG,IAAI,MAAM,EAAE,GAAG;AACvD,QAAI,OAAO,SAAS,UAAU,EAAE;AAC9B,WAAM,KAAK,0BAA0B,aAAa;AAClD;;AAGF,UAAM;KACN;;EAEL;;AAGH,SAAS,MACP,KACA,aACA,MACA;AACA,KAAI,UAAU,gBAAgB,GAAG,YAAY,iBAAiB;AAC9D,KAAI,UAAU,iBAAiB,WAAW;AAC1C,KAAI,UAAU,+BAA+B,IAAI;AACjD,KAAI,aAAa;AACjB,KAAI,IAAI,KAAK"}
1
+ {"version":3,"file":"backend-dev-plugin-CA3i3_ge.mjs","names":[],"sources":["../src/vite-plugin/backend-dev-plugin.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\n\nexport interface BackendDevPluginOptions {\n /** Entry module path relative to project root. @default \"/src/main.tsx\" */\n entry?: string;\n /** Production chunk filenames to stub as empty ES modules. */\n chunks?: string[];\n}\n\n/**\n * Serves dev-compatible responses at production asset paths so the Rails\n * backend can point `PORTAL_CDN_BASE` at `http://localhost:5173` and get\n * HMR + live source without any template changes.\n *\n * Handles:\n * - `/portal.js` → ESM shim that loads `@vite/client` + the real entry\n * - `/portal.css` → empty (Vite injects CSS via JS in dev)\n * - `/vendor.js`, `/query.js` → empty ES modules (no chunks in dev)\n */\nexport function backendDevPlugin(options?: BackendDevPluginOptions): Plugin {\n const entry = options?.entry ?? \"/src/main.tsx\";\n const chunks = options?.chunks ?? [\"vendor.js\", \"query.js\"];\n\n return {\n name: \"fluid-backend-dev\",\n apply: \"serve\",\n\n // Register BEFORE Vite's SPA fallback so these paths don't\n // get caught by the index.html catch-all.\n configureServer(server) {\n // Idempotency guard: the CLI injects this plugin and a vite.config may\n // also wire it — only register the middleware once.\n const flagged = server as unknown as { __fluidBackendDevWired?: boolean };\n if (flagged.__fluidBackendDevWired) return;\n flagged.__fluidBackendDevWired = true;\n\n server.middlewares.use((req, res, next) => {\n const raw = req.url ?? \"\";\n const qIndex = raw.indexOf(\"?\");\n const url = qIndex === -1 ? raw : raw.slice(0, qIndex);\n\n if (url === \"/portal.js\") {\n serve(\n res,\n \"application/javascript\",\n [\n `import RefreshRuntime from \"/@react-refresh\";`,\n `import \"/@vite/client\";`,\n `RefreshRuntime.injectIntoGlobalHook(window);`,\n `window.$RefreshReg$ = () => {};`,\n `window.$RefreshSig$ = () => (type) => type;`,\n `window.__vite_plugin_react_preamble_installed__ = true;`,\n `await import(\"${entry}\");`,\n ].join(\"\\n\"),\n );\n return;\n }\n\n if (url === \"/portal.css\") {\n serve(res, \"text/css\", \"/* dev: CSS injected via JS */\");\n return;\n }\n\n const chunkName = url.startsWith(\"/\") ? url.slice(1) : url;\n if (chunks.includes(chunkName)) {\n serve(res, \"application/javascript\", \"export {};\");\n return;\n }\n\n next();\n });\n },\n };\n}\n\nfunction serve(\n res: import(\"http\").ServerResponse,\n contentType: string,\n body: string,\n) {\n res.setHeader(\"Content-Type\", `${contentType}; charset=utf-8`);\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.statusCode = 200;\n res.end(body);\n}\n"],"mappings":";;;;;;;;;;;;;AAmBA,SAAgB,iBAAiB,SAA2C;CAC1E,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,SAAS,SAAS,UAAU,CAAC,aAAa,WAAW;AAE3D,QAAO;EACL,MAAM;EACN,OAAO;EAIP,gBAAgB,QAAQ;GAGtB,MAAM,UAAU;AAChB,OAAI,QAAQ,uBAAwB;AACpC,WAAQ,yBAAyB;AAEjC,UAAO,YAAY,KAAK,KAAK,KAAK,SAAS;IACzC,MAAM,MAAM,IAAI,OAAO;IACvB,MAAM,SAAS,IAAI,QAAQ,IAAI;IAC/B,MAAM,MAAM,WAAW,KAAK,MAAM,IAAI,MAAM,GAAG,OAAO;AAEtD,QAAI,QAAQ,cAAc;AACxB,WACE,KACA,0BACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA,iBAAiB,MAAM;MACxB,CAAC,KAAK,KAAK,CACb;AACD;;AAGF,QAAI,QAAQ,eAAe;AACzB,WAAM,KAAK,YAAY,iCAAiC;AACxD;;IAGF,MAAM,YAAY,IAAI,WAAW,IAAI,GAAG,IAAI,MAAM,EAAE,GAAG;AACvD,QAAI,OAAO,SAAS,UAAU,EAAE;AAC9B,WAAM,KAAK,0BAA0B,aAAa;AAClD;;AAGF,UAAM;KACN;;EAEL;;AAGH,SAAS,MACP,KACA,aACA,MACA;AACA,KAAI,UAAU,gBAAgB,GAAG,YAAY,iBAAiB;AAC9D,KAAI,UAAU,iBAAiB,WAAW;AAC1C,KAAI,UAAU,+BAA+B,IAAI;AACjD,KAAI,aAAa;AACjB,KAAI,IAAI,KAAK"}
@@ -131,7 +131,7 @@ async function buildManifestFromLocalFiles(portalDir, options = {}) {
131
131
  for (const [slug, theme] of themeEntries) themeSlugToRaw.set(slug, {
132
132
  id: syntheticId(`theme:${slug}`),
133
133
  name: theme.name,
134
- config: theme.config,
134
+ config: { ...theme.config },
135
135
  active: theme.active
136
136
  });
137
137
  const rawThemes = Array.from(themeSlugToRaw.values());
@@ -1 +1 @@
1
- {"version":3,"file":"build-manifest.mjs","names":[],"sources":["../src/vite-plugin/build-manifest.ts"],"sourcesContent":["/**\n * Build a RawManifestResponse from local portal/ files.\n *\n * Reads the portal directory structure produced by `fluid portal pull` and\n * assembles it into the same shape the SDK expects from the\n * `/api/fluid_os/definitions/active` endpoint, so the existing\n * `transformManifestToRepAppData()` pipeline works unchanged.\n */\n\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { join, basename, extname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\n\nimport type {\n LocalScreen,\n LocalTheme,\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\n} from \"../utils/transform.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types matching the RawManifestResponse shape from\n// packages/portal/sdk/src/transforms/index.ts\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface RawNavigationItem {\n id: number;\n label: string;\n slug?: string;\n icon?: string;\n screen_id?: number;\n parent_id?: number;\n source?: string;\n position: number;\n children: RawNavigationItem[];\n}\n\ninterface RawNavigation {\n id: number;\n name?: string;\n definition_id: number;\n navigation_items?: RawNavigationItem[];\n}\n\ninterface RawTheme {\n id: number;\n config?: Record<string, unknown> | null;\n active?: boolean | null;\n name?: string | null;\n}\n\ninterface RawScreen {\n id: number;\n definition_id?: number;\n name?: string | null;\n slug?: string | null;\n component_tree?: unknown;\n}\n\ninterface RawManifestProfile {\n name?: string;\n definition_id: number;\n themes?: RawTheme[];\n navigation?: RawNavigation;\n mobile_navigation?: RawNavigation;\n}\n\ninterface RawManifestResponse {\n manifest: {\n definition_id: number;\n published_version?: number;\n screens?: RawScreen[];\n profile?: RawManifestProfile;\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Synthetic ID generation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Generate a deterministic synthetic numeric ID from a string key.\n * Uses a CRC32-like hash so the same slug always produces the same ID\n * within a dev session, while avoiding collisions in practice.\n */\nfunction syntheticId(key: string): number {\n const hash = createHash(\"md5\").update(key).digest();\n // Read first 4 bytes as unsigned 32-bit integer, ensure positive\n return hash.readUInt32BE(0);\n}\n\n/**\n * Slug reported for the profile this builder synthesises when a portal has no\n * `portal/profiles/` files. Not a real filename — it exists so a host offering\n * a profile switcher has something stable to key a selection on.\n */\nconst SYNTHETIC_PROFILE_SLUG = \"default\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// File reading helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Read and parse a JSON file, returning null on failure.\n */\nasync function readJsonFile<T>(filePath: string): Promise<T | null> {\n try {\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Read all JSON files in a directory, returning an array of [slug, data] tuples.\n * Slug is the filename without extension.\n */\nasync function readJsonDir<T>(\n dirPath: string,\n): Promise<Array<[slug: string, data: T]>> {\n if (!existsSync(dirPath)) return [];\n\n const entries = await readdir(dirPath, { withFileTypes: true });\n const results: Array<[string, T]> = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || extname(entry.name) !== \".json\") continue;\n\n const slug = basename(entry.name, \".json\");\n const data = await readJsonFile<T>(join(dirPath, entry.name));\n if (data != null) {\n results.push([slug, data]);\n }\n }\n\n return results;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Navigation item conversion\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert local navigation items (which reference screens by slug)\n * back to the raw API format (which uses screen_id).\n */\nfunction toRawNavigationItems(\n items: readonly LocalNavigationItem[],\n screenSlugToId: Map<string, number>,\n navigationSlug: string,\n parentPath: readonly number[] = [],\n): RawNavigationItem[] {\n return items.map((item, index) => {\n const itemPath = [...parentPath, index];\n const rawItem: RawNavigationItem = {\n id:\n item.id ??\n syntheticId(`navigation-item:${navigationSlug}:${itemPath.join(\".\")}`),\n label: item.label ?? \"Untitled\",\n position: item.position ?? 0,\n children: toRawNavigationItems(\n item.children ?? [],\n screenSlugToId,\n navigationSlug,\n itemPath,\n ),\n };\n\n if (item.slug != null) rawItem.slug = item.slug;\n if (item.icon != null) rawItem.icon = item.icon;\n if (item.source != null) rawItem.source = item.source;\n if (item.parent_id != null) rawItem.parent_id = item.parent_id;\n\n // Resolve screen slug -> synthetic screen ID\n if (item.screen != null) {\n const screenId = screenSlugToId.get(item.screen);\n if (screenId != null) {\n rawItem.screen_id = screenId;\n }\n }\n\n return rawItem;\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Main builder\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BuildManifestOptions {\n /**\n * Slug (filename stem) of the profile to assemble. Defaults to the profile\n * flagged `default`, else the first one found — the historical behaviour.\n *\n * A definition can hold several profiles, each with its own navigation and\n * themes, and hosts that let a user preview a profile other than their own\n * (the fluid-admin builder, Mist's in-process preview) need to pick which\n * one lands in the manifest. Unknown slugs fall back to the default rather\n * than producing an empty profile, so a stale persisted selection can't\n * blank a preview.\n */\n readonly profileSlug?: string;\n}\n\n/** A profile discovered on disk, for hosts that offer a profile switcher. */\nexport interface LocalProfileSummary {\n readonly slug: string;\n readonly name: string;\n /** Stable synthetic id, derived from the slug — not a server id. */\n readonly id: number;\n readonly isDefault: boolean;\n}\n\n/**\n * List the profiles a portal directory defines, without building a manifest.\n *\n * Deliberately separate from `buildManifestFromLocalFiles` rather than widening\n * its return type: `portal-dev-plugin` serialises that function's result\n * directly as the `/api/app/manifest` response body, so any extra key would\n * leak into the wire payload.\n *\n * When `portal/profiles/` is absent or empty, reports the single synthetic\n * \"Default\" profile the builder falls back to, so a switcher never renders an\n * empty list for a scratch portal.\n */\nexport async function listLocalProfiles(\n portalDir: string,\n): Promise<LocalProfileSummary[]> {\n const profileEntries = await readJsonDir<LocalProfile>(\n join(portalDir, \"profiles\"),\n );\n\n if (profileEntries.length === 0) {\n return [\n {\n slug: SYNTHETIC_PROFILE_SLUG,\n name: \"Default\",\n id: syntheticId(`profile:${SYNTHETIC_PROFILE_SLUG}`),\n isDefault: true,\n },\n ];\n }\n\n // Exactly one entry is flagged default, and it is the same one the builder\n // would assemble. `buildManifestFromLocalFiles` resolves the default with\n // `find(([, p]) => p.default) ?? profileEntries[0]` — first-wins, with a\n // fallback to the first profile — so mirror both halves here. Local files\n // are hand-edited and can easily claim zero or several defaults; leaving\n // either case unnormalised would make a host's initial selection disagree\n // with the manifest it actually receives, decided by directory order.\n const defaultIndex = Math.max(\n profileEntries.findIndex(([, profile]) => profile.default === true),\n 0,\n );\n\n return profileEntries.map(([slug, profile], index) => ({\n slug,\n name: profile.name ?? slug,\n id: syntheticId(`profile:${slug}`),\n isDefault: index === defaultIndex,\n }));\n}\n\n/**\n * Build a `RawManifestResponse` from local portal/ files.\n *\n * The returned object matches the shape from `/api/fluid_os/definitions/active`\n * so the SDK's `transformManifestToRepAppData()` works without modification.\n */\nexport async function buildManifestFromLocalFiles(\n portalDir: string,\n options: BuildManifestOptions = {},\n): Promise<RawManifestResponse> {\n // 1. Read definition.json\n const definition = await readJsonFile<{ name?: string }>(\n join(portalDir, \"definition.json\"),\n );\n const definitionName = definition?.name ?? \"Local Dev\";\n const definitionId = syntheticId(`definition:${definitionName}`);\n\n // 2. Read all resource files in parallel\n const [screenEntries, themeEntries, navEntries, profileEntries] =\n await Promise.all([\n readJsonDir<LocalScreen>(join(portalDir, \"screens\")),\n readJsonDir<LocalTheme>(join(portalDir, \"themes\")),\n readJsonDir<LocalNavigation>(join(portalDir, \"navigations\")),\n readJsonDir<LocalProfile>(join(portalDir, \"profiles\")),\n ]);\n\n // 3. Build screen slug -> synthetic ID map\n const screenSlugToId = new Map<string, number>();\n for (const [slug] of screenEntries) {\n screenSlugToId.set(slug, syntheticId(`screen:${slug}`));\n }\n\n // 4. Build raw screens\n const rawScreens: RawScreen[] = screenEntries.map(([slug, screen]) => ({\n id: screenSlugToId.get(slug)!,\n definition_id: definitionId,\n name: screen.name,\n slug,\n component_tree: screen.component_tree,\n }));\n\n // 5. Build raw themes (keyed by slug for profile lookup)\n const themeSlugToRaw = new Map<string, RawTheme>();\n for (const [slug, theme] of themeEntries) {\n themeSlugToRaw.set(slug, {\n id: syntheticId(`theme:${slug}`),\n name: theme.name,\n config: theme.config,\n active: theme.active,\n });\n }\n const rawThemes = Array.from(themeSlugToRaw.values());\n\n // 6. Build navigation slug -> synthetic ID map\n const navSlugToId = new Map<string, number>();\n for (const [slug] of navEntries) {\n navSlugToId.set(slug, syntheticId(`navigation:${slug}`));\n }\n\n // Build raw navigations (keyed by slug for profile lookup)\n const rawNavigations = new Map<string, RawNavigation>();\n for (const [slug, nav] of navEntries) {\n rawNavigations.set(slug, {\n id: navSlugToId.get(slug)!,\n name: nav.name,\n definition_id: definitionId,\n navigation_items: toRawNavigationItems(\n nav.navigation_items ?? [],\n screenSlugToId,\n slug,\n ),\n });\n }\n\n // 7. Pick the profile to assemble: the requested slug when it resolves,\n // else the one flagged `default`, else the first. An unknown requested\n // slug falls back rather than yielding an empty profile, so a stale\n // persisted selection in a host can never blank the manifest.\n const requestedProfile =\n options.profileSlug != null\n ? (profileEntries.find(([slug]) => slug === options.profileSlug)?.[1] ??\n null)\n : null;\n\n const defaultProfile =\n requestedProfile ??\n profileEntries.find(([, p]) => p.default)?.[1] ??\n profileEntries[0]?.[1] ??\n null;\n\n // 8. Assemble the profile for the manifest\n let rawProfile: RawManifestProfile | undefined;\n if (defaultProfile) {\n // Resolve navigation slugs to raw navigation objects\n const navigation = defaultProfile.navigation\n ? rawNavigations.get(defaultProfile.navigation)\n : undefined;\n\n const mobileNavigation = defaultProfile.mobile_navigation\n ? rawNavigations.get(defaultProfile.mobile_navigation)\n : undefined;\n\n // Filter themes to those referenced by the profile (by slug)\n const profileThemeSet = new Set(defaultProfile.themes);\n const profileThemes =\n profileThemeSet.size > 0\n ? Array.from(profileThemeSet)\n .map((slug) => themeSlugToRaw.get(slug))\n .filter((t): t is RawTheme => t != null)\n : rawThemes; // If no themes specified in profile, include all\n\n rawProfile = {\n name: defaultProfile.name,\n definition_id: definitionId,\n themes: profileThemes,\n ...(navigation ? { navigation } : {}),\n ...(mobileNavigation ? { mobile_navigation: mobileNavigation } : {}),\n };\n } else if (rawThemes.length > 0 || rawNavigations.size > 0) {\n // No profile files but we have themes/navs — create a synthetic profile\n const firstNav =\n rawNavigations.size > 0\n ? rawNavigations.values().next().value\n : undefined;\n\n rawProfile = {\n name: \"Default\",\n definition_id: definitionId,\n themes: rawThemes,\n ...(firstNav ? { navigation: firstNav } : {}),\n };\n }\n\n // 9. Return assembled manifest\n return {\n manifest: {\n definition_id: definitionId,\n published_version: 0,\n screens: rawScreens,\n ...(rawProfile ? { profile: rawProfile } : {}),\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAuFA,SAAS,YAAY,KAAqB;AAGxC,QAFa,WAAW,MAAM,CAAC,OAAO,IAAI,CAAC,QAAQ,CAEvC,aAAa,EAAE;;;;;;;AAQ7B,MAAM,yBAAyB;;;;AAS/B,eAAe,aAAgB,UAAqC;AAClE,KAAI;EACF,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;AACjD,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;;;;;;AAQX,eAAe,YACb,SACyC;AACzC,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO,EAAE;CAEnC,MAAM,UAAU,MAAM,QAAQ,SAAS,EAAE,eAAe,MAAM,CAAC;CAC/D,MAAM,UAA8B,EAAE;AAEtC,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,MAAM,QAAQ,IAAI,QAAQ,MAAM,KAAK,KAAK,QAAS;EAExD,MAAM,OAAO,SAAS,MAAM,MAAM,QAAQ;EAC1C,MAAM,OAAO,MAAM,aAAgB,KAAK,SAAS,MAAM,KAAK,CAAC;AAC7D,MAAI,QAAQ,KACV,SAAQ,KAAK,CAAC,MAAM,KAAK,CAAC;;AAI9B,QAAO;;;;;;AAWT,SAAS,qBACP,OACA,gBACA,gBACA,aAAgC,EAAE,EACb;AACrB,QAAO,MAAM,KAAK,MAAM,UAAU;EAChC,MAAM,WAAW,CAAC,GAAG,YAAY,MAAM;EACvC,MAAM,UAA6B;GACjC,IACE,KAAK,MACL,YAAY,mBAAmB,eAAe,GAAG,SAAS,KAAK,IAAI,GAAG;GACxE,OAAO,KAAK,SAAS;GACrB,UAAU,KAAK,YAAY;GAC3B,UAAU,qBACR,KAAK,YAAY,EAAE,EACnB,gBACA,gBACA,SACD;GACF;AAED,MAAI,KAAK,QAAQ,KAAM,SAAQ,OAAO,KAAK;AAC3C,MAAI,KAAK,QAAQ,KAAM,SAAQ,OAAO,KAAK;AAC3C,MAAI,KAAK,UAAU,KAAM,SAAQ,SAAS,KAAK;AAC/C,MAAI,KAAK,aAAa,KAAM,SAAQ,YAAY,KAAK;AAGrD,MAAI,KAAK,UAAU,MAAM;GACvB,MAAM,WAAW,eAAe,IAAI,KAAK,OAAO;AAChD,OAAI,YAAY,KACd,SAAQ,YAAY;;AAIxB,SAAO;GACP;;;;;;;;;;;;;;AA2CJ,eAAsB,kBACpB,WACgC;CAChC,MAAM,iBAAiB,MAAM,YAC3B,KAAK,WAAW,WAAW,CAC5B;AAED,KAAI,eAAe,WAAW,EAC5B,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACN,IAAI,YAAY,WAAW,yBAAyB;EACpD,WAAW;EACZ,CACF;CAUH,MAAM,eAAe,KAAK,IACxB,eAAe,WAAW,GAAG,aAAa,QAAQ,YAAY,KAAK,EACnE,EACD;AAED,QAAO,eAAe,KAAK,CAAC,MAAM,UAAU,WAAW;EACrD;EACA,MAAM,QAAQ,QAAQ;EACtB,IAAI,YAAY,WAAW,OAAO;EAClC,WAAW,UAAU;EACtB,EAAE;;;;;;;;AASL,eAAsB,4BACpB,WACA,UAAgC,EAAE,EACJ;CAM9B,MAAM,eAAe,YAAY,eAJd,MAAM,aACvB,KAAK,WAAW,kBAAkB,CACnC,GACkC,QAAQ,cACqB;CAGhE,MAAM,CAAC,eAAe,cAAc,YAAY,kBAC9C,MAAM,QAAQ,IAAI;EAChB,YAAyB,KAAK,WAAW,UAAU,CAAC;EACpD,YAAwB,KAAK,WAAW,SAAS,CAAC;EAClD,YAA6B,KAAK,WAAW,cAAc,CAAC;EAC5D,YAA0B,KAAK,WAAW,WAAW,CAAC;EACvD,CAAC;CAGJ,MAAM,iCAAiB,IAAI,KAAqB;AAChD,MAAK,MAAM,CAAC,SAAS,cACnB,gBAAe,IAAI,MAAM,YAAY,UAAU,OAAO,CAAC;CAIzD,MAAM,aAA0B,cAAc,KAAK,CAAC,MAAM,aAAa;EACrE,IAAI,eAAe,IAAI,KAAK;EAC5B,eAAe;EACf,MAAM,OAAO;EACb;EACA,gBAAgB,OAAO;EACxB,EAAE;CAGH,MAAM,iCAAiB,IAAI,KAAuB;AAClD,MAAK,MAAM,CAAC,MAAM,UAAU,aAC1B,gBAAe,IAAI,MAAM;EACvB,IAAI,YAAY,SAAS,OAAO;EAChC,MAAM,MAAM;EACZ,QAAQ,MAAM;EACd,QAAQ,MAAM;EACf,CAAC;CAEJ,MAAM,YAAY,MAAM,KAAK,eAAe,QAAQ,CAAC;CAGrD,MAAM,8BAAc,IAAI,KAAqB;AAC7C,MAAK,MAAM,CAAC,SAAS,WACnB,aAAY,IAAI,MAAM,YAAY,cAAc,OAAO,CAAC;CAI1D,MAAM,iCAAiB,IAAI,KAA4B;AACvD,MAAK,MAAM,CAAC,MAAM,QAAQ,WACxB,gBAAe,IAAI,MAAM;EACvB,IAAI,YAAY,IAAI,KAAK;EACzB,MAAM,IAAI;EACV,eAAe;EACf,kBAAkB,qBAChB,IAAI,oBAAoB,EAAE,EAC1B,gBACA,KACD;EACF,CAAC;CAaJ,MAAM,kBALJ,QAAQ,eAAe,OAClB,eAAe,MAAM,CAAC,UAAU,SAAS,QAAQ,YAAY,GAAG,MACjE,OACA,SAIJ,eAAe,MAAM,GAAG,OAAO,EAAE,QAAQ,GAAG,MAC5C,eAAe,KAAK,MACpB;CAGF,IAAI;AACJ,KAAI,gBAAgB;EAElB,MAAM,aAAa,eAAe,aAC9B,eAAe,IAAI,eAAe,WAAW,GAC7C,KAAA;EAEJ,MAAM,mBAAmB,eAAe,oBACpC,eAAe,IAAI,eAAe,kBAAkB,GACpD,KAAA;EAGJ,MAAM,kBAAkB,IAAI,IAAI,eAAe,OAAO;EACtD,MAAM,gBACJ,gBAAgB,OAAO,IACnB,MAAM,KAAK,gBAAgB,CACxB,KAAK,SAAS,eAAe,IAAI,KAAK,CAAC,CACvC,QAAQ,MAAqB,KAAK,KAAK,GAC1C;AAEN,eAAa;GACX,MAAM,eAAe;GACrB,eAAe;GACf,QAAQ;GACR,GAAI,aAAa,EAAE,YAAY,GAAG,EAAE;GACpC,GAAI,mBAAmB,EAAE,mBAAmB,kBAAkB,GAAG,EAAE;GACpE;YACQ,UAAU,SAAS,KAAK,eAAe,OAAO,GAAG;EAE1D,MAAM,WACJ,eAAe,OAAO,IAClB,eAAe,QAAQ,CAAC,MAAM,CAAC,QAC/B,KAAA;AAEN,eAAa;GACX,MAAM;GACN,eAAe;GACf,QAAQ;GACR,GAAI,WAAW,EAAE,YAAY,UAAU,GAAG,EAAE;GAC7C;;AAIH,QAAO,EACL,UAAU;EACR,eAAe;EACf,mBAAmB;EACnB,SAAS;EACT,GAAI,aAAa,EAAE,SAAS,YAAY,GAAG,EAAE;EAC9C,EACF"}
1
+ {"version":3,"file":"build-manifest.mjs","names":[],"sources":["../src/vite-plugin/build-manifest.ts"],"sourcesContent":["/**\n * Build a RawManifestResponse from local portal/ files.\n *\n * Reads the portal directory structure produced by `fluid portal pull` and\n * assembles it into the same shape the SDK expects from the\n * `/api/fluid_os/definitions/active` endpoint, so the existing\n * `transformManifestToRepAppData()` pipeline works unchanged.\n */\n\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { join, basename, extname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\n\nimport type {\n LocalScreen,\n LocalTheme,\n LocalNavigation,\n LocalNavigationItem,\n LocalProfile,\n} from \"../utils/transform.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types matching the RawManifestResponse shape from\n// packages/portal/sdk/src/transforms/index.ts\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface RawNavigationItem {\n id: number;\n label: string;\n slug?: string;\n icon?: string;\n screen_id?: number;\n parent_id?: number;\n source?: string;\n position: number;\n children: RawNavigationItem[];\n}\n\ninterface RawNavigation {\n id: number;\n name?: string;\n definition_id: number;\n navigation_items?: RawNavigationItem[];\n}\n\ninterface RawTheme {\n id: number;\n config?: Record<string, unknown> | null;\n active?: boolean | null;\n name?: string | null;\n}\n\ninterface RawScreen {\n id: number;\n definition_id?: number;\n name?: string | null;\n slug?: string | null;\n component_tree?: unknown;\n}\n\ninterface RawManifestProfile {\n name?: string;\n definition_id: number;\n themes?: RawTheme[];\n navigation?: RawNavigation;\n mobile_navigation?: RawNavigation;\n}\n\ninterface RawManifestResponse {\n manifest: {\n definition_id: number;\n published_version?: number;\n screens?: RawScreen[];\n profile?: RawManifestProfile;\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Synthetic ID generation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Generate a deterministic synthetic numeric ID from a string key.\n * Uses a CRC32-like hash so the same slug always produces the same ID\n * within a dev session, while avoiding collisions in practice.\n */\nfunction syntheticId(key: string): number {\n const hash = createHash(\"md5\").update(key).digest();\n // Read first 4 bytes as unsigned 32-bit integer, ensure positive\n return hash.readUInt32BE(0);\n}\n\n/**\n * Slug reported for the profile this builder synthesises when a portal has no\n * `portal/profiles/` files. Not a real filename — it exists so a host offering\n * a profile switcher has something stable to key a selection on.\n */\nconst SYNTHETIC_PROFILE_SLUG = \"default\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// File reading helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Read and parse a JSON file, returning null on failure.\n */\nasync function readJsonFile<T>(filePath: string): Promise<T | null> {\n try {\n const content = await readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Read all JSON files in a directory, returning an array of [slug, data] tuples.\n * Slug is the filename without extension.\n */\nasync function readJsonDir<T>(\n dirPath: string,\n): Promise<Array<[slug: string, data: T]>> {\n if (!existsSync(dirPath)) return [];\n\n const entries = await readdir(dirPath, { withFileTypes: true });\n const results: Array<[string, T]> = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || extname(entry.name) !== \".json\") continue;\n\n const slug = basename(entry.name, \".json\");\n const data = await readJsonFile<T>(join(dirPath, entry.name));\n if (data != null) {\n results.push([slug, data]);\n }\n }\n\n return results;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Navigation item conversion\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert local navigation items (which reference screens by slug)\n * back to the raw API format (which uses screen_id).\n */\nfunction toRawNavigationItems(\n items: readonly LocalNavigationItem[],\n screenSlugToId: Map<string, number>,\n navigationSlug: string,\n parentPath: readonly number[] = [],\n): RawNavigationItem[] {\n return items.map((item, index) => {\n const itemPath = [...parentPath, index];\n const rawItem: RawNavigationItem = {\n id:\n item.id ??\n syntheticId(`navigation-item:${navigationSlug}:${itemPath.join(\".\")}`),\n label: item.label ?? \"Untitled\",\n position: item.position ?? 0,\n children: toRawNavigationItems(\n item.children ?? [],\n screenSlugToId,\n navigationSlug,\n itemPath,\n ),\n };\n\n if (item.slug != null) rawItem.slug = item.slug;\n if (item.icon != null) rawItem.icon = item.icon;\n if (item.source != null) rawItem.source = item.source;\n if (item.parent_id != null) rawItem.parent_id = item.parent_id;\n\n // Resolve screen slug -> synthetic screen ID\n if (item.screen != null) {\n const screenId = screenSlugToId.get(item.screen);\n if (screenId != null) {\n rawItem.screen_id = screenId;\n }\n }\n\n return rawItem;\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Main builder\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BuildManifestOptions {\n /**\n * Slug (filename stem) of the profile to assemble. Defaults to the profile\n * flagged `default`, else the first one found — the historical behaviour.\n *\n * A definition can hold several profiles, each with its own navigation and\n * themes, and hosts that let a user preview a profile other than their own\n * (the fluid-admin builder, Mist's in-process preview) need to pick which\n * one lands in the manifest. Unknown slugs fall back to the default rather\n * than producing an empty profile, so a stale persisted selection can't\n * blank a preview.\n */\n readonly profileSlug?: string;\n}\n\n/** A profile discovered on disk, for hosts that offer a profile switcher. */\nexport interface LocalProfileSummary {\n readonly slug: string;\n readonly name: string;\n /** Stable synthetic id, derived from the slug — not a server id. */\n readonly id: number;\n readonly isDefault: boolean;\n}\n\n/**\n * List the profiles a portal directory defines, without building a manifest.\n *\n * Deliberately separate from `buildManifestFromLocalFiles` rather than widening\n * its return type: `portal-dev-plugin` serialises that function's result\n * directly as the `/api/app/manifest` response body, so any extra key would\n * leak into the wire payload.\n *\n * When `portal/profiles/` is absent or empty, reports the single synthetic\n * \"Default\" profile the builder falls back to, so a switcher never renders an\n * empty list for a scratch portal.\n */\nexport async function listLocalProfiles(\n portalDir: string,\n): Promise<LocalProfileSummary[]> {\n const profileEntries = await readJsonDir<LocalProfile>(\n join(portalDir, \"profiles\"),\n );\n\n if (profileEntries.length === 0) {\n return [\n {\n slug: SYNTHETIC_PROFILE_SLUG,\n name: \"Default\",\n id: syntheticId(`profile:${SYNTHETIC_PROFILE_SLUG}`),\n isDefault: true,\n },\n ];\n }\n\n // Exactly one entry is flagged default, and it is the same one the builder\n // would assemble. `buildManifestFromLocalFiles` resolves the default with\n // `find(([, p]) => p.default) ?? profileEntries[0]` — first-wins, with a\n // fallback to the first profile — so mirror both halves here. Local files\n // are hand-edited and can easily claim zero or several defaults; leaving\n // either case unnormalised would make a host's initial selection disagree\n // with the manifest it actually receives, decided by directory order.\n const defaultIndex = Math.max(\n profileEntries.findIndex(([, profile]) => profile.default === true),\n 0,\n );\n\n return profileEntries.map(([slug, profile], index) => ({\n slug,\n name: profile.name ?? slug,\n id: syntheticId(`profile:${slug}`),\n isDefault: index === defaultIndex,\n }));\n}\n\n/**\n * Build a `RawManifestResponse` from local portal/ files.\n *\n * The returned object matches the shape from `/api/fluid_os/definitions/active`\n * so the SDK's `transformManifestToRepAppData()` works without modification.\n */\nexport async function buildManifestFromLocalFiles(\n portalDir: string,\n options: BuildManifestOptions = {},\n): Promise<RawManifestResponse> {\n // 1. Read definition.json\n const definition = await readJsonFile<{ name?: string }>(\n join(portalDir, \"definition.json\"),\n );\n const definitionName = definition?.name ?? \"Local Dev\";\n const definitionId = syntheticId(`definition:${definitionName}`);\n\n // 2. Read all resource files in parallel\n const [screenEntries, themeEntries, navEntries, profileEntries] =\n await Promise.all([\n readJsonDir<LocalScreen>(join(portalDir, \"screens\")),\n readJsonDir<LocalTheme>(join(portalDir, \"themes\")),\n readJsonDir<LocalNavigation>(join(portalDir, \"navigations\")),\n readJsonDir<LocalProfile>(join(portalDir, \"profiles\")),\n ]);\n\n // 3. Build screen slug -> synthetic ID map\n const screenSlugToId = new Map<string, number>();\n for (const [slug] of screenEntries) {\n screenSlugToId.set(slug, syntheticId(`screen:${slug}`));\n }\n\n // 4. Build raw screens\n const rawScreens: RawScreen[] = screenEntries.map(([slug, screen]) => ({\n id: screenSlugToId.get(slug)!,\n definition_id: definitionId,\n name: screen.name,\n slug,\n component_tree: screen.component_tree,\n }));\n\n // 5. Build raw themes (keyed by slug for profile lookup)\n const themeSlugToRaw = new Map<string, RawTheme>();\n for (const [slug, theme] of themeEntries) {\n themeSlugToRaw.set(slug, {\n id: syntheticId(`theme:${slug}`),\n name: theme.name,\n config: { ...theme.config },\n active: theme.active,\n });\n }\n const rawThemes = Array.from(themeSlugToRaw.values());\n\n // 6. Build navigation slug -> synthetic ID map\n const navSlugToId = new Map<string, number>();\n for (const [slug] of navEntries) {\n navSlugToId.set(slug, syntheticId(`navigation:${slug}`));\n }\n\n // Build raw navigations (keyed by slug for profile lookup)\n const rawNavigations = new Map<string, RawNavigation>();\n for (const [slug, nav] of navEntries) {\n rawNavigations.set(slug, {\n id: navSlugToId.get(slug)!,\n name: nav.name,\n definition_id: definitionId,\n navigation_items: toRawNavigationItems(\n nav.navigation_items ?? [],\n screenSlugToId,\n slug,\n ),\n });\n }\n\n // 7. Pick the profile to assemble: the requested slug when it resolves,\n // else the one flagged `default`, else the first. An unknown requested\n // slug falls back rather than yielding an empty profile, so a stale\n // persisted selection in a host can never blank the manifest.\n const requestedProfile =\n options.profileSlug != null\n ? (profileEntries.find(([slug]) => slug === options.profileSlug)?.[1] ??\n null)\n : null;\n\n const defaultProfile =\n requestedProfile ??\n profileEntries.find(([, p]) => p.default)?.[1] ??\n profileEntries[0]?.[1] ??\n null;\n\n // 8. Assemble the profile for the manifest\n let rawProfile: RawManifestProfile | undefined;\n if (defaultProfile) {\n // Resolve navigation slugs to raw navigation objects\n const navigation = defaultProfile.navigation\n ? rawNavigations.get(defaultProfile.navigation)\n : undefined;\n\n const mobileNavigation = defaultProfile.mobile_navigation\n ? rawNavigations.get(defaultProfile.mobile_navigation)\n : undefined;\n\n // Filter themes to those referenced by the profile (by slug)\n const profileThemeSet = new Set(defaultProfile.themes);\n const profileThemes =\n profileThemeSet.size > 0\n ? Array.from(profileThemeSet)\n .map((slug) => themeSlugToRaw.get(slug))\n .filter((t): t is RawTheme => t != null)\n : rawThemes; // If no themes specified in profile, include all\n\n rawProfile = {\n name: defaultProfile.name,\n definition_id: definitionId,\n themes: profileThemes,\n ...(navigation ? { navigation } : {}),\n ...(mobileNavigation ? { mobile_navigation: mobileNavigation } : {}),\n };\n } else if (rawThemes.length > 0 || rawNavigations.size > 0) {\n // No profile files but we have themes/navs — create a synthetic profile\n const firstNav =\n rawNavigations.size > 0\n ? rawNavigations.values().next().value\n : undefined;\n\n rawProfile = {\n name: \"Default\",\n definition_id: definitionId,\n themes: rawThemes,\n ...(firstNav ? { navigation: firstNav } : {}),\n };\n }\n\n // 9. Return assembled manifest\n return {\n manifest: {\n definition_id: definitionId,\n published_version: 0,\n screens: rawScreens,\n ...(rawProfile ? { profile: rawProfile } : {}),\n },\n };\n}\n"],"mappings":"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"}