@fluid-app/fluid-cli-portal 0.1.25 → 0.1.27
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.
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{pull-CxQwoQrZ.mjs → pull-hAdXOpgb.mjs} +9 -8
- package/dist/pull-hAdXOpgb.mjs.map +1 -0
- package/package.json +4 -4
- package/templates/base/.github/workflows/deploy.yml.template +68 -0
- package/templates/starter/README.md.template +28 -0
- package/templates/starter/vite.config.ts +9 -0
- package/dist/pull-CxQwoQrZ.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -386,7 +386,7 @@ interface components {
|
|
|
386
386
|
}; /** @description Permission configuration object defining access rights. Empty arrays mean 'allow all' for that dimension. */
|
|
387
387
|
FluidOSPermissions: {
|
|
388
388
|
/** @description Array of rank IDs that can access this profile. Empty array allows all ranks. */ranks?: number[]; /** @description Array of roles that can access this profile. Empty array allows all roles. */
|
|
389
|
-
roles?:
|
|
389
|
+
roles?: string[]; /** @description Array of platforms that can access this profile. Empty array allows all platforms. */
|
|
390
390
|
platform?: ("mobile" | "browser")[]; /** @description Array of country IDs that can access this profile. Empty array allows all countries. */
|
|
391
391
|
countries?: number[];
|
|
392
392
|
}; /** @description Standard error response */
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":["paths","operations","parameters","query","header","path","cookie","get","put","post","delete","options","head","patch","trace","id","definition_id","navigation_id","webhooks","Record","components","schemas","ResponseMeta","request_id","timestamp","PaginationMeta","total_count","total_pages","current_page","key","FluidOSNavigationItem","icon","label","parent_id","position","screen_id","slug","source","children","FluidOSPermissions","ranks","roles","platform","countries","Error","error","error_message","errors","meta","FluidOSRendererProfile","name","default","navigation","navigation_tree","themes","FluidOSRendererManifest","published_version","published_at","navigations","profile","screens","FluidOSNavigationBasic","FluidOSScreen","component_tree","FluidOSTheme","config","active","FluidOSDefinition","profiles","FluidOSDefinitionBasic","FluidOSDefinitionCreate","definition","FluidOSDefinitionUpdate","FluidOSScreenBasic","FluidOSScreenCreate","screen","FluidOSScreenUpdate","FluidOSProfile","permissions","mobile_navigation","FluidOSProfileWithScreens","FluidOSProfileCreate","mobile_navigation_id","navigation_attributes","navigation_items_attributes","mobile_navigation_attributes","theme_ids","FluidOSProfileUpdate","FluidOSThemeCreate","theme","FluidOSThemeUpdate","FluidOSNavigation","FluidOSNavigationWithItems","navigation_items","FluidOSNavigationCreate","FluidOSNavigationUpdate","FluidOSNavigationItemCreate","navigation_item","FluidOSNavigationItemUpdate","FluidOSVersion","manifest","FluidOSVersionUpdate","version","responses","requestBodies","headers","pathItems","$defs","listFluidOSDefinitions","page","per_page","requestBody","content","definitions","createFluidOSDefinition","getFluidOSDefinition","updateFluidOSDefinition","deleteFluidOSDefinition","listFluidOSNavigationItems","createFluidOSNavigationItem","getFluidOSNavigationItem","updateFluidOSNavigationItem","deleteFluidOSNavigationItem","listFluidOSNavigations","search_query","sorted_by","createFluidOSNavigation","getFluidOSNavigation","updateFluidOSNavigation","deleteFluidOSNavigation","listFluidOSProfiles","createFluidOSProfile","getDefaultFluidOSProfile","getFluidOSProfile","updateFluidOSProfile","deleteFluidOSProfile","getFluidOSManifest","country_id","rank","role","listFluidOSScreens","createFluidOSScreen","getFluidOSScreen","updateFluidOSScreen","deleteFluidOSScreen","listFluidOSThemes","createFluidOSTheme","getFluidOSTheme","updateFluidOSTheme","deleteFluidOSTheme","listFluidOSVersions","createFluidOSVersion","getFluidOSVersion","updateFluidOSVersion"],"sources":["../src/types.ts","../src/utils/package-manager.ts","../src/utils/file-system.ts","../src/utils/prompts.ts","../src/commands/create.ts","../src/commands/pull.ts","../src/commands/push.ts","../src/commands/widget-create.ts","../src/commands/version.ts","../src/commands/doctor.ts","../src/utils/mappings.ts","../src/utils/snapshot.ts","../../../api-clients/fluidos/src/generated/fluid_os.d.ts","../src/utils/transform.ts","../src/utils/push-validation.ts","../src/index.ts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["paths","operations","parameters","query","header","path","cookie","get","put","post","delete","options","head","patch","trace","id","definition_id","navigation_id","webhooks","Record","components","schemas","ResponseMeta","request_id","timestamp","PaginationMeta","total_count","total_pages","current_page","key","FluidOSNavigationItem","icon","label","parent_id","position","screen_id","slug","source","children","FluidOSPermissions","ranks","roles","platform","countries","Error","error","error_message","errors","meta","FluidOSRendererProfile","name","default","navigation","navigation_tree","themes","FluidOSRendererManifest","published_version","published_at","navigations","profile","screens","FluidOSNavigationBasic","FluidOSScreen","component_tree","FluidOSTheme","config","active","FluidOSDefinition","profiles","FluidOSDefinitionBasic","FluidOSDefinitionCreate","definition","FluidOSDefinitionUpdate","FluidOSScreenBasic","FluidOSScreenCreate","screen","FluidOSScreenUpdate","FluidOSProfile","permissions","mobile_navigation","FluidOSProfileWithScreens","FluidOSProfileCreate","mobile_navigation_id","navigation_attributes","navigation_items_attributes","mobile_navigation_attributes","theme_ids","FluidOSProfileUpdate","FluidOSThemeCreate","theme","FluidOSThemeUpdate","FluidOSNavigation","FluidOSNavigationWithItems","navigation_items","FluidOSNavigationCreate","FluidOSNavigationUpdate","FluidOSNavigationItemCreate","navigation_item","FluidOSNavigationItemUpdate","FluidOSVersion","manifest","FluidOSVersionUpdate","version","responses","requestBodies","headers","pathItems","$defs","listFluidOSDefinitions","page","per_page","requestBody","content","definitions","createFluidOSDefinition","getFluidOSDefinition","updateFluidOSDefinition","deleteFluidOSDefinition","listFluidOSNavigationItems","createFluidOSNavigationItem","getFluidOSNavigationItem","updateFluidOSNavigationItem","deleteFluidOSNavigationItem","listFluidOSNavigations","search_query","sorted_by","createFluidOSNavigation","getFluidOSNavigation","updateFluidOSNavigation","deleteFluidOSNavigation","listFluidOSProfiles","createFluidOSProfile","getDefaultFluidOSProfile","getFluidOSProfile","updateFluidOSProfile","deleteFluidOSProfile","getFluidOSManifest","country_id","rank","role","listFluidOSScreens","createFluidOSScreen","getFluidOSScreen","updateFluidOSScreen","deleteFluidOSScreen","listFluidOSThemes","createFluidOSTheme","getFluidOSTheme","updateFluidOSTheme","deleteFluidOSTheme","listFluidOSVersions","createFluidOSVersion","getFluidOSVersion","updateFluidOSVersion"],"sources":["../src/types.ts","../src/utils/package-manager.ts","../src/utils/file-system.ts","../src/utils/prompts.ts","../src/commands/create.ts","../src/commands/pull.ts","../src/commands/push.ts","../src/commands/widget-create.ts","../src/commands/version.ts","../src/commands/doctor.ts","../src/utils/mappings.ts","../src/utils/snapshot.ts","../../../api-clients/fluidos/src/generated/fluid_os.d.ts","../src/utils/transform.ts","../src/utils/push-validation.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as deleteFluidOSNavigation, B as updateFluidOSScreen, C as writeMappings, D as createFluidOSScreen, E as createFluidOSProfile, F as listFluidOSNavigationItems, H as updateFluidOSVersion, I as listFluidOSVersions, L as updateFluidOSNavigation, M as deleteFluidOSProfile, N as deleteFluidOSScreen, O as createFluidOSTheme, P as deleteFluidOSTheme, R as updateFluidOSNavigationItem, S as updateMapping, T as createFluidOSNavigationItem, U as createFetchClient, V as updateFluidOSTheme, _ as deriveSlug, a as buildThemeIdToSlugMap, b as resolveIdToSlug, c as transformNavigationItems, d as transformTheme, f as buildSnapshot, g as writeSnapshot, h as readSnapshot, i as buildNavigationIdToSlugMap, j as deleteFluidOSNavigationItem, k as createFluidOSVersion, l as transformProfile, m as diffAgainstSnapshot, o as deriveScreenSlug, p as computeFileHash, r as buildIdToSlugMap, s as transformNavigation, t as pullCommand, u as transformScreen, v as readMappings, w as createFluidOSNavigation, x as resolveSlugToId, y as removeMapping, z as updateFluidOSProfile } from "./pull-
|
|
1
|
+
import { A as deleteFluidOSNavigation, B as updateFluidOSScreen, C as writeMappings, D as createFluidOSScreen, E as createFluidOSProfile, F as listFluidOSNavigationItems, H as updateFluidOSVersion, I as listFluidOSVersions, L as updateFluidOSNavigation, M as deleteFluidOSProfile, N as deleteFluidOSScreen, O as createFluidOSTheme, P as deleteFluidOSTheme, R as updateFluidOSNavigationItem, S as updateMapping, T as createFluidOSNavigationItem, U as createFetchClient, V as updateFluidOSTheme, _ as deriveSlug, a as buildThemeIdToSlugMap, b as resolveIdToSlug, c as transformNavigationItems, d as transformTheme, f as buildSnapshot, g as writeSnapshot, h as readSnapshot, i as buildNavigationIdToSlugMap, j as deleteFluidOSNavigationItem, k as createFluidOSVersion, l as transformProfile, m as diffAgainstSnapshot, o as deriveScreenSlug, p as computeFileHash, r as buildIdToSlugMap, s as transformNavigation, t as pullCommand, u as transformScreen, v as readMappings, w as createFluidOSNavigation, x as resolveSlugToId, y as removeMapping, z as updateFluidOSProfile } from "./pull-hAdXOpgb.mjs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import ora from "ora";
|
|
@@ -510,7 +510,7 @@ async function autoPull(cwd) {
|
|
|
510
510
|
console.log(chalk.yellow("No portal/ directory found.") + " Attempting to pull content...");
|
|
511
511
|
console.log();
|
|
512
512
|
try {
|
|
513
|
-
const { pullCommand } = await import("./pull-
|
|
513
|
+
const { pullCommand } = await import("./pull-hAdXOpgb.mjs").then((n) => n.n);
|
|
514
514
|
await pullCommand.parseAsync([], { from: "user" });
|
|
515
515
|
return hasPortalContent(cwd);
|
|
516
516
|
} catch (err) {
|
|
@@ -108,7 +108,8 @@ function createFetchClient(config) {
|
|
|
108
108
|
} catch {
|
|
109
109
|
throw new ApiError(errorText.slice(0, 200) || `${method} request failed with status ${response.status}`, response.status, null);
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
const nestedError = typeof data.error === "object" && data.error !== null ? data.error.message : void 0;
|
|
112
|
+
throw new ApiError(data.message || data.error_message || (typeof nestedError === "string" ? nestedError : void 0) || `${method} request failed`, response.status, data.errors || data);
|
|
112
113
|
} else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
|
|
113
114
|
}
|
|
114
115
|
if (response.status === 204 || response.headers.get("content-length") === "0") return null;
|
|
@@ -205,7 +206,7 @@ function createFetchClient(config) {
|
|
|
205
206
|
* Retrieve a list of Fluid OS definitions for the current company
|
|
206
207
|
*
|
|
207
208
|
* @param client - Fetch client instance
|
|
208
|
-
* @param params
|
|
209
|
+
* @param [params] - params
|
|
209
210
|
*/
|
|
210
211
|
async function listFluidOSDefinitions(client, params) {
|
|
211
212
|
return client.get(`/api/company/fluid_os/definitions`, params);
|
|
@@ -264,7 +265,7 @@ async function deleteFluidOSNavigationItem(client, definition_id, navigation_id,
|
|
|
264
265
|
*
|
|
265
266
|
* @param client - Fetch client instance
|
|
266
267
|
* @param definition_id - definition_id
|
|
267
|
-
* @param params
|
|
268
|
+
* @param [params] - params
|
|
268
269
|
*/
|
|
269
270
|
async function listFluidOSNavigations(client, definition_id, params) {
|
|
270
271
|
return client.get(`/api/company/fluid_os/definitions/${definition_id}/navigations`, params);
|
|
@@ -309,7 +310,7 @@ async function deleteFluidOSNavigation(client, definition_id, id) {
|
|
|
309
310
|
*
|
|
310
311
|
* @param client - Fetch client instance
|
|
311
312
|
* @param definition_id - definition_id
|
|
312
|
-
* @param params
|
|
313
|
+
* @param [params] - params
|
|
313
314
|
*/
|
|
314
315
|
async function listFluidOSProfiles(client, definition_id, params) {
|
|
315
316
|
return client.get(`/api/company/fluid_os/definitions/${definition_id}/profiles`, params);
|
|
@@ -354,7 +355,7 @@ async function deleteFluidOSProfile(client, definition_id, id) {
|
|
|
354
355
|
*
|
|
355
356
|
* @param client - Fetch client instance
|
|
356
357
|
* @param definition_id - definition_id
|
|
357
|
-
* @param params
|
|
358
|
+
* @param [params] - params
|
|
358
359
|
*/
|
|
359
360
|
async function listFluidOSScreens(client, definition_id, params) {
|
|
360
361
|
return client.get(`/api/company/fluid_os/definitions/${definition_id}/screens`, params);
|
|
@@ -410,7 +411,7 @@ async function deleteFluidOSScreen(client, definition_id, id) {
|
|
|
410
411
|
*
|
|
411
412
|
* @param client - Fetch client instance
|
|
412
413
|
* @param definition_id - definition_id
|
|
413
|
-
* @param params
|
|
414
|
+
* @param [params] - params
|
|
414
415
|
*/
|
|
415
416
|
async function listFluidOSThemes(client, definition_id, params) {
|
|
416
417
|
return client.get(`/api/company/fluid_os/definitions/${definition_id}/themes`, params);
|
|
@@ -455,7 +456,7 @@ async function deleteFluidOSTheme(client, definition_id, id) {
|
|
|
455
456
|
*
|
|
456
457
|
* @param client - Fetch client instance
|
|
457
458
|
* @param definition_id - definition_id
|
|
458
|
-
* @param params
|
|
459
|
+
* @param [params] - params
|
|
459
460
|
*/
|
|
460
461
|
async function listFluidOSVersions(client, definition_id, params) {
|
|
461
462
|
return client.get(`/api/company/fluid_os/definitions/${definition_id}/versions`, params);
|
|
@@ -1147,4 +1148,4 @@ const pullCommand = new Command("pull").description("Pull a Fluid OS definition'
|
|
|
1147
1148
|
//#endregion
|
|
1148
1149
|
export { deleteFluidOSNavigation as A, updateFluidOSScreen as B, writeMappings as C, createFluidOSScreen as D, createFluidOSProfile as E, listFluidOSNavigationItems as F, updateFluidOSVersion as H, listFluidOSVersions as I, updateFluidOSNavigation as L, deleteFluidOSProfile as M, deleteFluidOSScreen as N, createFluidOSTheme as O, deleteFluidOSTheme as P, updateFluidOSNavigationItem as R, updateMapping as S, createFluidOSNavigationItem as T, createFetchClient as U, updateFluidOSTheme as V, 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, deleteFluidOSNavigationItem as j, createFluidOSVersion 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, createFluidOSNavigation as w, resolveSlugToId as x, removeMapping as y, updateFluidOSProfile as z };
|
|
1149
1150
|
|
|
1150
|
-
//# sourceMappingURL=pull-
|
|
1151
|
+
//# sourceMappingURL=pull-hAdXOpgb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-hAdXOpgb.mjs","names":["fluidOs.listFluidOSDefinitions","fluidOs.listFluidOSScreens","fluidOs.listFluidOSThemes","fluidOs.listFluidOSNavigations","fluidOs.listFluidOSProfiles","fluidOs.getFluidOSScreen","fluidOs.listFluidOSNavigationItems"],"sources":["../../../platform/api-client-core/src/fetch-client.ts","../../../api-clients/fluidos/src/namespaces/fluid_os.ts","../src/utils/atomic-write.ts","../src/utils/mappings.ts","../src/utils/snapshot.ts","../src/utils/transform.ts","../src/commands/pull.ts"],"sourcesContent":["/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n public readonly data: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): { name: string; message: string; status: number; data: unknown } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n };\n }\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n } = config;\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]\n value.forEach((item) => queryString.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n // Some Rails BFF endpoints return `{ error: { message, details } }`\n // instead of `{ message }` or `{ error_message }`. Fall through to\n // that shape last so callers always surface the real reason.\n const nestedError =\n typeof data.error === \"object\" && data.error !== null\n ? (data.error as { message?: unknown }).message\n : undefined;\n const msg =\n (data.message as string | undefined) ||\n (data.error_message as string | undefined) ||\n (typeof nestedError === \"string\" ? nestedError : undefined);\n throw new ApiError(\n msg || `${method} request failed`,\n response.status,\n data.errors || data,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const data = await response.json();\n return data as TResponse;\n } catch {\n try {\n // API declared JSON content-type but body isn't valid JSON\n const text = await response.text();\n return text as TResponse;\n } catch {\n return null as TResponse;\n }\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const { method = \"POST\", headers: customHeaders, signal } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","/**\n * Generated API client functions for fluid_os\n *\n * DO NOT EDIT THIS FILE DIRECTLY\n * This file is auto-generated. To update:\n * 1. Update the OpenAPI spec file\n * 2. Run: pnpm generate\n */\n\nimport type { FetchClient } from \"../lib/fetch-client\";\nimport type { operations } from \"../generated/fluid_os\";\n\n// ============================================================================\n// Fluid OS - Definitions\n// ============================================================================\n\n/**\n * List Fluid OS definitions\n * Retrieve a list of Fluid OS definitions for the current company\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listFluidOSDefinitions(\n client: FetchClient,\n params?: operations[\"listFluidOSDefinitions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSDefinitions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/company/fluid_os/definitions`, params);\n}\n\n/**\n * Create a Fluid OS definition\n * Create a new Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createFluidOSDefinition(\n client: FetchClient,\n body: operations[\"createFluidOSDefinition\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/company/fluid_os/definitions`, body);\n}\n\n/**\n * Get a Fluid OS definition\n * Retrieve a specific Fluid OS definition with all associations\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getFluidOSDefinition(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/company/fluid_os/definitions/${id}`);\n}\n\n/**\n * Update a Fluid OS definition\n * Update an existing Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSDefinition(\n client: FetchClient,\n id: string | number,\n body: operations[\"updateFluidOSDefinition\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(`/api/company/fluid_os/definitions/${id}`, body);\n}\n\n/**\n * Delete a Fluid OS definition\n * Delete a Definition\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteFluidOSDefinition(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/company/fluid_os/definitions/${id}`);\n}\n\n// ============================================================================\n// Fluid OS - Navigation Items\n// ============================================================================\n\n/**\n * List navigation items for a navigation\n * Retrieve a list of navigation items for a specific navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n */\nexport async function listFluidOSNavigationItems(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n): Promise<\n operations[\"listFluidOSNavigationItems\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items`,\n );\n}\n\n/**\n * Create a navigation item\n * Create a new navigation item for a navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param body - body\n */\nexport async function createFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n body: operations[\"createFluidOSNavigationItem\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items`,\n body,\n );\n}\n\n/**\n * Get a specific navigation item\n * Retrieve a specific navigation item\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param id - id\n */\nexport async function getFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`,\n );\n}\n\n/**\n * Update a navigation item\n * Update an existing navigation item\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSNavigationItem\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`,\n body,\n );\n}\n\n/**\n * Delete a navigation item\n * Delete a navigation item\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param id - id\n */\nexport async function deleteFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Navigations\n// ============================================================================\n\n/**\n * List navigations for a Fluid OS definition\n * Retrieve a list of navigations for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param [params] - params\n */\nexport async function listFluidOSNavigations(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSNavigations\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSNavigations\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations`,\n params,\n );\n}\n\n/**\n * Create a navigation for a Fluid OS definition\n * Create a new navigation for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSNavigation\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/navigations`,\n body,\n );\n}\n\n/**\n * Get a specific navigation\n * Retrieve a specific navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`,\n );\n}\n\n/**\n * Update a navigation\n * Update an existing navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSNavigation\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`,\n body,\n );\n}\n\n/**\n * Delete a navigation\n * Delete a navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Profiles\n// ============================================================================\n\n/**\n * List profiles for a Fluid OS definition\n * Retrieve a list of profiles for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param [params] - params\n */\nexport async function listFluidOSProfiles(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSProfiles\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSProfiles\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/profiles`,\n params,\n );\n}\n\n/**\n * Create a profile for a Fluid OS definition\n * Create a new profile for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSProfile\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/profiles`,\n body,\n );\n}\n\n/**\n * Get the default profile\n * Retrieve the default profile for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n */\nexport async function getDefaultFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n): Promise<\n operations[\"getDefaultFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/default`,\n );\n}\n\n/**\n * Get a specific profile\n * Retrieve a specific profile\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`,\n );\n}\n\n/**\n * Update a profile\n * Update an existing profile\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSProfile\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`,\n body,\n );\n}\n\n/**\n * Delete a profile\n * Delete a profile\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Public\n// ============================================================================\n\n/**\n * Get active Fluid OS definition\n * Retrieve the active Fluid OS definition manifest for a specific platform\n *\n * @param client - Fetch client instance\n * @param params - params\n */\nexport async function getFluidOSManifest(\n client: FetchClient,\n params: operations[\"getFluidOSManifest\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getFluidOSManifest\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/fluid_os/definitions/active`, params);\n}\n\n// ============================================================================\n// Fluid OS - Screens\n// ============================================================================\n\n/**\n * List screens for a Fluid OS definition\n * Retrieve a list of screens for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param [params] - params\n */\nexport async function listFluidOSScreens(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSScreens\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSScreens\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/screens`,\n params,\n );\n}\n\n/**\n * Create a screen for a Fluid OS definition\n * Create a new screen for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSScreen\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/screens`,\n body,\n );\n}\n\n/**\n * Get a specific screen\n * Retrieve a specific screen\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/screens/${id}`,\n );\n}\n\n/**\n * Update a screen\n * Update an existing screen\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSScreen\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/screens/${id}`,\n body,\n );\n}\n\n/**\n * Delete a screen\n * Delete a screen\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/screens/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Themes\n// ============================================================================\n\n/**\n * List themes for a Fluid OS definition\n * Retrieve a list of themes for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param [params] - params\n */\nexport async function listFluidOSThemes(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/themes`,\n params,\n );\n}\n\n/**\n * Create a theme for a Fluid OS definition\n * Create a new theme for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSTheme\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/themes`,\n body,\n );\n}\n\n/**\n * Get a specific theme\n * Retrieve a specific theme\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/themes/${id}`,\n );\n}\n\n/**\n * Update a theme\n * Update an existing theme\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSTheme\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/themes/${id}`,\n body,\n );\n}\n\n/**\n * Delete a theme\n * Delete a theme\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/themes/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Versions\n// ============================================================================\n\n/**\n * List versions for a Fluid OS definition\n * Retrieve a list of published versions for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param [params] - params\n */\nexport async function listFluidOSVersions(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSVersions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSVersions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/versions`,\n params,\n );\n}\n\n/**\n * Publish a new version of a Fluid OS definition\n * Publish a new version of the Fluid OS definition. This creates a snapshot of the current definition state including all screens, profiles, themes, and navigations. No request body is required - the manifest is built automatically from the current definition state.\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n */\nexport async function createFluidOSVersion(\n client: FetchClient,\n definition_id: string | number,\n): Promise<\n operations[\"createFluidOSVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/versions`,\n );\n}\n\n/**\n * Get a specific version\n * Retrieve a specific published version including its manifest\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSVersion(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/versions/${id}`,\n );\n}\n\n/**\n * Update a version\n * Update a version. Currently only supports activating/deactivating a version.\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSVersion(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSVersion\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/versions/${id}`,\n body,\n );\n}\n","/**\n * Atomic file write utility.\n *\n * Uses a write-then-rename pattern so readers always see either the old\n * file or the complete new file — never a partially-written state.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { rename, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Write a file atomically using a write-then-rename pattern.\n * Ensures readers always see either the old file or the complete new file.\n *\n * If `rename` fails after the temp file has been written, the temp file\n * is cleaned up on a best-effort basis before re-throwing the error.\n */\nexport async function atomicWriteFile(\n filePath: string,\n data: string,\n): Promise<void> {\n const dir = dirname(filePath);\n const tmp = join(dir, `.tmp-${randomBytes(6).toString(\"hex\")}`);\n try {\n await writeFile(tmp, data, \"utf-8\");\n await rename(tmp, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file\n await unlink(tmp).catch(() => {});\n throw err;\n }\n}\n","/**\n * Slug-to-ID mapping system for portal content sync.\n *\n * Maps human-readable slugs (derived from resource names) to API IDs,\n * enabling the file system to use readable directory/file names while\n * maintaining the link back to server-side resources.\n *\n * Persisted in `.portal-sync/mappings.json`.\n */\n\nimport { readFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Definition metadata stored at the top level of the mappings file. */\nexport interface DefinitionMapping {\n readonly name: string;\n readonly id: number;\n}\n\n/**\n * Resource types that support slug-to-ID mappings.\n * \"countries\" and \"ranks\" use display names as keys (not slugified).\n */\nexport type MappedResourceType =\n | \"screens\"\n | \"themes\"\n | \"navigations\"\n | \"profiles\"\n | \"countries\"\n | \"ranks\";\n\n/** Full mappings structure persisted to disk. */\nexport interface PortalMappings {\n readonly definition: DefinitionMapping;\n readonly screens: Record<string, number>;\n readonly themes: Record<string, number>;\n readonly navigations: Record<string, number>;\n readonly profiles: Record<string, number>;\n readonly countries: Record<string, number>;\n readonly ranks: Record<string, number>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Runtime check that a parsed value has the expected PortalMappings shape. */\nfunction isMappings(value: unknown): value is PortalMappings {\n if (!value || typeof value !== \"object\") return false;\n const m = value as Record<string, unknown>;\n\n // Validate inner DefinitionMapping fields\n const def = m.definition as Record<string, unknown> | null | undefined;\n if (!def || typeof def !== \"object\") return false;\n if (typeof def.name !== \"string\" || typeof def.id !== \"number\") return false;\n\n return (\n typeof m.screens === \"object\" &&\n m.screens !== null &&\n typeof m.themes === \"object\" &&\n m.themes !== null &&\n typeof m.navigations === \"object\" &&\n m.navigations !== null &&\n typeof m.profiles === \"object\" &&\n m.profiles !== null &&\n typeof m.countries === \"object\" &&\n m.countries !== null &&\n typeof m.ranks === \"object\" &&\n m.ranks !== null\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Read / Write\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst MAPPINGS_FILE = \"mappings.json\";\n\n/**\n * Read the mappings file from the `.portal-sync/` directory.\n * Returns `null` if the file does not exist.\n */\nexport async function readMappings(\n portalSyncDir: string,\n): Promise<PortalMappings | null> {\n try {\n const filePath = join(portalSyncDir, MAPPINGS_FILE);\n const content = await readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(content);\n if (!isMappings(parsed)) {\n throw new Error(`Malformed mappings file: ${filePath}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Write the mappings file to the `.portal-sync/` directory.\n * Creates the directory if it does not exist.\n */\nexport async function writeMappings(\n portalSyncDir: string,\n mappings: PortalMappings,\n): Promise<void> {\n const filePath = join(portalSyncDir, MAPPINGS_FILE);\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, JSON.stringify(mappings, null, 2) + \"\\n\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slug derivation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Derive a filesystem-safe slug from a resource name.\n *\n * - Lowercases the name\n * - Replaces non-alphanumeric characters (except hyphens) with hyphens\n * - Collapses consecutive hyphens\n * - Trims leading/trailing hyphens\n *\n * If the derived slug collides with an existing slug in `existingSlugs`,\n * a numeric suffix is appended (e.g., `home-2`, `home-3`).\n */\nexport function deriveSlug(\n name: string,\n existingSlugs: ReadonlySet<string> = new Set(),\n): string {\n const base =\n name\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\") || \"unnamed\";\n\n if (!existingSlugs.has(base)) {\n return base;\n }\n\n // Collision detected — append a numeric suffix\n let counter = 2;\n while (existingSlugs.has(`${base}-${counter}`)) {\n counter++;\n }\n return `${base}-${counter}`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Lookup helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve a slug to its server-side ID.\n * Returns `undefined` if the slug is not mapped.\n */\nexport function resolveSlugToId(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n): number | undefined {\n return mappings[resourceType][slug];\n}\n\n/**\n * Resolve a server-side ID back to its slug.\n * Returns `undefined` if no slug maps to the given ID.\n */\nexport function resolveIdToSlug(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n id: number,\n): string | undefined {\n const entries = mappings[resourceType];\n for (const [slug, mappedId] of Object.entries(entries)) {\n if (mappedId === id) {\n return slug;\n }\n }\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Mutation helpers (return new objects — no mutation)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Add or update a mapping entry for a given resource type.\n * Returns a new `PortalMappings` object (immutable update).\n */\nexport function updateMapping(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n id: number,\n): PortalMappings {\n return {\n ...mappings,\n [resourceType]: {\n ...mappings[resourceType],\n [slug]: id,\n },\n };\n}\n\n/**\n * Remove a mapping entry for a given resource type.\n * Returns a new `PortalMappings` object (immutable update).\n * If the slug does not exist, the original mappings are returned unchanged.\n */\nexport function removeMapping(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n): PortalMappings {\n if (!(slug in mappings[resourceType])) {\n return mappings;\n }\n\n const { [slug]: _removed, ...rest } = mappings[resourceType];\n return {\n ...mappings,\n [resourceType]: rest,\n };\n}\n","/**\n * Hash-based snapshot system for portal content sync.\n *\n * Tracks file hashes to detect local changes since the last pull,\n * enabling efficient push operations that only send modified files.\n *\n * Persisted in `.portal-sync/snapshot.json`.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, readdir, mkdir, stat } from \"node:fs/promises\";\nimport { join, dirname, relative, sep } from \"node:path\";\n\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Maximum number of concurrent file operations. */\nconst MAX_CONCURRENCY = 20;\n\n/** SHA-256 hex digest of a file's contents. */\nexport type FileHash = string;\n\n/** Map of relative file paths to their content hashes. */\nexport type FileHashMap = Record<string, FileHash>;\n\n/** Snapshot structure persisted to `.portal-sync/snapshot.json`. */\nexport interface Snapshot {\n /** Human-readable name of the portal definition. */\n readonly definition: string;\n /** Server-side ID of the portal definition. */\n readonly definition_id: number;\n /** ISO 8601 timestamp of when the snapshot was created. */\n readonly pulled_at: string;\n /** Map of relative file paths → SHA-256 hashes. */\n readonly files: FileHashMap;\n}\n\n/** Result of diffing current files against a snapshot. */\nexport interface SnapshotDiff {\n /** Files that exist on disk but not in the snapshot. */\n readonly new: readonly string[];\n /** Files that exist in both but have different hashes. */\n readonly changed: readonly string[];\n /** Files that exist in the snapshot but not on disk. */\n readonly deleted: readonly string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Run async tasks with bounded concurrency to avoid exhausting file descriptors.\n */\nasync function mapWithLimit<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let index = 0;\n\n async function worker(): Promise<void> {\n while (index < items.length) {\n const i = index++;\n results[i] = await fn(items[i]!);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () =>\n worker(),\n );\n await Promise.all(workers);\n return results;\n}\n\n/** Runtime check that a parsed value has the expected Snapshot shape. */\nfunction isSnapshot(value: unknown): value is Snapshot {\n if (!value || typeof value !== \"object\") return false;\n const s = value as Record<string, unknown>;\n return (\n typeof s.definition === \"string\" &&\n typeof s.definition_id === \"number\" &&\n typeof s.pulled_at === \"string\" &&\n typeof s.files === \"object\" &&\n s.files !== null\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute the SHA-256 hash of a file's contents.\n * Returns the hex-encoded digest.\n */\nexport async function computeFileHash(filePath: string): Promise<FileHash> {\n const content = await readFile(filePath);\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Read / Write\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SNAPSHOT_FILE = \"snapshot.json\";\n\n/**\n * Read the snapshot file from the `.portal-sync/` directory.\n * Returns `null` if the file does not exist.\n */\nexport async function readSnapshot(\n portalSyncDir: string,\n): Promise<Snapshot | null> {\n try {\n const filePath = join(portalSyncDir, SNAPSHOT_FILE);\n const content = await readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(content);\n if (!isSnapshot(parsed)) {\n throw new Error(`Malformed snapshot file: ${filePath}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Write the snapshot file to the `.portal-sync/` directory.\n * Creates the directory if it does not exist.\n */\nexport async function writeSnapshot(\n portalSyncDir: string,\n snapshot: Snapshot,\n): Promise<void> {\n const filePath = join(portalSyncDir, SNAPSHOT_FILE);\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, JSON.stringify(snapshot, null, 2) + \"\\n\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Diff\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Recursively collect all files in a directory, returning paths\n * relative to `baseDir`.\n */\nasync function collectFiles(\n dir: string,\n baseDir: string = dir,\n): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n // Skip .portal-sync directory\n if (entry.name === \".portal-sync\") continue;\n\n if (entry.isSymbolicLink()) {\n // Resolve symlink to determine if it points to a file or directory\n const realStat = await stat(fullPath);\n if (realStat.isDirectory()) {\n files.push(...(await collectFiles(fullPath, baseDir)));\n } else if (realStat.isFile()) {\n files.push(relative(baseDir, fullPath).split(sep).join(\"/\"));\n }\n } else if (entry.isDirectory()) {\n files.push(...(await collectFiles(fullPath, baseDir)));\n } else {\n files.push(relative(baseDir, fullPath).split(sep).join(\"/\"));\n }\n }\n\n return files;\n}\n\n/**\n * Compare the current portal directory contents against a snapshot.\n *\n * Returns lists of new, changed, and deleted files (relative paths).\n */\nexport async function diffAgainstSnapshot(\n portalDir: string,\n snapshot: Snapshot,\n): Promise<SnapshotDiff> {\n const currentFiles = await collectFiles(portalDir);\n\n const snapshotPaths = new Set(Object.keys(snapshot.files));\n\n const newFiles: string[] = [];\n const changedFiles: string[] = [];\n\n // Check current files against snapshot (bounded concurrency)\n await mapWithLimit(currentFiles, MAX_CONCURRENCY, async (filePath) => {\n const hash = await computeFileHash(join(portalDir, filePath));\n\n if (!snapshotPaths.has(filePath)) {\n newFiles.push(filePath);\n } else if (snapshot.files[filePath] !== hash) {\n changedFiles.push(filePath);\n }\n });\n\n // Find deleted files (in snapshot but not on disk)\n const currentSet = new Set(currentFiles);\n const deletedFiles = [...snapshotPaths].filter(\n (filePath) => !currentSet.has(filePath),\n );\n\n return {\n new: [...newFiles].sort(),\n changed: [...changedFiles].sort(),\n deleted: [...deletedFiles].sort(),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Build\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build a fresh snapshot from the current portal directory contents.\n *\n * Hashes every file in `portalDir` (excluding `.portal-sync/`)\n * and records the definition metadata.\n */\nexport async function buildSnapshot(\n portalDir: string,\n definitionName: string,\n definitionId: number,\n): Promise<Snapshot> {\n const files = await collectFiles(portalDir);\n\n const hashEntries = await mapWithLimit(\n files,\n MAX_CONCURRENCY,\n async (filePath) => {\n const hash = await computeFileHash(join(portalDir, filePath));\n return [filePath, hash] as const;\n },\n );\n\n // Sort entries for deterministic output\n hashEntries.sort(([a], [b]) => a.localeCompare(b));\n\n const fileHashes: Record<string, FileHash> = {};\n for (const [path, hash] of hashEntries) {\n fileHashes[path] = hash;\n }\n\n return {\n definition: definitionName,\n definition_id: definitionId,\n pulled_at: new Date().toISOString(),\n files: fileHashes,\n };\n}\n","/**\n * Transformation utilities for converting Fluid OS API responses\n * into the local file format used by the portal content sync system.\n */\n\nimport type { components } from \"@fluid-app/fluidos-api-client\";\nimport { deriveSlug } from \"./mappings\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API types (aliases for readability)\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiScreen = components[\"schemas\"][\"FluidOSScreen\"];\ntype ApiTheme = components[\"schemas\"][\"FluidOSTheme\"];\ntype ApiNavigationBasic = components[\"schemas\"][\"FluidOSNavigationBasic\"];\ntype ApiNavigationItem = components[\"schemas\"][\"FluidOSNavigationItem\"];\ntype ApiProfile = components[\"schemas\"][\"FluidOSProfile\"];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Local file types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LocalScreen {\n readonly name: string;\n readonly component_tree: Record<string, unknown>[];\n}\n\nexport interface LocalTheme {\n readonly name: string;\n readonly config: Record<string, unknown>;\n readonly active: boolean;\n}\n\nexport interface LocalNavigationItem {\n readonly id: number;\n readonly icon: string | null;\n readonly label: string | null;\n readonly screen: string | null;\n readonly slug: string | null;\n readonly source: string;\n readonly position: number | null;\n readonly parent_id: number | null;\n readonly children: LocalNavigationItem[];\n}\n\nexport interface LocalNavigation {\n readonly name: string;\n readonly platform: string;\n readonly navigation_items: LocalNavigationItem[];\n}\n\nexport interface LocalProfile {\n readonly name: string;\n readonly default: boolean;\n readonly navigation: string | null;\n readonly mobile_navigation: string | null;\n readonly themes: string[];\n readonly permissions: {\n readonly ranks: number[];\n readonly roles: string[];\n readonly platform: string[];\n readonly countries: number[];\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Transform an API screen response into the local file format.\n * Strips server-only fields and normalizes `component_tree` to always be an array.\n */\nexport function transformScreen(screen: ApiScreen): LocalScreen {\n const name = screen.name ?? \"\";\n const rawTree = screen.component_tree;\n\n // Normalize component_tree to always be an array\n let componentTree: Record<string, unknown>[];\n if (rawTree == null) {\n componentTree = [];\n } else if (Array.isArray(rawTree)) {\n componentTree = rawTree as Record<string, unknown>[];\n } else {\n componentTree = [rawTree];\n }\n\n return { name, component_tree: componentTree };\n}\n\n/**\n * Derive a slug for a screen. Screens use the existing `slug` field from the API.\n * Falls back to slugifying the name if no slug exists.\n */\nexport function deriveScreenSlug(\n screen: ApiScreen,\n existingSlugs: Set<string>,\n): string {\n if (screen.slug) {\n // Sanitize: strip path-separator and other unsafe characters before using\n // the API slug as a filesystem path segment.\n const safe = screen.slug.replace(/[^a-z0-9_-]/gi, \"-\");\n if (!existingSlugs.has(safe)) {\n return safe;\n }\n return deriveSlug(safe, existingSlugs);\n }\n return deriveSlug(screen.name ?? \"unnamed\", existingSlugs);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Theme transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Transform an API theme response into the local file format.\n * Passes theme config through as-is (hex conversion is a separate ticket).\n */\nexport function transformTheme(theme: ApiTheme): LocalTheme {\n return {\n name: theme.name ?? \"\",\n config: theme.config ?? {},\n active: theme.active ?? false,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Navigation transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build an ID → slug lookup map by inverting a slug → id mapping.\n */\nexport function buildIdToSlugMap(\n mappings: Record<string, number>,\n): Map<number, string> {\n const map = new Map<number, string>();\n for (const [slug, id] of Object.entries(mappings)) {\n map.set(id, slug);\n }\n return map;\n}\n\n/**\n * Transform API navigation items into local format.\n * Converts `screen_id` to `screen` slug reference. Keeps `id` for sync.\n */\nexport function transformNavigationItems(\n items: ApiNavigationItem[],\n screenIdToSlug: Map<number, string>,\n): LocalNavigationItem[] {\n return items.map((item) => ({\n id: item.id,\n icon: item.icon ?? null,\n label: item.label ?? null,\n screen: item.screen_id\n ? (screenIdToSlug.get(item.screen_id) ?? null)\n : null,\n slug: item.slug ?? null,\n source: item.source,\n position: item.position ?? null,\n parent_id: item.parent_id ?? null,\n children: transformNavigationItems(item.children ?? [], screenIdToSlug),\n }));\n}\n\n/**\n * Transform an API navigation with its items into the local file format.\n */\nexport function transformNavigation(\n nav: ApiNavigationBasic,\n items: ApiNavigationItem[],\n screenIdToSlug: Map<number, string>,\n): LocalNavigation {\n return {\n name: nav.name ?? \"\",\n platform: nav.platform,\n navigation_items: transformNavigationItems(items, screenIdToSlug),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Profile transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Typed alias for call-site clarity. */\nexport const buildNavigationIdToSlugMap: (\n mappings: Record<string, number>,\n) => Map<number, string> = buildIdToSlugMap;\n\n/** Typed alias for call-site clarity. */\nexport const buildThemeIdToSlugMap: (\n mappings: Record<string, number>,\n) => Map<number, string> = buildIdToSlugMap;\n\n/**\n * Transform an API profile response into the local file format.\n * Converts navigation_id, mobile_navigation_id, and theme_ids to slug references.\n *\n * TODO (REP-842): Country/rank IDs in profile permissions need to be translated\n * to human-readable names via the main Fluid API. For now, the permission arrays\n * are written as-is with integer IDs.\n */\nexport function transformProfile(\n profile: ApiProfile,\n navIdToSlug: Map<number, string>,\n themeIdToSlug: Map<number, string>,\n): LocalProfile {\n const navigation = profile.navigation\n ? (navIdToSlug.get(profile.navigation.id) ?? null)\n : null;\n\n const mobileNavigation = profile.mobile_navigation\n ? (navIdToSlug.get(profile.mobile_navigation.id) ?? null)\n : null;\n\n const themes = (profile.themes ?? [])\n .map((t) => themeIdToSlug.get(t.id))\n .filter((slug): slug is string => slug != null);\n\n const permissions = profile.permissions ?? {};\n\n return {\n name: profile.name ?? \"\",\n default: profile.default ?? false,\n navigation,\n mobile_navigation: mobileNavigation,\n themes,\n permissions: {\n ranks: permissions.ranks ?? [],\n roles: permissions.roles ?? [],\n platform: permissions.platform ?? [],\n countries: permissions.countries ?? [],\n },\n };\n}\n","/**\n * `fluid portal pull` command\n *\n * Pulls a Fluid OS definition's resources (screens, themes, navigations,\n * profiles) from the API and writes them to the local `portal/` directory\n * as JSON files, along with `.portal-sync/` metadata for push diffing.\n */\n\nimport { Command } from \"commander\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport { createFetchClient } from \"@fluid-app/fluidos-api-client\";\nimport type { FetchClient, components } from \"@fluid-app/fluidos-api-client\";\nimport { fluidOs } from \"@fluid-app/fluidos-api-client\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport pLimit from \"p-limit\";\nimport { join } from \"node:path\";\nimport { mkdir, writeFile, rm, rename } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport prompts from \"prompts\";\n\nimport { deriveSlug, writeMappings } from \"../utils/mappings.js\";\nimport type { PortalMappings } from \"../utils/mappings.js\";\nimport {\n readSnapshot,\n diffAgainstSnapshot,\n buildSnapshot,\n writeSnapshot,\n} from \"../utils/snapshot.js\";\nimport {\n transformScreen,\n deriveScreenSlug,\n transformTheme,\n transformNavigation,\n transformProfile,\n buildIdToSlugMap,\n buildNavigationIdToSlugMap,\n buildThemeIdToSlugMap,\n} from \"../utils/transform.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiDefinitionBasic = components[\"schemas\"][\"FluidOSDefinitionBasic\"];\n\ninterface PullOptions {\n app?: string;\n force?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_DIR = \"portal\";\nconst PORTAL_SYNC_DIR = \".portal-sync\";\nconst PAGE_LIMIT = 500;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an authenticated FetchClient using the stored CLI profile.\n */\nfunction createClient(): FetchClient {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n if (!profile) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profile.name) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n\n return createFetchClient({\n baseUrl,\n getAuthToken: () => token,\n });\n}\n\n/**\n * Select a definition interactively or by name.\n */\nasync function fetchAllDefinitions(\n client: FetchClient,\n): Promise<ApiDefinitionBasic[]> {\n const all: ApiDefinitionBasic[] = [];\n let page = 1;\n\n while (true) {\n const response = await fluidOs.listFluidOSDefinitions(client, {\n page,\n per_page: PAGE_LIMIT,\n });\n const batch = response.definitions ?? [];\n all.push(...batch);\n\n const totalPages = response.meta?.total_pages ?? 1;\n if (page >= totalPages || batch.length === 0) break;\n page++;\n }\n\n return all;\n}\n\nasync function selectDefinition(\n client: FetchClient,\n appName?: string,\n): Promise<ApiDefinitionBasic> {\n const definitions = await fetchAllDefinitions(client);\n\n if (definitions.length === 0) {\n throw new Error(\n \"No Fluid OS definitions found. Create one in the admin dashboard first.\",\n );\n }\n\n // If --app flag provided, find by name\n if (appName) {\n const match = definitions.find(\n (d) => d.name?.toLowerCase() === appName.toLowerCase(),\n );\n if (!match) {\n const availableNames = definitions\n .map((d) => chalk.cyan(d.name ?? \"(unnamed)\"))\n .join(\", \");\n throw new Error(\n `No definition found matching \"${appName}\". Available: ${availableNames}`,\n );\n }\n return match;\n }\n\n // Interactive selector\n const { definitionId } = await prompts({\n type: \"select\",\n name: \"definitionId\",\n message: \"Select a Fluid OS definition to pull\",\n choices: definitions.map((d) => ({\n title: d.name ?? \"(unnamed)\",\n value: d.id,\n })),\n });\n\n if (definitionId == null) {\n throw new Error(\"No definition selected.\");\n }\n\n const selected = definitions.find((d) => d.id === definitionId);\n if (!selected) {\n throw new Error(\"Selected definition not found.\");\n }\n\n return selected;\n}\n\n/**\n * Check for definition switching conflicts.\n * Returns true if it's safe to proceed.\n */\nasync function checkDefinitionSwitch(\n cwd: string,\n newDefinitionId: number,\n newDefinitionName: string,\n force: boolean,\n): Promise<boolean> {\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n // No existing portal directory — always safe\n if (!existsSync(portalDir)) {\n return true;\n }\n\n // Read existing snapshot to see what definition was pulled\n const snapshot = await readSnapshot(portalSyncDir);\n if (!snapshot) {\n // No snapshot means no prior pull — safe to overwrite\n return true;\n }\n\n // Same definition — this is a refresh\n if (snapshot.definition_id === newDefinitionId) {\n return true;\n }\n\n // Different definition — check for local changes\n const diff = await diffAgainstSnapshot(portalDir, snapshot);\n const hasChanges =\n diff.new.length > 0 || diff.changed.length > 0 || diff.deleted.length > 0;\n\n if (hasChanges && !force) {\n console.log();\n console.log(\n chalk.yellow(\"Warning:\") +\n \" Switching from \" +\n chalk.cyan(snapshot.definition) +\n \" to \" +\n chalk.cyan(newDefinitionName) +\n \" with unpushed local changes.\",\n );\n console.log();\n console.log(\" Modified: \" + diff.changed.length + \" file(s)\");\n console.log(\" New: \" + diff.new.length + \" file(s)\");\n console.log(\" Deleted: \" + diff.deleted.length + \" file(s)\");\n console.log();\n console.log(\n \"Use \" +\n chalk.cyan(\"--force\") +\n \" to discard local changes and switch definitions.\",\n );\n return false;\n }\n\n return true;\n}\n\n/**\n * Write a JSON file to the portal directory, creating subdirectories as needed.\n */\nasync function writePortalFile(\n portalDir: string,\n relativePath: string,\n data: unknown,\n): Promise<void> {\n const filePath = join(portalDir, relativePath);\n const dir = join(filePath, \"..\");\n await mkdir(dir, { recursive: true });\n await writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const pullCommand: Command = new Command(\"pull\")\n .description(\n \"Pull a Fluid OS definition's resources to the local portal/ directory\",\n )\n .option(\"--app <name>\", \"Definition name (skips interactive selector)\")\n .option(\"--force\", \"Overwrite local changes when switching definitions\")\n .action(async (options: PullOptions) => {\n const cwd = process.cwd();\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Pull\"));\n console.log();\n\n // ── Authenticate ──────────────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Authenticating...\");\n\n let client: FetchClient;\n try {\n client = createClient();\n spinner.succeed(\"Authenticated\");\n } catch (err) {\n spinner.fail(\"Authentication failed\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Select definition ─────────────────────────────────────────────\n let definition: ApiDefinitionBasic;\n try {\n definition = await selectDefinition(client, options.app);\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n const definitionId = definition.id;\n const definitionName = definition.name ?? \"(unnamed)\";\n\n console.log(\n chalk.gray(\"Definition: \") +\n chalk.white(definitionName) +\n chalk.gray(` (ID: ${definitionId})`),\n );\n console.log();\n\n // ── Check definition switch ───────────────────────────────────────\n let canProceed: boolean;\n try {\n canProceed = await checkDefinitionSwitch(\n cwd,\n definitionId,\n definitionName,\n options.force ?? false,\n );\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n if (!canProceed) {\n process.exit(1);\n }\n\n // ── Fetch all resources in parallel ───────────────────────────────\n spinner.start(\"Fetching resources...\");\n\n let screenBasics: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSScreens>>[\"screens\"]\n >;\n let themes: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSThemes>>[\"themes\"]\n >;\n let navigations: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSNavigations>>[\"navigations\"]\n >;\n let profiles: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSProfiles>>[\"profiles\"]\n >;\n let screens: NonNullable<\n Awaited<ReturnType<typeof fluidOs.getFluidOSScreen>>[\"screen\"]\n >[];\n const navigationItemsMap = new Map<\n number,\n components[\"schemas\"][\"FluidOSNavigationItem\"][]\n >();\n\n try {\n const [\n screensResponse,\n themesResponse,\n navigationsResponse,\n profilesResponse,\n ] = await Promise.all([\n fluidOs.listFluidOSScreens(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOs.listFluidOSThemes(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOs.listFluidOSNavigations(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOs.listFluidOSProfiles(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n ]);\n\n screenBasics = screensResponse.screens ?? [];\n themes = themesResponse.themes ?? [];\n navigations = navigationsResponse.navigations ?? [];\n profiles = profilesResponse.profiles ?? [];\n\n // Warn if any list hits the page limit\n if (screenBasics.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: screen count hit the ${PAGE_LIMIT}-item limit; some screens may be missing.`,\n ),\n );\n }\n if (themes.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: theme count hit the ${PAGE_LIMIT}-item limit; some themes may be missing.`,\n ),\n );\n }\n if (navigations.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: navigation count hit the ${PAGE_LIMIT}-item limit; some navigations may be missing.`,\n ),\n );\n }\n if (profiles.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: profile count hit the ${PAGE_LIMIT}-item limit; some profiles may be missing.`,\n ),\n );\n }\n\n spinner.text = \"Fetching screen details...\";\n\n // Limit concurrency to avoid hitting API rate limits\n const limit = pLimit(10);\n\n // Fetch full screen details (list endpoint doesn't include component_tree)\n screens = await Promise.all(\n screenBasics.map((s) =>\n limit(async () => {\n const res = await fluidOs.getFluidOSScreen(\n client,\n definitionId,\n s.id,\n );\n if (!res.screen) {\n throw new Error(`Failed to fetch details for screen ID ${s.id}`);\n }\n return res.screen;\n }),\n ),\n );\n\n // Fetch navigation items for each navigation.\n // NOTE: The listFluidOSNavigationItems API endpoint does not support\n // pagination query parameters (per_page/page). Navigation items are\n // returned as a nested tree structure and are not paginated.\n spinner.text = \"Fetching navigation items...\";\n await Promise.all(\n navigations.map((nav) =>\n limit(async () => {\n const res = await fluidOs.listFluidOSNavigationItems(\n client,\n definitionId,\n nav.id,\n );\n navigationItemsMap.set(nav.id, res.navigation_items ?? []);\n }),\n ),\n );\n\n spinner.succeed(\n `Fetched ${screens.length} screen(s), ${themes.length} theme(s), ${navigations.length} navigation(s), ${profiles.length} profile(s)`,\n );\n } catch (err) {\n spinner.fail(\"Failed to fetch resources\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Build slug mappings ───────────────────────────────────────────\n spinner.start(\"Transforming resources...\");\n\n // Screen mappings: use existing slug field when available\n const screenSlugs = new Set<string>();\n const screenMappings: Record<string, number> = {};\n for (const screen of screens) {\n const slug = deriveScreenSlug(screen, screenSlugs);\n screenSlugs.add(slug);\n screenMappings[slug] = screen.id;\n }\n\n // Theme mappings: slugify from name\n const themeSlugs = new Set<string>();\n const themeMappings: Record<string, number> = {};\n for (const theme of themes) {\n const slug = deriveSlug(theme.name ?? \"unnamed\", themeSlugs);\n themeSlugs.add(slug);\n themeMappings[slug] = theme.id;\n }\n\n // Navigation mappings: slugify from name\n const navSlugs = new Set<string>();\n const navMappings: Record<string, number> = {};\n for (const nav of navigations) {\n const slug = deriveSlug(nav.name ?? \"unnamed\", navSlugs);\n navSlugs.add(slug);\n navMappings[slug] = nav.id;\n }\n\n // Profile mappings: slugify from name\n const profileSlugs = new Set<string>();\n const profileMappings: Record<string, number> = {};\n for (const profile of profiles) {\n const slug = deriveSlug(profile.name ?? \"unnamed\", profileSlugs);\n profileSlugs.add(slug);\n profileMappings[slug] = profile.id;\n }\n\n // Build reverse-lookup maps for ID → slug references\n const screenIdToSlug = buildIdToSlugMap(screenMappings);\n const navIdToSlug = buildNavigationIdToSlugMap(navMappings);\n const themeIdToSlug = buildThemeIdToSlugMap(themeMappings);\n const profileIdToSlug = buildIdToSlugMap(profileMappings);\n\n spinner.succeed(\"Resources transformed\");\n\n // ── Clean and write files ─────────────────────────────────────────\n spinner.start(\"Writing files...\");\n\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n // Write to a temporary directory first, then atomically swap into place.\n // This ensures the existing portal/ directory is only removed once all\n // new content has been successfully written.\n const tmpPortalDir = portalDir + \".tmp\";\n\n try {\n // Clean up any leftover temp directory from a previous failed run\n if (existsSync(tmpPortalDir)) {\n await rm(tmpPortalDir, { recursive: true });\n }\n await mkdir(tmpPortalDir, { recursive: true });\n\n // Write definition.json\n await writePortalFile(tmpPortalDir, \"definition.json\", {\n name: definitionName,\n });\n\n // Write screens\n for (const screen of screens) {\n const slug = screenIdToSlug.get(screen.id)!;\n const local = transformScreen(screen);\n await writePortalFile(tmpPortalDir, `screens/${slug}.json`, local);\n }\n\n // Write themes\n for (const theme of themes) {\n const slug = themeIdToSlug.get(theme.id)!;\n const local = transformTheme(theme);\n await writePortalFile(tmpPortalDir, `themes/${slug}.json`, local);\n }\n\n // Write navigations\n for (const nav of navigations) {\n const slug = navIdToSlug.get(nav.id)!;\n const items = navigationItemsMap.get(nav.id) ?? [];\n const local = transformNavigation(nav, items, screenIdToSlug);\n await writePortalFile(tmpPortalDir, `navigations/${slug}.json`, local);\n }\n\n // Write profiles\n for (const profile of profiles) {\n const slug = profileIdToSlug.get(profile.id);\n if (slug) {\n const local = transformProfile(profile, navIdToSlug, themeIdToSlug);\n await writePortalFile(tmpPortalDir, `profiles/${slug}.json`, local);\n }\n }\n\n // Atomic swap: remove old directory, rename temp into place\n if (existsSync(portalDir)) {\n await rm(portalDir, { recursive: true });\n }\n await rename(tmpPortalDir, portalDir);\n } catch (err) {\n // Clean up temp directory on failure\n if (existsSync(tmpPortalDir)) {\n await rm(tmpPortalDir, { recursive: true }).catch(() => {});\n }\n spinner.fail(\"Failed to write files\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Write metadata (mappings + snapshot) ────────────────────────────\n // Separated from the file-write block so that a metadata failure does\n // not incorrectly report total failure when portal files are already\n // on disk after the atomic swap.\n try {\n const mappings: PortalMappings = {\n definition: { name: definitionName, id: definitionId },\n screens: screenMappings,\n themes: themeMappings,\n navigations: navMappings,\n profiles: profileMappings,\n countries: {},\n ranks: {},\n };\n await writeMappings(portalSyncDir, mappings);\n\n const snapshot = await buildSnapshot(\n portalDir,\n definitionName,\n definitionId,\n );\n await writeSnapshot(portalSyncDir, snapshot);\n\n spinner.succeed(\"Files written\");\n } catch (err) {\n spinner.warn(\"Files written (metadata sync failed)\");\n console.log();\n console.log(\n chalk.yellow(\"Warning:\") +\n \" Portal files were written successfully, but metadata sync failed.\" +\n \" Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" again to regenerate metadata.\",\n );\n console.log(\n chalk.gray(\" Detail: \") +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n }\n\n // ── Summary ───────────────────────────────────────────────────────\n console.log();\n console.log(chalk.green.bold(\"Pull complete!\"));\n console.log();\n console.log(chalk.gray(\" portal/definition.json\"));\n console.log(\n chalk.gray(` portal/screens/ `) +\n chalk.white(`${screens.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/themes/ `) +\n chalk.white(`${themes.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/navigations/ `) +\n chalk.white(`${navigations.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/profiles/ `) +\n chalk.white(`${profiles.length} file(s)`),\n );\n console.log(\n chalk.gray(` .portal-sync/ `) +\n chalk.white(\"mappings.json + snapshot.json\"),\n );\n console.log();\n });\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/fluid-cli-portal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Fluid CLI plugin for building portal applications",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"p-limit": "^7.3.0",
|
|
33
33
|
"prompts": "^2.4.2",
|
|
34
34
|
"tsx": "^4.21.0",
|
|
35
|
-
"@fluid-app/fluid-cli": "0.1.
|
|
35
|
+
"@fluid-app/fluid-cli": "0.1.9"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@swc/core": "^1.15.18",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"tsdown": "^0.21.0",
|
|
45
45
|
"typescript": "^5",
|
|
46
46
|
"vite": "^6.0.0",
|
|
47
|
-
"@fluid-app/
|
|
48
|
-
"@fluid-app/
|
|
47
|
+
"@fluid-app/fluidos-api-client": "0.1.0",
|
|
48
|
+
"@fluid-app/typescript-config": "0.0.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Deploy Portal
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
GCP_PROJECT: fluid-417204
|
|
10
|
+
GCS_BUCKET: gs://portals-cdn/{{projectName}}/assets
|
|
11
|
+
CDN_URL_MAP: custom-domains-c42c
|
|
12
|
+
CDN_INVALIDATION_PATH: /{{projectName}}/assets/*
|
|
13
|
+
# CDN frontend hostname — update this to match your load balancer's domain.
|
|
14
|
+
# Using the raw GCS URL bypasses Cloud CDN; the CDN-fronted URL ensures
|
|
15
|
+
# chunk imports are served (and invalidated) through the cache.
|
|
16
|
+
CDN_HOSTNAME: https://cdn.example.com
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build-and-upload:
|
|
20
|
+
name: Build and upload to GCS
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
concurrency:
|
|
23
|
+
group: portal-deploy-$\{{ github.ref }}
|
|
24
|
+
cancel-in-progress: false
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- name: Checkout repo
|
|
28
|
+
uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- name: Setup pnpm
|
|
31
|
+
uses: pnpm/action-setup@v4
|
|
32
|
+
|
|
33
|
+
- name: Setup Node
|
|
34
|
+
uses: actions/setup-node@v4
|
|
35
|
+
with:
|
|
36
|
+
node-version: 24.x
|
|
37
|
+
cache: pnpm
|
|
38
|
+
|
|
39
|
+
- name: Install deps
|
|
40
|
+
run: pnpm install --frozen-lockfile
|
|
41
|
+
|
|
42
|
+
- name: Build portal
|
|
43
|
+
run: pnpm build
|
|
44
|
+
env:
|
|
45
|
+
VITE_ASSET_BASE: $\{{ env.CDN_HOSTNAME }}/{{projectName}}/assets/
|
|
46
|
+
|
|
47
|
+
- name: Authenticate to Google Cloud
|
|
48
|
+
uses: google-github-actions/auth@v2
|
|
49
|
+
with:
|
|
50
|
+
project_id: $\{{ env.GCP_PROJECT }}
|
|
51
|
+
credentials_json: $\{{ secrets.GCP_SA_JSON }}
|
|
52
|
+
|
|
53
|
+
- name: Setup Cloud SDK
|
|
54
|
+
uses: google-github-actions/setup-gcloud@v2
|
|
55
|
+
with:
|
|
56
|
+
project_id: $\{{ env.GCP_PROJECT }}
|
|
57
|
+
|
|
58
|
+
- name: Upload assets to GCS
|
|
59
|
+
run: |
|
|
60
|
+
gcloud storage rsync ./dist $\{{ env.GCS_BUCKET }} \
|
|
61
|
+
--recursive \
|
|
62
|
+
--delete-unmatched-destination-objects
|
|
63
|
+
|
|
64
|
+
- name: Invalidate CDN cache
|
|
65
|
+
run: |
|
|
66
|
+
gcloud compute url-maps invalidate-cdn-cache $\{{ env.CDN_URL_MAP }} \
|
|
67
|
+
--path="$\{{ env.CDN_INVALIDATION_PATH }}" \
|
|
68
|
+
--global
|
|
@@ -59,6 +59,34 @@ src/
|
|
|
59
59
|
└── Dashboard.tsx # Dashboard with multiple widgets
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Deployment
|
|
63
|
+
|
|
64
|
+
This project includes a GitHub Actions workflow (`.github/workflows/deploy.yml`) that deploys your portal to Google Cloud Storage + Cloud CDN.
|
|
65
|
+
|
|
66
|
+
### How it works
|
|
67
|
+
|
|
68
|
+
1. **Trigger** — pushes to `main` (or manual dispatch)
|
|
69
|
+
2. **Build** — runs `pnpm build`, producing stable filenames (`portal.js`, `portal.css`)
|
|
70
|
+
3. **Upload** — syncs `dist/` to `gs://portals-cdn/{{projectName}}/assets/` via `gcloud storage rsync`
|
|
71
|
+
4. **Invalidate** — clears the CDN cache for your tenant's asset prefix
|
|
72
|
+
|
|
73
|
+
### Setup
|
|
74
|
+
|
|
75
|
+
1. Create a GCP service account with Storage Object Admin and Compute Load Balancer Admin roles
|
|
76
|
+
2. Add the service account JSON key as a GitHub Actions secret named `GCP_SA_JSON`
|
|
77
|
+
3. Set `CDN_HOSTNAME` in `.github/workflows/deploy.yml` to your Cloud CDN load balancer's frontend domain
|
|
78
|
+
4. Update `GCP_PROJECT` and `CDN_URL_MAP` if your project differs from the defaults
|
|
79
|
+
|
|
80
|
+
### Environment variables
|
|
81
|
+
|
|
82
|
+
| Variable | Where | Purpose |
|
|
83
|
+
|----------|-------|---------|
|
|
84
|
+
| `GCP_SA_JSON` | GitHub secret | GCP service account credentials |
|
|
85
|
+
| `GCP_PROJECT` | Workflow env | Google Cloud project ID |
|
|
86
|
+
| `CDN_URL_MAP` | Workflow env | Cloud CDN URL map name for cache invalidation |
|
|
87
|
+
| `CDN_HOSTNAME` | Workflow env | CDN frontend domain (load balancer hostname) |
|
|
88
|
+
| `VITE_ASSET_BASE` | Build env | Derived from `CDN_HOSTNAME` (set automatically) |
|
|
89
|
+
|
|
62
90
|
## Customization
|
|
63
91
|
|
|
64
92
|
### Modify Navigation
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@fluid-app/portal-sdk/vite";
|
|
9
9
|
|
|
10
10
|
export default defineConfig({
|
|
11
|
+
base: process.env.VITE_ASSET_BASE ?? "/",
|
|
11
12
|
plugins: [
|
|
12
13
|
react(),
|
|
13
14
|
tailwindcss(),
|
|
@@ -23,6 +24,14 @@ export default defineConfig({
|
|
|
23
24
|
preview: fileURLToPath(new URL("preview.html", import.meta.url)),
|
|
24
25
|
},
|
|
25
26
|
output: {
|
|
27
|
+
entryFileNames: (chunk) =>
|
|
28
|
+
chunk.name === "preview" ? "preview.js" : "portal.js",
|
|
29
|
+
assetFileNames: (asset) =>
|
|
30
|
+
asset.names?.some((n) => n.endsWith(".css"))
|
|
31
|
+
? "portal.[ext]"
|
|
32
|
+
: "assets/[name].[ext]",
|
|
33
|
+
chunkFileNames: (chunk) =>
|
|
34
|
+
`${chunk.name.replace(/-[A-Za-z0-9_-]{8}$/, "")}.js`,
|
|
26
35
|
manualChunks(id) {
|
|
27
36
|
if (
|
|
28
37
|
id.includes("node_modules/react-dom") ||
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pull-CxQwoQrZ.mjs","names":["fluidOs.listFluidOSDefinitions","fluidOs.listFluidOSScreens","fluidOs.listFluidOSThemes","fluidOs.listFluidOSNavigations","fluidOs.listFluidOSProfiles","fluidOs.getFluidOSScreen","fluidOs.listFluidOSNavigationItems"],"sources":["../../../platform/api-client-core/src/fetch-client.ts","../../../api-clients/fluidos/src/namespaces/fluid_os.ts","../src/utils/atomic-write.ts","../src/utils/mappings.ts","../src/utils/snapshot.ts","../src/utils/transform.ts","../src/commands/pull.ts"],"sourcesContent":["/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n public readonly data: unknown;\n\n constructor(message: string, status: number, data?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): { name: string; message: string; status: number; data: unknown } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n };\n }\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n } = config;\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]\n value.forEach((item) => queryString.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n const msg = (data.message || data.error_message) as string | undefined;\n throw new ApiError(\n msg || `${method} request failed`,\n response.status,\n data.errors || data,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const data = await response.json();\n return data as TResponse;\n } catch {\n try {\n // API declared JSON content-type but body isn't valid JSON\n const text = await response.text();\n return text as TResponse;\n } catch {\n return null as TResponse;\n }\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const { method = \"POST\", headers: customHeaders, signal } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (signal) fetchOptions.signal = signal;\n response = await fetch(url, fetchOptions);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","/**\n * Generated API client functions for fluid_os\n *\n * DO NOT EDIT THIS FILE DIRECTLY\n * This file is auto-generated. To update:\n * 1. Update the OpenAPI spec file\n * 2. Run: pnpm generate\n */\n\nimport type { FetchClient } from \"../lib/fetch-client\";\nimport type { operations } from \"../generated/fluid_os\";\n\n// ============================================================================\n// Fluid OS - Definitions\n// ============================================================================\n\n/**\n * List Fluid OS definitions\n * Retrieve a list of Fluid OS definitions for the current company\n *\n * @param client - Fetch client instance\n * @param params? - params?\n */\nexport async function listFluidOSDefinitions(\n client: FetchClient,\n params?: operations[\"listFluidOSDefinitions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSDefinitions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/company/fluid_os/definitions`, params);\n}\n\n/**\n * Create a Fluid OS definition\n * Create a new Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createFluidOSDefinition(\n client: FetchClient,\n body: operations[\"createFluidOSDefinition\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/company/fluid_os/definitions`, body);\n}\n\n/**\n * Get a Fluid OS definition\n * Retrieve a specific Fluid OS definition with all associations\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getFluidOSDefinition(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/company/fluid_os/definitions/${id}`);\n}\n\n/**\n * Update a Fluid OS definition\n * Update an existing Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSDefinition(\n client: FetchClient,\n id: string | number,\n body: operations[\"updateFluidOSDefinition\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(`/api/company/fluid_os/definitions/${id}`, body);\n}\n\n/**\n * Delete a Fluid OS definition\n * Delete a Definition\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteFluidOSDefinition(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSDefinition\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/company/fluid_os/definitions/${id}`);\n}\n\n// ============================================================================\n// Fluid OS - Navigation Items\n// ============================================================================\n\n/**\n * List navigation items for a navigation\n * Retrieve a list of navigation items for a specific navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n */\nexport async function listFluidOSNavigationItems(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n): Promise<\n operations[\"listFluidOSNavigationItems\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items`,\n );\n}\n\n/**\n * Create a navigation item\n * Create a new navigation item for a navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param body - body\n */\nexport async function createFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n body: operations[\"createFluidOSNavigationItem\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items`,\n body,\n );\n}\n\n/**\n * Get a specific navigation item\n * Retrieve a specific navigation item\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param id - id\n */\nexport async function getFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`,\n );\n}\n\n/**\n * Update a navigation item\n * Update an existing navigation item\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSNavigationItem\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`,\n body,\n );\n}\n\n/**\n * Delete a navigation item\n * Delete a navigation item\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param navigation_id - navigation_id\n * @param id - id\n */\nexport async function deleteFluidOSNavigationItem(\n client: FetchClient,\n definition_id: string | number,\n navigation_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSNavigationItem\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Navigations\n// ============================================================================\n\n/**\n * List navigations for a Fluid OS definition\n * Retrieve a list of navigations for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param params? - params?\n */\nexport async function listFluidOSNavigations(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSNavigations\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSNavigations\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations`,\n params,\n );\n}\n\n/**\n * Create a navigation for a Fluid OS definition\n * Create a new navigation for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSNavigation\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/navigations`,\n body,\n );\n}\n\n/**\n * Get a specific navigation\n * Retrieve a specific navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`,\n );\n}\n\n/**\n * Update a navigation\n * Update an existing navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSNavigation\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`,\n body,\n );\n}\n\n/**\n * Delete a navigation\n * Delete a navigation\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSNavigation(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSNavigation\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Profiles\n// ============================================================================\n\n/**\n * List profiles for a Fluid OS definition\n * Retrieve a list of profiles for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param params? - params?\n */\nexport async function listFluidOSProfiles(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSProfiles\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSProfiles\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/profiles`,\n params,\n );\n}\n\n/**\n * Create a profile for a Fluid OS definition\n * Create a new profile for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSProfile\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/profiles`,\n body,\n );\n}\n\n/**\n * Get the default profile\n * Retrieve the default profile for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n */\nexport async function getDefaultFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n): Promise<\n operations[\"getDefaultFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/default`,\n );\n}\n\n/**\n * Get a specific profile\n * Retrieve a specific profile\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`,\n );\n}\n\n/**\n * Update a profile\n * Update an existing profile\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSProfile\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`,\n body,\n );\n}\n\n/**\n * Delete a profile\n * Delete a profile\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSProfile(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSProfile\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Public\n// ============================================================================\n\n/**\n * Get active Fluid OS definition\n * Retrieve the active Fluid OS definition manifest for a specific platform\n *\n * @param client - Fetch client instance\n * @param params - params\n */\nexport async function getFluidOSManifest(\n client: FetchClient,\n params: operations[\"getFluidOSManifest\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getFluidOSManifest\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/fluid_os/definitions/active`, params);\n}\n\n// ============================================================================\n// Fluid OS - Screens\n// ============================================================================\n\n/**\n * List screens for a Fluid OS definition\n * Retrieve a list of screens for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param params? - params?\n */\nexport async function listFluidOSScreens(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSScreens\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSScreens\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/screens`,\n params,\n );\n}\n\n/**\n * Create a screen for a Fluid OS definition\n * Create a new screen for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSScreen\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/screens`,\n body,\n );\n}\n\n/**\n * Get a specific screen\n * Retrieve a specific screen\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/screens/${id}`,\n );\n}\n\n/**\n * Update a screen\n * Update an existing screen\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSScreen\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/screens/${id}`,\n body,\n );\n}\n\n/**\n * Delete a screen\n * Delete a screen\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSScreen(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSScreen\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/screens/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Themes\n// ============================================================================\n\n/**\n * List themes for a Fluid OS definition\n * Retrieve a list of themes for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param params? - params?\n */\nexport async function listFluidOSThemes(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/themes`,\n params,\n );\n}\n\n/**\n * Create a theme for a Fluid OS definition\n * Create a new theme for a Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param body - body\n */\nexport async function createFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n body: operations[\"createFluidOSTheme\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/themes`,\n body,\n );\n}\n\n/**\n * Get a specific theme\n * Retrieve a specific theme\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/themes/${id}`,\n );\n}\n\n/**\n * Update a theme\n * Update an existing theme\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSTheme\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/themes/${id}`,\n body,\n );\n}\n\n/**\n * Delete a theme\n * Delete a theme\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function deleteFluidOSTheme(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"deleteFluidOSTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/company/fluid_os/definitions/${definition_id}/themes/${id}`,\n );\n}\n\n// ============================================================================\n// Fluid OS - Versions\n// ============================================================================\n\n/**\n * List versions for a Fluid OS definition\n * Retrieve a list of published versions for a specific Fluid OS definition\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param params? - params?\n */\nexport async function listFluidOSVersions(\n client: FetchClient,\n definition_id: string | number,\n params?: operations[\"listFluidOSVersions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFluidOSVersions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/versions`,\n params,\n );\n}\n\n/**\n * Publish a new version of a Fluid OS definition\n * Publish a new version of the Fluid OS definition. This creates a snapshot of the current definition state including all screens, profiles, themes, and navigations. No request body is required - the manifest is built automatically from the current definition state.\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n */\nexport async function createFluidOSVersion(\n client: FetchClient,\n definition_id: string | number,\n): Promise<\n operations[\"createFluidOSVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/company/fluid_os/definitions/${definition_id}/versions`,\n );\n}\n\n/**\n * Get a specific version\n * Retrieve a specific published version including its manifest\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n */\nexport async function getFluidOSVersion(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n): Promise<\n operations[\"getFluidOSVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/company/fluid_os/definitions/${definition_id}/versions/${id}`,\n );\n}\n\n/**\n * Update a version\n * Update a version. Currently only supports activating/deactivating a version.\n *\n * @param client - Fetch client instance\n * @param definition_id - definition_id\n * @param id - id\n * @param body - body\n */\nexport async function updateFluidOSVersion(\n client: FetchClient,\n definition_id: string | number,\n id: string | number,\n body: operations[\"updateFluidOSVersion\"][\"requestBody\"][\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateFluidOSVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/company/fluid_os/definitions/${definition_id}/versions/${id}`,\n body,\n );\n}\n","/**\n * Atomic file write utility.\n *\n * Uses a write-then-rename pattern so readers always see either the old\n * file or the complete new file — never a partially-written state.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { rename, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Write a file atomically using a write-then-rename pattern.\n * Ensures readers always see either the old file or the complete new file.\n *\n * If `rename` fails after the temp file has been written, the temp file\n * is cleaned up on a best-effort basis before re-throwing the error.\n */\nexport async function atomicWriteFile(\n filePath: string,\n data: string,\n): Promise<void> {\n const dir = dirname(filePath);\n const tmp = join(dir, `.tmp-${randomBytes(6).toString(\"hex\")}`);\n try {\n await writeFile(tmp, data, \"utf-8\");\n await rename(tmp, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file\n await unlink(tmp).catch(() => {});\n throw err;\n }\n}\n","/**\n * Slug-to-ID mapping system for portal content sync.\n *\n * Maps human-readable slugs (derived from resource names) to API IDs,\n * enabling the file system to use readable directory/file names while\n * maintaining the link back to server-side resources.\n *\n * Persisted in `.portal-sync/mappings.json`.\n */\n\nimport { readFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Definition metadata stored at the top level of the mappings file. */\nexport interface DefinitionMapping {\n readonly name: string;\n readonly id: number;\n}\n\n/**\n * Resource types that support slug-to-ID mappings.\n * \"countries\" and \"ranks\" use display names as keys (not slugified).\n */\nexport type MappedResourceType =\n | \"screens\"\n | \"themes\"\n | \"navigations\"\n | \"profiles\"\n | \"countries\"\n | \"ranks\";\n\n/** Full mappings structure persisted to disk. */\nexport interface PortalMappings {\n readonly definition: DefinitionMapping;\n readonly screens: Record<string, number>;\n readonly themes: Record<string, number>;\n readonly navigations: Record<string, number>;\n readonly profiles: Record<string, number>;\n readonly countries: Record<string, number>;\n readonly ranks: Record<string, number>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Runtime check that a parsed value has the expected PortalMappings shape. */\nfunction isMappings(value: unknown): value is PortalMappings {\n if (!value || typeof value !== \"object\") return false;\n const m = value as Record<string, unknown>;\n\n // Validate inner DefinitionMapping fields\n const def = m.definition as Record<string, unknown> | null | undefined;\n if (!def || typeof def !== \"object\") return false;\n if (typeof def.name !== \"string\" || typeof def.id !== \"number\") return false;\n\n return (\n typeof m.screens === \"object\" &&\n m.screens !== null &&\n typeof m.themes === \"object\" &&\n m.themes !== null &&\n typeof m.navigations === \"object\" &&\n m.navigations !== null &&\n typeof m.profiles === \"object\" &&\n m.profiles !== null &&\n typeof m.countries === \"object\" &&\n m.countries !== null &&\n typeof m.ranks === \"object\" &&\n m.ranks !== null\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Read / Write\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst MAPPINGS_FILE = \"mappings.json\";\n\n/**\n * Read the mappings file from the `.portal-sync/` directory.\n * Returns `null` if the file does not exist.\n */\nexport async function readMappings(\n portalSyncDir: string,\n): Promise<PortalMappings | null> {\n try {\n const filePath = join(portalSyncDir, MAPPINGS_FILE);\n const content = await readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(content);\n if (!isMappings(parsed)) {\n throw new Error(`Malformed mappings file: ${filePath}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Write the mappings file to the `.portal-sync/` directory.\n * Creates the directory if it does not exist.\n */\nexport async function writeMappings(\n portalSyncDir: string,\n mappings: PortalMappings,\n): Promise<void> {\n const filePath = join(portalSyncDir, MAPPINGS_FILE);\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, JSON.stringify(mappings, null, 2) + \"\\n\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slug derivation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Derive a filesystem-safe slug from a resource name.\n *\n * - Lowercases the name\n * - Replaces non-alphanumeric characters (except hyphens) with hyphens\n * - Collapses consecutive hyphens\n * - Trims leading/trailing hyphens\n *\n * If the derived slug collides with an existing slug in `existingSlugs`,\n * a numeric suffix is appended (e.g., `home-2`, `home-3`).\n */\nexport function deriveSlug(\n name: string,\n existingSlugs: ReadonlySet<string> = new Set(),\n): string {\n const base =\n name\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\") || \"unnamed\";\n\n if (!existingSlugs.has(base)) {\n return base;\n }\n\n // Collision detected — append a numeric suffix\n let counter = 2;\n while (existingSlugs.has(`${base}-${counter}`)) {\n counter++;\n }\n return `${base}-${counter}`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Lookup helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve a slug to its server-side ID.\n * Returns `undefined` if the slug is not mapped.\n */\nexport function resolveSlugToId(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n): number | undefined {\n return mappings[resourceType][slug];\n}\n\n/**\n * Resolve a server-side ID back to its slug.\n * Returns `undefined` if no slug maps to the given ID.\n */\nexport function resolveIdToSlug(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n id: number,\n): string | undefined {\n const entries = mappings[resourceType];\n for (const [slug, mappedId] of Object.entries(entries)) {\n if (mappedId === id) {\n return slug;\n }\n }\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Mutation helpers (return new objects — no mutation)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Add or update a mapping entry for a given resource type.\n * Returns a new `PortalMappings` object (immutable update).\n */\nexport function updateMapping(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n id: number,\n): PortalMappings {\n return {\n ...mappings,\n [resourceType]: {\n ...mappings[resourceType],\n [slug]: id,\n },\n };\n}\n\n/**\n * Remove a mapping entry for a given resource type.\n * Returns a new `PortalMappings` object (immutable update).\n * If the slug does not exist, the original mappings are returned unchanged.\n */\nexport function removeMapping(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n): PortalMappings {\n if (!(slug in mappings[resourceType])) {\n return mappings;\n }\n\n const { [slug]: _removed, ...rest } = mappings[resourceType];\n return {\n ...mappings,\n [resourceType]: rest,\n };\n}\n","/**\n * Hash-based snapshot system for portal content sync.\n *\n * Tracks file hashes to detect local changes since the last pull,\n * enabling efficient push operations that only send modified files.\n *\n * Persisted in `.portal-sync/snapshot.json`.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, readdir, mkdir, stat } from \"node:fs/promises\";\nimport { join, dirname, relative, sep } from \"node:path\";\n\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Maximum number of concurrent file operations. */\nconst MAX_CONCURRENCY = 20;\n\n/** SHA-256 hex digest of a file's contents. */\nexport type FileHash = string;\n\n/** Map of relative file paths to their content hashes. */\nexport type FileHashMap = Record<string, FileHash>;\n\n/** Snapshot structure persisted to `.portal-sync/snapshot.json`. */\nexport interface Snapshot {\n /** Human-readable name of the portal definition. */\n readonly definition: string;\n /** Server-side ID of the portal definition. */\n readonly definition_id: number;\n /** ISO 8601 timestamp of when the snapshot was created. */\n readonly pulled_at: string;\n /** Map of relative file paths → SHA-256 hashes. */\n readonly files: FileHashMap;\n}\n\n/** Result of diffing current files against a snapshot. */\nexport interface SnapshotDiff {\n /** Files that exist on disk but not in the snapshot. */\n readonly new: readonly string[];\n /** Files that exist in both but have different hashes. */\n readonly changed: readonly string[];\n /** Files that exist in the snapshot but not on disk. */\n readonly deleted: readonly string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Run async tasks with bounded concurrency to avoid exhausting file descriptors.\n */\nasync function mapWithLimit<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let index = 0;\n\n async function worker(): Promise<void> {\n while (index < items.length) {\n const i = index++;\n results[i] = await fn(items[i]!);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () =>\n worker(),\n );\n await Promise.all(workers);\n return results;\n}\n\n/** Runtime check that a parsed value has the expected Snapshot shape. */\nfunction isSnapshot(value: unknown): value is Snapshot {\n if (!value || typeof value !== \"object\") return false;\n const s = value as Record<string, unknown>;\n return (\n typeof s.definition === \"string\" &&\n typeof s.definition_id === \"number\" &&\n typeof s.pulled_at === \"string\" &&\n typeof s.files === \"object\" &&\n s.files !== null\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute the SHA-256 hash of a file's contents.\n * Returns the hex-encoded digest.\n */\nexport async function computeFileHash(filePath: string): Promise<FileHash> {\n const content = await readFile(filePath);\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Read / Write\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SNAPSHOT_FILE = \"snapshot.json\";\n\n/**\n * Read the snapshot file from the `.portal-sync/` directory.\n * Returns `null` if the file does not exist.\n */\nexport async function readSnapshot(\n portalSyncDir: string,\n): Promise<Snapshot | null> {\n try {\n const filePath = join(portalSyncDir, SNAPSHOT_FILE);\n const content = await readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(content);\n if (!isSnapshot(parsed)) {\n throw new Error(`Malformed snapshot file: ${filePath}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Write the snapshot file to the `.portal-sync/` directory.\n * Creates the directory if it does not exist.\n */\nexport async function writeSnapshot(\n portalSyncDir: string,\n snapshot: Snapshot,\n): Promise<void> {\n const filePath = join(portalSyncDir, SNAPSHOT_FILE);\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, JSON.stringify(snapshot, null, 2) + \"\\n\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Diff\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Recursively collect all files in a directory, returning paths\n * relative to `baseDir`.\n */\nasync function collectFiles(\n dir: string,\n baseDir: string = dir,\n): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n // Skip .portal-sync directory\n if (entry.name === \".portal-sync\") continue;\n\n if (entry.isSymbolicLink()) {\n // Resolve symlink to determine if it points to a file or directory\n const realStat = await stat(fullPath);\n if (realStat.isDirectory()) {\n files.push(...(await collectFiles(fullPath, baseDir)));\n } else if (realStat.isFile()) {\n files.push(relative(baseDir, fullPath).split(sep).join(\"/\"));\n }\n } else if (entry.isDirectory()) {\n files.push(...(await collectFiles(fullPath, baseDir)));\n } else {\n files.push(relative(baseDir, fullPath).split(sep).join(\"/\"));\n }\n }\n\n return files;\n}\n\n/**\n * Compare the current portal directory contents against a snapshot.\n *\n * Returns lists of new, changed, and deleted files (relative paths).\n */\nexport async function diffAgainstSnapshot(\n portalDir: string,\n snapshot: Snapshot,\n): Promise<SnapshotDiff> {\n const currentFiles = await collectFiles(portalDir);\n\n const snapshotPaths = new Set(Object.keys(snapshot.files));\n\n const newFiles: string[] = [];\n const changedFiles: string[] = [];\n\n // Check current files against snapshot (bounded concurrency)\n await mapWithLimit(currentFiles, MAX_CONCURRENCY, async (filePath) => {\n const hash = await computeFileHash(join(portalDir, filePath));\n\n if (!snapshotPaths.has(filePath)) {\n newFiles.push(filePath);\n } else if (snapshot.files[filePath] !== hash) {\n changedFiles.push(filePath);\n }\n });\n\n // Find deleted files (in snapshot but not on disk)\n const currentSet = new Set(currentFiles);\n const deletedFiles = [...snapshotPaths].filter(\n (filePath) => !currentSet.has(filePath),\n );\n\n return {\n new: [...newFiles].sort(),\n changed: [...changedFiles].sort(),\n deleted: [...deletedFiles].sort(),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Build\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build a fresh snapshot from the current portal directory contents.\n *\n * Hashes every file in `portalDir` (excluding `.portal-sync/`)\n * and records the definition metadata.\n */\nexport async function buildSnapshot(\n portalDir: string,\n definitionName: string,\n definitionId: number,\n): Promise<Snapshot> {\n const files = await collectFiles(portalDir);\n\n const hashEntries = await mapWithLimit(\n files,\n MAX_CONCURRENCY,\n async (filePath) => {\n const hash = await computeFileHash(join(portalDir, filePath));\n return [filePath, hash] as const;\n },\n );\n\n // Sort entries for deterministic output\n hashEntries.sort(([a], [b]) => a.localeCompare(b));\n\n const fileHashes: Record<string, FileHash> = {};\n for (const [path, hash] of hashEntries) {\n fileHashes[path] = hash;\n }\n\n return {\n definition: definitionName,\n definition_id: definitionId,\n pulled_at: new Date().toISOString(),\n files: fileHashes,\n };\n}\n","/**\n * Transformation utilities for converting Fluid OS API responses\n * into the local file format used by the portal content sync system.\n */\n\nimport type { components } from \"@fluid-app/fluidos-api-client\";\nimport { deriveSlug } from \"./mappings\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API types (aliases for readability)\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiScreen = components[\"schemas\"][\"FluidOSScreen\"];\ntype ApiTheme = components[\"schemas\"][\"FluidOSTheme\"];\ntype ApiNavigationBasic = components[\"schemas\"][\"FluidOSNavigationBasic\"];\ntype ApiNavigationItem = components[\"schemas\"][\"FluidOSNavigationItem\"];\ntype ApiProfile = components[\"schemas\"][\"FluidOSProfile\"];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Local file types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LocalScreen {\n readonly name: string;\n readonly component_tree: Record<string, unknown>[];\n}\n\nexport interface LocalTheme {\n readonly name: string;\n readonly config: Record<string, unknown>;\n readonly active: boolean;\n}\n\nexport interface LocalNavigationItem {\n readonly id: number;\n readonly icon: string | null;\n readonly label: string | null;\n readonly screen: string | null;\n readonly slug: string | null;\n readonly source: string;\n readonly position: number | null;\n readonly parent_id: number | null;\n readonly children: LocalNavigationItem[];\n}\n\nexport interface LocalNavigation {\n readonly name: string;\n readonly platform: string;\n readonly navigation_items: LocalNavigationItem[];\n}\n\nexport interface LocalProfile {\n readonly name: string;\n readonly default: boolean;\n readonly navigation: string | null;\n readonly mobile_navigation: string | null;\n readonly themes: string[];\n readonly permissions: {\n readonly ranks: number[];\n readonly roles: string[];\n readonly platform: string[];\n readonly countries: number[];\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Transform an API screen response into the local file format.\n * Strips server-only fields and normalizes `component_tree` to always be an array.\n */\nexport function transformScreen(screen: ApiScreen): LocalScreen {\n const name = screen.name ?? \"\";\n const rawTree = screen.component_tree;\n\n // Normalize component_tree to always be an array\n let componentTree: Record<string, unknown>[];\n if (rawTree == null) {\n componentTree = [];\n } else if (Array.isArray(rawTree)) {\n componentTree = rawTree as Record<string, unknown>[];\n } else {\n componentTree = [rawTree];\n }\n\n return { name, component_tree: componentTree };\n}\n\n/**\n * Derive a slug for a screen. Screens use the existing `slug` field from the API.\n * Falls back to slugifying the name if no slug exists.\n */\nexport function deriveScreenSlug(\n screen: ApiScreen,\n existingSlugs: Set<string>,\n): string {\n if (screen.slug) {\n // Sanitize: strip path-separator and other unsafe characters before using\n // the API slug as a filesystem path segment.\n const safe = screen.slug.replace(/[^a-z0-9_-]/gi, \"-\");\n if (!existingSlugs.has(safe)) {\n return safe;\n }\n return deriveSlug(safe, existingSlugs);\n }\n return deriveSlug(screen.name ?? \"unnamed\", existingSlugs);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Theme transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Transform an API theme response into the local file format.\n * Passes theme config through as-is (hex conversion is a separate ticket).\n */\nexport function transformTheme(theme: ApiTheme): LocalTheme {\n return {\n name: theme.name ?? \"\",\n config: theme.config ?? {},\n active: theme.active ?? false,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Navigation transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build an ID → slug lookup map by inverting a slug → id mapping.\n */\nexport function buildIdToSlugMap(\n mappings: Record<string, number>,\n): Map<number, string> {\n const map = new Map<number, string>();\n for (const [slug, id] of Object.entries(mappings)) {\n map.set(id, slug);\n }\n return map;\n}\n\n/**\n * Transform API navigation items into local format.\n * Converts `screen_id` to `screen` slug reference. Keeps `id` for sync.\n */\nexport function transformNavigationItems(\n items: ApiNavigationItem[],\n screenIdToSlug: Map<number, string>,\n): LocalNavigationItem[] {\n return items.map((item) => ({\n id: item.id,\n icon: item.icon ?? null,\n label: item.label ?? null,\n screen: item.screen_id\n ? (screenIdToSlug.get(item.screen_id) ?? null)\n : null,\n slug: item.slug ?? null,\n source: item.source,\n position: item.position ?? null,\n parent_id: item.parent_id ?? null,\n children: transformNavigationItems(item.children ?? [], screenIdToSlug),\n }));\n}\n\n/**\n * Transform an API navigation with its items into the local file format.\n */\nexport function transformNavigation(\n nav: ApiNavigationBasic,\n items: ApiNavigationItem[],\n screenIdToSlug: Map<number, string>,\n): LocalNavigation {\n return {\n name: nav.name ?? \"\",\n platform: nav.platform,\n navigation_items: transformNavigationItems(items, screenIdToSlug),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Profile transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Typed alias for call-site clarity. */\nexport const buildNavigationIdToSlugMap: (\n mappings: Record<string, number>,\n) => Map<number, string> = buildIdToSlugMap;\n\n/** Typed alias for call-site clarity. */\nexport const buildThemeIdToSlugMap: (\n mappings: Record<string, number>,\n) => Map<number, string> = buildIdToSlugMap;\n\n/**\n * Transform an API profile response into the local file format.\n * Converts navigation_id, mobile_navigation_id, and theme_ids to slug references.\n *\n * TODO (REP-842): Country/rank IDs in profile permissions need to be translated\n * to human-readable names via the main Fluid API. For now, the permission arrays\n * are written as-is with integer IDs.\n */\nexport function transformProfile(\n profile: ApiProfile,\n navIdToSlug: Map<number, string>,\n themeIdToSlug: Map<number, string>,\n): LocalProfile {\n const navigation = profile.navigation\n ? (navIdToSlug.get(profile.navigation.id) ?? null)\n : null;\n\n const mobileNavigation = profile.mobile_navigation\n ? (navIdToSlug.get(profile.mobile_navigation.id) ?? null)\n : null;\n\n const themes = (profile.themes ?? [])\n .map((t) => themeIdToSlug.get(t.id))\n .filter((slug): slug is string => slug != null);\n\n const permissions = profile.permissions ?? {};\n\n return {\n name: profile.name ?? \"\",\n default: profile.default ?? false,\n navigation,\n mobile_navigation: mobileNavigation,\n themes,\n permissions: {\n ranks: permissions.ranks ?? [],\n roles: permissions.roles ?? [],\n platform: permissions.platform ?? [],\n countries: permissions.countries ?? [],\n },\n };\n}\n","/**\n * `fluid portal pull` command\n *\n * Pulls a Fluid OS definition's resources (screens, themes, navigations,\n * profiles) from the API and writes them to the local `portal/` directory\n * as JSON files, along with `.portal-sync/` metadata for push diffing.\n */\n\nimport { Command } from \"commander\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport { createFetchClient } from \"@fluid-app/fluidos-api-client\";\nimport type { FetchClient, components } from \"@fluid-app/fluidos-api-client\";\nimport { fluidOs } from \"@fluid-app/fluidos-api-client\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport pLimit from \"p-limit\";\nimport { join } from \"node:path\";\nimport { mkdir, writeFile, rm, rename } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport prompts from \"prompts\";\n\nimport { deriveSlug, writeMappings } from \"../utils/mappings.js\";\nimport type { PortalMappings } from \"../utils/mappings.js\";\nimport {\n readSnapshot,\n diffAgainstSnapshot,\n buildSnapshot,\n writeSnapshot,\n} from \"../utils/snapshot.js\";\nimport {\n transformScreen,\n deriveScreenSlug,\n transformTheme,\n transformNavigation,\n transformProfile,\n buildIdToSlugMap,\n buildNavigationIdToSlugMap,\n buildThemeIdToSlugMap,\n} from \"../utils/transform.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiDefinitionBasic = components[\"schemas\"][\"FluidOSDefinitionBasic\"];\n\ninterface PullOptions {\n app?: string;\n force?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_DIR = \"portal\";\nconst PORTAL_SYNC_DIR = \".portal-sync\";\nconst PAGE_LIMIT = 500;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an authenticated FetchClient using the stored CLI profile.\n */\nfunction createClient(): FetchClient {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n if (!profile) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profile.name) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n\n return createFetchClient({\n baseUrl,\n getAuthToken: () => token,\n });\n}\n\n/**\n * Select a definition interactively or by name.\n */\nasync function fetchAllDefinitions(\n client: FetchClient,\n): Promise<ApiDefinitionBasic[]> {\n const all: ApiDefinitionBasic[] = [];\n let page = 1;\n\n while (true) {\n const response = await fluidOs.listFluidOSDefinitions(client, {\n page,\n per_page: PAGE_LIMIT,\n });\n const batch = response.definitions ?? [];\n all.push(...batch);\n\n const totalPages = response.meta?.total_pages ?? 1;\n if (page >= totalPages || batch.length === 0) break;\n page++;\n }\n\n return all;\n}\n\nasync function selectDefinition(\n client: FetchClient,\n appName?: string,\n): Promise<ApiDefinitionBasic> {\n const definitions = await fetchAllDefinitions(client);\n\n if (definitions.length === 0) {\n throw new Error(\n \"No Fluid OS definitions found. Create one in the admin dashboard first.\",\n );\n }\n\n // If --app flag provided, find by name\n if (appName) {\n const match = definitions.find(\n (d) => d.name?.toLowerCase() === appName.toLowerCase(),\n );\n if (!match) {\n const availableNames = definitions\n .map((d) => chalk.cyan(d.name ?? \"(unnamed)\"))\n .join(\", \");\n throw new Error(\n `No definition found matching \"${appName}\". Available: ${availableNames}`,\n );\n }\n return match;\n }\n\n // Interactive selector\n const { definitionId } = await prompts({\n type: \"select\",\n name: \"definitionId\",\n message: \"Select a Fluid OS definition to pull\",\n choices: definitions.map((d) => ({\n title: d.name ?? \"(unnamed)\",\n value: d.id,\n })),\n });\n\n if (definitionId == null) {\n throw new Error(\"No definition selected.\");\n }\n\n const selected = definitions.find((d) => d.id === definitionId);\n if (!selected) {\n throw new Error(\"Selected definition not found.\");\n }\n\n return selected;\n}\n\n/**\n * Check for definition switching conflicts.\n * Returns true if it's safe to proceed.\n */\nasync function checkDefinitionSwitch(\n cwd: string,\n newDefinitionId: number,\n newDefinitionName: string,\n force: boolean,\n): Promise<boolean> {\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n // No existing portal directory — always safe\n if (!existsSync(portalDir)) {\n return true;\n }\n\n // Read existing snapshot to see what definition was pulled\n const snapshot = await readSnapshot(portalSyncDir);\n if (!snapshot) {\n // No snapshot means no prior pull — safe to overwrite\n return true;\n }\n\n // Same definition — this is a refresh\n if (snapshot.definition_id === newDefinitionId) {\n return true;\n }\n\n // Different definition — check for local changes\n const diff = await diffAgainstSnapshot(portalDir, snapshot);\n const hasChanges =\n diff.new.length > 0 || diff.changed.length > 0 || diff.deleted.length > 0;\n\n if (hasChanges && !force) {\n console.log();\n console.log(\n chalk.yellow(\"Warning:\") +\n \" Switching from \" +\n chalk.cyan(snapshot.definition) +\n \" to \" +\n chalk.cyan(newDefinitionName) +\n \" with unpushed local changes.\",\n );\n console.log();\n console.log(\" Modified: \" + diff.changed.length + \" file(s)\");\n console.log(\" New: \" + diff.new.length + \" file(s)\");\n console.log(\" Deleted: \" + diff.deleted.length + \" file(s)\");\n console.log();\n console.log(\n \"Use \" +\n chalk.cyan(\"--force\") +\n \" to discard local changes and switch definitions.\",\n );\n return false;\n }\n\n return true;\n}\n\n/**\n * Write a JSON file to the portal directory, creating subdirectories as needed.\n */\nasync function writePortalFile(\n portalDir: string,\n relativePath: string,\n data: unknown,\n): Promise<void> {\n const filePath = join(portalDir, relativePath);\n const dir = join(filePath, \"..\");\n await mkdir(dir, { recursive: true });\n await writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const pullCommand: Command = new Command(\"pull\")\n .description(\n \"Pull a Fluid OS definition's resources to the local portal/ directory\",\n )\n .option(\"--app <name>\", \"Definition name (skips interactive selector)\")\n .option(\"--force\", \"Overwrite local changes when switching definitions\")\n .action(async (options: PullOptions) => {\n const cwd = process.cwd();\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Pull\"));\n console.log();\n\n // ── Authenticate ──────────────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Authenticating...\");\n\n let client: FetchClient;\n try {\n client = createClient();\n spinner.succeed(\"Authenticated\");\n } catch (err) {\n spinner.fail(\"Authentication failed\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Select definition ─────────────────────────────────────────────\n let definition: ApiDefinitionBasic;\n try {\n definition = await selectDefinition(client, options.app);\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n const definitionId = definition.id;\n const definitionName = definition.name ?? \"(unnamed)\";\n\n console.log(\n chalk.gray(\"Definition: \") +\n chalk.white(definitionName) +\n chalk.gray(` (ID: ${definitionId})`),\n );\n console.log();\n\n // ── Check definition switch ───────────────────────────────────────\n let canProceed: boolean;\n try {\n canProceed = await checkDefinitionSwitch(\n cwd,\n definitionId,\n definitionName,\n options.force ?? false,\n );\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n if (!canProceed) {\n process.exit(1);\n }\n\n // ── Fetch all resources in parallel ───────────────────────────────\n spinner.start(\"Fetching resources...\");\n\n let screenBasics: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSScreens>>[\"screens\"]\n >;\n let themes: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSThemes>>[\"themes\"]\n >;\n let navigations: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSNavigations>>[\"navigations\"]\n >;\n let profiles: NonNullable<\n Awaited<ReturnType<typeof fluidOs.listFluidOSProfiles>>[\"profiles\"]\n >;\n let screens: NonNullable<\n Awaited<ReturnType<typeof fluidOs.getFluidOSScreen>>[\"screen\"]\n >[];\n const navigationItemsMap = new Map<\n number,\n components[\"schemas\"][\"FluidOSNavigationItem\"][]\n >();\n\n try {\n const [\n screensResponse,\n themesResponse,\n navigationsResponse,\n profilesResponse,\n ] = await Promise.all([\n fluidOs.listFluidOSScreens(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOs.listFluidOSThemes(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOs.listFluidOSNavigations(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOs.listFluidOSProfiles(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n ]);\n\n screenBasics = screensResponse.screens ?? [];\n themes = themesResponse.themes ?? [];\n navigations = navigationsResponse.navigations ?? [];\n profiles = profilesResponse.profiles ?? [];\n\n // Warn if any list hits the page limit\n if (screenBasics.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: screen count hit the ${PAGE_LIMIT}-item limit; some screens may be missing.`,\n ),\n );\n }\n if (themes.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: theme count hit the ${PAGE_LIMIT}-item limit; some themes may be missing.`,\n ),\n );\n }\n if (navigations.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: navigation count hit the ${PAGE_LIMIT}-item limit; some navigations may be missing.`,\n ),\n );\n }\n if (profiles.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: profile count hit the ${PAGE_LIMIT}-item limit; some profiles may be missing.`,\n ),\n );\n }\n\n spinner.text = \"Fetching screen details...\";\n\n // Limit concurrency to avoid hitting API rate limits\n const limit = pLimit(10);\n\n // Fetch full screen details (list endpoint doesn't include component_tree)\n screens = await Promise.all(\n screenBasics.map((s) =>\n limit(async () => {\n const res = await fluidOs.getFluidOSScreen(\n client,\n definitionId,\n s.id,\n );\n if (!res.screen) {\n throw new Error(`Failed to fetch details for screen ID ${s.id}`);\n }\n return res.screen;\n }),\n ),\n );\n\n // Fetch navigation items for each navigation.\n // NOTE: The listFluidOSNavigationItems API endpoint does not support\n // pagination query parameters (per_page/page). Navigation items are\n // returned as a nested tree structure and are not paginated.\n spinner.text = \"Fetching navigation items...\";\n await Promise.all(\n navigations.map((nav) =>\n limit(async () => {\n const res = await fluidOs.listFluidOSNavigationItems(\n client,\n definitionId,\n nav.id,\n );\n navigationItemsMap.set(nav.id, res.navigation_items ?? []);\n }),\n ),\n );\n\n spinner.succeed(\n `Fetched ${screens.length} screen(s), ${themes.length} theme(s), ${navigations.length} navigation(s), ${profiles.length} profile(s)`,\n );\n } catch (err) {\n spinner.fail(\"Failed to fetch resources\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Build slug mappings ───────────────────────────────────────────\n spinner.start(\"Transforming resources...\");\n\n // Screen mappings: use existing slug field when available\n const screenSlugs = new Set<string>();\n const screenMappings: Record<string, number> = {};\n for (const screen of screens) {\n const slug = deriveScreenSlug(screen, screenSlugs);\n screenSlugs.add(slug);\n screenMappings[slug] = screen.id;\n }\n\n // Theme mappings: slugify from name\n const themeSlugs = new Set<string>();\n const themeMappings: Record<string, number> = {};\n for (const theme of themes) {\n const slug = deriveSlug(theme.name ?? \"unnamed\", themeSlugs);\n themeSlugs.add(slug);\n themeMappings[slug] = theme.id;\n }\n\n // Navigation mappings: slugify from name\n const navSlugs = new Set<string>();\n const navMappings: Record<string, number> = {};\n for (const nav of navigations) {\n const slug = deriveSlug(nav.name ?? \"unnamed\", navSlugs);\n navSlugs.add(slug);\n navMappings[slug] = nav.id;\n }\n\n // Profile mappings: slugify from name\n const profileSlugs = new Set<string>();\n const profileMappings: Record<string, number> = {};\n for (const profile of profiles) {\n const slug = deriveSlug(profile.name ?? \"unnamed\", profileSlugs);\n profileSlugs.add(slug);\n profileMappings[slug] = profile.id;\n }\n\n // Build reverse-lookup maps for ID → slug references\n const screenIdToSlug = buildIdToSlugMap(screenMappings);\n const navIdToSlug = buildNavigationIdToSlugMap(navMappings);\n const themeIdToSlug = buildThemeIdToSlugMap(themeMappings);\n const profileIdToSlug = buildIdToSlugMap(profileMappings);\n\n spinner.succeed(\"Resources transformed\");\n\n // ── Clean and write files ─────────────────────────────────────────\n spinner.start(\"Writing files...\");\n\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n // Write to a temporary directory first, then atomically swap into place.\n // This ensures the existing portal/ directory is only removed once all\n // new content has been successfully written.\n const tmpPortalDir = portalDir + \".tmp\";\n\n try {\n // Clean up any leftover temp directory from a previous failed run\n if (existsSync(tmpPortalDir)) {\n await rm(tmpPortalDir, { recursive: true });\n }\n await mkdir(tmpPortalDir, { recursive: true });\n\n // Write definition.json\n await writePortalFile(tmpPortalDir, \"definition.json\", {\n name: definitionName,\n });\n\n // Write screens\n for (const screen of screens) {\n const slug = screenIdToSlug.get(screen.id)!;\n const local = transformScreen(screen);\n await writePortalFile(tmpPortalDir, `screens/${slug}.json`, local);\n }\n\n // Write themes\n for (const theme of themes) {\n const slug = themeIdToSlug.get(theme.id)!;\n const local = transformTheme(theme);\n await writePortalFile(tmpPortalDir, `themes/${slug}.json`, local);\n }\n\n // Write navigations\n for (const nav of navigations) {\n const slug = navIdToSlug.get(nav.id)!;\n const items = navigationItemsMap.get(nav.id) ?? [];\n const local = transformNavigation(nav, items, screenIdToSlug);\n await writePortalFile(tmpPortalDir, `navigations/${slug}.json`, local);\n }\n\n // Write profiles\n for (const profile of profiles) {\n const slug = profileIdToSlug.get(profile.id);\n if (slug) {\n const local = transformProfile(profile, navIdToSlug, themeIdToSlug);\n await writePortalFile(tmpPortalDir, `profiles/${slug}.json`, local);\n }\n }\n\n // Atomic swap: remove old directory, rename temp into place\n if (existsSync(portalDir)) {\n await rm(portalDir, { recursive: true });\n }\n await rename(tmpPortalDir, portalDir);\n } catch (err) {\n // Clean up temp directory on failure\n if (existsSync(tmpPortalDir)) {\n await rm(tmpPortalDir, { recursive: true }).catch(() => {});\n }\n spinner.fail(\"Failed to write files\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Write metadata (mappings + snapshot) ────────────────────────────\n // Separated from the file-write block so that a metadata failure does\n // not incorrectly report total failure when portal files are already\n // on disk after the atomic swap.\n try {\n const mappings: PortalMappings = {\n definition: { name: definitionName, id: definitionId },\n screens: screenMappings,\n themes: themeMappings,\n navigations: navMappings,\n profiles: profileMappings,\n countries: {},\n ranks: {},\n };\n await writeMappings(portalSyncDir, mappings);\n\n const snapshot = await buildSnapshot(\n portalDir,\n definitionName,\n definitionId,\n );\n await writeSnapshot(portalSyncDir, snapshot);\n\n spinner.succeed(\"Files written\");\n } catch (err) {\n spinner.warn(\"Files written (metadata sync failed)\");\n console.log();\n console.log(\n chalk.yellow(\"Warning:\") +\n \" Portal files were written successfully, but metadata sync failed.\" +\n \" Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" again to regenerate metadata.\",\n );\n console.log(\n chalk.gray(\" Detail: \") +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n }\n\n // ── Summary ───────────────────────────────────────────────────────\n console.log();\n console.log(chalk.green.bold(\"Pull complete!\"));\n console.log();\n console.log(chalk.gray(\" portal/definition.json\"));\n console.log(\n chalk.gray(` portal/screens/ `) +\n chalk.white(`${screens.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/themes/ `) +\n chalk.white(`${themes.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/navigations/ `) +\n chalk.white(`${navigations.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/profiles/ `) +\n chalk.white(`${profiles.length} file(s)`),\n );\n console.log(\n chalk.gray(` .portal-sync/ `) +\n chalk.white(\"mappings.json + snapshot.json\"),\n );\n console.log();\n });\n"],"mappings":"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"}
|