@fluid-app/fluid-cli-portal 0.1.52 → 0.1.54

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.
Files changed (35) hide show
  1. package/README.md +3 -0
  2. package/dist/{backend-dev-plugin-B9-S4Is-.mjs → backend-dev-plugin-CY3yxgJ1.mjs} +1 -1
  3. package/dist/{backend-dev-plugin-B9-S4Is-.mjs.map → backend-dev-plugin-CY3yxgJ1.mjs.map} +1 -1
  4. package/dist/build-manifest.d.mts +91 -0
  5. package/dist/build-manifest.d.mts.map +1 -0
  6. package/dist/build-manifest.mjs +180 -0
  7. package/dist/build-manifest.mjs.map +1 -0
  8. package/dist/index.d.mts +657 -741
  9. package/dist/index.d.mts.map +1 -1
  10. package/dist/index.mjs +1627 -1829
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/portal-dev-plugin-CHlxpXc5.mjs +232 -0
  13. package/dist/portal-dev-plugin-CHlxpXc5.mjs.map +1 -0
  14. package/dist/portal-widget-dev-plugin-B53Lf9Ke.mjs +144 -0
  15. package/dist/portal-widget-dev-plugin-B53Lf9Ke.mjs.map +1 -0
  16. package/dist/pull-CUR8tbW9.mjs +1633 -0
  17. package/dist/pull-CUR8tbW9.mjs.map +1 -0
  18. package/dist/sdk.gen-DuPhfdxN.mjs +469 -0
  19. package/dist/sdk.gen-DuPhfdxN.mjs.map +1 -0
  20. package/dist/src-CpH5SpDH.mjs +2157 -0
  21. package/dist/src-CpH5SpDH.mjs.map +1 -0
  22. package/dist/vite-plugin.d.mts +15 -56
  23. package/dist/vite-plugin.d.mts.map +1 -1
  24. package/dist/vite-plugin.mjs +6 -3
  25. package/package.json +12 -3
  26. package/templates/base/.gitignore.template +6 -0
  27. package/templates/base/.oxlintrc.json +1 -2
  28. package/templates/base/AGENTS.md +4 -0
  29. package/templates/base/skills/fluid-portal-authoring/SKILL.md +238 -10
  30. package/templates/starter/.env.example +4 -3
  31. package/templates/starter/README.md.template +14 -1
  32. package/dist/portal-dev-plugin-BHE8Z4yr.mjs +0 -289
  33. package/dist/portal-dev-plugin-BHE8Z4yr.mjs.map +0 -1
  34. package/dist/pull-XOsFaqz2.mjs +0 -1204
  35. package/dist/pull-XOsFaqz2.mjs.map +0 -1
