@fluid-app/fluid-cli-portal 0.1.45 → 0.1.47

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.
@@ -0,0 +1,233 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
2
+ import { basename, extname, join, resolve, sep } from "node:path";
3
+ import { readFile, readdir } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { createHash } from "node:crypto";
6
+ //#region src/vite-plugin/build-manifest.ts
7
+ /**
8
+ * Build a RawManifestResponse from local portal/ files.
9
+ *
10
+ * Reads the portal directory structure produced by `fluid portal pull` and
11
+ * assembles it into the same shape the SDK expects from the
12
+ * `/api/fluid_os/definitions/active` endpoint, so the existing
13
+ * `transformManifestToRepAppData()` pipeline works unchanged.
14
+ */
15
+ /**
16
+ * Generate a deterministic synthetic numeric ID from a string key.
17
+ * Uses a CRC32-like hash so the same slug always produces the same ID
18
+ * within a dev session, while avoiding collisions in practice.
19
+ */
20
+ function syntheticId(key) {
21
+ return createHash("md5").update(key).digest().readUInt32BE(0);
22
+ }
23
+ /**
24
+ * Read and parse a JSON file, returning null on failure.
25
+ */
26
+ async function readJsonFile(filePath) {
27
+ try {
28
+ const content = await readFile(filePath, "utf-8");
29
+ return JSON.parse(content);
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ /**
35
+ * Read all JSON files in a directory, returning an array of [slug, data] tuples.
36
+ * Slug is the filename without extension.
37
+ */
38
+ async function readJsonDir(dirPath) {
39
+ if (!existsSync(dirPath)) return [];
40
+ const entries = await readdir(dirPath, { withFileTypes: true });
41
+ const results = [];
42
+ for (const entry of entries) {
43
+ if (!entry.isFile() || extname(entry.name) !== ".json") continue;
44
+ const slug = basename(entry.name, ".json");
45
+ const data = await readJsonFile(join(dirPath, entry.name));
46
+ if (data != null) results.push([slug, data]);
47
+ }
48
+ return results;
49
+ }
50
+ /**
51
+ * Convert local navigation items (which reference screens by slug)
52
+ * back to the raw API format (which uses screen_id).
53
+ */
54
+ function toRawNavigationItems(items, screenSlugToId) {
55
+ return items.map((item) => {
56
+ const rawItem = {
57
+ id: item.id,
58
+ label: item.label ?? "Untitled",
59
+ position: item.position ?? 0,
60
+ children: toRawNavigationItems(item.children ?? [], screenSlugToId)
61
+ };
62
+ if (item.slug != null) rawItem.slug = item.slug;
63
+ if (item.icon != null) rawItem.icon = item.icon;
64
+ if (item.source != null) rawItem.source = item.source;
65
+ if (item.parent_id != null) rawItem.parent_id = item.parent_id;
66
+ if (item.screen != null) {
67
+ const screenId = screenSlugToId.get(item.screen);
68
+ if (screenId != null) rawItem.screen_id = screenId;
69
+ }
70
+ return rawItem;
71
+ });
72
+ }
73
+ /**
74
+ * Build a `RawManifestResponse` from local portal/ files.
75
+ *
76
+ * The returned object matches the shape from `/api/fluid_os/definitions/active`
77
+ * so the SDK's `transformManifestToRepAppData()` works without modification.
78
+ */
79
+ async function buildManifestFromLocalFiles(portalDir) {
80
+ const definitionId = syntheticId(`definition:${(await readJsonFile(join(portalDir, "definition.json")))?.name ?? "Local Dev"}`);
81
+ const [screenEntries, themeEntries, navEntries, profileEntries] = await Promise.all([
82
+ readJsonDir(join(portalDir, "screens")),
83
+ readJsonDir(join(portalDir, "themes")),
84
+ readJsonDir(join(portalDir, "navigations")),
85
+ readJsonDir(join(portalDir, "profiles"))
86
+ ]);
87
+ const screenSlugToId = /* @__PURE__ */ new Map();
88
+ for (const [slug] of screenEntries) screenSlugToId.set(slug, syntheticId(`screen:${slug}`));
89
+ const rawScreens = screenEntries.map(([slug, screen]) => ({
90
+ id: screenSlugToId.get(slug),
91
+ definition_id: definitionId,
92
+ name: screen.name,
93
+ slug,
94
+ component_tree: screen.component_tree
95
+ }));
96
+ const themeSlugToRaw = /* @__PURE__ */ new Map();
97
+ for (const [slug, theme] of themeEntries) themeSlugToRaw.set(slug, {
98
+ id: syntheticId(`theme:${slug}`),
99
+ name: theme.name,
100
+ config: theme.config,
101
+ active: theme.active
102
+ });
103
+ const rawThemes = Array.from(themeSlugToRaw.values());
104
+ const navSlugToId = /* @__PURE__ */ new Map();
105
+ for (const [slug] of navEntries) navSlugToId.set(slug, syntheticId(`navigation:${slug}`));
106
+ const rawNavigations = /* @__PURE__ */ new Map();
107
+ for (const [slug, nav] of navEntries) rawNavigations.set(slug, {
108
+ id: navSlugToId.get(slug),
109
+ name: nav.name,
110
+ definition_id: definitionId,
111
+ navigation_items: toRawNavigationItems(nav.navigation_items ?? [], screenSlugToId)
112
+ });
113
+ const defaultProfile = profileEntries.find(([, p]) => p.default)?.[1] ?? profileEntries[0]?.[1] ?? null;
114
+ let rawProfile;
115
+ if (defaultProfile) {
116
+ const navigation = defaultProfile.navigation ? rawNavigations.get(defaultProfile.navigation) : void 0;
117
+ const mobileNavigation = defaultProfile.mobile_navigation ? rawNavigations.get(defaultProfile.mobile_navigation) : void 0;
118
+ const profileThemeSet = new Set(defaultProfile.themes);
119
+ const profileThemes = profileThemeSet.size > 0 ? Array.from(profileThemeSet).map((slug) => themeSlugToRaw.get(slug)).filter((t) => t != null) : rawThemes;
120
+ rawProfile = {
121
+ name: defaultProfile.name,
122
+ definition_id: definitionId,
123
+ themes: profileThemes,
124
+ ...navigation ? { navigation } : {},
125
+ ...mobileNavigation ? { mobile_navigation: mobileNavigation } : {}
126
+ };
127
+ } else if (rawThemes.length > 0 || rawNavigations.size > 0) {
128
+ const firstNav = rawNavigations.size > 0 ? rawNavigations.values().next().value : void 0;
129
+ rawProfile = {
130
+ name: "Default",
131
+ definition_id: definitionId,
132
+ themes: rawThemes,
133
+ ...firstNav ? { navigation: firstNav } : {}
134
+ };
135
+ }
136
+ return { manifest: {
137
+ definition_id: definitionId,
138
+ published_version: 0,
139
+ screens: rawScreens,
140
+ ...rawProfile ? { profile: rawProfile } : {}
141
+ } };
142
+ }
143
+ //#endregion
144
+ //#region src/vite-plugin/portal-dev-plugin.ts
145
+ /**
146
+ * Vite plugin for local portal content development.
147
+ *
148
+ * Intercepts the manifest API request (`/api/fluid_os/definitions/active`)
149
+ * and serves content assembled from the local `portal/` directory.
150
+ * Watches portal files for changes and triggers a full page reload.
151
+ */
152
+ var portal_dev_plugin_exports = /* @__PURE__ */ __exportAll({ portalDevPlugin: () => portalDevPlugin });
153
+ const DEBOUNCE_MS = 100;
154
+ function portalDevPlugin(options) {
155
+ let portalDir;
156
+ let cachedManifest = null;
157
+ let buildInFlight = null;
158
+ return {
159
+ name: "fluid-portal-dev",
160
+ enforce: "pre",
161
+ configResolved(config) {
162
+ const root = config.root ?? process.cwd();
163
+ portalDir = options?.portalDir ? resolve(root, options.portalDir) : join(root, "portal");
164
+ },
165
+ configureServer(server) {
166
+ if (server.__fluidPortalDevWired) return;
167
+ server.__fluidPortalDevWired = true;
168
+ const watchDir = portalDir;
169
+ if (existsSync(watchDir)) {
170
+ server.watcher.add(watchDir);
171
+ let reloadTimer = null;
172
+ const onFileChange = (filePath) => {
173
+ if (!filePath.startsWith(watchDir)) return;
174
+ cachedManifest = null;
175
+ buildInFlight = null;
176
+ console.log(`[fluid-portal-dev] File changed: ${filePath.replace(watchDir + sep, "")}`);
177
+ if (reloadTimer) clearTimeout(reloadTimer);
178
+ reloadTimer = setTimeout(() => {
179
+ reloadTimer = null;
180
+ server.ws.send({
181
+ type: "full-reload",
182
+ path: "*"
183
+ });
184
+ }, DEBOUNCE_MS);
185
+ };
186
+ server.watcher.on("change", onFileChange);
187
+ server.watcher.on("add", onFileChange);
188
+ server.watcher.on("unlink", onFileChange);
189
+ }
190
+ const isManifestRequest = (url) => url.includes("/api/app/manifest") || url.includes("/api/fluid_os/definitions/active");
191
+ server.middlewares.use((req, res, next) => {
192
+ const url = req.url;
193
+ if (!url || !isManifestRequest(url)) {
194
+ next();
195
+ return;
196
+ }
197
+ (async () => {
198
+ try {
199
+ if (!existsSync(portalDir)) {
200
+ next();
201
+ return;
202
+ }
203
+ if (cachedManifest == null) {
204
+ if (!buildInFlight) buildInFlight = buildManifestFromLocalFiles(portalDir).then((m) => {
205
+ cachedManifest = JSON.stringify(m);
206
+ buildInFlight = null;
207
+ return cachedManifest;
208
+ }).catch((err) => {
209
+ buildInFlight = null;
210
+ throw err;
211
+ });
212
+ cachedManifest = await buildInFlight;
213
+ }
214
+ res.writeHead(200, {
215
+ "Content-Type": "application/json",
216
+ "X-Portal-Dev-Mode": "true",
217
+ "Cache-Control": "no-store"
218
+ });
219
+ res.end(cachedManifest);
220
+ } catch (err) {
221
+ console.error("[fluid-portal-dev] Error building manifest:", err);
222
+ res.writeHead(500, { "Content-Type": "application/json" });
223
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : "Failed to build manifest from local files" }));
224
+ }
225
+ })();
226
+ });
227
+ }
228
+ };
229
+ }
230
+ //#endregion
231
+ export { portal_dev_plugin_exports as n, buildManifestFromLocalFiles as r, portalDevPlugin as t };
232
+
233
+ //# sourceMappingURL=portal-dev-plugin-DBoPIt_V.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-dev-plugin-DBoPIt_V.mjs","names":[],"sources":["../src/vite-plugin/build-manifest.ts","../src/vite-plugin/portal-dev-plugin.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// 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: LocalNavigationItem[],\n screenSlugToId: Map<string, number>,\n): RawNavigationItem[] {\n return items.map((item) => {\n const rawItem: RawNavigationItem = {\n id: item.id,\n label: item.label ?? \"Untitled\",\n position: item.position ?? 0,\n children: toRawNavigationItems(item.children ?? [], screenSlugToId),\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\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): 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 ),\n });\n }\n\n // 7. Find the default profile (or use the first one)\n const defaultProfile =\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","/**\n * Vite plugin for local portal content development.\n *\n * Intercepts the manifest API request (`/api/fluid_os/definitions/active`)\n * and serves content assembled from the local `portal/` directory.\n * Watches portal files for changes and triggers a full page reload.\n */\n\nimport { join, resolve, sep } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { buildManifestFromLocalFiles } from \"./build-manifest.js\";\n\n/** Options for the portal dev plugin. */\nexport interface PortalDevPluginOptions {\n portalDir?: string;\n}\n\nconst DEBOUNCE_MS = 100;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function portalDevPlugin(options?: PortalDevPluginOptions): any {\n let portalDir: string;\n let cachedManifest: string | null = null;\n let buildInFlight: Promise<string> | null = null;\n\n return {\n name: \"fluid-portal-dev\",\n enforce: \"pre\" as const,\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n configResolved(config: any) {\n const root: string = config.root ?? process.cwd();\n portalDir = options?.portalDir\n ? resolve(root, options.portalDir)\n : join(root, \"portal\");\n },\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n configureServer(server: any) {\n // Idempotency: `fluid portal dev` injects this plugin, and a portal's\n // own vite.config may also wire it. Only the first instance registers\n // the watcher + middleware so we don't double-reload or double-serve.\n if (server.__fluidPortalDevWired) return;\n server.__fluidPortalDevWired = true;\n\n const watchDir = portalDir;\n\n if (existsSync(watchDir)) {\n server.watcher.add(watchDir);\n\n let reloadTimer: ReturnType<typeof setTimeout> | null = null;\n\n const onFileChange = (filePath: string) => {\n if (!filePath.startsWith(watchDir)) return;\n\n cachedManifest = null;\n buildInFlight = null;\n\n console.log(\n `[fluid-portal-dev] File changed: ${filePath.replace(watchDir + sep, \"\")}`,\n );\n\n if (reloadTimer) clearTimeout(reloadTimer);\n reloadTimer = setTimeout(() => {\n reloadTimer = null;\n server.ws.send({ type: \"full-reload\", path: \"*\" });\n }, DEBOUNCE_MS);\n };\n\n server.watcher.on(\"change\", onFileChange);\n server.watcher.on(\"add\", onFileChange);\n server.watcher.on(\"unlink\", onFileChange);\n }\n\n // ── Middleware to intercept manifest requests ───────────────────\n // Two routes return the SAME `{ manifest }` payload:\n // - /api/app/manifest — the portal-tenant BFF route the\n // current SDK runtime (useFluidApp → app_manifest_show) fetches.\n // - /api/fluid_os/definitions/active — the legacy FluidOS route.\n // We serve both so the local portal/ draft feeds the running app\n // regardless of which runtime it uses (the SDK adapter reads\n // `response.manifest`, matching buildManifestFromLocalFiles' output).\n const isManifestRequest = (url: string): boolean =>\n url.includes(\"/api/app/manifest\") ||\n url.includes(\"/api/fluid_os/definitions/active\");\n\n server.middlewares.use(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req: any, res: any, next: () => void) => {\n const url: string | undefined = req.url;\n\n if (!url || !isManifestRequest(url)) {\n next();\n return;\n }\n\n (async () => {\n try {\n if (!existsSync(portalDir)) {\n // Fall through to the Vite proxy so requests reach the real\n // API when local portal content is unavailable (e.g. --skip-pull).\n next();\n return;\n }\n\n if (cachedManifest == null) {\n if (!buildInFlight) {\n buildInFlight = buildManifestFromLocalFiles(portalDir)\n .then((m) => {\n cachedManifest = JSON.stringify(m);\n buildInFlight = null;\n return cachedManifest;\n })\n .catch((err) => {\n buildInFlight = null; // allow retry on next request\n throw err;\n });\n }\n cachedManifest = await buildInFlight;\n }\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"X-Portal-Dev-Mode\": \"true\",\n \"Cache-Control\": \"no-store\",\n });\n res.end(cachedManifest);\n } catch (err) {\n console.error(\"[fluid-portal-dev] Error building manifest:\", err);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error:\n err instanceof Error\n ? err.message\n : \"Failed to build manifest from local files\",\n }),\n );\n }\n })();\n },\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import "node:module";
1
+ import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
2
2
  import { Command } from "commander";
