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

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 { N as __exportAll } from "./pull-BZqZAiSF.mjs";
1
+ import { F as __exportAll } from "./pull-C26wQlO_.mjs";
2
2
  import { buildManifestFromLocalFiles } from "./build-manifest.mjs";
3
3
  import { join, resolve, sep } from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
@@ -229,4 +229,4 @@ function portalDevPlugin(options) {
229
229
  //#endregion
230
230
  export { portal_dev_plugin_exports as n, portalDevPlugin as t };
231
231
 
232
- //# sourceMappingURL=portal-dev-plugin-DfEBjr96.mjs.map
232
+ //# sourceMappingURL=portal-dev-plugin-BbXPp3ts.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"portal-dev-plugin-DfEBjr96.mjs","names":[],"sources":["../src/vite-plugin/portal-dev-plugin.ts"],"sourcesContent":["/**\n * Vite plugin for local portal content development.\n *\n * Intercepts the manifest API request and serves content assembled from the\n * local `portal/` directory. Watches portal files for changes and triggers a\n * full page reload.\n *\n * The manifest is intercepted on BOTH paths the SDK has used across versions\n * (`/api/app/manifest` since portal-sdk 0.1.4xx, `/api/fluid_os/definitions/\n * active` before that) so a newer SDK against an older-generation app — or\n * vice versa — still gets the local draft.\n */\n\nimport { join, resolve, sep } from \"node:path\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { getAuthToken, getFluidApiBase } from \"@fluid-app/fluid-cli\";\nimport { buildManifestFromLocalFiles } from \"./build-manifest.js\";\n\n/** Options for the portal dev plugin. */\nexport interface PortalDevPluginOptions {\n portalDir?: string;\n /**\n * When true (default), dev-server requests are forced same-origin: the\n * plugin blanks `import.meta.env.VITE_API_URL` in serve mode and proxies\n * `/api` to the configured Fluid host instead. Without this, a `.env`\n * pointing VITE_API_URL at a remote API makes the SPA fetch its manifest\n * cross-origin — silently bypassing this plugin and rendering the remote\n * app instead of the local `portal/` draft. Set false to opt out and let\n * the SPA call the remote API directly.\n */\n sameOriginDev?: boolean;\n /**\n * Override the `/api` dev-proxy target. Defaults to VITE_API_URL from the\n * process env / .env files, then the authenticated company's\n * `<subdomain>.portal.fluid.app` tenant BFF, then https://api.fluid.app.\n */\n apiProxyTarget?: string;\n /**\n * Override tenant-target discovery. Primarily useful to keep embedded plugin\n * integrations and tests deterministic.\n */\n resolveTenantApiTarget?: () => Promise<string | undefined>;\n}\n\nconst DEBOUNCE_MS = 100;\n\n/** Manifest endpoints across SDK generations — all serve the local draft. */\nconst MANIFEST_PATHS = [\n \"/api/app/manifest\",\n \"/api/fluid_os/definitions/active\",\n];\n\nconst DEFAULT_API_URL = \"https://api.fluid.app\";\nconst TENANT_LOOKUP_TIMEOUT_MS = 3_000;\nconst tenantTargetPromises = new WeakMap<\n () => Promise<string | undefined>,\n Promise<string | undefined>\n>();\n\ninterface CompanyMeResponse {\n data?: {\n company?: {\n subdomain?: unknown;\n };\n };\n}\n\nexport interface TenantApiTargetDependencies {\n token?: string | null;\n apiBase?: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n}\n\n/**\n * Resolve the portal tenant BFF for the CLI's project-aware Fluid profile.\n *\n * Portal member APIs do not live on api.fluid.app or `<tenant>.fluid.app`.\n * Their OpenAPI server is `<tenant>.portal.fluid.app`, and using either of\n * the former hosts produces a wall of unrelated 404s in otherwise-valid\n * local portals.\n */\nexport async function resolvePortalTenantApiTarget(\n dependencies: TenantApiTargetDependencies = {},\n): Promise<string | undefined> {\n const token =\n dependencies.token !== undefined ? dependencies.token : getAuthToken();\n if (!token) return undefined;\n\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n dependencies.timeoutMs ?? TENANT_LOOKUP_TIMEOUT_MS,\n );\n\n try {\n const response = await (dependencies.fetchImpl ?? fetch)(\n `${(dependencies.apiBase ?? getFluidApiBase()).replace(/\\/+$/, \"\")}/api/company/v1/companies/me`,\n {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n },\n );\n if (!response.ok) return undefined;\n\n const body = (await response.json()) as CompanyMeResponse;\n const subdomain = body.data?.company?.subdomain;\n if (\n typeof subdomain !== \"string\" ||\n !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(subdomain)\n ) {\n return undefined;\n }\n\n return `https://${subdomain}.portal.fluid.app`;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Fallback .env reader for VITE_API_URL, used only when vite's `loadEnv`\n * can't be imported (it's a declared dependency, so this is defensive — but\n * a broken install must degrade to \"wrong host\" rather than \"dev server\n * won't boot\"). Mirrors the basic lookup order: live env > .env.local >\n * .env. Unlike loadEnv it ignores .env.[mode] files and variable expansion.\n */\nfunction readViteApiUrlFallback(root: string): string | undefined {\n if (process.env.VITE_API_URL) return process.env.VITE_API_URL;\n if (process.env.NEXT_PUBLIC_API_BASE_URL) {\n return process.env.NEXT_PUBLIC_API_BASE_URL;\n }\n for (const file of [\".env.local\", \".env\"]) {\n const path = join(root, file);\n if (!existsSync(path)) continue;\n for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n // Quoted values stop at the closing `\"`; unquoted values stop before\n // an inline ` # comment` tail (dotenv semantics — a trailing comment\n // must not leak into the proxy target URL).\n const match =\n /^\\s*VITE_API_URL\\s*=\\s*(?:\"([^\"]*)\"|(.*?))\\s*(?:#.*)?$/.exec(line);\n const value = match?.[1] ?? match?.[2];\n if (value) return value;\n }\n }\n return undefined;\n}\n\nfunction storefrontSubdomainFromTarget(target: string): string | undefined {\n try {\n const hostname = new URL(target).hostname;\n const match = /^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\\.fluid\\.app$/.exec(\n hostname,\n );\n const subdomain = match?.[1];\n if (\n !subdomain ||\n [\"admin\", \"api\", \"app\", \"docs\", \"portal\", \"upload\"].includes(subdomain)\n ) {\n return undefined;\n }\n return subdomain;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve VITE_API_URL the way vite itself would — `loadEnv` honors\n * .env.[mode], .env.[mode].local, envDir, and variable expansion, which the\n * fallback parser does not (a mode-specific staging API must not silently\n * fall back to production).\n */\nasync function resolveViteApiUrl(\n root: string,\n mode: string,\n envDir: string | undefined,\n): Promise<string | undefined> {\n try {\n const { loadEnv } = await import(\"vite\");\n const env = loadEnv(mode, envDir ?? root, [\"VITE_\", \"NEXT_PUBLIC_\"]);\n return (\n env.VITE_API_URL ||\n process.env.VITE_API_URL ||\n // Portals migrated from Next.js conventions configure the API host as\n // NEXT_PUBLIC_API_BASE_URL — honored by `fluid portal dev` since it\n // was introduced, so it stays a recognized source here.\n env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n undefined\n );\n } catch {\n return readViteApiUrlFallback(root);\n }\n}\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 /** Bumped on every watched file change. A build carries the value it\n * started under; if it no longer matches when the build resolves, the\n * build read pre-edit state and must not be cached. */\n let manifestGeneration = 0;\n const resolveTenantTarget =\n options?.resolveTenantApiTarget ?? resolvePortalTenantApiTarget;\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 async config(config: any, env: any) {\n if (env?.command !== \"serve\" || options?.sameOriginDev === false) {\n return {};\n }\n\n const root: string = config.root ?? process.cwd();\n const appDefinesProxy = config.server?.proxy?.[\"/api\"] !== undefined;\n const configuredTarget =\n options?.apiProxyTarget || appDefinesProxy\n ? undefined\n : await resolveViteApiUrl(\n root,\n env?.mode ?? \"development\",\n config.envDir,\n );\n const tenantTarget =\n options?.apiProxyTarget || appDefinesProxy || configuredTarget\n ? undefined\n : await (() => {\n const existing = tenantTargetPromises.get(resolveTenantTarget);\n if (existing) return existing;\n\n const pending = resolveTenantTarget();\n tenantTargetPromises.set(resolveTenantTarget, pending);\n return pending;\n })();\n const target =\n options?.apiProxyTarget ??\n configuredTarget ??\n tenantTarget ??\n DEFAULT_API_URL;\n const storefrontSubdomain = storefrontSubdomainFromTarget(target);\n\n if (tenantTarget) {\n console.log(`[fluid-portal-dev] Tenant API proxy: ${tenantTarget}`);\n console.log(\n \"[fluid-portal-dev] Member data still requires a portal handoff session; localhost requests may return 401.\",\n );\n } else if (\n !options?.apiProxyTarget &&\n !appDefinesProxy &&\n !configuredTarget\n ) {\n console.warn(\n `[fluid-portal-dev] Could not resolve the company tenant API; falling back to ${DEFAULT_API_URL}. ` +\n \"Built-in member screens may not load. Set VITE_API_URL explicitly or sign in with the project's Fluid profile.\",\n );\n } else if (storefrontSubdomain) {\n console.warn(\n `[fluid-portal-dev] ${target} is the storefront host, not the Portal tenant BFF. ` +\n `Built-in member APIs live at https://${storefrontSubdomain}.portal.fluid.app; remove the override to let the CLI resolve it.`,\n );\n }\n\n // Vite merges a plugin's returned config OVER the user config, so an\n // app's own explicit define / /api proxy is only preserved by checking\n // for it and yielding here. BOTH API-host env vars must be blanked:\n // dev.ts's envPrefix exposes NEXT_PUBLIC_API_BASE_URL to the SPA too,\n // so leaving it defined would recreate the exact cross-origin bypass\n // this hook exists to prevent, just for Next.js-migrated portals.\n const defines: Record<string, string> = {};\n for (const key of [\n \"import.meta.env.VITE_API_URL\",\n \"import.meta.env.NEXT_PUBLIC_API_BASE_URL\",\n ]) {\n if (config.define?.[key] === undefined) {\n defines[key] = JSON.stringify(\"\");\n }\n }\n const proxy = !appDefinesProxy\n ? { \"/api\": { target, changeOrigin: true } }\n : {};\n\n return {\n define: defines,\n server: { proxy },\n };\n },\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 manifestGeneration += 1;\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 // Every MANIFEST_PATHS route returns the SAME `{ manifest }` payload:\n // the portal-tenant BFF route the current SDK runtime fetches\n // (useFluidApp → app_manifest_show) and the legacy FluidOS route, so\n // the local portal/ draft feeds the running app regardless of which\n // runtime generation it uses (the SDK adapter reads\n // `response.manifest`, matching buildManifestFromLocalFiles' output).\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 || !MANIFEST_PATHS.some((path) => url.includes(path))) {\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 let manifestJson = cachedManifest;\n if (manifestJson == null) {\n if (!buildInFlight) {\n // Stamp the build with the generation it was started\n // under. onFileChange bumps that counter, so a build\n // whose inputs changed underneath it can be recognized\n // when it finally resolves.\n const generation = manifestGeneration;\n buildInFlight = buildManifestFromLocalFiles(portalDir)\n .then((m) => {\n const json = JSON.stringify(m);\n // Only a build still current may populate the\n // cache. Caching a superseded one pinned the\n // pre-edit snapshot permanently: a non-null cache\n // means no later request ever rebuilds, so the\n // edit stayed invisible until some unrelated file\n // happened to change outside a build window.\n if (generation === manifestGeneration) {\n cachedManifest = json;\n buildInFlight = null;\n }\n return json;\n })\n .catch((err) => {\n // Same guard: a superseded build must not clear a\n // newer one's handle.\n if (generation === manifestGeneration) {\n buildInFlight = null; // allow retry on next request\n }\n throw err;\n });\n }\n // This response serves what was built for it, which is\n // correct even when superseded — the request predates\n // the edit. The point is that the NEXT one rebuilds.\n manifestJson = 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(manifestJson);\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
+ {"version":3,"file":"portal-dev-plugin-BbXPp3ts.mjs","names":[],"sources":["../src/vite-plugin/portal-dev-plugin.ts"],"sourcesContent":["/**\n * Vite plugin for local portal content development.\n *\n * Intercepts the manifest API request and serves content assembled from the\n * local `portal/` directory. Watches portal files for changes and triggers a\n * full page reload.\n *\n * The manifest is intercepted on BOTH paths the SDK has used across versions\n * (`/api/app/manifest` since portal-sdk 0.1.4xx, `/api/fluid_os/definitions/\n * active` before that) so a newer SDK against an older-generation app — or\n * vice versa — still gets the local draft.\n */\n\nimport { join, resolve, sep } from \"node:path\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { getAuthToken, getFluidApiBase } from \"@fluid-app/fluid-cli\";\nimport { buildManifestFromLocalFiles } from \"./build-manifest.js\";\n\n/** Options for the portal dev plugin. */\nexport interface PortalDevPluginOptions {\n portalDir?: string;\n /**\n * When true (default), dev-server requests are forced same-origin: the\n * plugin blanks `import.meta.env.VITE_API_URL` in serve mode and proxies\n * `/api` to the configured Fluid host instead. Without this, a `.env`\n * pointing VITE_API_URL at a remote API makes the SPA fetch its manifest\n * cross-origin — silently bypassing this plugin and rendering the remote\n * app instead of the local `portal/` draft. Set false to opt out and let\n * the SPA call the remote API directly.\n */\n sameOriginDev?: boolean;\n /**\n * Override the `/api` dev-proxy target. Defaults to VITE_API_URL from the\n * process env / .env files, then the authenticated company's\n * `<subdomain>.portal.fluid.app` tenant BFF, then https://api.fluid.app.\n */\n apiProxyTarget?: string;\n /**\n * Override tenant-target discovery. Primarily useful to keep embedded plugin\n * integrations and tests deterministic.\n */\n resolveTenantApiTarget?: () => Promise<string | undefined>;\n}\n\nconst DEBOUNCE_MS = 100;\n\n/** Manifest endpoints across SDK generations — all serve the local draft. */\nconst MANIFEST_PATHS = [\n \"/api/app/manifest\",\n \"/api/fluid_os/definitions/active\",\n];\n\nconst DEFAULT_API_URL = \"https://api.fluid.app\";\nconst TENANT_LOOKUP_TIMEOUT_MS = 3_000;\nconst tenantTargetPromises = new WeakMap<\n () => Promise<string | undefined>,\n Promise<string | undefined>\n>();\n\ninterface CompanyMeResponse {\n data?: {\n company?: {\n subdomain?: unknown;\n };\n };\n}\n\nexport interface TenantApiTargetDependencies {\n token?: string | null;\n apiBase?: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n}\n\n/**\n * Resolve the portal tenant BFF for the CLI's project-aware Fluid profile.\n *\n * Portal member APIs do not live on api.fluid.app or `<tenant>.fluid.app`.\n * Their OpenAPI server is `<tenant>.portal.fluid.app`, and using either of\n * the former hosts produces a wall of unrelated 404s in otherwise-valid\n * local portals.\n */\nexport async function resolvePortalTenantApiTarget(\n dependencies: TenantApiTargetDependencies = {},\n): Promise<string | undefined> {\n const token =\n dependencies.token !== undefined ? dependencies.token : getAuthToken();\n if (!token) return undefined;\n\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n dependencies.timeoutMs ?? TENANT_LOOKUP_TIMEOUT_MS,\n );\n\n try {\n const response = await (dependencies.fetchImpl ?? fetch)(\n `${(dependencies.apiBase ?? getFluidApiBase()).replace(/\\/+$/, \"\")}/api/company/v1/companies/me`,\n {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n },\n );\n if (!response.ok) return undefined;\n\n const body = (await response.json()) as CompanyMeResponse;\n const subdomain = body.data?.company?.subdomain;\n if (\n typeof subdomain !== \"string\" ||\n !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(subdomain)\n ) {\n return undefined;\n }\n\n return `https://${subdomain}.portal.fluid.app`;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Fallback .env reader for VITE_API_URL, used only when vite's `loadEnv`\n * can't be imported (it's a declared dependency, so this is defensive — but\n * a broken install must degrade to \"wrong host\" rather than \"dev server\n * won't boot\"). Mirrors the basic lookup order: live env > .env.local >\n * .env. Unlike loadEnv it ignores .env.[mode] files and variable expansion.\n */\nfunction readViteApiUrlFallback(root: string): string | undefined {\n if (process.env.VITE_API_URL) return process.env.VITE_API_URL;\n if (process.env.NEXT_PUBLIC_API_BASE_URL) {\n return process.env.NEXT_PUBLIC_API_BASE_URL;\n }\n for (const file of [\".env.local\", \".env\"]) {\n const path = join(root, file);\n if (!existsSync(path)) continue;\n for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n // Quoted values stop at the closing `\"`; unquoted values stop before\n // an inline ` # comment` tail (dotenv semantics — a trailing comment\n // must not leak into the proxy target URL).\n const match =\n /^\\s*VITE_API_URL\\s*=\\s*(?:\"([^\"]*)\"|(.*?))\\s*(?:#.*)?$/.exec(line);\n const value = match?.[1] ?? match?.[2];\n if (value) return value;\n }\n }\n return undefined;\n}\n\nfunction storefrontSubdomainFromTarget(target: string): string | undefined {\n try {\n const hostname = new URL(target).hostname;\n const match = /^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\\.fluid\\.app$/.exec(\n hostname,\n );\n const subdomain = match?.[1];\n if (\n !subdomain ||\n [\"admin\", \"api\", \"app\", \"docs\", \"portal\", \"upload\"].includes(subdomain)\n ) {\n return undefined;\n }\n return subdomain;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve VITE_API_URL the way vite itself would — `loadEnv` honors\n * .env.[mode], .env.[mode].local, envDir, and variable expansion, which the\n * fallback parser does not (a mode-specific staging API must not silently\n * fall back to production).\n */\nasync function resolveViteApiUrl(\n root: string,\n mode: string,\n envDir: string | undefined,\n): Promise<string | undefined> {\n try {\n const { loadEnv } = await import(\"vite\");\n const env = loadEnv(mode, envDir ?? root, [\"VITE_\", \"NEXT_PUBLIC_\"]);\n return (\n env.VITE_API_URL ||\n process.env.VITE_API_URL ||\n // Portals migrated from Next.js conventions configure the API host as\n // NEXT_PUBLIC_API_BASE_URL — honored by `fluid portal dev` since it\n // was introduced, so it stays a recognized source here.\n env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n undefined\n );\n } catch {\n return readViteApiUrlFallback(root);\n }\n}\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 /** Bumped on every watched file change. A build carries the value it\n * started under; if it no longer matches when the build resolves, the\n * build read pre-edit state and must not be cached. */\n let manifestGeneration = 0;\n const resolveTenantTarget =\n options?.resolveTenantApiTarget ?? resolvePortalTenantApiTarget;\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 async config(config: any, env: any) {\n if (env?.command !== \"serve\" || options?.sameOriginDev === false) {\n return {};\n }\n\n const root: string = config.root ?? process.cwd();\n const appDefinesProxy = config.server?.proxy?.[\"/api\"] !== undefined;\n const configuredTarget =\n options?.apiProxyTarget || appDefinesProxy\n ? undefined\n : await resolveViteApiUrl(\n root,\n env?.mode ?? \"development\",\n config.envDir,\n );\n const tenantTarget =\n options?.apiProxyTarget || appDefinesProxy || configuredTarget\n ? undefined\n : await (() => {\n const existing = tenantTargetPromises.get(resolveTenantTarget);\n if (existing) return existing;\n\n const pending = resolveTenantTarget();\n tenantTargetPromises.set(resolveTenantTarget, pending);\n return pending;\n })();\n const target =\n options?.apiProxyTarget ??\n configuredTarget ??\n tenantTarget ??\n DEFAULT_API_URL;\n const storefrontSubdomain = storefrontSubdomainFromTarget(target);\n\n if (tenantTarget) {\n console.log(`[fluid-portal-dev] Tenant API proxy: ${tenantTarget}`);\n console.log(\n \"[fluid-portal-dev] Member data still requires a portal handoff session; localhost requests may return 401.\",\n );\n } else if (\n !options?.apiProxyTarget &&\n !appDefinesProxy &&\n !configuredTarget\n ) {\n console.warn(\n `[fluid-portal-dev] Could not resolve the company tenant API; falling back to ${DEFAULT_API_URL}. ` +\n \"Built-in member screens may not load. Set VITE_API_URL explicitly or sign in with the project's Fluid profile.\",\n );\n } else if (storefrontSubdomain) {\n console.warn(\n `[fluid-portal-dev] ${target} is the storefront host, not the Portal tenant BFF. ` +\n `Built-in member APIs live at https://${storefrontSubdomain}.portal.fluid.app; remove the override to let the CLI resolve it.`,\n );\n }\n\n // Vite merges a plugin's returned config OVER the user config, so an\n // app's own explicit define / /api proxy is only preserved by checking\n // for it and yielding here. BOTH API-host env vars must be blanked:\n // dev.ts's envPrefix exposes NEXT_PUBLIC_API_BASE_URL to the SPA too,\n // so leaving it defined would recreate the exact cross-origin bypass\n // this hook exists to prevent, just for Next.js-migrated portals.\n const defines: Record<string, string> = {};\n for (const key of [\n \"import.meta.env.VITE_API_URL\",\n \"import.meta.env.NEXT_PUBLIC_API_BASE_URL\",\n ]) {\n if (config.define?.[key] === undefined) {\n defines[key] = JSON.stringify(\"\");\n }\n }\n const proxy = !appDefinesProxy\n ? { \"/api\": { target, changeOrigin: true } }\n : {};\n\n return {\n define: defines,\n server: { proxy },\n };\n },\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 manifestGeneration += 1;\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 // Every MANIFEST_PATHS route returns the SAME `{ manifest }` payload:\n // the portal-tenant BFF route the current SDK runtime fetches\n // (useFluidApp → app_manifest_show) and the legacy FluidOS route, so\n // the local portal/ draft feeds the running app regardless of which\n // runtime generation it uses (the SDK adapter reads\n // `response.manifest`, matching buildManifestFromLocalFiles' output).\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 || !MANIFEST_PATHS.some((path) => url.includes(path))) {\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 let manifestJson = cachedManifest;\n if (manifestJson == null) {\n if (!buildInFlight) {\n // Stamp the build with the generation it was started\n // under. onFileChange bumps that counter, so a build\n // whose inputs changed underneath it can be recognized\n // when it finally resolves.\n const generation = manifestGeneration;\n buildInFlight = buildManifestFromLocalFiles(portalDir)\n .then((m) => {\n const json = JSON.stringify(m);\n // Only a build still current may populate the\n // cache. Caching a superseded one pinned the\n // pre-edit snapshot permanently: a non-null cache\n // means no later request ever rebuilds, so the\n // edit stayed invisible until some unrelated file\n // happened to change outside a build window.\n if (generation === manifestGeneration) {\n cachedManifest = json;\n buildInFlight = null;\n }\n return json;\n })\n .catch((err) => {\n // Same guard: a superseded build must not clear a\n // newer one's handle.\n if (generation === manifestGeneration) {\n buildInFlight = null; // allow retry on next request\n }\n throw err;\n });\n }\n // This response serves what was built for it, which is\n // correct even when superseded — the request predates\n // the edit. The point is that the NEXT one rebuilds.\n manifestJson = 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(manifestJson);\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,5 +1,5 @@
1
- import { N as __exportAll } from "./pull-BZqZAiSF.mjs";
2
- import { c as fluidWidgetDevPlugin, l as handleOptions, u as setCorsHeaders } from "./src-CpaIqsaL.mjs";
1
+ import { F as __exportAll } from "./pull-C26wQlO_.mjs";
2
+ import { c as fluidWidgetDevPlugin, l as handleOptions, u as setCorsHeaders } from "./src-2znVq55R.mjs";
3
3
  import path from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  //#region src/vite-plugin/portal-widget-dev-plugin.ts
@@ -122,8 +122,10 @@ function virtualWorkerEntryPlugin() {
122
122
  },
123
123
  load(id) {
124
124
  if (id !== VIRTUAL_ENTRY_PATH) return null;
125
- return `import { startWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
126
- import * as source from "${WIDGET_CONFIG_MODULE_PATH}";
125
+ return `import { prepareRemoteDomWidgetWorker, startWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
126
+
127
+ prepareRemoteDomWidgetWorker();
128
+ const source = await import("${WIDGET_CONFIG_MODULE_PATH}");
127
129
 
128
130
  const packages = source.widgetPackages;
129
131
  const widgetPackage =
@@ -141,4 +143,4 @@ if (widgetPackage) {
141
143
  //#endregion
142
144
  export { portal_widget_dev_plugin_exports as n, portalWidgetDevPlugin as t };
143
145
 
144
- //# sourceMappingURL=portal-widget-dev-plugin-DUPIALuT.mjs.map
146
+ //# sourceMappingURL=portal-widget-dev-plugin-CwD90Z8O.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"portal-widget-dev-plugin-DUPIALuT.mjs","names":[],"sources":["../src/vite-plugin/portal-widget-dev-plugin.ts"],"sourcesContent":["/**\n * Dev routes that let a portal preview render the project's own widgets before\n * they are published.\n *\n * A portal keeps company-owned widget sources in `src/widgets.config.ts` and\n * publishes them with `fluid portal deploy`. Until that deploy runs, the widget\n * exists only on disk — the catalog has never heard of it, so a preview could\n * not show it and the only way to see your work was to publish it. Publishing\n * is not a safe iteration loop: a deploy is live immediately, there is no\n * rollback, and a duplicate `packageId@version` is ignored so every attempt\n * needs a version bump.\n *\n * These routes close that gap by serving the same two endpoints a standalone\n * widget project serves — `/__widget-packages__` (a canonical package\n * descriptor) and `/__runtime-entry__` (the worker bootstrap). A host that can\n * already render published packages needs no new code to render a local one:\n * the descriptor shape is identical, so it merges straight into the catalog\n * array.\n *\n * The route implementations are reused wholesale from\n * `@fluid-app/fluid-cli-widget-tooling`. Its bootstrap script in particular is\n * load-bearing — it exists to fix a message-buffer race that silently strands\n * worker mounts — and must not be reimplemented here.\n *\n * Imported from widget-tooling rather than from the widget CLI deliberately:\n * this package lists widget-tooling in its tsdown `alwaysBundle`, so the routes\n * compile into the portal dist. An import across published CLIs stays external\n * and resolves to un-built TypeScript at runtime.\n *\n * Two things differ from a standalone project, and both are passed as options:\n * the package source lives at `src/widgets.config.ts` rather than\n * `fluid.widget.config.ts`, and it is owned by `company` rather than `droplet`.\n * A portal also has no `startWidgetPackage()` module of its own, so this plugin\n * generates one.\n */\n\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n fluidWidgetDevPlugin,\n handleOptions,\n setCorsHeaders,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport type { Plugin } from \"vite\";\n\n/** Package source, relative to the Vite root. */\nconst WIDGET_CONFIG_MODULE_PATH = \"/src/widgets.config.ts\";\n\n/**\n * Generated worker entry. Requestable (no `\\0` prefix) because the bootstrap\n * script imports it by URL from the dev server rather than through Vite's\n * module graph.\n */\nconst VIRTUAL_ENTRY_PATH = \"/@fluid-portal-widget-entry.js\";\n\nconst WIDGET_PACKAGES_ROUTES = new Set([\n \"/__widget-packages__\",\n \"/__widget-packages__/\",\n]);\nconst RUNTIME_ENTRY_ROUTES = new Set([\n \"/__runtime-entry__\",\n \"/__runtime-entry__/\",\n]);\n\nexport function portalWidgetDevPlugin(): Plugin[] {\n return [\n missingConfigGuardPlugin(),\n virtualWorkerEntryPlugin(),\n fluidWidgetDevPlugin({\n entryModulePath: VIRTUAL_ENTRY_PATH,\n configModulePath: WIDGET_CONFIG_MODULE_PATH,\n owner: \"company\",\n // A portal with no custom widget yet is the normal case, not a fault.\n allowNoPackages: true,\n }),\n ];\n}\n\n/**\n * Short-circuits both routes when the project has no `src/widgets.config.ts`.\n *\n * Most portals never gain one. Without this the delegated routes would try to\n * load a module that does not exist and log a resolution failure on every poll\n * — the preview polls continuously — so the expected case would be the noisiest\n * thing in the log. Registered before the delegated plugin so its middleware\n * runs first.\n */\nfunction missingConfigGuardPlugin(): Plugin {\n let configFilePath = \"\";\n\n return {\n name: \"fluid-portal-widget-dev-missing-config-guard\",\n apply: \"serve\",\n configResolved(config) {\n configFilePath = path.join(\n config.root,\n WIDGET_CONFIG_MODULE_PATH.replace(/^\\//, \"\"),\n );\n },\n configureServer(server) {\n server.middlewares.use((request, response, next): void => {\n const pathname = (request.url ?? \"\").split(\"?\")[0] ?? \"\";\n const isWidgetRoute =\n WIDGET_PACKAGES_ROUTES.has(pathname) ||\n RUNTIME_ENTRY_ROUTES.has(pathname);\n if (!isWidgetRoute) {\n next();\n return;\n }\n // Checked per request, not at startup: `fluid portal widget create`\n // can add the file while the dev server is already running.\n if (existsSync(configFilePath)) {\n next();\n return;\n }\n\n // Answer a preflight exactly as the delegated routes do. Two\n // implementations of the same route disagreeing is bad enough; the\n // guard is the one that runs in the common case, since most portals\n // never gain a widget config, so the divergence would have been the\n // DEFAULT path rather than the edge.\n if (handleOptions(request, response)) return;\n setCorsHeaders(response);\n if (WIDGET_PACKAGES_ROUTES.has(pathname)) {\n response.setHeader(\"Content-Type\", \"application/json\");\n response.statusCode = 200;\n response.end(\"[]\");\n return;\n }\n response.setHeader(\"Content-Type\", \"text/javascript\");\n response.statusCode = 200;\n response.end(\"// No portal widget package in this project.\\n\");\n });\n },\n };\n}\n\n/**\n * Serves the worker entry a portal project does not have.\n *\n * A standalone widget project ships `src/index.ts` calling\n * `startWidgetPackage()`. A portal's `src/widgets.config.ts` only declares the\n * package, so this module supplies the missing call. Export precedence matches\n * the shared descriptor builder's, so the worker starts the same package the\n * descriptor advertises.\n */\nfunction virtualWorkerEntryPlugin(): Plugin {\n return {\n name: \"fluid-portal-widget-dev-entry\",\n apply: \"serve\",\n resolveId(id) {\n return id === VIRTUAL_ENTRY_PATH ? id : null;\n },\n load(id) {\n if (id !== VIRTUAL_ENTRY_PATH) return null;\n return `import { startWidgetPackage } from \"@fluid-app/portal-sdk/widgets/worker\";\nimport * as source from \"${WIDGET_CONFIG_MODULE_PATH}\";\n\nconst packages = source.widgetPackages;\nconst widgetPackage =\n (Array.isArray(packages) && packages.length > 0 ? packages[0] : undefined) ??\n source.widgetPackage ??\n source.default;\n\nif (widgetPackage) {\n startWidgetPackage(widgetPackage);\n}\n`;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAM,4BAA4B;;;;;;AAOlC,MAAM,qBAAqB;AAE3B,MAAM,yBAAyB,IAAI,IAAI,CACrC,wBACA,wBACD,CAAC;AACF,MAAM,uBAAuB,IAAI,IAAI,CACnC,sBACA,sBACD,CAAC;AAEF,SAAgB,wBAAkC;AAChD,QAAO;EACL,0BAA0B;EAC1B,0BAA0B;EAC1B,qBAAqB;GACnB,iBAAiB;GACjB,kBAAkB;GAClB,OAAO;GAEP,iBAAiB;GAClB,CAAC;EACH;;;;;;;;;;;AAYH,SAAS,2BAAmC;CAC1C,IAAI,iBAAiB;AAErB,QAAO;EACL,MAAM;EACN,OAAO;EACP,eAAe,QAAQ;AACrB,oBAAiB,KAAK,KACpB,OAAO,MACP,0BAA0B,QAAQ,OAAO,GAAG,CAC7C;;EAEH,gBAAgB,QAAQ;AACtB,UAAO,YAAY,KAAK,SAAS,UAAU,SAAe;IACxD,MAAM,YAAY,QAAQ,OAAO,IAAI,MAAM,IAAI,CAAC,MAAM;AAItD,QAAI,EAFF,uBAAuB,IAAI,SAAS,IACpC,qBAAqB,IAAI,SAAS,GAChB;AAClB,WAAM;AACN;;AAIF,QAAI,WAAW,eAAe,EAAE;AAC9B,WAAM;AACN;;AAQF,QAAI,cAAc,SAAS,SAAS,CAAE;AACtC,mBAAe,SAAS;AACxB,QAAI,uBAAuB,IAAI,SAAS,EAAE;AACxC,cAAS,UAAU,gBAAgB,mBAAmB;AACtD,cAAS,aAAa;AACtB,cAAS,IAAI,KAAK;AAClB;;AAEF,aAAS,UAAU,gBAAgB,kBAAkB;AACrD,aAAS,aAAa;AACtB,aAAS,IAAI,iDAAiD;KAC9D;;EAEL;;;;;;;;;;;AAYH,SAAS,2BAAmC;AAC1C,QAAO;EACL,MAAM;EACN,OAAO;EACP,UAAU,IAAI;AACZ,UAAO,OAAO,qBAAqB,KAAK;;EAE1C,KAAK,IAAI;AACP,OAAI,OAAO,mBAAoB,QAAO;AACtC,UAAO;2BACc,0BAA0B;;;;;;;;;;;;;EAalD"}
1
+ {"version":3,"file":"portal-widget-dev-plugin-CwD90Z8O.mjs","names":[],"sources":["../src/vite-plugin/portal-widget-dev-plugin.ts"],"sourcesContent":["/**\n * Dev routes that let a portal preview render the project's own widgets before\n * they are published.\n *\n * A portal keeps company-owned widget sources in `src/widgets.config.ts` and\n * publishes them with `fluid portal deploy`. Until that deploy runs, the widget\n * exists only on disk — the catalog has never heard of it, so a preview could\n * not show it and the only way to see your work was to publish it. Publishing\n * is not a safe iteration loop: a deploy is live immediately, there is no\n * rollback, and a duplicate `packageId@version` is ignored so every attempt\n * needs a version bump.\n *\n * These routes close that gap by serving the same two endpoints a standalone\n * widget project serves — `/__widget-packages__` (a canonical package\n * descriptor) and `/__runtime-entry__` (the worker bootstrap). A host that can\n * already render published packages needs no new code to render a local one:\n * the descriptor shape is identical, so it merges straight into the catalog\n * array.\n *\n * The route implementations are reused wholesale from\n * `@fluid-app/fluid-cli-widget-tooling`. Its bootstrap script in particular is\n * load-bearing — it exists to fix a message-buffer race that silently strands\n * worker mounts — and must not be reimplemented here.\n *\n * Imported from widget-tooling rather than from the widget CLI deliberately:\n * this package lists widget-tooling in its tsdown `alwaysBundle`, so the routes\n * compile into the portal dist. An import across published CLIs stays external\n * and resolves to un-built TypeScript at runtime.\n *\n * Two things differ from a standalone project, and both are passed as options:\n * the package source lives at `src/widgets.config.ts` rather than\n * `fluid.widget.config.ts`, and it is owned by `company` rather than `droplet`.\n * A portal also has no `startWidgetPackage()` module of its own, so this plugin\n * generates one.\n */\n\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n fluidWidgetDevPlugin,\n handleOptions,\n setCorsHeaders,\n} from \"@fluid-app/fluid-cli-widget-tooling\";\nimport type { Plugin } from \"vite\";\n\n/** Package source, relative to the Vite root. */\nconst WIDGET_CONFIG_MODULE_PATH = \"/src/widgets.config.ts\";\n\n/**\n * Generated worker entry. Requestable (no `\\0` prefix) because the bootstrap\n * script imports it by URL from the dev server rather than through Vite's\n * module graph.\n */\nconst VIRTUAL_ENTRY_PATH = \"/@fluid-portal-widget-entry.js\";\n\nconst WIDGET_PACKAGES_ROUTES = new Set([\n \"/__widget-packages__\",\n \"/__widget-packages__/\",\n]);\nconst RUNTIME_ENTRY_ROUTES = new Set([\n \"/__runtime-entry__\",\n \"/__runtime-entry__/\",\n]);\n\nexport function portalWidgetDevPlugin(): Plugin[] {\n return [\n missingConfigGuardPlugin(),\n virtualWorkerEntryPlugin(),\n fluidWidgetDevPlugin({\n entryModulePath: VIRTUAL_ENTRY_PATH,\n configModulePath: WIDGET_CONFIG_MODULE_PATH,\n owner: \"company\",\n // A portal with no custom widget yet is the normal case, not a fault.\n allowNoPackages: true,\n }),\n ];\n}\n\n/**\n * Short-circuits both routes when the project has no `src/widgets.config.ts`.\n *\n * Most portals never gain one. Without this the delegated routes would try to\n * load a module that does not exist and log a resolution failure on every poll\n * — the preview polls continuously — so the expected case would be the noisiest\n * thing in the log. Registered before the delegated plugin so its middleware\n * runs first.\n */\nfunction missingConfigGuardPlugin(): Plugin {\n let configFilePath = \"\";\n\n return {\n name: \"fluid-portal-widget-dev-missing-config-guard\",\n apply: \"serve\",\n configResolved(config) {\n configFilePath = path.join(\n config.root,\n WIDGET_CONFIG_MODULE_PATH.replace(/^\\//, \"\"),\n );\n },\n configureServer(server) {\n server.middlewares.use((request, response, next): void => {\n const pathname = (request.url ?? \"\").split(\"?\")[0] ?? \"\";\n const isWidgetRoute =\n WIDGET_PACKAGES_ROUTES.has(pathname) ||\n RUNTIME_ENTRY_ROUTES.has(pathname);\n if (!isWidgetRoute) {\n next();\n return;\n }\n // Checked per request, not at startup: `fluid portal widget create`\n // can add the file while the dev server is already running.\n if (existsSync(configFilePath)) {\n next();\n return;\n }\n\n // Answer a preflight exactly as the delegated routes do. Two\n // implementations of the same route disagreeing is bad enough; the\n // guard is the one that runs in the common case, since most portals\n // never gain a widget config, so the divergence would have been the\n // DEFAULT path rather than the edge.\n if (handleOptions(request, response)) return;\n setCorsHeaders(response);\n if (WIDGET_PACKAGES_ROUTES.has(pathname)) {\n response.setHeader(\"Content-Type\", \"application/json\");\n response.statusCode = 200;\n response.end(\"[]\");\n return;\n }\n response.setHeader(\"Content-Type\", \"text/javascript\");\n response.statusCode = 200;\n response.end(\"// No portal widget package in this project.\\n\");\n });\n },\n };\n}\n\n/**\n * Serves the worker entry a portal project does not have.\n *\n * A standalone widget project ships `src/index.ts` calling\n * `startWidgetPackage()`. A portal's `src/widgets.config.ts` only declares the\n * package, so this module supplies the missing call. Export precedence matches\n * the shared descriptor builder's, so the worker starts the same package the\n * descriptor advertises.\n */\nfunction virtualWorkerEntryPlugin(): Plugin {\n return {\n name: \"fluid-portal-widget-dev-entry\",\n apply: \"serve\",\n resolveId(id) {\n return id === VIRTUAL_ENTRY_PATH ? id : null;\n },\n load(id) {\n if (id !== VIRTUAL_ENTRY_PATH) return null;\n return `import { prepareRemoteDomWidgetWorker, startWidgetPackage } from \"@fluid-app/portal-sdk/widgets/worker\";\n\nprepareRemoteDomWidgetWorker();\nconst source = await import(\"${WIDGET_CONFIG_MODULE_PATH}\");\n\nconst packages = source.widgetPackages;\nconst widgetPackage =\n (Array.isArray(packages) && packages.length > 0 ? packages[0] : undefined) ??\n source.widgetPackage ??\n source.default;\n\nif (widgetPackage) {\n startWidgetPackage(widgetPackage);\n}\n`;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAM,4BAA4B;;;;;;AAOlC,MAAM,qBAAqB;AAE3B,MAAM,yBAAyB,IAAI,IAAI,CACrC,wBACA,wBACD,CAAC;AACF,MAAM,uBAAuB,IAAI,IAAI,CACnC,sBACA,sBACD,CAAC;AAEF,SAAgB,wBAAkC;AAChD,QAAO;EACL,0BAA0B;EAC1B,0BAA0B;EAC1B,qBAAqB;GACnB,iBAAiB;GACjB,kBAAkB;GAClB,OAAO;GAEP,iBAAiB;GAClB,CAAC;EACH;;;;;;;;;;;AAYH,SAAS,2BAAmC;CAC1C,IAAI,iBAAiB;AAErB,QAAO;EACL,MAAM;EACN,OAAO;EACP,eAAe,QAAQ;AACrB,oBAAiB,KAAK,KACpB,OAAO,MACP,0BAA0B,QAAQ,OAAO,GAAG,CAC7C;;EAEH,gBAAgB,QAAQ;AACtB,UAAO,YAAY,KAAK,SAAS,UAAU,SAAe;IACxD,MAAM,YAAY,QAAQ,OAAO,IAAI,MAAM,IAAI,CAAC,MAAM;AAItD,QAAI,EAFF,uBAAuB,IAAI,SAAS,IACpC,qBAAqB,IAAI,SAAS,GAChB;AAClB,WAAM;AACN;;AAIF,QAAI,WAAW,eAAe,EAAE;AAC9B,WAAM;AACN;;AAQF,QAAI,cAAc,SAAS,SAAS,CAAE;AACtC,mBAAe,SAAS;AACxB,QAAI,uBAAuB,IAAI,SAAS,EAAE;AACxC,cAAS,UAAU,gBAAgB,mBAAmB;AACtD,cAAS,aAAa;AACtB,cAAS,IAAI,KAAK;AAClB;;AAEF,aAAS,UAAU,gBAAgB,kBAAkB;AACrD,aAAS,aAAa;AACtB,aAAS,IAAI,iDAAiD;KAC9D;;EAEL;;;;;;;;;;;AAYH,SAAS,2BAAmC;AAC1C,QAAO;EACL,MAAM;EACN,OAAO;EACP,UAAU,IAAI;AACZ,UAAO,OAAO,qBAAqB,KAAK;;EAE1C,KAAK,IAAI;AACP,OAAI,OAAO,mBAAoB,QAAO;AACtC,UAAO;;;+BAGkB,0BAA0B;;;;;;;;;;;;;EAatD"}
@@ -1,4 +1,4 @@
1
- import { D as fluidOsV0UpdateFluidOsscreen, E as fluidOsV0UpdateFluidOsprofile, O as fluidOsV0UpdateFluidOstheme, S as fluidOsV0ListFluidOsversions, T as fluidOsV0UpdateFluidOsnavigationItem, _ as fluidOsV0ListFluidOsnavigationItems, a as fluidOsV0CreateFluidOsscreen, b as fluidOsV0ListFluidOsscreens, d as fluidOsV0DeleteFluidOsnavigationItem, f as fluidOsV0DeleteFluidOsprofile, g as fluidOsV0ListFluidOsdefinitions, h as fluidOsV0GetFluidOsscreen, i as fluidOsV0CreateFluidOsprofile, k as fluidOsV0UpdateFluidOsversion, m as fluidOsV0DeleteFluidOstheme, n as fluidOsV0CreateFluidOsnavigation, o as fluidOsV0CreateFluidOstheme, p as fluidOsV0DeleteFluidOsscreen, r as fluidOsV0CreateFluidOsnavigationItem, s as fluidOsV0CreateFluidOsversion, u as fluidOsV0DeleteFluidOsnavigation, v as fluidOsV0ListFluidOsnavigations, w as fluidOsV0UpdateFluidOsnavigation, x as fluidOsV0ListFluidOsthemes, y as fluidOsV0ListFluidOsprofiles } from "./sdk.gen-VRGYiLVJ.mjs";
1
+ import { C as fluidOsV0ListWidgetPackages, D as fluidOsV0UpdateFluidOsscreen, E as fluidOsV0UpdateFluidOsprofile, O as fluidOsV0UpdateFluidOstheme, S as fluidOsV0ListFluidOsversions, T as fluidOsV0UpdateFluidOsnavigationItem, _ as fluidOsV0ListFluidOsnavigationItems, a as fluidOsV0CreateFluidOsscreen, b as fluidOsV0ListFluidOsscreens, d as fluidOsV0DeleteFluidOsnavigationItem, f as fluidOsV0DeleteFluidOsprofile, g as fluidOsV0ListFluidOsdefinitions, h as fluidOsV0GetFluidOsscreen, i as fluidOsV0CreateFluidOsprofile, k as fluidOsV0UpdateFluidOsversion, m as fluidOsV0DeleteFluidOstheme, n as fluidOsV0CreateFluidOsnavigation, o as fluidOsV0CreateFluidOstheme, p as fluidOsV0DeleteFluidOsscreen, r as fluidOsV0CreateFluidOsnavigationItem, s as fluidOsV0CreateFluidOsversion, u as fluidOsV0DeleteFluidOsnavigation, v as fluidOsV0ListFluidOsnavigations, w as fluidOsV0UpdateFluidOsnavigation, x as fluidOsV0ListFluidOsthemes, y as fluidOsV0ListFluidOsprofiles } from "./sdk.gen-VRGYiLVJ.mjs";
2
2
  import "node:module";
3
3
  import { Command } from "commander";
4
4
  import chalk from "chalk";
@@ -841,6 +841,7 @@ function createFluidOsClient(options) {
841
841
  });
842
842
  }
843
843
  const fluidOsApi = {
844
+ listWidgetPackages: (client) => fluidOsV0ListWidgetPackages({ client }),
844
845
  listDefinitions: (client, query) => fluidOsV0ListFluidOsdefinitions({
845
846
  client,
846
847
  query
@@ -1249,19 +1250,57 @@ async function diffAgainstSnapshot(portalDir, snapshot) {
1249
1250
  * and records the definition metadata.
1250
1251
  */
1251
1252
  async function buildSnapshot(portalDir, definitionName, definitionId) {
1252
- const hashEntries = await mapWithLimit(await collectFiles(portalDir), MAX_CONCURRENCY, async (filePath) => {
1253
+ const files = await collectFiles(portalDir);
1254
+ const hashEntries = await mapWithLimit(files, MAX_CONCURRENCY, async (filePath) => {
1253
1255
  return [filePath, await computeFileHash(join(portalDir, filePath))];
1254
1256
  });
1255
1257
  hashEntries.sort(([a], [b]) => a.localeCompare(b));
1256
1258
  const fileHashes = {};
1257
1259
  for (const [path, hash] of hashEntries) fileHashes[path] = hash;
1260
+ const networkWidgetGrants = await collectSnapshotNetworkWidgetGrants(portalDir, files);
1258
1261
  return {
1259
1262
  definition: definitionName,
1260
1263
  definition_id: definitionId,
1261
1264
  pulled_at: (/* @__PURE__ */ new Date()).toISOString(),
1262
- files: fileHashes
1265
+ files: fileHashes,
1266
+ network_widget_grants: networkWidgetGrants
1263
1267
  };
1264
1268
  }
1269
+ async function collectSnapshotNetworkWidgetGrants(portalDir, files) {
1270
+ const entries = await mapWithLimit(files.filter((file) => file.startsWith("screens/") && file.endsWith(".json")), MAX_CONCURRENCY, async (file) => {
1271
+ try {
1272
+ return [file, collectNetworkWidgetGrants(JSON.parse(await readFile(join(portalDir, file), "utf8")))];
1273
+ } catch {
1274
+ return [file, []];
1275
+ }
1276
+ });
1277
+ return Object.fromEntries(entries.filter(([, grants]) => grants.length > 0));
1278
+ }
1279
+ function collectNetworkWidgetGrants(value) {
1280
+ const grants = [];
1281
+ const visit = (candidate) => {
1282
+ if (!candidate || typeof candidate !== "object") return;
1283
+ if (Array.isArray(candidate)) {
1284
+ for (const item of candidate) visit(item);
1285
+ return;
1286
+ }
1287
+ const record = candidate;
1288
+ const networkAccess = asRecord(asRecord(record.capabilityGrants)?.networkAccess);
1289
+ if (typeof record.id === "string" && typeof record.type === "string" && typeof networkAccess?.packageId === "string" && typeof networkAccess.packageVersion === "string" && typeof networkAccess.capabilityVersion === "string") grants.push({
1290
+ widgetId: record.id,
1291
+ widgetType: record.type,
1292
+ packageId: networkAccess.packageId,
1293
+ packageVersion: networkAccess.packageVersion,
1294
+ capabilityVersion: networkAccess.capabilityVersion
1295
+ });
1296
+ for (const child of Object.values(record)) visit(child);
1297
+ };
1298
+ visit(value);
1299
+ return grants;
1300
+ }
1301
+ function asRecord(value) {
1302
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1303
+ }
1265
1304
  //#endregion
1266
1305
  //#region ../../../node_modules/.pnpm/colorjs.io@0.5.2/node_modules/colorjs.io/dist/color.js
1267
1306
  function multiplyMatrices(A, B) {
@@ -6692,7 +6731,7 @@ function transformProfile(profile, navIdToSlug, themeIdToSlug) {
6692
6731
  }
6693
6732
  };
6694
6733
  }
6695
- const PORTAL_SCHEMA_BASE_URL = `https://assets.fluid.app/docs/schema/portal/1.0.0`;
6734
+ const PORTAL_SCHEMA_BASE_URL = `https://assets.fluid.app/docs/schema/portal/1.1.0`;
6696
6735
  const PORTAL_RESOURCE_KINDS = [
6697
6736
  "definition",
6698
6737
  "screen",
@@ -12967,7 +13006,7 @@ var require__2020 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
12967
13006
  const generatedSchemas = {
12968
13007
  "data-source": {
12969
13008
  $schema: "https://json-schema.org/draft/2020-12/schema",
12970
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json",
13009
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json",
12971
13010
  title: "Fluid Portal Data Source",
12972
13011
  type: "object",
12973
13012
  properties: {
@@ -13148,7 +13187,7 @@ const generatedSchemas = {
13148
13187
  },
13149
13188
  "theme-config": {
13150
13189
  $schema: "https://json-schema.org/draft/2020-12/schema",
13151
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/theme-config.schema.json",
13190
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/theme-config.schema.json",
13152
13191
  title: "Fluid Portal Structured Theme Configuration",
13153
13192
  type: "object",
13154
13193
  properties: {
@@ -13720,7 +13759,7 @@ const generatedSchemas = {
13720
13759
  },
13721
13760
  widget: {
13722
13761
  $schema: "https://json-schema.org/draft/2020-12/schema",
13723
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/widget.schema.json",
13762
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/widget.schema.json",
13724
13763
  title: "Fluid Portal Widget Node",
13725
13764
  $ref: "#/$defs/widgetNode",
13726
13765
  $defs: {
@@ -13901,7 +13940,7 @@ const generatedSchemas = {
13901
13940
  },
13902
13941
  additionalProperties: false
13903
13942
  },
13904
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
13943
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
13905
13944
  columnIndex: {
13906
13945
  type: "integer",
13907
13946
  minimum: 0
@@ -14024,7 +14063,7 @@ const generatedSchemas = {
14024
14063
  },
14025
14064
  additionalProperties: false
14026
14065
  },
14027
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14066
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14028
14067
  columnIndex: {
14029
14068
  type: "integer",
14030
14069
  minimum: 0
@@ -14141,7 +14180,7 @@ const generatedSchemas = {
14141
14180
  },
14142
14181
  additionalProperties: false
14143
14182
  },
14144
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14183
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14145
14184
  columnIndex: {
14146
14185
  type: "integer",
14147
14186
  minimum: 0
@@ -14279,7 +14318,7 @@ const generatedSchemas = {
14279
14318
  },
14280
14319
  additionalProperties: false
14281
14320
  },
14282
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14321
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14283
14322
  columnIndex: {
14284
14323
  type: "integer",
14285
14324
  minimum: 0
@@ -14447,7 +14486,7 @@ const generatedSchemas = {
14447
14486
  },
14448
14487
  additionalProperties: false
14449
14488
  },
14450
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14489
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14451
14490
  columnIndex: {
14452
14491
  type: "integer",
14453
14492
  minimum: 0
@@ -14565,7 +14604,7 @@ const generatedSchemas = {
14565
14604
  },
14566
14605
  additionalProperties: false
14567
14606
  },
14568
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14607
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14569
14608
  columnIndex: {
14570
14609
  type: "integer",
14571
14610
  minimum: 0
@@ -14694,7 +14733,7 @@ const generatedSchemas = {
14694
14733
  },
14695
14734
  additionalProperties: false
14696
14735
  },
14697
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14736
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14698
14737
  columnIndex: {
14699
14738
  type: "integer",
14700
14739
  minimum: 0
@@ -14751,7 +14790,7 @@ const generatedSchemas = {
14751
14790
  },
14752
14791
  additionalProperties: false
14753
14792
  },
14754
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14793
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14755
14794
  columnIndex: {
14756
14795
  type: "integer",
14757
14796
  minimum: 0
@@ -14851,7 +14890,7 @@ const generatedSchemas = {
14851
14890
  },
14852
14891
  additionalProperties: false
14853
14892
  },
14854
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14893
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14855
14894
  columnIndex: {
14856
14895
  type: "integer",
14857
14896
  minimum: 0
@@ -14925,7 +14964,7 @@ const generatedSchemas = {
14925
14964
  },
14926
14965
  additionalProperties: false
14927
14966
  },
14928
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
14967
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
14929
14968
  columnIndex: {
14930
14969
  type: "integer",
14931
14970
  minimum: 0
@@ -15017,7 +15056,7 @@ const generatedSchemas = {
15017
15056
  },
15018
15057
  additionalProperties: false
15019
15058
  },
15020
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
15059
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
15021
15060
  columnIndex: {
15022
15061
  type: "integer",
15023
15062
  minimum: 0
@@ -15141,7 +15180,7 @@ const generatedSchemas = {
15141
15180
  },
15142
15181
  additionalProperties: false
15143
15182
  },
15144
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
15183
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
15145
15184
  columnIndex: {
15146
15185
  type: "integer",
15147
15186
  minimum: 0
@@ -15393,7 +15432,7 @@ const generatedSchemas = {
15393
15432
  },
15394
15433
  additionalProperties: false
15395
15434
  },
15396
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
15435
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
15397
15436
  columnIndex: {
15398
15437
  type: "integer",
15399
15438
  minimum: 0
@@ -15464,7 +15503,7 @@ const generatedSchemas = {
15464
15503
  },
15465
15504
  additionalProperties: false
15466
15505
  },
15467
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
15506
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
15468
15507
  columnIndex: {
15469
15508
  type: "integer",
15470
15509
  minimum: 0
@@ -15604,7 +15643,7 @@ const generatedSchemas = {
15604
15643
  },
15605
15644
  additionalProperties: false
15606
15645
  },
15607
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
15646
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
15608
15647
  columnIndex: {
15609
15648
  type: "integer",
15610
15649
  minimum: 0
@@ -15730,7 +15769,7 @@ const generatedSchemas = {
15730
15769
  },
15731
15770
  additionalProperties: false
15732
15771
  },
15733
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
15772
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
15734
15773
  columnIndex: {
15735
15774
  type: "integer",
15736
15775
  minimum: 0
@@ -16146,7 +16185,7 @@ const generatedSchemas = {
16146
16185
  },
16147
16186
  additionalProperties: false
16148
16187
  },
16149
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16188
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16150
16189
  columnIndex: {
16151
16190
  type: "integer",
16152
16191
  minimum: 0
@@ -16262,7 +16301,7 @@ const generatedSchemas = {
16262
16301
  },
16263
16302
  additionalProperties: false
16264
16303
  },
16265
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16304
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16266
16305
  columnIndex: {
16267
16306
  type: "integer",
16268
16307
  minimum: 0
@@ -16384,7 +16423,7 @@ const generatedSchemas = {
16384
16423
  },
16385
16424
  additionalProperties: false
16386
16425
  },
16387
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16426
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16388
16427
  columnIndex: {
16389
16428
  type: "integer",
16390
16429
  minimum: 0
@@ -16447,7 +16486,7 @@ const generatedSchemas = {
16447
16486
  },
16448
16487
  additionalProperties: false
16449
16488
  },
16450
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16489
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16451
16490
  columnIndex: {
16452
16491
  type: "integer",
16453
16492
  minimum: 0
@@ -16547,7 +16586,7 @@ const generatedSchemas = {
16547
16586
  },
16548
16587
  additionalProperties: false
16549
16588
  },
16550
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16589
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16551
16590
  columnIndex: {
16552
16591
  type: "integer",
16553
16592
  minimum: 0
@@ -16576,7 +16615,7 @@ const generatedSchemas = {
16576
16615
  } },
16577
16616
  additionalProperties: false
16578
16617
  },
16579
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16618
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16580
16619
  columnIndex: {
16581
16620
  type: "integer",
16582
16621
  minimum: 0
@@ -16712,7 +16751,7 @@ const generatedSchemas = {
16712
16751
  },
16713
16752
  additionalProperties: false
16714
16753
  },
16715
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16754
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16716
16755
  columnIndex: {
16717
16756
  type: "integer",
16718
16757
  minimum: 0
@@ -16845,7 +16884,7 @@ const generatedSchemas = {
16845
16884
  },
16846
16885
  additionalProperties: false
16847
16886
  },
16848
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
16887
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16849
16888
  columnIndex: {
16850
16889
  type: "integer",
16851
16890
  minimum: 0
@@ -16964,7 +17003,7 @@ const generatedSchemas = {
16964
17003
  },
16965
17004
  additionalProperties: false
16966
17005
  },
16967
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
17006
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
16968
17007
  columnIndex: {
16969
17008
  type: "integer",
16970
17009
  minimum: 0
@@ -17056,7 +17095,7 @@ const generatedSchemas = {
17056
17095
  },
17057
17096
  additionalProperties: false
17058
17097
  },
17059
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
17098
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
17060
17099
  columnIndex: {
17061
17100
  type: "integer",
17062
17101
  minimum: 0
@@ -17084,10 +17123,37 @@ const generatedSchemas = {
17084
17123
  type: "object",
17085
17124
  additionalProperties: { $ref: "#/$defs/jsonValue" }
17086
17125
  },
17087
- dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/data-source.schema.json" },
17126
+ dataSource: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/data-source.schema.json" },
17088
17127
  columnIndex: {
17089
17128
  type: "integer",
17090
17129
  minimum: 0
17130
+ },
17131
+ capabilityGrants: {
17132
+ type: "object",
17133
+ properties: { networkAccess: {
17134
+ type: "object",
17135
+ properties: {
17136
+ packageId: {
17137
+ type: "string",
17138
+ pattern: "\\S"
17139
+ },
17140
+ packageVersion: {
17141
+ type: "string",
17142
+ pattern: "\\S"
17143
+ },
17144
+ capabilityVersion: {
17145
+ type: "string",
17146
+ pattern: "\\S"
17147
+ }
17148
+ },
17149
+ required: [
17150
+ "packageId",
17151
+ "packageVersion",
17152
+ "capabilityVersion"
17153
+ ],
17154
+ additionalProperties: false
17155
+ } },
17156
+ additionalProperties: false
17091
17157
  }
17092
17158
  },
17093
17159
  required: [
@@ -17102,7 +17168,7 @@ const generatedSchemas = {
17102
17168
  },
17103
17169
  definition: {
17104
17170
  $schema: "https://json-schema.org/draft/2020-12/schema",
17105
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/definition.schema.json",
17171
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/definition.schema.json",
17106
17172
  title: "Fluid Portal Definition",
17107
17173
  type: "object",
17108
17174
  properties: {
@@ -17117,7 +17183,7 @@ const generatedSchemas = {
17117
17183
  },
17118
17184
  screen: {
17119
17185
  $schema: "https://json-schema.org/draft/2020-12/schema",
17120
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/screen.schema.json",
17186
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/screen.schema.json",
17121
17187
  title: "Fluid Portal Screen",
17122
17188
  type: "object",
17123
17189
  properties: {
@@ -17129,7 +17195,7 @@ const generatedSchemas = {
17129
17195
  component_tree: {
17130
17196
  type: "array",
17131
17197
  maxItems: 1,
17132
- items: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/widget.schema.json" }
17198
+ items: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/widget.schema.json" }
17133
17199
  }
17134
17200
  },
17135
17201
  required: ["name", "component_tree"],
@@ -17137,7 +17203,7 @@ const generatedSchemas = {
17137
17203
  },
17138
17204
  theme: {
17139
17205
  $schema: "https://json-schema.org/draft/2020-12/schema",
17140
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/theme.schema.json",
17206
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/theme.schema.json",
17141
17207
  title: "Fluid Portal Theme",
17142
17208
  type: "object",
17143
17209
  properties: {
@@ -17146,7 +17212,7 @@ const generatedSchemas = {
17146
17212
  pattern: "^https://assets\\.fluid\\.app/docs/schema/portal/(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?/theme\\.schema\\.json$"
17147
17213
  },
17148
17214
  name: { type: "string" },
17149
- config: { $ref: "https://assets.fluid.app/docs/schema/portal/1.0.0/theme-config.schema.json" },
17215
+ config: { $ref: "https://assets.fluid.app/docs/schema/portal/1.1.0/theme-config.schema.json" },
17150
17216
  active: { type: "boolean" }
17151
17217
  },
17152
17218
  required: [
@@ -17158,7 +17224,7 @@ const generatedSchemas = {
17158
17224
  },
17159
17225
  navigation: {
17160
17226
  $schema: "https://json-schema.org/draft/2020-12/schema",
17161
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/navigation.schema.json",
17227
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/navigation.schema.json",
17162
17228
  title: "Fluid Portal Navigation",
17163
17229
  type: "object",
17164
17230
  properties: {
@@ -17210,7 +17276,7 @@ const generatedSchemas = {
17210
17276
  },
17211
17277
  profile: {
17212
17278
  $schema: "https://json-schema.org/draft/2020-12/schema",
17213
- $id: "https://assets.fluid.app/docs/schema/portal/1.0.0/profile.schema.json",
17279
+ $id: "https://assets.fluid.app/docs/schema/portal/1.1.0/profile.schema.json",
17214
17280
  title: "Fluid Portal Profile",
17215
17281
  type: "object",
17216
17282
  properties: {
@@ -17644,6 +17710,6 @@ const pullCommand = new Command("pull").description("Pull a Fluid OS definition'
17644
17710
  console.log();
17645
17711
  });
17646
17712
  //#endregion
17647
- export { ApiError as A, updateMapping as C, fluidOsApi as D, createFluidOsClient as E, assertStandalonePortalRemoteActionAllowed as M, __exportAll as N, toFluidOsDynamicObject as O, resolveSlugToId as S, atomicWriteFile as T, writeSnapshot as _, buildNavigationIdToSlugMap as a, removeMapping as b, transformNavigation as c, transformScreen as d, transformTheme as f, readSnapshot as g, diffAgainstSnapshot as h, buildIdToSlugMap as i, STANDALONE_PORTAL_MARKER as j, createFluidOsHeyApiClient as k, transformNavigationItems as l, computeFileHash as m, pull_exports as n, buildThemeIdToSlugMap as o, buildSnapshot as p, validatePortalResource as r, deriveScreenSlug as s, pullCommand as t, transformProfile as u, deriveSlug as v, writeMappings as w, resolveIdToSlug as x, readMappings as y };
17713
+ export { toFluidOsDynamicObject as A, resolveIdToSlug as C, atomicWriteFile as D, writeMappings as E, __exportAll as F, ApiError as M, STANDALONE_PORTAL_MARKER as N, createFluidOsClient as O, assertStandalonePortalRemoteActionAllowed as P, removeMapping as S, updateMapping as T, diffAgainstSnapshot as _, buildNavigationIdToSlugMap as a, deriveSlug as b, transformNavigation as c, transformScreen as d, transformTheme as f, computeFileHash as g, collectSnapshotNetworkWidgetGrants as h, buildIdToSlugMap as i, createFluidOsHeyApiClient as j, fluidOsApi as k, transformNavigationItems as l, collectNetworkWidgetGrants as m, pull_exports as n, buildThemeIdToSlugMap as o, buildSnapshot as p, validatePortalResource as r, deriveScreenSlug as s, pullCommand as t, transformProfile as u, readSnapshot as v, resolveSlugToId as w, readMappings as x, writeSnapshot as y };
17648
17714
 
17649
- //# sourceMappingURL=pull-BZqZAiSF.mjs.map
17715
+ //# sourceMappingURL=pull-C26wQlO_.mjs.map