@@ -1,1204 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
2
- import { Command } from "commander";
3
- import chalk from "chalk";
4
- import ora from "ora";
5
- import { dirname, join, relative, sep } from "node:path";
6
- import { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
7
- import { getActiveProfile, getAuthToken } from "@fluid-app/fluid-cli";
8
- import prompts from "prompts";
9
- import { existsSync } from "node:fs";
10
- import pLimit from "p-limit";
11
- import { createHash, randomBytes } from "node:crypto";
12
- //#region ../../platform/api-client-core/src/fetch-client.ts
13
- /**
14
- * API Error class compatible with fluid-admin's ApiError
15
- */
16
- var ApiError = class ApiError extends Error {
17
- status;
18
- data;
19
- requestId;
20
- constructor(message, status, data, requestId) {
21
- super(message);
22
- this.name = "ApiError";
23
- this.status = status;
24
- this.data = data;
25
- this.requestId = requestId;
26
- if ("captureStackTrace" in Error) Error.captureStackTrace(this, ApiError);
27
- }
28
- toJSON() {
29
- return {
30
- name: this.name,
31
- message: this.message,
32
- status: this.status,
33
- data: this.data,
34
- requestId: this.requestId
35
- };
36
- }
37
- };
38
- function getStringRequestId(value) {
39
- if (typeof value !== "string") return;
40
- const trimmed = value.trim();
41
- return trimmed.length > 0 ? trimmed : void 0;
42
- }
43
- function getRequestIdFromHeaders(headers) {
44
- return getStringRequestId(headers.get("x-request-id")) ?? getStringRequestId(headers.get("request-id")) ?? getStringRequestId(headers.get("X-Request-ID"));
45
- }
46
- function getRequestIdFromJsonBody(body) {
47
- if (!body || typeof body !== "object" || Array.isArray(body)) return;
48
- const record = body;
49
- const meta = record.meta;
50
- return getStringRequestId(record.request_id) ?? getStringRequestId(record.requestId) ?? (meta && typeof meta === "object" && !Array.isArray(meta) ? getStringRequestId(meta.request_id) ?? getStringRequestId(meta.requestId) : void 0);
51
- }
52
- /**
53
- * Creates a configured fetch client instance
54
- */
55
- function createFetchClient(config) {
56
- const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {}, credentials, cache, networkRetry, throwOnInvalidJson = false } = config;
57
- const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);
58
- const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);
59
- /**
60
- * Build headers for a request
61
- */
62
- async function buildHeaders(customHeaders) {
63
- const headers = {
64
- Accept: "application/json",
65
- "Content-Type": "application/json",
66
- ...defaultHeaders,
67
- ...customHeaders
68
- };
69
- if (getAuthToken) {
70
- const token = await getAuthToken();
71
- if (token) headers.Authorization = `Bearer ${token}`;
72
- }
73
- return headers;
74
- }
75
- /**
76
- * Join baseUrl + endpoint via string concatenation (matches fetchApi).
77
- * Using `new URL(endpoint, baseUrl)` would strip any path prefix from
78
- * baseUrl (e.g. "/api") when the endpoint starts with "/".
79
- */
80
- function joinUrl(endpoint) {
81
- return `${baseUrl}${endpoint}`;
82
- }
83
- /**
84
- * Build URL with query parameters for GET requests
85
- * Compatible with fluid-admin's query param handling
86
- */
87
- function buildUrl(endpoint, params) {
88
- const fullUrl = joinUrl(endpoint);
89
- if (!params || Object.keys(params).length === 0) return fullUrl;
90
- const queryString = new URLSearchParams();
91
- Object.entries(params).forEach(([key, value]) => {
92
- if (value === void 0 || value === null) return;
93
- if (Array.isArray(value)) value.forEach((item) => queryString.append(`${key}[]`, String(item)));
94
- else if (typeof value === "object") Object.entries(value).forEach(([subKey, subValue]) => {
95
- if (subValue === void 0 || subValue === null) return;
96
- if (Array.isArray(subValue)) subValue.forEach((item) => queryString.append(`${key}[${subKey}][]`, String(item)));
97
- else queryString.append(`${key}[${subKey}]`, String(subValue));
98
- });
99
- else queryString.append(key, String(value));
100
- });
101
- const qs = queryString.toString();
102
- return qs ? `${fullUrl}?${qs}` : fullUrl;
103
- }
104
- /**
105
- * Shared response handler for both JSON and FormData requests.
106
- * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.
107
- */
108
- async function handleResponse(response, method, _url) {
109
- const headerRequestId = getRequestIdFromHeaders(response.headers);
110
- if (response.status === 401 && onAuthError) onAuthError();
111
- if (!response.ok) {
112
- const errorText = await response.text().catch(() => "");
113
- if (response.headers.get("content-type")?.includes("application/json")) {
114
- let data;
115
- try {
116
- data = JSON.parse(errorText);
117
- } catch {
118
- throw new ApiError(errorText.slice(0, 200) || `${method} request failed with status ${response.status}`, response.status, null, headerRequestId);
119
- }
120
- const nestedError = typeof data.error === "object" && data.error !== null ? data.error.message : void 0;
121
- throw new ApiError(data.message || data.error_message || (typeof nestedError === "string" ? nestedError : void 0) || `${method} request failed`, response.status, data.errors || data, headerRequestId ?? getRequestIdFromJsonBody(data));
122
- } else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null, headerRequestId);
123
- }
124
- if (response.status === 204 || response.headers.get("content-length") === "0") return null;
125
- if (response.headers.get("content-type")?.includes("application/json")) {
126
- const responseText = await response.text();
127
- try {
128
- return JSON.parse(responseText);
129
- } catch {
130
- if (throwOnInvalidJson) throw new ApiError("Failed to parse response as JSON", response.status, null, headerRequestId);
131
- return responseText ? responseText : null;
132
- }
133
- }
134
- return null;
135
- }
136
- function getNetworkRetryDelayMs(retryAttempt) {
137
- return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);
138
- }
139
- async function waitForNetworkRetry(retryAttempt) {
140
- const delayMs = getNetworkRetryDelayMs(retryAttempt);
141
- if (delayMs <= 0) return;
142
- await new Promise((resolve) => setTimeout(resolve, delayMs));
143
- }
144
- async function fetchWithNetworkRetry(url, fetchOptions, signal) {
145
- let retryCount = 0;
146
- while (true) try {
147
- return await fetch(url, fetchOptions);
148
- } catch (networkError) {
149
- if (signal?.aborted || retryCount >= maxNetworkRetries) throw networkError;
150
- retryCount += 1;
151
- await waitForNetworkRetry(retryCount);
152
- if (signal?.aborted) throw networkError;
153
- }
154
- }
155
- /**
156
- * Main request function
157
- */
158
- async function request(endpoint, options = {}) {
159
- const { method = "GET", headers: customHeaders, params, body, signal, priority } = options;
160
- const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);
161
- const headers = await buildHeaders(customHeaders);
162
- let response;
163
- try {
164
- const fetchOptions = {
165
- method,
166
- headers
167
- };
168
- if (credentials) fetchOptions.credentials = credentials;
169
- if (cache) fetchOptions.cache = cache;
170
- if (priority) fetchOptions.priority = priority;
171
- const serializedBody = body && method !== "GET" ? JSON.stringify(body) : null;
172
- if (serializedBody) fetchOptions.body = serializedBody;
173
- if (signal) fetchOptions.signal = signal;
174
- response = await fetchWithNetworkRetry(url, fetchOptions, signal);
175
- } catch (networkError) {
176
- throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
177
- }
178
- return handleResponse(response, method, url);
179
- }
180
- /**
181
- * Request with FormData (for file uploads)
182
- */
183
- async function requestWithFormData(endpoint, formData, options = {}) {
184
- const { method = "POST", headers: customHeaders, signal, priority } = options;
185
- const url = joinUrl(endpoint);
186
- const headers = await buildHeaders(customHeaders);
187
- delete headers["Content-Type"];
188
- let response;
189
- try {
190
- const fetchOptions = {
191
- method,
192
- headers,
193
- body: formData
194
- };
195
- if (credentials) fetchOptions.credentials = credentials;
196
- if (cache) fetchOptions.cache = cache;
197
- if (priority) fetchOptions.priority = priority;
198
- if (signal) fetchOptions.signal = signal;
199
- response = await fetchWithNetworkRetry(url, fetchOptions, signal);
200
- } catch (networkError) {
201
- throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
202
- }
203
- return handleResponse(response, method, url);
204
- }
205
- return {
206
- request,
207
- requestWithFormData,
208
- get: (endpoint, params, options) => request(endpoint, {
209
- ...options,
210
- method: "GET",
211
- ...params && { params }
212
- }),
213
- post: (endpoint, body, options) => request(endpoint, {
214
- ...options,
215
- method: "POST",
216
- body
217
- }),
218
- put: (endpoint, body, options) => request(endpoint, {
219
- ...options,
220
- method: "PUT",
221
- body
222
- }),
223
- patch: (endpoint, body, options) => request(endpoint, {
224
- ...options,
225
- method: "PATCH",
226
- body
227
- }),
228
- delete: (endpoint, options) => request(endpoint, {
229
- ...options,
230
- method: "DELETE"
231
- })
232
- };
233
- }
234
- //#endregion
235
- //#region ../../api-clients/fluidos/src/namespaces/fluid_os_v0.ts
236
- /**
237
- * List Fluid OS definitions
238
- * Retrieve a list of Fluid OS definitions for the current company
239
- *
240
- * @param client - Fetch client instance
241
- * @param [params] - params
242
- */
243
- async function fluid_os_v0_list_fluid_osdefinitions(client, params) {
244
- return client.get(`/api/company/fluid_os/definitions`, params);
245
- }
246
- /**
247
- * List navigations for a Fluid OS definition
248
- * Retrieve a list of navigations for a specific Fluid OS definition
249
- *
250
- * @param client - Fetch client instance
251
- * @param definition_id - definition_id
252
- * @param [params] - params
253
- */
254
- async function fluid_os_v0_list_fluid_osnavigations(client, definition_id, params) {
255
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/navigations`, params);
256
- }
257
- /**
258
- * Create a navigation for a Fluid OS definition
259
- * Create a new navigation for a Fluid OS definition
260
- *
261
- * @param client - Fetch client instance
262
- * @param definition_id - definition_id
263
- * @param body - body
264
- */
265
- async function fluid_os_v0_create_fluid_osnavigation(client, definition_id, body) {
266
- return client.post(`/api/company/fluid_os/definitions/${definition_id}/navigations`, body);
267
- }
268
- /**
269
- * Update a navigation
270
- * Update an existing navigation
271
- *
272
- * @param client - Fetch client instance
273
- * @param definition_id - definition_id
274
- * @param id - id
275
- * @param body - body
276
- */
277
- async function fluid_os_v0_update_fluid_osnavigation(client, definition_id, id, body) {
278
- return client.put(`/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`, body);
279
- }
280
- /**
281
- * Delete a navigation
282
- * Delete a navigation
283
- *
284
- * @param client - Fetch client instance
285
- * @param definition_id - definition_id
286
- * @param id - id
287
- */
288
- async function fluid_os_v0_delete_fluid_osnavigation(client, definition_id, id) {
289
- return client.delete(`/api/company/fluid_os/definitions/${definition_id}/navigations/${id}`);
290
- }
291
- /**
292
- * List navigation items for a navigation
293
- * Retrieve a list of navigation items for a specific navigation
294
- *
295
- * @param client - Fetch client instance
296
- * @param definition_id - definition_id
297
- * @param navigation_id - navigation_id
298
- */
299
- async function fluid_os_v0_list_fluid_osnavigation_items(client, definition_id, navigation_id) {
300
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items`);
301
- }
302
- /**
303
- * Create a navigation item
304
- * Create a new navigation item for a navigation
305
- *
306
- * @param client - Fetch client instance
307
- * @param definition_id - definition_id
308
- * @param navigation_id - navigation_id
309
- * @param body - body
310
- */
311
- async function fluid_os_v0_create_fluid_osnavigation_item(client, definition_id, navigation_id, body) {
312
- return client.post(`/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items`, body);
313
- }
314
- /**
315
- * Update a navigation item
316
- * Update an existing navigation item
317
- *
318
- * @param client - Fetch client instance
319
- * @param definition_id - definition_id
320
- * @param navigation_id - navigation_id
321
- * @param id - id
322
- * @param body - body
323
- */
324
- async function fluid_os_v0_update_fluid_osnavigation_item(client, definition_id, navigation_id, id, body) {
325
- return client.put(`/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`, body);
326
- }
327
- /**
328
- * Delete a navigation item
329
- * Delete a navigation item
330
- *
331
- * @param client - Fetch client instance
332
- * @param definition_id - definition_id
333
- * @param navigation_id - navigation_id
334
- * @param id - id
335
- */
336
- async function fluid_os_v0_delete_fluid_osnavigation_item(client, definition_id, navigation_id, id) {
337
- return client.delete(`/api/company/fluid_os/definitions/${definition_id}/navigations/${navigation_id}/navigation_items/${id}`);
338
- }
339
- /**
340
- * List profiles for a Fluid OS definition
341
- * Retrieve a list of profiles for a specific Fluid OS definition
342
- *
343
- * @param client - Fetch client instance
344
- * @param definition_id - definition_id
345
- * @param [params] - params
346
- */
347
- async function fluid_os_v0_list_fluid_osprofiles(client, definition_id, params) {
348
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/profiles`, params);
349
- }
350
- /**
351
- * Create a profile for a Fluid OS definition
352
- * Create a new profile for a Fluid OS definition
353
- *
354
- * @param client - Fetch client instance
355
- * @param definition_id - definition_id
356
- * @param body - body
357
- */
358
- async function fluid_os_v0_create_fluid_osprofile(client, definition_id, body) {
359
- return client.post(`/api/company/fluid_os/definitions/${definition_id}/profiles`, body);
360
- }
361
- /**
362
- * Update a profile
363
- * Update an existing profile
364
- *
365
- * @param client - Fetch client instance
366
- * @param definition_id - definition_id
367
- * @param id - id
368
- * @param body - body
369
- */
370
- async function fluid_os_v0_update_fluid_osprofile(client, definition_id, id, body) {
371
- return client.put(`/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`, body);
372
- }
373
- /**
374
- * Delete a profile
375
- * Delete a profile
376
- *
377
- * @param client - Fetch client instance
378
- * @param definition_id - definition_id
379
- * @param id - id
380
- */
381
- async function fluid_os_v0_delete_fluid_osprofile(client, definition_id, id) {
382
- return client.delete(`/api/company/fluid_os/definitions/${definition_id}/profiles/${id}`);
383
- }
384
- /**
385
- * List screens for a Fluid OS definition
386
- * Retrieve a list of screens for a specific Fluid OS definition
387
- *
388
- * @param client - Fetch client instance
389
- * @param definition_id - definition_id
390
- * @param [params] - params
391
- */
392
- async function fluid_os_v0_list_fluid_osscreens(client, definition_id, params) {
393
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/screens`, params);
394
- }
395
- /**
396
- * Create a screen for a Fluid OS definition
397
- * Create a new screen for a Fluid OS definition
398
- *
399
- * @param client - Fetch client instance
400
- * @param definition_id - definition_id
401
- * @param body - body
402
- */
403
- async function fluid_os_v0_create_fluid_osscreen(client, definition_id, body) {
404
- return client.post(`/api/company/fluid_os/definitions/${definition_id}/screens`, body);
405
- }
406
- /**
407
- * Get a specific screen
408
- * Retrieve a specific screen
409
- *
410
- * @param client - Fetch client instance
411
- * @param definition_id - definition_id
412
- * @param id - id
413
- */
414
- async function fluid_os_v0_get_fluid_osscreen(client, definition_id, id) {
415
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/screens/${id}`);
416
- }
417
- /**
418
- * Update a screen
419
- * Update an existing screen
420
- *
421
- * @param client - Fetch client instance
422
- * @param definition_id - definition_id
423
- * @param id - id
424
- * @param body - body
425
- */
426
- async function fluid_os_v0_update_fluid_osscreen(client, definition_id, id, body) {
427
- return client.put(`/api/company/fluid_os/definitions/${definition_id}/screens/${id}`, body);
428
- }
429
- /**
430
- * Delete a screen
431
- * Delete a screen
432
- *
433
- * @param client - Fetch client instance
434
- * @param definition_id - definition_id
435
- * @param id - id
436
- */
437
- async function fluid_os_v0_delete_fluid_osscreen(client, definition_id, id) {
438
- return client.delete(`/api/company/fluid_os/definitions/${definition_id}/screens/${id}`);
439
- }
440
- /**
441
- * List themes for a Fluid OS definition
442
- * Retrieve a list of themes for a specific Fluid OS definition
443
- *
444
- * @param client - Fetch client instance
445
- * @param definition_id - definition_id
446
- * @param [params] - params
447
- */
448
- async function fluid_os_v0_list_fluid_osthemes(client, definition_id, params) {
449
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/themes`, params);
450
- }
451
- /**
452
- * Create a theme for a Fluid OS definition
453
- * Create a new theme for a Fluid OS definition
454
- *
455
- * @param client - Fetch client instance
456
- * @param definition_id - definition_id
457
- * @param body - body
458
- */
459
- async function fluid_os_v0_create_fluid_ostheme(client, definition_id, body) {
460
- return client.post(`/api/company/fluid_os/definitions/${definition_id}/themes`, body);
461
- }
462
- /**
463
- * Update a theme
464
- * Update an existing theme
465
- *
466
- * @param client - Fetch client instance
467
- * @param definition_id - definition_id
468
- * @param id - id
469
- * @param body - body
470
- */
471
- async function fluid_os_v0_update_fluid_ostheme(client, definition_id, id, body) {
472
- return client.put(`/api/company/fluid_os/definitions/${definition_id}/themes/${id}`, body);
473
- }
474
- /**
475
- * Delete a theme
476
- * Delete a theme
477
- *
478
- * @param client - Fetch client instance
479
- * @param definition_id - definition_id
480
- * @param id - id
481
- */
482
- async function fluid_os_v0_delete_fluid_ostheme(client, definition_id, id) {
483
- return client.delete(`/api/company/fluid_os/definitions/${definition_id}/themes/${id}`);
484
- }
485
- /**
486
- * List versions for a Fluid OS definition
487
- * Retrieve a list of published versions for a specific Fluid OS definition
488
- *
489
- * @param client - Fetch client instance
490
- * @param definition_id - definition_id
491
- * @param [params] - params
492
- */
493
- async function fluid_os_v0_list_fluid_osversions(client, definition_id, params) {
494
- return client.get(`/api/company/fluid_os/definitions/${definition_id}/versions`, params);
495
- }
496
- /**
497
- * Publish a new version of a Fluid OS definition
498
- * 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.
499
- *
500
- * @param client - Fetch client instance
501
- * @param definition_id - definition_id
502
- */
503
- async function fluid_os_v0_create_fluid_osversion(client, definition_id) {
504
- return client.post(`/api/company/fluid_os/definitions/${definition_id}/versions`);
505
- }
506
- /**
507
- * Update a version
508
- * Update a version. Currently only supports activating/deactivating a version.
509
- *
510
- * @param client - Fetch client instance
511
- * @param definition_id - definition_id
512
- * @param id - id
513
- * @param body - body
514
- */
515
- async function fluid_os_v0_update_fluid_osversion(client, definition_id, id, body) {
516
- return client.put(`/api/company/fluid_os/definitions/${definition_id}/versions/${id}`, body);
517
- }
518
- /**
519
- * Create a Fluid OS widget package version
520
- * Start a company-owned Fluid OS widget package publish flow and return signed upload URLs for each artifact.
521
- *
522
- * @param client - Fetch client instance
523
- * @param body - body
524
- */
525
- async function fluid_os_v0_create_widget_package_version(client, body) {
526
- return client.post(`/api/company/fluid_os/widget_package_versions`, body);
527
- }
528
- /**
529
- * Complete a Fluid OS widget package upload
530
- * Verify uploaded artifacts for a company-owned Fluid OS widget package version and approve the version when verification succeeds. Uploaded manifest.json is verified for matching packageId, matching version, and valid company-owned `company.<package_key>.` widget type prefixes.
531
- *
532
- * @param client - Fetch client instance
533
- * @param version - version
534
- * @param [body] - body
535
- */
536
- async function fluid_os_v0_complete_widget_package_version_upload(client, version, body) {
537
- return client.post(`/api/company/fluid_os/widget_package_versions/${version}/complete_upload`, body);
538
- }
539
- //#endregion
540
- //#region src/utils/atomic-write.ts
541
- /**
542
- * Atomic file write utility.
543
- *
544
- * Uses a write-then-rename pattern so readers always see either the old
545
- * file or the complete new file — never a partially-written state.
546
- */
547
- /**
548
- * Write a file atomically using a write-then-rename pattern.
549
- * Ensures readers always see either the old file or the complete new file.
550
- *
551
- * If `rename` fails after the temp file has been written, the temp file
552
- * is cleaned up on a best-effort basis before re-throwing the error.
553
- */
554
- async function atomicWriteFile(filePath, data) {
555
- const tmp = join(dirname(filePath), `.tmp-${randomBytes(6).toString("hex")}`);
556
- try {
557
- await writeFile(tmp, data, "utf-8");
558
- await rename(tmp, filePath);
559
- } catch (err) {
560
- await unlink(tmp).catch(() => {});
561
- throw err;
562
- }
563
- }
564
- //#endregion
565
- //#region src/utils/mappings.ts
566
- /**
567
- * Slug-to-ID mapping system for portal content sync.
568
- *
569
- * Maps human-readable slugs (derived from resource names) to API IDs,
570
- * enabling the file system to use readable directory/file names while
571
- * maintaining the link back to server-side resources.
572
- *
573
- * Persisted in `.portal-sync/mappings.json`.
574
- */
575
- /** Runtime check that a parsed value has the expected PortalMappings shape. */
576
- function isMappings(value) {
577
- if (!value || typeof value !== "object") return false;
578
- const m = value;
579
- const def = m.definition;
580
- if (!def || typeof def !== "object") return false;
581
- if (typeof def.name !== "string" || typeof def.id !== "number") return false;
582
- return typeof m.screens === "object" && m.screens !== null && typeof m.themes === "object" && m.themes !== null && typeof m.navigations === "object" && m.navigations !== null && typeof m.profiles === "object" && m.profiles !== null && typeof m.countries === "object" && m.countries !== null && typeof m.ranks === "object" && m.ranks !== null;
583
- }
584
- const MAPPINGS_FILE = "mappings.json";
585
- /**
586
- * Read the mappings file from the `.portal-sync/` directory.
587
- * Returns `null` if the file does not exist.
588
- */
589
- async function readMappings(portalSyncDir) {
590
- try {
591
- const filePath = join(portalSyncDir, MAPPINGS_FILE);
592
- const content = await readFile(filePath, "utf-8");
593
- const parsed = JSON.parse(content);
594
- if (!isMappings(parsed)) throw new Error(`Malformed mappings file: ${filePath}`);
595
- return parsed;
596
- } catch (err) {
597
- if (err.code === "ENOENT") return null;
598
- throw err;
599
- }
600
- }
601
- /**
602
- * Write the mappings file to the `.portal-sync/` directory.
603
- * Creates the directory if it does not exist.
604
- */
605
- async function writeMappings(portalSyncDir, mappings) {
606
- const filePath = join(portalSyncDir, MAPPINGS_FILE);
607
- await mkdir(dirname(filePath), { recursive: true });
608
- await atomicWriteFile(filePath, JSON.stringify(mappings, null, 2) + "\n");
609
- }
610
- /**
611
- * Derive a filesystem-safe slug from a resource name.
612
- *
613
- * - Lowercases the name
614
- * - Replaces non-alphanumeric characters (except hyphens) with hyphens
615
- * - Collapses consecutive hyphens
616
- * - Trims leading/trailing hyphens
617
- *
618
- * If the derived slug collides with an existing slug in `existingSlugs`,
619
- * a numeric suffix is appended (e.g., `home-2`, `home-3`).
620
- */
621
- function deriveSlug(name, existingSlugs = /* @__PURE__ */ new Set()) {
622
- const base = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
623
- if (!existingSlugs.has(base)) return base;
624
- let counter = 2;
625
- while (existingSlugs.has(`${base}-${counter}`)) counter++;
626
- return `${base}-${counter}`;
627
- }
628
- /**
629
- * Resolve a slug to its server-side ID.
630
- * Returns `undefined` if the slug is not mapped.
631
- */
632
- function resolveSlugToId(mappings, resourceType, slug) {
633
- return mappings[resourceType][slug];
634
- }
635
- /**
636
- * Resolve a server-side ID back to its slug.
637
- * Returns `undefined` if no slug maps to the given ID.
638
- */
639
- function resolveIdToSlug(mappings, resourceType, id) {
640
- const entries = mappings[resourceType];
641
- for (const [slug, mappedId] of Object.entries(entries)) if (mappedId === id) return slug;
642
- }
643
- /**
644
- * Add or update a mapping entry for a given resource type.
645
- * Returns a new `PortalMappings` object (immutable update).
646
- */
647
- function updateMapping(mappings, resourceType, slug, id) {
648
- return {
649
- ...mappings,
650
- [resourceType]: {
651
- ...mappings[resourceType],
652
- [slug]: id
653
- }
654
- };
655
- }
656
- /**
657
- * Remove a mapping entry for a given resource type.
658
- * Returns a new `PortalMappings` object (immutable update).
659
- * If the slug does not exist, the original mappings are returned unchanged.
660
- */
661
- function removeMapping(mappings, resourceType, slug) {
662
- if (!(slug in mappings[resourceType])) return mappings;
663
- const { [slug]: _removed, ...rest } = mappings[resourceType];
664
- return {
665
- ...mappings,
666
- [resourceType]: rest
667
- };
668
- }
669
- //#endregion
670
- //#region src/utils/snapshot.ts
671
- /**
672
- * Hash-based snapshot system for portal content sync.
673
- *
674
- * Tracks file hashes to detect local changes since the last pull,
675
- * enabling efficient push operations that only send modified files.
676
- *
677
- * Persisted in `.portal-sync/snapshot.json`.
678
- */
679
- /** Maximum number of concurrent file operations. */
680
- const MAX_CONCURRENCY = 20;
681
- /**
682
- * Run async tasks with bounded concurrency to avoid exhausting file descriptors.
683
- */
684
- async function mapWithLimit(items, limit, fn) {
685
- const results = new Array(items.length);
686
- let index = 0;
687
- async function worker() {
688
- while (index < items.length) {
689
- const i = index++;
690
- results[i] = await fn(items[i]);
691
- }
692
- }
693
- const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
694
- await Promise.all(workers);
695
- return results;
696
- }
697
- /** Runtime check that a parsed value has the expected Snapshot shape. */
698
- function isSnapshot(value) {
699
- if (!value || typeof value !== "object") return false;
700
- const s = value;
701
- return typeof s.definition === "string" && typeof s.definition_id === "number" && typeof s.pulled_at === "string" && typeof s.files === "object" && s.files !== null;
702
- }
703
- /**
704
- * Compute the SHA-256 hash of a file's contents.
705
- * Returns the hex-encoded digest.
706
- */
707
- async function computeFileHash(filePath) {
708
- const content = await readFile(filePath);
709
- return createHash("sha256").update(content).digest("hex");
710
- }
711
- const SNAPSHOT_FILE = "snapshot.json";
712
- /**
713
- * Read the snapshot file from the `.portal-sync/` directory.
714
- * Returns `null` if the file does not exist.
715
- */
716
- async function readSnapshot(portalSyncDir) {
717
- try {
718
- const filePath = join(portalSyncDir, SNAPSHOT_FILE);
719
- const content = await readFile(filePath, "utf-8");
720
- const parsed = JSON.parse(content);
721
- if (!isSnapshot(parsed)) throw new Error(`Malformed snapshot file: ${filePath}`);
722
- return parsed;
723
- } catch (err) {
724
- if (err.code === "ENOENT") return null;
725
- throw err;
726
- }
727
- }
728
- /**
729
- * Write the snapshot file to the `.portal-sync/` directory.
730
- * Creates the directory if it does not exist.
731
- */
732
- async function writeSnapshot(portalSyncDir, snapshot) {
733
- const filePath = join(portalSyncDir, SNAPSHOT_FILE);
734
- await mkdir(dirname(filePath), { recursive: true });
735
- await atomicWriteFile(filePath, JSON.stringify(snapshot, null, 2) + "\n");
736
- }
737
- /**
738
- * Recursively collect all files in a directory, returning paths
739
- * relative to `baseDir`.
740
- */
741
- async function collectFiles(dir, baseDir = dir) {
742
- const entries = await readdir(dir, { withFileTypes: true });
743
- const files = [];
744
- for (const entry of entries) {
745
- const fullPath = join(dir, entry.name);
746
- if (entry.name === ".portal-sync") continue;
747
- if (entry.isSymbolicLink()) {
748
- const realStat = await stat(fullPath);
749
- if (realStat.isDirectory()) files.push(...await collectFiles(fullPath, baseDir));
750
- else if (realStat.isFile()) files.push(relative(baseDir, fullPath).split(sep).join("/"));
751
- } else if (entry.isDirectory()) files.push(...await collectFiles(fullPath, baseDir));
752
- else files.push(relative(baseDir, fullPath).split(sep).join("/"));
753
- }
754
- return files;
755
- }
756
- /**
757
- * Compare the current portal directory contents against a snapshot.
758
- *
759
- * Returns lists of new, changed, and deleted files (relative paths).
760
- */
761
- async function diffAgainstSnapshot(portalDir, snapshot) {
762
- const currentFiles = await collectFiles(portalDir);
763
- const snapshotPaths = new Set(Object.keys(snapshot.files));
764
- const newFiles = [];
765
- const changedFiles = [];
766
- await mapWithLimit(currentFiles, MAX_CONCURRENCY, async (filePath) => {
767
- const hash = await computeFileHash(join(portalDir, filePath));
768
- if (!snapshotPaths.has(filePath)) newFiles.push(filePath);
769
- else if (snapshot.files[filePath] !== hash) changedFiles.push(filePath);
770
- });
771
- const currentSet = new Set(currentFiles);
772
- const deletedFiles = [...snapshotPaths].filter((filePath) => !currentSet.has(filePath));
773
- return {
774
- new: [...newFiles].sort(),
775
- changed: [...changedFiles].sort(),
776
- deleted: [...deletedFiles].sort()
777
- };
778
- }
779
- /**
780
- * Build a fresh snapshot from the current portal directory contents.
781
- *
782
- * Hashes every file in `portalDir` (excluding `.portal-sync/`)
783
- * and records the definition metadata.
784
- */
785
- async function buildSnapshot(portalDir, definitionName, definitionId) {
786
- const hashEntries = await mapWithLimit(await collectFiles(portalDir), MAX_CONCURRENCY, async (filePath) => {
787
- return [filePath, await computeFileHash(join(portalDir, filePath))];
788
- });
789
- hashEntries.sort(([a], [b]) => a.localeCompare(b));
790
- const fileHashes = {};
791
- for (const [path, hash] of hashEntries) fileHashes[path] = hash;
792
- return {
793
- definition: definitionName,
794
- definition_id: definitionId,
795
- pulled_at: (/* @__PURE__ */ new Date()).toISOString(),
796
- files: fileHashes
797
- };
798
- }
799
- //#endregion
800
- //#region src/utils/transform.ts
801
- /**
802
- * Transform an API screen response into the local file format.
803
- * Strips server-only fields and normalizes `component_tree` to always be an array.
804
- */
805
- function transformScreen(screen) {
806
- const name = screen.name ?? "";
807
- const rawTree = screen.component_tree;
808
- let componentTree;
809
- if (rawTree == null) componentTree = [];
810
- else if (Array.isArray(rawTree)) componentTree = rawTree;
811
- else componentTree = [rawTree];
812
- return {
813
- name,
814
- component_tree: componentTree
815
- };
816
- }
817
- /**
818
- * Derive a slug for a screen. Screens use the existing `slug` field from the API.
819
- * Falls back to slugifying the name if no slug exists.
820
- */
821
- function deriveScreenSlug(screen, existingSlugs) {
822
- if (screen.slug) {
823
- const safe = screen.slug.replace(/[^a-z0-9_-]/gi, "-");
824
- if (!existingSlugs.has(safe)) return safe;
825
- return deriveSlug(safe, existingSlugs);
826
- }
827
- return deriveSlug(screen.name ?? "unnamed", existingSlugs);
828
- }
829
- /**
830
- * Transform an API theme response into the local file format.
831
- * Passes theme config through as-is (hex conversion is a separate ticket).
832
- */
833
- function transformTheme(theme) {
834
- return {
835
- name: theme.name ?? "",
836
- config: theme.config ?? {},
837
- active: theme.active ?? false
838
- };
839
- }
840
- /**
841
- * Build an ID → slug lookup map by inverting a slug → id mapping.
842
- */
843
- function buildIdToSlugMap(mappings) {
844
- const map = /* @__PURE__ */ new Map();
845
- for (const [slug, id] of Object.entries(mappings)) map.set(id, slug);
846
- return map;
847
- }
848
- /**
849
- * Transform API navigation items into local format.
850
- * Converts `screen_id` to `screen` slug reference. Keeps `id` for sync.
851
- */
852
- function transformNavigationItems(items, screenIdToSlug) {
853
- return items.map((item) => ({
854
- id: item.id,
855
- icon: item.icon ?? null,
856
- label: item.label ?? null,
857
- screen: item.screen_id ? screenIdToSlug.get(item.screen_id) ?? null : null,
858
- slug: item.slug ?? null,
859
- source: item.source ?? "user",
860
- position: item.position ?? null,
861
- parent_id: item.parent_id ?? null,
862
- children: transformNavigationItems(item.children ?? [], screenIdToSlug)
863
- }));
864
- }
865
- /**
866
- * Transform an API navigation with its items into the local file format.
867
- */
868
- function transformNavigation(nav, items, screenIdToSlug) {
869
- return {
870
- name: nav.name ?? "",
871
- platform: nav.platform,
872
- navigation_items: transformNavigationItems(items, screenIdToSlug)
873
- };
874
- }
875
- /** Typed alias for call-site clarity. */
876
- const buildNavigationIdToSlugMap = buildIdToSlugMap;
877
- /** Typed alias for call-site clarity. */
878
- const buildThemeIdToSlugMap = buildIdToSlugMap;
879
- /**
880
- * Transform an API profile response into the local file format.
881
- * Converts navigation_id, mobile_navigation_id, and theme_ids to slug references.
882
- *
883
- * TODO (REP-842): Country/rank IDs in profile permissions need to be translated
884
- * to human-readable names via the main Fluid API. For now, the permission arrays
885
- * are written as-is with integer IDs.
886
- */
887
- function transformProfile(profile, navIdToSlug, themeIdToSlug) {
888
- const navigation = profile.navigation ? navIdToSlug.get(profile.navigation.id) ?? null : null;
889
- const mobileNavigation = profile.mobile_navigation ? navIdToSlug.get(profile.mobile_navigation.id) ?? null : null;
890
- const themes = (profile.themes ?? []).map((t) => themeIdToSlug.get(t.id)).filter((slug) => slug != null);
891
- const permissions = profile.permissions ?? {};
892
- return {
893
- name: profile.name ?? "",
894
- default: profile.default ?? false,
895
- navigation,
896
- mobile_navigation: mobileNavigation,
897
- themes,
898
- permissions: {
899
- ranks: permissions.ranks ?? [],
900
- roles: permissions.roles ?? [],
901
- platform: permissions.platform ?? [],
902
- countries: permissions.countries ?? []
903
- }
904
- };
905
- }
906
- //#endregion
907
- //#region src/commands/pull.ts
908
- /**
909
- * `fluid portal pull` command
910
- *
911
- * Pulls a Fluid OS definition's resources (screens, themes, navigations,
912
- * profiles) from the API and writes them to the local `portal/` directory
913
- * as JSON files, along with `.portal-sync/` metadata for push diffing.
914
- */
915
- var pull_exports = /* @__PURE__ */ __exportAll({ pullCommand: () => pullCommand });
916
- const PORTAL_DIR = "portal";
917
- const PORTAL_SYNC_DIR = ".portal-sync";
918
- const PAGE_LIMIT = 500;
919
- /**
920
- * Create an authenticated FetchClient using the stored CLI profile.
921
- */
922
- function createClient() {
923
- const token = getAuthToken();
924
- if (!token) {
925
- const profile = getActiveProfile();
926
- if (!profile) throw new Error("Not logged in. Run " + chalk.cyan("fluid login") + " first.");
927
- throw new Error("No auth token found for profile " + chalk.cyan(profile.name) + ". Run " + chalk.cyan("fluid login") + " to re-authenticate.");
928
- }
929
- return createFetchClient({
930
- baseUrl: process.env["FLUID_API_BASE"] ?? "https://api.fluid.app",
931
- getAuthToken: () => token
932
- });
933
- }
934
- /**
935
- * Select a definition interactively or by name.
936
- */
937
- async function fetchAllDefinitions(client) {
938
- const all = [];
939
- let page = 1;
940
- while (true) {
941
- const response = await fluid_os_v0_list_fluid_osdefinitions(client, {
942
- page,
943
- per_page: PAGE_LIMIT
944
- });
945
- const batch = response.definitions ?? [];
946
- all.push(...batch);
947
- const totalPages = response.meta?.total_pages ?? 1;
948
- if (page >= totalPages || batch.length === 0) break;
949
- page++;
950
- }
951
- return all;
952
- }
953
- async function selectDefinition(client, appName) {
954
- const definitions = await fetchAllDefinitions(client);
955
- if (definitions.length === 0) throw new Error("No Fluid OS definitions found. Create one in the admin dashboard first.");
956
- if (appName) {
957
- const match = definitions.find((d) => d.name?.toLowerCase() === appName.toLowerCase());
958
- if (!match) {
959
- const availableNames = definitions.map((d) => chalk.cyan(d.name ?? "(unnamed)")).join(", ");
960
- throw new Error(`No definition found matching "${appName}". Available: ${availableNames}`);
961
- }
962
- return match;
963
- }
964
- const { definitionId } = await prompts({
965
- type: "select",
966
- name: "definitionId",
967
- message: "Select a Fluid OS definition to pull",
968
- choices: definitions.map((d) => ({
969
- title: d.name ?? "(unnamed)",
970
- value: d.id
971
- }))
972
- });
973
- if (definitionId == null) throw new Error("No definition selected.");
974
- const selected = definitions.find((d) => d.id === definitionId);
975
- if (!selected) throw new Error("Selected definition not found.");
976
- return selected;
977
- }
978
- /**
979
- * Check for definition switching conflicts.
980
- * Returns true if it's safe to proceed.
981
- */
982
- async function checkDefinitionSwitch(cwd, newDefinitionId, newDefinitionName, force) {
983
- const portalDir = join(cwd, PORTAL_DIR);
984
- const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);
985
- if (!existsSync(portalDir)) return true;
986
- const snapshot = await readSnapshot(portalSyncDir);
987
- if (!snapshot) return true;
988
- if (snapshot.definition_id === newDefinitionId) return true;
989
- const diff = await diffAgainstSnapshot(portalDir, snapshot);
990
- if ((diff.new.length > 0 || diff.changed.length > 0 || diff.deleted.length > 0) && !force) {
991
- console.log();
992
- console.log(chalk.yellow("Warning:") + " Switching from " + chalk.cyan(snapshot.definition) + " to " + chalk.cyan(newDefinitionName) + " with unpushed local changes.");
993
- console.log();
994
- console.log(" Modified: " + diff.changed.length + " file(s)");
995
- console.log(" New: " + diff.new.length + " file(s)");
996
- console.log(" Deleted: " + diff.deleted.length + " file(s)");
997
- console.log();
998
- console.log("Use " + chalk.cyan("--force") + " to discard local changes and switch definitions.");
999
- return false;
1000
- }
1001
- return true;
1002
- }
1003
- /**
1004
- * Write a JSON file to the portal directory, creating subdirectories as needed.
1005
- */
1006
- async function writePortalFile(portalDir, relativePath, data) {
1007
- const filePath = join(portalDir, relativePath);
1008
- await mkdir(join(filePath, ".."), { recursive: true });
1009
- await writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
1010
- }
1011
- const pullCommand = new Command("pull").description("Pull a Fluid OS definition's resources to the local portal/ directory").option("--app <name>", "Definition name (skips interactive selector)").option("--force", "Overwrite local changes when switching definitions").action(async (options) => {
1012
- const cwd = process.cwd();
1013
- console.log();
1014
- console.log(chalk.blue.bold("Fluid Portal Pull"));
1015
- console.log();
1016
- const spinner = ora();
1017
- spinner.start("Authenticating...");
1018
- let client;
1019
- try {
1020
- client = createClient();
1021
- spinner.succeed("Authenticated");
1022
- } catch (err) {
1023
- spinner.fail("Authentication failed");
1024
- console.log();
1025
- console.log(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
1026
- console.log();
1027
- process.exit(1);
1028
- }
1029
- let definition;
1030
- try {
1031
- definition = await selectDefinition(client, options.app);
1032
- } catch (err) {
1033
- console.log();
1034
- console.log(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
1035
- console.log();
1036
- process.exit(1);
1037
- }
1038
- const definitionId = definition.id;
1039
- const definitionName = definition.name ?? "(unnamed)";
1040
- console.log(chalk.gray("Definition: ") + chalk.white(definitionName) + chalk.gray(` (ID: ${definitionId})`));
1041
- console.log();
1042
- let canProceed;
1043
- try {
1044
- canProceed = await checkDefinitionSwitch(cwd, definitionId, definitionName, options.force ?? false);
1045
- } catch (err) {
1046
- console.log();
1047
- console.log(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
1048
- console.log();
1049
- process.exit(1);
1050
- }
1051
- if (!canProceed) process.exit(1);
1052
- spinner.start("Fetching resources...");
1053
- let screenBasics;
1054
- let themes;
1055
- let navigations;
1056
- let profiles;
1057
- let screens;
1058
- const navigationItemsMap = /* @__PURE__ */ new Map();
1059
- try {
1060
- const [screensResponse, themesResponse, navigationsResponse, profilesResponse] = await Promise.all([
1061
- fluid_os_v0_list_fluid_osscreens(client, definitionId, { per_page: PAGE_LIMIT }),
1062
- fluid_os_v0_list_fluid_osthemes(client, definitionId, { per_page: PAGE_LIMIT }),
1063
- fluid_os_v0_list_fluid_osnavigations(client, definitionId, { per_page: PAGE_LIMIT }),
1064
- fluid_os_v0_list_fluid_osprofiles(client, definitionId, { per_page: PAGE_LIMIT })
1065
- ]);
1066
- screenBasics = screensResponse.screens ?? [];
1067
- themes = themesResponse.themes ?? [];
1068
- navigations = navigationsResponse.navigations ?? [];
1069
- profiles = profilesResponse.profiles ?? [];
1070
- if (screenBasics.length === PAGE_LIMIT) console.warn(chalk.yellow(`Warning: screen count hit the ${PAGE_LIMIT}-item limit; some screens may be missing.`));
1071
- if (themes.length === PAGE_LIMIT) console.warn(chalk.yellow(`Warning: theme count hit the ${PAGE_LIMIT}-item limit; some themes may be missing.`));
1072
- if (navigations.length === PAGE_LIMIT) console.warn(chalk.yellow(`Warning: navigation count hit the ${PAGE_LIMIT}-item limit; some navigations may be missing.`));
1073
- if (profiles.length === PAGE_LIMIT) console.warn(chalk.yellow(`Warning: profile count hit the ${PAGE_LIMIT}-item limit; some profiles may be missing.`));
1074
- spinner.text = "Fetching screen details...";
1075
- const limit = pLimit(10);
1076
- screens = await Promise.all(screenBasics.map((s) => limit(async () => {
1077
- const res = await fluid_os_v0_get_fluid_osscreen(client, definitionId, s.id);
1078
- if (!res.screen) throw new Error(`Failed to fetch details for screen ID ${s.id}`);
1079
- return res.screen;
1080
- })));
1081
- spinner.text = "Fetching navigation items...";
1082
- await Promise.all(navigations.map((nav) => limit(async () => {
1083
- const res = await fluid_os_v0_list_fluid_osnavigation_items(client, definitionId, nav.id);
1084
- navigationItemsMap.set(nav.id, res.navigation_items ?? []);
1085
- })));
1086
- spinner.succeed(`Fetched ${screens.length} screen(s), ${themes.length} theme(s), ${navigations.length} navigation(s), ${profiles.length} profile(s)`);
1087
- } catch (err) {
1088
- spinner.fail("Failed to fetch resources");
1089
- console.log();
1090
- console.log(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
1091
- console.log();
1092
- process.exit(1);
1093
- }
1094
- spinner.start("Transforming resources...");
1095
- const screenSlugs = /* @__PURE__ */ new Set();
1096
- const screenMappings = {};
1097
- for (const screen of screens) {
1098
- const slug = deriveScreenSlug(screen, screenSlugs);
1099
- screenSlugs.add(slug);
1100
- screenMappings[slug] = screen.id;
1101
- }
1102
- const themeSlugs = /* @__PURE__ */ new Set();
1103
- const themeMappings = {};
1104
- for (const theme of themes) {
1105
- const slug = deriveSlug(theme.name ?? "unnamed", themeSlugs);
1106
- themeSlugs.add(slug);
1107
- themeMappings[slug] = theme.id;
1108
- }
1109
- const navSlugs = /* @__PURE__ */ new Set();
1110
- const navMappings = {};
1111
- for (const nav of navigations) {
1112
- const slug = deriveSlug(nav.name ?? "unnamed", navSlugs);
1113
- navSlugs.add(slug);
1114
- navMappings[slug] = nav.id;
1115
- }
1116
- const profileSlugs = /* @__PURE__ */ new Set();
1117
- const profileMappings = {};
1118
- for (const profile of profiles) {
1119
- const slug = deriveSlug(profile.name ?? "unnamed", profileSlugs);
1120
- profileSlugs.add(slug);
1121
- profileMappings[slug] = profile.id;
1122
- }
1123
- const screenIdToSlug = buildIdToSlugMap(screenMappings);
1124
- const navIdToSlug = buildNavigationIdToSlugMap(navMappings);
1125
- const themeIdToSlug = buildThemeIdToSlugMap(themeMappings);
1126
- const profileIdToSlug = buildIdToSlugMap(profileMappings);
1127
- spinner.succeed("Resources transformed");
1128
- spinner.start("Writing files...");
1129
- const portalDir = join(cwd, PORTAL_DIR);
1130
- const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);
1131
- const tmpPortalDir = portalDir + ".tmp";
1132
- try {
1133
- if (existsSync(tmpPortalDir)) await rm(tmpPortalDir, { recursive: true });
1134
- await mkdir(tmpPortalDir, { recursive: true });
1135
- await writePortalFile(tmpPortalDir, "definition.json", { name: definitionName });
1136
- for (const screen of screens) {
1137
- const slug = screenIdToSlug.get(screen.id);
1138
- const local = transformScreen(screen);
1139
- await writePortalFile(tmpPortalDir, `screens/${slug}.json`, local);
1140
- }
1141
- for (const theme of themes) {
1142
- const slug = themeIdToSlug.get(theme.id);
1143
- const local = transformTheme(theme);
1144
- await writePortalFile(tmpPortalDir, `themes/${slug}.json`, local);
1145
- }
1146
- for (const nav of navigations) {
1147
- const slug = navIdToSlug.get(nav.id);
1148
- const local = transformNavigation(nav, navigationItemsMap.get(nav.id) ?? [], screenIdToSlug);
1149
- await writePortalFile(tmpPortalDir, `navigations/${slug}.json`, local);
1150
- }
1151
- for (const profile of profiles) {
1152
- const slug = profileIdToSlug.get(profile.id);
1153
- if (slug) {
1154
- const local = transformProfile(profile, navIdToSlug, themeIdToSlug);
1155
- await writePortalFile(tmpPortalDir, `profiles/${slug}.json`, local);
1156
- }
1157
- }
1158
- if (existsSync(portalDir)) await rm(portalDir, { recursive: true });
1159
- await rename(tmpPortalDir, portalDir);
1160
- } catch (err) {
1161
- if (existsSync(tmpPortalDir)) await rm(tmpPortalDir, { recursive: true }).catch(() => {});
1162
- spinner.fail("Failed to write files");
1163
- console.log();
1164
- console.log(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
1165
- console.log();
1166
- process.exit(1);
1167
- }
1168
- try {
1169
- await writeMappings(portalSyncDir, {
1170
- definition: {
1171
- name: definitionName,
1172
- id: definitionId
1173
- },
1174
- screens: screenMappings,
1175
- themes: themeMappings,
1176
- navigations: navMappings,
1177
- profiles: profileMappings,
1178
- countries: {},
1179
- ranks: {}
1180
- });
1181
- await writeSnapshot(portalSyncDir, await buildSnapshot(portalDir, definitionName, definitionId));
1182
- spinner.succeed("Files written");
1183
- } catch (err) {
1184
- spinner.warn("Files written (metadata sync failed)");
1185
- console.log();
1186
- console.log(chalk.yellow("Warning:") + " Portal files were written successfully, but metadata sync failed. Run " + chalk.cyan("fluid portal pull") + " again to regenerate metadata.");
1187
- console.log(chalk.gray(" Detail: ") + (err instanceof Error ? err.message : String(err)));
1188
- console.log();
1189
- }
1190
- console.log();
1191
- console.log(chalk.green.bold("Pull complete!"));
1192
- console.log();
1193
- console.log(chalk.gray(" portal/definition.json"));
1194
- console.log(chalk.gray(` portal/screens/ `) + chalk.white(`${screens.length} file(s)`));
1195
- console.log(chalk.gray(` portal/themes/ `) + chalk.white(`${themes.length} file(s)`));
1196
- console.log(chalk.gray(` portal/navigations/ `) + chalk.white(`${navigations.length} file(s)`));
1197
- console.log(chalk.gray(` portal/profiles/ `) + chalk.white(`${profiles.length} file(s)`));
1198
- console.log(chalk.gray(` .portal-sync/ `) + chalk.white("mappings.json + snapshot.json"));
1199
- console.log();
1200
- });
1201
- //#endregion
1202
- export { fluid_os_v0_create_fluid_osversion as A, fluid_os_v0_update_fluid_osnavigation as B, writeMappings as C, fluid_os_v0_create_fluid_osprofile as D, fluid_os_v0_create_fluid_osnavigation_item as E, fluid_os_v0_delete_fluid_osscreen as F, fluid_os_v0_update_fluid_osversion as G, fluid_os_v0_update_fluid_osprofile as H, fluid_os_v0_delete_fluid_ostheme as I, createFetchClient as K, fluid_os_v0_list_fluid_osdefinitions as L, fluid_os_v0_delete_fluid_osnavigation as M, fluid_os_v0_delete_fluid_osnavigation_item as N, fluid_os_v0_create_fluid_osscreen as O, fluid_os_v0_delete_fluid_osprofile as P, fluid_os_v0_list_fluid_osnavigation_items as R, updateMapping as S, fluid_os_v0_create_fluid_osnavigation as T, fluid_os_v0_update_fluid_osscreen as U, fluid_os_v0_update_fluid_osnavigation_item as V, fluid_os_v0_update_fluid_ostheme as W, deriveSlug as _, buildThemeIdToSlugMap as a, resolveIdToSlug as b, transformNavigationItems as c, transformTheme as d, buildSnapshot as f, writeSnapshot as g, readSnapshot as h, buildNavigationIdToSlugMap as i, fluid_os_v0_create_widget_package_version as j, fluid_os_v0_create_fluid_ostheme as k, transformProfile as l, diffAgainstSnapshot as m, pull_exports as n, deriveScreenSlug as o, computeFileHash as p, buildIdToSlugMap as r, transformNavigation as s, pullCommand as t, transformScreen as u, readMappings as v, fluid_os_v0_complete_widget_package_version_upload as w, resolveSlugToId as x, removeMapping as y, fluid_os_v0_list_fluid_osversions as z };
1203
-
1204
- //# sourceMappingURL=pull-XOsFaqz2.mjs.map