3
3
  import chalk from "chalk";
4
4
  import ora from "ora";
@@ -9,18 +9,6 @@ import prompts from "prompts";
9
9
  import { existsSync } from "node:fs";
10
10
  import pLimit from "p-limit";
11
11
  import { createHash, randomBytes } from "node:crypto";
12
- //#region \0rolldown/runtime.js
13
- var __defProp = Object.defineProperty;
14
- var __exportAll = (all, no_symbols) => {
15
- let target = {};
16
- for (var name in all) __defProp(target, name, {
17
- get: all[name],
18
- enumerable: true
19
- });
20
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
21
- return target;
22
- };
23
- //#endregion
24
12
  //#region ../../platform/api-client-core/src/fetch-client.ts
25
13
  /**
26
14
  * API Error class compatible with fluid-admin's ApiError
@@ -1213,4 +1201,4 @@ const pullCommand = new Command("pull").description("Pull a Fluid OS definition'
1213
1201
  //#endregion
1214
1202
  export { fluid_os_v0_create_fluid_osversion as A, fluid_os_v0_update_fluid_osnavigation as B, writeMappings as C, fluid_os_v0_create_fluid_osprofile as D, fluid_os_v0_create_fluid_osnavigation_item as E, fluid_os_v0_delete_fluid_osscreen as F, fluid_os_v0_update_fluid_osversion as G, fluid_os_v0_update_fluid_osprofile as H, fluid_os_v0_delete_fluid_ostheme as I, createFetchClient as K, fluid_os_v0_list_fluid_osdefinitions as L, fluid_os_v0_delete_fluid_osnavigation as M, fluid_os_v0_delete_fluid_osnavigation_item as N, fluid_os_v0_create_fluid_osscreen as O, fluid_os_v0_delete_fluid_osprofile as P, fluid_os_v0_list_fluid_osnavigation_items as R, updateMapping as S, fluid_os_v0_create_fluid_osnavigation as T, fluid_os_v0_update_fluid_osscreen as U, fluid_os_v0_update_fluid_osnavigation_item as V, fluid_os_v0_update_fluid_ostheme as W, deriveSlug as _, buildThemeIdToSlugMap as a, resolveIdToSlug as b, transformNavigationItems as c, transformTheme as d, buildSnapshot as f, writeSnapshot as g, readSnapshot as h, buildNavigationIdToSlugMap as i, fluid_os_v0_create_widget_package_version as j, fluid_os_v0_create_fluid_ostheme as k, transformProfile as l, diffAgainstSnapshot as m, pull_exports as n, deriveScreenSlug as o, computeFileHash as p, buildIdToSlugMap as r, transformNavigation as s, pullCommand as t, transformScreen as u, readMappings as v, fluid_os_v0_complete_widget_package_version_upload as w, resolveSlugToId as x, removeMapping as y, fluid_os_v0_list_fluid_osversions as z };
1215
1203
 
1216
- //# sourceMappingURL=pull-CG5dELOX.mjs.map
1204
+ //# sourceMappingURL=pull-XOsFaqz2.mjs.map