@fluid-app/fluid-cli-theme-dev 0.1.63 → 0.1.65

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 (137) hide show
  1. package/README.md +1 -1
  2. package/dist/dist-cli-preview/.vite/manifest.json +225 -225
  3. package/dist/dist-cli-preview/account-screens.js +1 -1
  4. package/dist/dist-cli-preview/chunks/{Breadcrumb-CZ-skiUy.js → Breadcrumb-BQKo8hPc.js} +1 -1
  5. package/dist/dist-cli-preview/chunks/{Combobox-BGiCYa56.js → Combobox-cf11L_T-.js} +4 -4
  6. package/dist/dist-cli-preview/chunks/{ContactsScreen-vsNgy_gn.js → ContactsScreen-gIiy4u7c.js} +1160 -1155
  7. package/dist/dist-cli-preview/chunks/{MemberManagementProviders-DMwkh3JW.js → MemberManagementProviders-C1bwc3tB.js} +258 -224
  8. package/dist/dist-cli-preview/chunks/{MessagingScreen-DzJOwPGH.js → MessagingScreen-B8ZxAE0L.js} +3904 -3927
  9. package/dist/dist-cli-preview/chunks/{MobileActionSheet-DqVPgFKI.js → MobileActionSheet-DMeTsLlK.js} +100 -100
  10. package/dist/dist-cli-preview/chunks/{MySiteScreen-BSmWlCKA.js → MySiteScreen-Dtw1rsF9.js} +69 -69
  11. package/dist/dist-cli-preview/chunks/OrdersScreen-C-b6i5jB.js +9 -0
  12. package/dist/dist-cli-preview/chunks/{OrdersScreen-CVEl9FnN.js → OrdersScreen-D9HfcTaa.js} +247 -249
  13. package/dist/dist-cli-preview/chunks/{ProfileScreen-BC5gyJDY.js → ProfileScreen-DwXozaLs.js} +1051 -965
  14. package/dist/dist-cli-preview/chunks/{SubscriptionsScreen-CFM4Pufg.js → SubscriptionsScreen-CIdYNpWT.js} +3 -3
  15. package/dist/dist-cli-preview/chunks/{account-screen-runtime-DxhJm6Qd.js → account-screen-runtime-DYMunv0A.js} +2482 -2407
  16. package/dist/dist-cli-preview/chunks/ar-DaN3xEy4.js +351 -0
  17. package/dist/dist-cli-preview/chunks/{arrow-left-UFKI-PQf.js → arrow-left-Bd7dJtIV.js} +1 -1
  18. package/dist/dist-cli-preview/chunks/bg-D3Np0de3.js +351 -0
  19. package/dist/dist-cli-preview/chunks/bn-BDSehnZ3.js +351 -0
  20. package/dist/dist-cli-preview/chunks/{cli-preview-DunPfnjU.js → cli-preview-BT_ARKjk.js} +13 -13
  21. package/dist/dist-cli-preview/chunks/{client.gen-CSTBgfxj.js → client.gen-Ck6Pm2jV.js} +246 -236
  22. package/dist/dist-cli-preview/chunks/contacts-ChSWj70z.js +4 -0
  23. package/dist/dist-cli-preview/chunks/{countries-api-adapter-B3usO25w.js → countries-api-adapter-lxSE748x.js} +2 -2
  24. package/dist/dist-cli-preview/chunks/{create-card-entry-container-DHgsbSyr.js → create-card-entry-container-D6kSC2Aw.js} +20726 -20509
  25. package/dist/dist-cli-preview/chunks/cs-CrmATj4L.js +351 -0
  26. package/dist/dist-cli-preview/chunks/da-CmJdErjd.js +351 -0
  27. package/dist/dist-cli-preview/chunks/de-C0KTNBZq.js +351 -0
  28. package/dist/dist-cli-preview/chunks/el-BOQs49GU.js +351 -0
  29. package/dist/dist-cli-preview/chunks/es-Dt3VSQPu.js +351 -0
  30. package/dist/dist-cli-preview/chunks/fi-B8UZ7eJL.js +351 -0
  31. package/dist/dist-cli-preview/chunks/fr-BomEiNL0.js +351 -0
  32. package/dist/dist-cli-preview/chunks/he-CfmnXjsr.js +351 -0
  33. package/dist/dist-cli-preview/chunks/{hey-api-client-DSvKWGPq.js → hey-api-client-DGRBsZ7Y.js} +3 -3
  34. package/dist/dist-cli-preview/chunks/hi-CTk5zGoJ.js +351 -0
  35. package/dist/dist-cli-preview/chunks/hr-DaNToqEm.js +351 -0
  36. package/dist/dist-cli-preview/chunks/hu-lcAuIwez.js +351 -0
  37. package/dist/dist-cli-preview/chunks/id-CEThcKZG.js +351 -0
  38. package/dist/dist-cli-preview/chunks/it-BCRCoDOg.js +351 -0
  39. package/dist/dist-cli-preview/chunks/ja-BIkFdIfq.js +351 -0
  40. package/dist/dist-cli-preview/chunks/ko-CD3bk6gv.js +351 -0
  41. package/dist/dist-cli-preview/chunks/{log-out-CBUq5Hd-.js → log-out-CSPZjXTK.js} +1 -1
  42. package/dist/dist-cli-preview/chunks/{map-pin-DHfjSyoR.js → map-pin-B2ScoijR.js} +1 -1
  43. package/dist/dist-cli-preview/chunks/messaging-Bq5UPkvG.js +4 -0
  44. package/dist/dist-cli-preview/chunks/{mount-account-screen-pc1VISwF.js → mount-account-screen-C_CdoGir.js} +2453 -2392
  45. package/dist/dist-cli-preview/chunks/ms-DI4mGa5X.js +351 -0
  46. package/dist/dist-cli-preview/chunks/mysite-DcpuKsuW.js +4 -0
  47. package/dist/dist-cli-preview/chunks/nl-B-XAD3Ra.js +351 -0
  48. package/dist/dist-cli-preview/chunks/no-C_NmYGbe.js +351 -0
  49. package/dist/dist-cli-preview/chunks/orders-B_57xKkq.js +4 -0
  50. package/dist/dist-cli-preview/chunks/pl-mQRzJZEW.js +351 -0
  51. package/dist/dist-cli-preview/chunks/{plus-C-lo0pFM.js → plus-CocoE5gC.js} +1 -1
  52. package/dist/dist-cli-preview/chunks/profile-BfM2KxKe.js +4 -0
  53. package/dist/dist-cli-preview/chunks/pt-BYMWPjog.js +351 -0
  54. package/dist/dist-cli-preview/chunks/ro-B98EuFQI.js +351 -0
  55. package/dist/dist-cli-preview/chunks/ru-DBO9IasC.js +351 -0
  56. package/dist/dist-cli-preview/chunks/schemas-D94nZk5x.js +5374 -0
  57. package/dist/dist-cli-preview/chunks/{screen-route-slug-utils-B254xI_J.js → screen-route-slug-utils-BDjSpDoe.js} +1 -1
  58. package/dist/dist-cli-preview/chunks/{sheet-haptics-context-D2WUiXJm.js → sheet-haptics-context-iOjKcNDj.js} +1 -1
  59. package/dist/dist-cli-preview/chunks/{sidebar-CMSWZCPS.js → sidebar-Ckzom4Xc.js} +12 -12
  60. package/dist/dist-cli-preview/chunks/sk-hRvNJfiN.js +351 -0
  61. package/dist/dist-cli-preview/chunks/{store-api-context-BIdILNWL.js → store-api-context-B96Ip2FB.js} +1 -1
  62. package/dist/dist-cli-preview/chunks/subscriptions-8VtIA4w9.js +4 -0
  63. package/dist/dist-cli-preview/chunks/sv-noYtTQbO.js +351 -0
  64. package/dist/dist-cli-preview/chunks/th-CJCAKsR-.js +351 -0
  65. package/dist/dist-cli-preview/chunks/tl-Bx8pJ7p7.js +351 -0
  66. package/dist/dist-cli-preview/chunks/tr-C4tCNiV7.js +351 -0
  67. package/dist/dist-cli-preview/chunks/{trash-2-Bq4IqH0j.js → trash-2-wVweQZMw.js} +4 -4
  68. package/dist/dist-cli-preview/chunks/uk-xFjXQDP3.js +351 -0
  69. package/dist/dist-cli-preview/chunks/{use-optional-store-Csor1Z3z.js → use-optional-store-lZUz976C.js} +2 -2
  70. package/dist/dist-cli-preview/chunks/use-store-DVmgsDXy.js +103 -0
  71. package/dist/dist-cli-preview/chunks/{useInfiniteQuery-BpFtQ7DH.js → useInfiniteQuery-Dtzov9uh.js} +1 -1
  72. package/dist/dist-cli-preview/chunks/{user-BGrQJ1GJ.js → user-B81IrSgp.js} +1 -1
  73. package/dist/dist-cli-preview/chunks/{users-DBL0isYy.js → users-BXTjoCUz.js} +1 -1
  74. package/dist/dist-cli-preview/chunks/vi-BAvhRTgb.js +351 -0
  75. package/dist/dist-cli-preview/chunks/zh_CN-Cyq9zeex.js +351 -0
  76. package/dist/dist-cli-preview/chunks/zh_TW-DFM5MhMA.js +351 -0
  77. package/dist/index.mjs +346 -32
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/skills/themes-cart-feedback/SKILL.md +9 -14
  80. package/dist/skills/themes-review/SKILL.md +40 -34
  81. package/dist/skills/themes-review/references/blocks-vs-sections.md +2 -2
  82. package/dist/skills/themes-review/references/dead-code.md +1 -1
  83. package/dist/skills/themes-review/references/dynamism.md +9 -3
  84. package/dist/skills/themes-review/references/editor-attributes.md +25 -26
  85. package/dist/skills/themes-review/references/examples.md +3 -4
  86. package/dist/skills/themes-review/references/fairshare-attributes.md +5 -5
  87. package/dist/skills/themes-review/references/global-settings.md +56 -67
  88. package/dist/skills/themes-review/references/liquid-correctness.md +10 -15
  89. package/dist/skills/themes-review/references/media-tag.md +8 -7
  90. package/dist/skills/themes-review/references/navigation.md +18 -13
  91. package/dist/skills/themes-review/references/security-accessibility.md +11 -6
  92. package/dist/skills/themes-review/references/setting-types.md +136 -77
  93. package/dist/skills/themes-settings-schema/SKILL.md +15 -2
  94. package/package.json +2 -2
  95. package/dist/dist-cli-preview/chunks/OrdersScreen-d4fJE_dm.js +0 -9
  96. package/dist/dist-cli-preview/chunks/ar-B7NQmkZ1.js +0 -341
  97. package/dist/dist-cli-preview/chunks/bg-CxfL8rZN.js +0 -341
  98. package/dist/dist-cli-preview/chunks/bn-CdhHCK--.js +0 -341
  99. package/dist/dist-cli-preview/chunks/contacts-CpYlEgAq.js +0 -4
  100. package/dist/dist-cli-preview/chunks/cs-DTe-xGeI.js +0 -341
  101. package/dist/dist-cli-preview/chunks/da-B8Cl_vkl.js +0 -341
  102. package/dist/dist-cli-preview/chunks/de-D8cYy8G-.js +0 -341
  103. package/dist/dist-cli-preview/chunks/el-Chjin2JW.js +0 -341
  104. package/dist/dist-cli-preview/chunks/es-mp8RwFN4.js +0 -341
  105. package/dist/dist-cli-preview/chunks/fi-91gcpxoy.js +0 -341
  106. package/dist/dist-cli-preview/chunks/fr-ekvUCKTH.js +0 -341
  107. package/dist/dist-cli-preview/chunks/he-B7QiuquT.js +0 -341
  108. package/dist/dist-cli-preview/chunks/hi-DNNhi7ud.js +0 -341
  109. package/dist/dist-cli-preview/chunks/hr-CAwxMKX_.js +0 -341
  110. package/dist/dist-cli-preview/chunks/hu-D_oQwH4Q.js +0 -341
  111. package/dist/dist-cli-preview/chunks/id-oeAszBoo.js +0 -341
  112. package/dist/dist-cli-preview/chunks/isAfter-BUGgByEf.js +0 -1414
  113. package/dist/dist-cli-preview/chunks/it-Dme6GXSG.js +0 -341
  114. package/dist/dist-cli-preview/chunks/ja-CRnvQNCa.js +0 -341
  115. package/dist/dist-cli-preview/chunks/ko-8mk8A3_I.js +0 -341
  116. package/dist/dist-cli-preview/chunks/messaging-DkyH4CPP.js +0 -4
  117. package/dist/dist-cli-preview/chunks/ms-Bs0GvdOw.js +0 -341
  118. package/dist/dist-cli-preview/chunks/mysite-wvfITV7T.js +0 -4
  119. package/dist/dist-cli-preview/chunks/nl-tsI6lLxl.js +0 -341
  120. package/dist/dist-cli-preview/chunks/no-DMot6qX0.js +0 -341
  121. package/dist/dist-cli-preview/chunks/orders-c13r499A.js +0 -4
  122. package/dist/dist-cli-preview/chunks/pl-DmTxONNg.js +0 -341
  123. package/dist/dist-cli-preview/chunks/profile-CoWo9Sxe.js +0 -4
  124. package/dist/dist-cli-preview/chunks/pt-DgmwnAV-.js +0 -341
  125. package/dist/dist-cli-preview/chunks/ro-B4M17oRc.js +0 -341
  126. package/dist/dist-cli-preview/chunks/ru-BS2gZnbs.js +0 -341
  127. package/dist/dist-cli-preview/chunks/schemas-9_ZwC5nC.js +0 -3977
  128. package/dist/dist-cli-preview/chunks/sk-BdDjjHfn.js +0 -341
  129. package/dist/dist-cli-preview/chunks/subscriptions-BHALQrcU.js +0 -4
  130. package/dist/dist-cli-preview/chunks/sv-CW7FL5HA.js +0 -341
  131. package/dist/dist-cli-preview/chunks/th-BYBMKmSh.js +0 -341
  132. package/dist/dist-cli-preview/chunks/tl-CDigaLsK.js +0 -341
  133. package/dist/dist-cli-preview/chunks/tr-PxiurNCr.js +0 -341
  134. package/dist/dist-cli-preview/chunks/uk-B4aYnJ8t.js +0 -341
  135. package/dist/dist-cli-preview/chunks/vi-BoKSdzH3.js +0 -341
  136. package/dist/dist-cli-preview/chunks/zh_CN-DrCrXsZC.js +0 -341
  137. package/dist/dist-cli-preview/chunks/zh_TW-JmyC1W5K.js +0 -341
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["delay","isRecord","isRecord","themes.getApplicationThemeAvailableThemeables","settingTypesJson.types","isRecord","parseDocument","isRecord","isRecord","themes.listThemeResources","themes.getThemeAssets","isRecord","themes.createFileResource","themes.destroyFileResource","themes.updateThemeResource","themes.deleteThemeResource","isRecord","themes.listApplicationThemes","themes.getApplicationTheme","themes.cloneApplicationThemeForDevelopment","themes.createApplicationTheme","themes.getApplicationTheme","themes.createApplicationTheme","themes.getApplicationTheme","themes.publishApplicationTheme"],"sources":["../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/api.ts","../src/plugin-state.ts","../src/theme/dev-server/member-preview-contract.ts","../src/theme/navigation-members.ts","../../../api-clients/themes/src/namespaces/v0.ts","../src/commands/navigate.ts","../src/theme-config.ts","../src/theme/mime-type.ts","../src/theme/resource-key.ts","../../../platform/theme-schema/src/setting-types.json","../../../platform/theme-schema/src/types.ts","../../../platform/theme-schema/src/validate-settings.ts","../../../platform/theme-schema/src/validate-blocks.ts","../../../platform/theme-schema/src/validate.ts","../../../platform/theme-schema/src/sections.ts","../src/theme/file.ts","../src/theme/fluid-ignore.ts","../src/theme/root.ts","../src/theme/dev-server/member-screen-assets.ts","../src/theme/dev-server/hot-reload.ts","../src/theme/dev-server/member-preview-html.ts","../src/theme/dev-server/member-preview.ts","../src/theme/dev-server/sse.ts","../src/theme/dev-server/proxy.ts","../src/theme/format-error.ts","../src/theme/dev-server/watcher.ts","../src/theme/background-pull-guard.ts","../src/theme/case-collisions.ts","../src/theme/asset-manifest.ts","../src/theme/stylesheet-keys.ts","../src/theme/member-dependencies.ts","../src/theme/member-configuration.ts","../src/theme/syncer.ts","../src/theme/liquid-delimiters.ts","../src/theme/dev-server/port-preflight.ts","../src/theme/dev-server/index.ts","../src/theme/dev-remote-baseline.ts","../src/theme/shadow-repo.ts","../src/theme-picker.ts","../src/workspace.ts","../src/commands/dev.ts","../src/theme/merge-push.ts","../src/theme/auto-baseline.ts","../src/theme/legacy-migration.ts","../src/theme/sync-identity.ts","../src/commands/push.ts","../src/theme/background-pull.ts","../src/theme/member-transfer.ts","../src/theme/merge-pull.ts","../src/commands/pull.ts","../src/commands/lint.ts","../src/commands/init.ts","../src/skills/install.ts","../src/commands/skills.ts","../src/commands/theme.ts","../src/index.ts"],"sourcesContent":["/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * `??` rather than `||` so an explicitly empty `errors` (`\"\"`, `0`, `false`)\n * is preserved instead of silently falling back to the whole envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n return (body.errors ?? body) as ApiErrorData;\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\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 /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\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 priority?: RequestInit[\"priority\"];\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\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\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(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\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 cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\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 const headerRequestId = getRequestIdFromHeaders(response.headers);\n\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 // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = 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 headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\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 const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\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 priority,\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 if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\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 fetchWithNetworkRetry(url, fetchOptions, signal);\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 {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = 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 (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\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","import {\n ApiError,\n createFetchClient,\n type FetchClient,\n type RequestOptions,\n} from \"@fluid-app/api-client-core\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nexport type ApiClient = FetchClient;\n\n/** Base URL for all API calls. Set FLUID_API_BASE to route through a BFF. */\nexport function getApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\n// Only the desktop's unattended worker opts in. Gate auth resolution to pace\n// even concurrent identity/resource requests within a background theme pull.\nlet backgroundGate = Promise.resolve();\nasync function backgroundToken(tokenOverride?: string): Promise<string | null> {\n backgroundGate = backgroundGate.then(\n () => new Promise((resolve) => setTimeout(resolve, 1_000)),\n );\n await backgroundGate;\n return tokenOverride ?? getAuthToken() ?? null;\n}\n\nexport function createApiClient(tokenOverride?: string): ApiClient {\n const client = createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () =>\n process.env[\"FLUID_BACKGROUND_SYNC\"] === \"1\"\n ? backgroundToken(tokenOverride)\n : (tokenOverride ?? getAuthToken() ?? null),\n });\n const request = async <TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> => {\n for (let attempt = 0; ; attempt++) {\n options.signal?.throwIfAborted();\n try {\n return await client.request<TResponse>(endpoint, options);\n } catch (error) {\n // Only retry reads: a failed write response may follow a successful\n // server mutation. Preserve the final API error and request ID.\n if (\n (options.method ?? \"GET\") !== \"GET\" ||\n !(error instanceof ApiError) ||\n ![502, 503, 504].includes(error.status) ||\n attempt >= 2 ||\n options.signal?.aborted\n ) {\n throw error;\n }\n await delay(500 * 2 ** attempt, undefined, { signal: options.signal });\n }\n }\n };\n return {\n ...client,\n request,\n get: (endpoint, params, options) =>\n request(endpoint, { ...options, method: \"GET\", params }),\n };\n}\n\nexport function requireToken(): string {\n const token = getAuthToken();\n if (!token) {\n console.error(\"Not logged in. Run `fluid login` first.\");\n process.exit(1);\n }\n return token;\n}\n","import { existsSync } from \"node:fs\";\nimport { readConfig, updateConfig } from \"@fluid-app/fluid-cli\";\n\nexport interface DevThemeRef {\n id: number;\n name: string;\n /** Theme that was pulled when this isolated dev target was created. */\n sourceThemeId?: number;\n}\n\ninterface ThemeDevState {\n /**\n * Dev themes keyed per project, so `theme dev` in one working copy never\n * reuses (and clobbers) another project's sandbox theme. See `devThemeKey`.\n * Entries are pruned once their theme directory no longer exists, so the map\n * can't grow without bound as projects (and one-off/temp dirs) come and go.\n */\n devThemes?: Record<string, DevThemeRef>;\n /** Most recently started dev theme — `navigate`'s default target. */\n lastDevThemeId?: number;\n /**\n * Legacy single global dev theme id. Older CLI versions stored one dev theme\n * here regardless of project. Read once for migration (see `getDevTheme`),\n * then dropped in favour of `devThemes`.\n */\n devThemeId?: number;\n /** Legacy companion to `devThemeId`. */\n devThemeName?: string;\n}\n\nconst PLUGIN_KEY = \"theme-dev\";\n\nfunction getState(): ThemeDevState {\n const config = readConfig();\n return (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n}\n\n/** Extract the absolute theme root from a `company:themeRoot` key. */\nfunction themeRootFromKey(key: string): string {\n const sep = key.indexOf(\":\");\n return sep === -1 ? key : key.slice(sep + 1);\n}\n\n/**\n * Set `key` to `theme`, dropping any entries whose theme directory no longer\n * exists. Tying an entry's lifetime to its directory keeps the map bounded —\n * abandoned/deleted projects fall out the next time `theme dev` runs anywhere.\n */\nfunction withDevTheme(\n existing: Record<string, DevThemeRef> | undefined,\n key: string,\n theme: DevThemeRef,\n): Record<string, DevThemeRef> {\n const next: Record<string, DevThemeRef> = {};\n for (const [k, v] of Object.entries(existing ?? {})) {\n if (existsSync(themeRootFromKey(k))) next[k] = v;\n }\n next[key] = theme;\n return next;\n}\n\n/**\n * Stable key identifying a dev theme's owning project: the Fluid company\n * (subdomains are globally unique) plus the absolute theme root. Two working\n * copies — or the same copy pulled from two companies — get distinct keys.\n */\nexport function devThemeKey(\n company: string | undefined,\n themeRoot: string,\n): string {\n return `${company ?? \"default\"}:${themeRoot}`;\n}\n\n/**\n * The dev theme stored for a project key, if any. Falls back once to the legacy\n * global `devThemeId` (older CLI versions) and adopts it for this key — clearing\n * the legacy fields so a second project can't adopt the same theme and collide.\n */\nexport function getDevTheme(key: string): DevThemeRef | undefined {\n const state = getState();\n const existing = state.devThemes?.[key];\n if (existing) return existing;\n\n if (state.devThemeId) {\n const migrated: DevThemeRef = {\n id: state.devThemeId,\n name: state.devThemeName ?? `Development #${state.devThemeId}`,\n };\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const { devThemeId: _id, devThemeName: _name, ...rest } = current;\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...rest,\n devThemes: withDevTheme(rest.devThemes, key, migrated),\n lastDevThemeId: migrated.id,\n },\n },\n };\n });\n return migrated;\n }\n\n return undefined;\n}\n\n/** Store (or refresh) the dev theme for a project key and mark it most-recent. */\nexport function setDevTheme(key: string, theme: DevThemeRef): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...current,\n devThemes: withDevTheme(current.devThemes, key, theme),\n lastDevThemeId: theme.id,\n },\n },\n };\n });\n}\n\n/** Forget a project's dev theme (it was deleted remotely or is no longer a dev theme). */\nexport function clearDevTheme(key: string): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const removed = current.devThemes?.[key];\n if (!removed) return config;\n const { [key]: _removed, ...rest } = current.devThemes ?? {};\n const next: ThemeDevState = { ...current, devThemes: rest };\n // Don't leave `navigate` pointing at a theme we just forgot.\n if (current.lastDevThemeId === removed.id) {\n next.lastDevThemeId = undefined;\n }\n return {\n ...config,\n plugins: { ...config.plugins, [PLUGIN_KEY]: next },\n };\n });\n}\n\n/**\n * Mark a theme as the most recently started dev server (`navigate`'s default)\n * without recording it as a project's dev theme — used for the `--theme`\n * escape hatch, which may target an arbitrary (non-dev) theme.\n */\nexport function setLastDevThemeId(id: number): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: { ...current, lastDevThemeId: id },\n },\n };\n });\n}\n\n/**\n * The dev theme to target by default in `navigate` — the most recently started\n * dev server. Falls back to the legacy global id for users who haven't yet run\n * the per-project `theme dev`.\n */\nexport function getLastDevThemeId(): number | undefined {\n const state = getState();\n return state.lastDevThemeId ?? state.devThemeId;\n}\n","/** A deliberately small projection of the Rails metadata and render contracts. */\nexport interface MemberPreviewPage {\n id: number;\n title: string;\n path: string;\n published: boolean;\n}\nexport interface MemberPreviewSite {\n id: number;\n name: string;\n pages: MemberPreviewPage[];\n}\nexport interface MemberPreviewMetadata {\n members_sites: MemberPreviewSite[];\n system_screens: Array<{ key: string }>;\n}\nexport interface MemberPreviewSelection {\n members_site_id: number;\n country_id?: number;\n language_id?: number;\n member_type_id?: string;\n}\nexport class MemberPreviewError extends Error {\n constructor(\n message: string,\n readonly status: number = 502,\n ) {\n super(message);\n }\n}\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nexport function isId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value > 0;\n}\n/** The metadata API still exposes storefront-era /accounts paths. Account hosts omit it. */\nexport function accountPath(path: string): string {\n return path === \"/accounts\"\n ? \"/\"\n : path.startsWith(\"/accounts/\")\n ? path.slice(\"/accounts\".length)\n : path;\n}\nexport function isProductionPath(value: unknown): value is string {\n if (\n typeof value !== \"string\" ||\n !value.startsWith(\"/\") ||\n value.startsWith(\"//\") ||\n value.includes(\"\\\\\")\n )\n return false;\n const parsed = new URL(value, \"http://preview.invalid\");\n return parsed.pathname === value && !value.startsWith(\"/_members\");\n}\nexport function parseMemberMetadata(value: unknown): MemberPreviewMetadata {\n if (\n !isRecord(value) ||\n !Array.isArray(value.members_sites) ||\n !Array.isArray(value.system_screens)\n ) {\n throw new MemberPreviewError(\n \"This server does not provide compatible member preview metadata.\",\n );\n }\n const sites = value.members_sites.map((site): MemberPreviewSite => {\n if (\n !isRecord(site) ||\n !isId(site.id) ||\n typeof site.name !== \"string\" ||\n !Array.isArray(site.pages)\n )\n throw new MemberPreviewError(\"Invalid member site metadata.\");\n const pages = site.pages.map((page): MemberPreviewPage => {\n if (\n !isRecord(page) ||\n !isId(page.id) ||\n typeof page.title !== \"string\" ||\n !isProductionPath(page.path) ||\n typeof page.published !== \"boolean\"\n )\n throw new MemberPreviewError(\"Invalid member page metadata.\");\n return {\n id: page.id,\n title: page.title,\n path: accountPath(page.path),\n published: page.published,\n };\n });\n if (new Set(pages.map((p) => p.path)).size !== pages.length)\n throw new MemberPreviewError(\"Ambiguous member page paths.\");\n return { id: site.id, name: site.name, pages };\n });\n if (new Set(sites.map((s) => s.id)).size !== sites.length)\n throw new MemberPreviewError(\"Duplicate member site identifiers.\");\n const screens = value.system_screens.map((screen): { key: string } => {\n if (\n !isRecord(screen) ||\n typeof screen.key !== \"string\" ||\n !/^[a-z][a-z0-9_-]*$/.test(screen.key)\n )\n throw new MemberPreviewError(\"Invalid system screen metadata.\");\n return { key: screen.key };\n });\n return { members_sites: sites, system_screens: screens };\n}\nexport function parseSelection(\n params: URLSearchParams,\n): MemberPreviewSelection {\n const site = numericParam(params, \"members_site_id\");\n if (!site)\n throw new MemberPreviewError(\n \"Select a member site at /_members first.\",\n 400,\n );\n const memberType = params.get(\"member_type_id\");\n if (\n params.getAll(\"member_type_id\").length > 1 ||\n (memberType !== null && !/^[\\w-]{1,100}$/.test(memberType))\n )\n throw new MemberPreviewError(\"Invalid member type selection.\", 400);\n return {\n members_site_id: site,\n country_id: numericParam(params, \"country_id\"),\n language_id: numericParam(params, \"language_id\"),\n ...(memberType ? { member_type_id: memberType } : {}),\n };\n}\nfunction numericParam(\n params: URLSearchParams,\n key: string,\n): number | undefined {\n const value = params.get(key);\n if (value === null) return undefined;\n if (\n params.getAll(key).length !== 1 ||\n !/^[1-9]\\d*$/.test(value) ||\n !isId(Number(value))\n )\n throw new MemberPreviewError(`Invalid ${key}.`, 400);\n return Number(value);\n}\nexport function memberPreviewUrl(\n path: string,\n selection: MemberPreviewSelection,\n): string {\n const url = new URL(path, \"http://preview.invalid\");\n if (url.origin !== \"http://preview.invalid\")\n throw new MemberPreviewError(\"Expected a local member path.\", 400);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n url.pathname = `/_members${url.pathname}`;\n for (const key of [\n \"members_site_id\",\n \"country_id\",\n \"language_id\",\n \"member_type_id\",\n ] as const) {\n const value = selection[key];\n if (value === undefined) url.searchParams.delete(key);\n else url.searchParams.set(key, String(value));\n }\n return url.pathname + url.search + url.hash;\n}\nexport function parseMemberRender(\n value: unknown,\n themeId: number,\n selection: MemberPreviewSelection,\n pageId: number | null,\n screenKey: string | null = null,\n): { html: string; styles: string; templateStyles: string } {\n const preview = isRecord(value) ? value.preview : undefined;\n if (\n !isRecord(preview) ||\n preview.contract_version !== 1 ||\n preview.mode !== \"sample\" ||\n preview.theme_id !== themeId ||\n preview.members_site_id !== selection.members_site_id ||\n preview.members_page_id !== pageId ||\n preview.system_screen_key !== screenKey ||\n typeof preview.html_content !== \"string\" ||\n !(\n preview.page_stylesheet === null ||\n typeof preview.page_stylesheet === \"string\"\n ) ||\n !(\n preview.template_stylesheet === null ||\n typeof preview.template_stylesheet === \"string\"\n )\n ) {\n throw new MemberPreviewError(\n \"Incompatible member preview response. Update the server and CLI together.\",\n );\n }\n return {\n html: preview.html_content,\n styles: preview.page_stylesheet ?? \"\",\n templateStyles: preview.template_stylesheet ?? \"\",\n };\n}\n","import {\n isRecord,\n parseMemberMetadata,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./dev-server/member-preview-contract.js\";\n\ninterface NavigatorMetadata extends MemberPreviewMetadata {\n supported_system_screens: string[];\n}\n\n/** Read the running server's acknowledged route map, without sending credentials. */\nexport async function readPreviewMetadata(\n address: string,\n themeId: number,\n): Promise<NavigatorMetadata | undefined> {\n let value: unknown;\n try {\n const response = await fetch(`${address}/_members/_metadata`, {\n redirect: \"error\",\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) throw new Error(\"Member metadata unavailable\");\n value = await response.json();\n } catch {\n console.warn(\n \"Member preview is unavailable. Start an updated theme dev server to list member pages.\",\n );\n return undefined;\n }\n if (!isRecord(value) || value.mode !== \"sample\") {\n throw new Error(\n \"The running server does not provide compatible preview metadata.\",\n );\n }\n if (value.theme_id !== themeId) {\n throw new Error(\n `The running server is not serving theme #${themeId}. Use its theme ID or restart theme dev.`,\n );\n }\n if (\n !Array.isArray(value.supported_system_screens) ||\n !value.supported_system_screens.every(\n (key): key is string => typeof key === \"string\",\n )\n ) {\n throw new Error(\n \"The running server does not provide preview capabilities.\",\n );\n }\n return {\n ...parseMemberMetadata(value),\n supported_system_screens: value.supported_system_screens,\n };\n}\n\nexport function memberChoices(\n metadata: NavigatorMetadata | undefined,\n): Array<{ title: string; value: string }> {\n if (!metadata) return [];\n return metadata.members_sites.flatMap((site) => {\n const prefix = `Member · ${site.name} (#${site.id}) · `;\n const selection = { members_site_id: site.id };\n return [\n ...site.pages.map((page) => ({\n title: `${prefix}${page.title}${page.published ? \"\" : \" (draft)\"} — ${page.path}`,\n value: memberPreviewUrl(page.path, selection),\n })),\n ...metadata.system_screens\n .filter((screen) =>\n metadata.supported_system_screens.includes(screen.key),\n )\n .map((screen) => {\n return {\n title: `${prefix}${screen.key}`,\n value: memberPreviewUrl(`/${screen.key}`, selection),\n };\n }),\n ];\n });\n}\n\n/** A watcher update may remove a page while the prompt is open. */\nexport async function validateMemberDestination(\n address: string,\n themeId: number,\n path: string,\n): Promise<void> {\n const metadata = await readPreviewMetadata(address, themeId);\n if (!memberChoices(metadata).some((choice) => choice.value === path)) {\n throw new Error(\n \"This member page is no longer available. Run the navigator again to refresh the choices.\",\n );\n }\n}\n","/**\n * Generated API client functions for v0\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/v0\";\n\n// ============================================================================\n// applicationthemetemplates\n// ============================================================================\n\n/**\n * Lists all theme templates\n *\n *\n * @param client - Fetch client instance\n\n */\nexport async function listThemeTemplates(\n client: FetchClient,\n): Promise<\n operations[\"listThemeTemplates\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates`);\n}\n\n/**\n * Creates a theme template\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n * @param params - params\n */\nexport async function createThemeTemplate(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n params: operations[\"createThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"createThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates`, body, { params });\n}\n\n/**\n * List all mysite themes\n * List all mysite themes\n *\n * @param client - Fetch client instance\n\n */\nexport async function listMysiteThemes(\n client: FetchClient,\n): Promise<\n operations[\"listMysiteThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/mysite_themes`);\n}\n\n/**\n * Retrieves a theme template\n * Returns a theme template with details. For section templates whose schema\ndeclares `@theme` or named standalone block references, the response\nincludes an `available_theme_blocks` array with the resolved block schemas\n(name, settings, presets). Private blocks (underscore-prefixed) are excluded\nfrom `@theme` results but included when explicitly referenced by name.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Updates a theme template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplate(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_theme_templates/${id}`, body);\n}\n\n/**\n * Deletes a theme template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Returns all available themeables for theme template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeTemplateAvailableThemeables(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeTemplateAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_themeables`,\n );\n}\n\n/**\n * Get available variables for a theme template\n * Get available variables that can be used in the theme template\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeTemplateAvailableVariables(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeTemplateAvailableVariables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_variables`,\n );\n}\n\n/**\n * Clones a theme template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/clone`);\n}\n\n/**\n * Publishes the template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function publishThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"publishThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/publish`);\n}\n\n/**\n * Render a system screen in the selected member site's theme\n * Uses the same layout, settings, localization, and GlobalEmbeds pipeline as template previews. The screen body is sealed and never persisted. Unsaved shared settings and layout overrides are accepted; screen body, stylesheet, variables, resource and version overrides are rejected. The real SDK element uses read-only sample data in the builder without member credentials or live member API calls. The builder displays the response in its preview canvas.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function renderThemeSystemScreenPage(\n client: FetchClient,\n body: NonNullable<\n operations[\"renderThemeSystemScreenPage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeSystemScreenPage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/render_page`, body);\n}\n\n/**\n * Renders a page for a theme template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function renderThemeTemplatePage(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"renderThemeTemplatePage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeTemplatePage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_theme_templates/${id}/render_page`,\n body,\n );\n}\n\n/**\n * Renders a section template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function renderThemeTemplateSection(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"renderThemeTemplateSection\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/render_section`);\n}\n\n/**\n * Sets a theme template as default\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function setDefaultThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"setDefaultThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/set_default`);\n}\n\n/**\n * Updates themeable records to be used by the specified template\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function updateThemeTemplateThemeables(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"updateThemeTemplateThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(`/api/application_theme_templates/${id}/themeables_update`);\n}\n\n// ============================================================================\n// application-themes\n// ============================================================================\n\n/**\n * List application themes\n * Get all application themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listApplicationThemes(\n client: FetchClient,\n params?: operations[\"listApplicationThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes`, params);\n}\n\n/**\n * Create an application theme\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createApplicationTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createApplicationTheme\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes`, body);\n}\n\n/**\n * Get current active application theme\n *\n *\n * @param client - Fetch client instance\n\n */\nexport async function getActiveApplicationTheme(\n client: FetchClient,\n): Promise<\n operations[\"getActiveApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/active`);\n}\n\n/**\n * Import an application theme from zip file\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function importApplicationThemeFromZip(\n client: FetchClient,\n body: NonNullable<\n operations[\"importApplicationThemeFromZip\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"importApplicationThemeFromZip\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/import_zip`, body);\n}\n\n/**\n * Get an application theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationTheme(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationTheme\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}`, params);\n}\n\n/**\n * Update an application theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateApplicationTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_themes/${id}`, body);\n}\n\n/**\n * Delete an application theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_themes/${id}`);\n}\n\n/**\n * Returns available themeables for a given type scoped to the theme's company\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationThemeAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationThemeAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Clone an application theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/clone`);\n}\n\n/**\n * Create a development reference clone of an application theme\n * Creates an isolated development theme while preserving existing DAM and ImageKit references without transferring asset bytes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function cloneApplicationThemeForDevelopment(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"cloneApplicationThemeForDevelopment\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"cloneApplicationThemeForDevelopment\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${id}/clone_for_development`,\n body,\n );\n}\n\n/**\n * Get a short-lived download URL for the theme at a past version\n * Returns a pre-signed URL for a tarball of the theme's connected\nrepository at one commit. The URL is fetched **directly from the Git\nprovider by the client** and needs no authentication of its own — Fluid\nmints it and never proxies the bytes, so repository credentials stay\nserver-side.\n\nOne request returns the entire tree, binary assets included as real\nbytes. That matters for restoring a past version: an asset's current\nURL points at its current content, so anything built from per-file\nlinks would silently fail to revert images.\n\nRead-only. This exposes a version; it does not apply one. Clients with\na local checkout restore by writing the archive into their theme\nfolder, then pushing — the same local-then-publish flow as the Mist and\nPortal rollbacks. A client without one (the admin editor) uses the\ngit_restore operation instead, which applies a version to the theme's\ndraft records server-side.\n\nThe URL is short-lived. Re-request it rather than caching it.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param params - params\n */\nexport async function getApplicationThemeGitArchiveUrl(\n client: FetchClient,\n id: string | number,\n params: operations[\"getApplicationThemeGitArchiveUrl\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeGitArchiveUrl\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_archive_url`, params);\n}\n\n/**\n * Connect the theme to a Git repository, provisioning one if needed\n * Ensures the theme has a connected Git repository, creating and\nprovisioning one when it doesn't already have it.\n\nIdempotent — an already-connected theme returns its repository\nunchanged, and one whose earlier provisioning failed has it retried.\nSafe to call twice.\n\nProvisioning the upstream repository happens in the background, so a\nfreshly created connection reports `status: provisioning` and becomes\n`connected` shortly after. Poll the git_versions operation, or retry\nthis one, to observe the change.\n\nThis connects themes that automatic provisioning deliberately skips —\ndevelopment themes among them — because an explicit request is a\ndifferent intent from \"provision every theme on creation\".\n\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function connectApplicationThemeToGit(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"connectApplicationThemeToGit\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_connect`);\n}\n\n/**\n * Restore the theme's draft content to a past published version\n * Applies one published version back to the theme, for a client that has\nno local checkout to stage it in. The git_archive_url operation is the\nequivalent for one that does.\n\nPublishes as it writes: every record goes through the theme's own write\nseam, which publishes it. Publishing is per record and there is no\nwhole-theme publish, so a restore that only touched drafts would leave\nthe caller publishing every template by hand. For an **active** theme\nthis therefore goes live.\n\nIt remains undoable: the version being left behind is still in the\nhistory this restored from.\n\nThe version is read as a single archive rather than file by file, and\napplied in one transaction: a tree that cannot be written leaves the\ntheme as it was rather than half-restored.\n\nFiles the target version does not contain are removed, which is what\nmakes this a restore rather than a merge — a template added after that\nversion would otherwise survive into a tree that never had it.\n\nUndoing a restore is another restore: the version you were on is still\nin the history you restored from.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function restoreApplicationThemeGitVersion(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"restoreApplicationThemeGitVersion\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"restoreApplicationThemeGitVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_restore`, body);\n}\n\n/**\n * List a theme's published version history from its connected Git repo\n * Returns the recent commits on the theme's connected Git repository,\nnewest first. Themes are DB-canonical: Fluid writes one commit per\npublish, so this log is the theme's published version history.\n\n`author_name` is the committing identity, which for anything Fluid\npushed is the Fluid GitHub App. The person who published is named in\n`message`.\n\nRead-only — this endpoint never provisions or repairs a repository.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function listApplicationThemeGitVersions(\n client: FetchClient,\n id: string | number,\n params?: operations[\"listApplicationThemeGitVersions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemeGitVersions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_versions`, params);\n}\n\n/**\n * Import an application theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function importApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.post(`/api/application_themes/${id}/import`);\n}\n\n/**\n * Publishes the theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function publishApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"publishApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/publish`);\n}\n\n/**\n * Get theme assets\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeAssets(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeAssets\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/theme_assets`);\n}\n\n// ============================================================================\n// applicationthemeresources\n// ============================================================================\n\n/**\n * Lists all theme resources\n * Lists resources for a theme owned by the current company, including the canonical members_sites.json snapshot. Themes without member sites receive an explicit empty version 2 document. Companyless root themes are not accessible through this endpoint. The content field remains JSON-encoded text.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function listThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"listThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${application_theme_id}/resources`,\n );\n}\n\n/**\n * Updates a theme resource\n * Submit member template dependencies together with members_sites.json using member_files so navigation is captured before any file changes. The files and member graph apply atomically; a rejected dependency rolls back the bundle. Existing single-resource writes remain supported. Missing manifests preserve existing configuration; deleting the file is refused.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function updateThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"updateThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_themes/${application_theme_id}/resources`,\n body,\n );\n}\n\n/**\n * Deletes a theme resource\n *\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function deleteThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"deleteThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"deleteThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/application_themes/${application_theme_id}/resources`,\n { body },\n );\n}\n\n/**\n * Commits the theme's current state as one version\n * Called once by the CLI AFTER its per-file push loop, and the only thing that turns a push into a commit — the per-file writes themselves just mark the theme changed. This is the other half of check_push, which runs once before the loop. Omitting it is safe: the server sweeps anything left unsynced, so an older CLI lags by at most a sweep rather than going unsynced.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function syncThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"syncThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/sync`,\n );\n}\n\n/**\n * Merge-aware push preflight: CLI checks its stored base_sha matches the server's current content_version_sha before starting the per-file push loop. Enforcement lives on the per-file PUT/DELETE too; this endpoint is UX so the CLI can fail fast before touching any state.\n *\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function checkPushThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"checkPushThemeResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"checkPushThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/check_push`,\n body,\n );\n}\n\n// ============================================================================\n// file-resources\n// ============================================================================\n\n/**\n * Returns a list of file resources\n *\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listFileResources(\n client: FetchClient,\n params?: operations[\"listFileResources\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources`, params);\n}\n\n/**\n * Creates a file resource\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createFileResource(\n client: FetchClient,\n body: NonNullable<\n operations[\"createFileResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFileResource\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources`, body);\n}\n\n/**\n * Creates multiple file resources\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkCreateFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkCreateFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkCreateFileResources\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources/bulk_create`, body);\n}\n\n/**\n * Deletes multiple file resources\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkDestroyFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkDestroyFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkDestroyFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/bulk_destroy`, { body });\n}\n\n/**\n * Shows a file resource\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function showFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"showFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources/${id}`);\n}\n\n/**\n * Deletes a file resource\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function destroyFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"destroyFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/${id}`);\n}\n\n// ============================================================================\n// root-themes\n// ============================================================================\n\n/**\n * List root themes\n * Get all root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listRootThemes(\n client: FetchClient,\n params?: operations[\"listRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes`, params);\n}\n\n/**\n * Create a root theme\n *\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createRootTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes`, body);\n}\n\n/**\n * List company root themes\n * Get all company root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listCompanyRootThemes(\n client: FetchClient,\n params?: operations[\"listCompanyRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listCompanyRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes/my`, params);\n}\n\n/**\n * Update a root theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/root_themes/${id}`, body);\n}\n\n/**\n * Delete a root theme\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteRootTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/root_themes/${id}`);\n}\n\n/**\n * Update a root theme status\n *\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootThemeStatus(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootThemeStatus\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootThemeStatus\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes/${id}/status`, body);\n}\n\n// ============================================================================\n// theme-region-rules\n// ============================================================================\n\n/**\n * List theme region rules\n * Retrieve a list of theme region rules for the current company\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeRegionRules(\n client: FetchClient,\n params?: operations[\"listThemeRegionRules\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeRegionRules\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules`, params);\n}\n\n/**\n * Create theme region rule\n * Create a new theme region rule\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createThemeRegionRule(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createThemeRegionRule\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/theme_region_rules`, body);\n}\n\n/**\n * Show theme region rule\n * Retrieve a specific theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules/${id}`);\n}\n\n/**\n * Update theme region rule\n * Update an existing theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeRegionRule(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/theme_region_rules/${id}`, body);\n}\n\n/**\n * Delete theme region rule\n * Delete a theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.delete(`/api/theme_region_rules/${id}`);\n}\n","import { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { getLastDevThemeId } from \"../plugin-state.js\";\nimport {\n memberChoices,\n readPreviewMetadata,\n validateMemberDestination,\n} from \"../theme/navigation-members.js\";\nimport { themes } from \"@fluid-app/themes-api-client\";\n\nfunction localSuggest(\n input: string,\n choices: prompts.Choice[],\n): prompts.Choice[] {\n if (!input) return choices;\n const lower = input.toLowerCase();\n return choices.filter((c) => c.title.toLowerCase().includes(lower));\n}\n\ninterface ThemeTemplate {\n id: number;\n name: string;\n themeable_type: string;\n default: boolean;\n}\n\ninterface TemplatesResponse {\n templates: ThemeTemplate[];\n}\n\nconst THEMEABLE_TYPE_MAP: Record<string, string> = {\n \"/home\": \"home_page\",\n \"/home/shop\": \"shop_page\",\n \"/home/join\": \"join_page\",\n \"/cart\": \"cart_page\",\n \"/home/blog\": \"post_page\",\n \"/home/categories\": \"category_page\",\n \"/home/collections\": \"collection_page\",\n};\n\nconst STATIC_ROUTES = [\n { label: \"Home\", path: \"/home\" },\n { label: \"Shop\", path: \"/home/shop\" },\n { label: \"Join / Sign Up\", path: \"/home/join\" },\n { label: \"Cart\", path: \"/cart\" },\n { label: \"Blog\", path: \"/home/blog\" },\n { label: \"Categories (all)\", path: \"/home/categories\" },\n { label: \"Collections (all)\", path: \"/home/collections\" },\n] as const;\n\nconst RESOURCE_ROUTES = [\n {\n label: \"Category\",\n type: \"category\",\n template: \"/home/categories/%s\",\n fallback: \"/home/categories\",\n },\n {\n label: \"Collection\",\n type: \"collection\",\n template: \"/home/collections/%s\",\n fallback: \"/home/collections\",\n },\n {\n label: \"Product\",\n type: \"product\",\n template: \"/home/products/%s\",\n fallback: \"/home/shop\",\n },\n {\n label: \"Library\",\n type: \"library\",\n template: \"/home/libraries/%s\",\n fallback: \"/home/libraries\",\n },\n {\n label: \"Post\",\n type: \"post\",\n template: \"/home/posts/%s\",\n fallback: \"/home/blog\",\n },\n {\n label: \"Media\",\n type: \"medium\",\n template: \"/home/media/%s\",\n fallback: \"/home/media\",\n },\n {\n label: \"Enrollment Pack\",\n type: \"enrollment_pack\",\n template: \"/home/enrollments/%s\",\n fallback: \"/home/join\",\n },\n {\n label: \"Page\",\n type: \"page\",\n template: \"/home/pages/%s\",\n fallback: \"/home/pages\",\n },\n] as const;\n\nasync function fetchTemplatesForType(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n): Promise<ThemeTemplate[]> {\n const params = new URLSearchParams({\n application_theme_id: String(themeId),\n themeable_type: themeableType,\n published: \"true\",\n });\n const body = await api.get<TemplatesResponse>(\n `/api/application_theme_templates?${params}`,\n );\n return body.templates ?? [];\n}\n\nasync function selectTemplate(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n onCancel: () => void,\n): Promise<number | null> {\n const templates = await fetchTemplatesForType(api, themeId, themeableType);\n if (templates.length <= 1) return null;\n\n const templateChoices = templates.map((t) => ({\n title: `${t.name}${t.default ? \" (default)\" : \"\"}`,\n value: t.id,\n }));\n const { templateId } = await prompts(\n {\n type: \"autocomplete\",\n name: \"templateId\",\n message: \"Select a template\",\n choices: templateChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n return templateId ?? null;\n}\n\nexport function createNavigateCommand(): Command {\n return new Command(\"navigate\")\n .description(\"Interactively navigate to a route in the dev server browser\")\n .option(\"--host <host>\", \"Dev server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Dev server port\", \"9292\")\n .option(\"-t, --theme <id>\", \"Theme ID (defaults to active dev theme)\")\n .action(async (opts: { host: string; port: string; theme?: string }) => {\n requireToken();\n\n const themeId = opts.theme ? Number(opts.theme) : getLastDevThemeId();\n\n if (!themeId) {\n console.error(\n \"No active dev theme. Run `fluid theme dev` first, or pass --theme <id>.\",\n );\n process.exit(1);\n }\n\n const address = `http://${opts.host}:${opts.port}`;\n\n await navigateTheme(createApiClient(), themeId, address);\n });\n}\n\n/** Shared by theme navigate and theme dev --navigate. */\nexport async function navigateTheme(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n address: string,\n): Promise<void> {\n type Choice = {\n title: string;\n value:\n | string\n | {\n resourceType: string;\n template: string;\n fallback: string;\n label: string;\n };\n };\n const choices: Choice[] = [\n ...STATIC_ROUTES.map((r) => ({ title: r.label, value: r.path })),\n ...RESOURCE_ROUTES.map((r) => ({\n title: `${r.label} (select specific)`,\n value: {\n resourceType: r.type,\n template: r.template,\n fallback: r.fallback,\n label: r.label,\n },\n })),\n ];\n\n let cancelled = false;\n const onCancel = () => {\n cancelled = true;\n return false;\n };\n\n const metadata = await readPreviewMetadata(address, themeId);\n choices.push(...memberChoices(metadata));\n const unavailable = metadata?.system_screens.filter(\n (screen) => !metadata.supported_system_screens.includes(screen.key),\n );\n if (unavailable?.length)\n console.log(\n `System-screen preview unavailable: ${unavailable.map((screen) => screen.key).join(\", \")}.`,\n );\n\n const { dest } = await prompts(\n {\n type: \"autocomplete\",\n name: \"dest\",\n message: \"Select a route\",\n choices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n if (!dest) return;\n\n let path: string;\n let themeableType: string | undefined;\n\n if (typeof dest === \"string\") {\n if (dest.startsWith(\"/_members/\")) {\n await validateMemberDestination(address, themeId, dest);\n }\n path = dest;\n themeableType = THEMEABLE_TYPE_MAP[dest];\n } else {\n themeableType = dest.resourceType;\n const body = await themes.getApplicationThemeAvailableThemeables(\n api,\n themeId,\n { themeable: dest.resourceType, per_page: 50 },\n );\n const resources = body.available_themeables ?? [];\n\n if (!resources.length) {\n console.log(`No ${dest.label} resources found, using listing page.`);\n path = dest.fallback;\n } else {\n const resourceChoices = resources.map((r) => ({\n title: r.title ?? r.slug ?? \"Untitled\",\n value: r.slug,\n }));\n const { slug } = await prompts(\n {\n type: \"autocomplete\",\n name: \"slug\",\n message: `Select a ${dest.label.toLowerCase()}`,\n choices: resourceChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n if (cancelled || !slug) return;\n path = dest.template.replace(\"%s\", encodeURIComponent(slug as string));\n }\n }\n\n let templateParam = \"\";\n if (themeableType) {\n const templateId = await selectTemplate(\n api,\n themeId,\n themeableType,\n onCancel,\n );\n if (templateId) {\n templateParam = `?theme_template_id=${templateId}`;\n }\n }\n\n if (cancelled) return;\n const url = `${address}${path}${templateParam}`;\n console.log(`\\nNavigating to: ${url}\\n`);\n const open = (await import(\"open\")).default;\n await open(url);\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface ThemeConfig {\n themeId: number;\n themeName: string;\n company: string;\n /**\n * Server's `content_version_sha` at the moment of the last pull.\n * Sent back to the server as `base_sha` on push — the server rejects\n * with 409 if it no longer matches, so a push against stale data\n * can't silently clobber. Optional so a `.fluid-theme.json` written\n * by an older CLI (checksums-era) still deserializes; when absent,\n * the CLI falls back to the pre-merge-aware behavior (no server-side\n * check).\n */\n baseSha?: string;\n /** Digest of the managed ImageKit references captured with this baseline. */\n assetManifestSha?: string;\n}\n\n/**\n * Shape of a `.fluid-theme.json` written by the pre-shadow-repo CLI.\n * We read it only to migrate the shadow repo forward on first\n * new-CLI use; we never write this shape back.\n */\nexport interface LegacyThemeConfig extends ThemeConfig {\n lastPulledAt?: string | null;\n checksums?: Record<string, string>;\n}\n\nconst CONFIG_FILE = \".fluid-theme.json\";\n\n/**\n * `company` must be the bare subdomain slug — the dev server builds\n * `<company>.fluid.app` from it, so a full domain here produces an\n * unreachable `<slug>.fluid.app.fluid.app` host. Some external tooling\n * writes the full domain (or a URL); accept those and reduce them to\n * the subdomain.\n */\nfunction normalizeCompany(company: string): string {\n const host = company\n .replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\")\n .replace(/[/?#].*$/, \"\");\n return host.replace(/\\.fluid\\.app$/i, \"\");\n}\n\nfunction configPath(themeRoot: string): string {\n return join(themeRoot, CONFIG_FILE);\n}\n\n/** Read `.fluid-theme.json` from a theme directory, or null if it doesn't exist. */\nexport function readThemeConfig(themeRoot: string): ThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n const config = JSON.parse(raw) as ThemeConfig;\n if (typeof config.company === \"string\") {\n config.company = normalizeCompany(config.company);\n }\n return config;\n } catch {\n return null;\n }\n}\n\n/**\n * Read a legacy config (pre-shadow-repo). Used only by the migration\n * path on first new-CLI pull to seed shadow HEAD from files whose\n * local content still matches their stored sha256 checksum.\n */\nexport function readLegacyThemeConfig(\n themeRoot: string,\n): LegacyThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n return JSON.parse(raw) as LegacyThemeConfig;\n } catch {\n return null;\n }\n}\n\n/** Write `.fluid-theme.json` to a theme directory. */\nexport function writeThemeConfig(themeRoot: string, config: ThemeConfig): void {\n const path = configPath(themeRoot);\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n","const TEXT_TYPES: Record<string, string> = {\n \".liquid\": \"text/x-liquid\",\n \".json\": \"application/json\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".html\": \"text/html\",\n \".txt\": \"text/plain\",\n \".md\": \"text/markdown\",\n \".svg\": \"image/svg+xml\",\n};\n\nconst BINARY_TYPES: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".otf\": \"font/otf\",\n \".pdf\": \"application/pdf\",\n \".zip\": \"application/zip\",\n \".mp4\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n};\n\nexport interface MimeType {\n name: string;\n isText: boolean;\n}\n\nexport function mimeTypeFor(ext: string): MimeType {\n const text = TEXT_TYPES[ext];\n if (text) return { name: text, isText: true };\n\n const binary = BINARY_TYPES[ext];\n if (binary) return { name: binary, isText: false };\n\n return { name: \"application/octet-stream\", isText: false };\n}\n","const THEME_LEVEL_RESOURCE_KEYS = new Set([\n \"global_styles.css\",\n \"styles.css\",\n \"variables.json\",\n \"members_sites.json\",\n]);\n\nconst COMPOSITE_RESOURCE_FILE_NAMES = new Set([\n \"index.liquid\",\n \"styles.css\",\n \"variables.json\",\n]);\n\nfunction hasSafeSegments(key: string): boolean {\n const segments = key.split(\"/\");\n return segments.every(\n (segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\",\n );\n}\n\nexport function normalizeThemeResourceKey(value: string): string {\n return value.replaceAll(\"\\\\\", \"/\");\n}\n\n/**\n * Whether a relative file path is a resource key accepted by the Fluid themes\n * resource API. Local project files (package manifests, QA evidence, scripts,\n * source baselines, and similar agent artifacts) must never be uploaded.\n */\nexport function isThemeResourceKey(relativePath: string): boolean {\n const key = normalizeThemeResourceKey(relativePath);\n if (!hasSafeSegments(key)) return false;\n if (THEME_LEVEL_RESOURCE_KEYS.has(key)) return true;\n\n const segments = key.split(\"/\");\n const prefix = segments[0];\n const fileName = segments.at(-1);\n if (!prefix || !fileName || segments.length < 2) return false;\n\n if (\n prefix === \"assets\" ||\n prefix === \"config\" ||\n prefix === \"locales\" ||\n prefix === \"layouts\"\n ) {\n return true;\n }\n\n return segments.length >= 3 && COMPOSITE_RESOURCE_FILE_NAMES.has(fileName);\n}\n","","import settingTypesJson from \"./setting-types.json\" with { type: \"json\" };\n\nexport type SettingType =\n | \"text\"\n | \"plaintext\"\n | \"rich_text\"\n | \"richtext\"\n | \"textarea\"\n | \"html\"\n | \"html_textarea\"\n | \"url\"\n | \"range\"\n | \"number\"\n | \"select\"\n | \"radio\"\n | \"checkbox\"\n | \"color\"\n | \"color_background\"\n | \"font\"\n | \"font_picker\"\n | \"image\"\n | \"image_picker\"\n | \"video_picker\"\n | \"media_picker\"\n | \"text_alignment\"\n | \"media_fit\"\n | \"corner_radius\"\n | \"padding\"\n | \"border\"\n | \"gradient_overlay\"\n | \"header\"\n | \"product\"\n | \"products\"\n | \"collection\"\n | \"collections\"\n | \"category\"\n | \"categories\"\n | \"blog\"\n | \"posts\"\n | \"post\"\n | \"enrollment\"\n | \"enrollments\"\n | \"enrollment_pack\"\n | \"forms\"\n | \"media\"\n | \"variant\"\n | \"link_list\"\n | \"product_list\"\n | \"products_list\"\n | \"collection_list\"\n | \"collections_list\"\n | \"category_list\"\n | \"categories_list\"\n | \"posts_list\"\n | \"enrollment_list\"\n | \"enrollments_list\"\n | \"blog_list\"\n | \"blogs_list\"\n | \"post_list\"\n | \"enrollment_packs_list\";\n\n// Runtime list loaded from the canonical JSON — used for validation.\nexport const VALID_SETTING_TYPES: readonly string[] = Object.values(\n settingTypesJson.types as Record<string, string[]>,\n).flat();\n\n// Compile-time drift guard: if a type exists in the SettingType union but\n// not in setting-types.json, this object literal will error on the missing key.\n// When adding types to setting-types.json, also add them to SettingType above.\nconst _settingTypeCheck: Record<SettingType, true> = {\n text: true,\n plaintext: true,\n rich_text: true,\n richtext: true,\n textarea: true,\n html: true,\n html_textarea: true,\n url: true,\n range: true,\n number: true,\n select: true,\n radio: true,\n checkbox: true,\n color: true,\n color_background: true,\n font: true,\n font_picker: true,\n image: true,\n image_picker: true,\n video_picker: true,\n media_picker: true,\n text_alignment: true,\n media_fit: true,\n corner_radius: true,\n padding: true,\n border: true,\n gradient_overlay: true,\n header: true,\n product: true,\n products: true,\n collection: true,\n collections: true,\n category: true,\n categories: true,\n blog: true,\n posts: true,\n post: true,\n enrollment: true,\n enrollments: true,\n enrollment_pack: true,\n forms: true,\n media: true,\n variant: true,\n link_list: true,\n product_list: true,\n products_list: true,\n collection_list: true,\n collections_list: true,\n category_list: true,\n categories_list: true,\n posts_list: true,\n enrollment_list: true,\n enrollments_list: true,\n blog_list: true,\n blogs_list: true,\n post_list: true,\n enrollment_packs_list: true,\n} satisfies Record<SettingType, true>;\nvoid _settingTypeCheck;\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\nexport interface SchemaSetting {\n type: SettingType;\n id: string;\n default?: string | number | boolean | null;\n label?: string;\n options?: SelectOption[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n content?: string;\n visible_if?: Record<string, unknown>;\n}\n\nexport interface SchemaBlock {\n type: string;\n name?: string;\n limit?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[];\n}\n\nexport interface SchemaPreset {\n name?: string;\n category?: string;\n settings?: Record<string, unknown>;\n blocks?: Array<{ type: string; settings?: Record<string, unknown> }>;\n}\n\nexport interface SectionSchema {\n name?: string;\n tag?: string;\n class?: string;\n enabled_on?: { templates?: string[] };\n disabled_on?: { templates?: string[] };\n max_blocks?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[] | Record<string, unknown>;\n presets?: SchemaPreset[];\n}\n\nexport type BlocksSchemaType = \"array\" | \"object\" | \"unknown\";\n\n/**\n * Structured locator describing the schema element a diagnostic refers to.\n *\n * The rule logic (this package) is intentionally position-agnostic so it can\n * be shared by the CLI (`fluid theme push`) and the CodeMirror-based theme /\n * visual editor alike. Position-aware consumers use `target` to map a\n * diagnostic back to a source range without re-deriving the validation rules.\n */\nexport type SettingDiagnosticTarget = {\n kind: \"setting\";\n /** Index of the setting within its `settings` array. */\n index: number;\n /** The setting's `id`, when present — used to locate the offending entry. */\n settingId?: string;\n /** The setting's `type`, when present — used to locate the offending entry. */\n settingType?: string;\n /** Which field the diagnostic concerns. */\n field: \"id\" | \"type\";\n};\n\nexport type BlockDiagnosticTarget = {\n kind: \"block\";\n /** Index of the block within its `blocks` array. */\n index: number;\n /** The block's `type`, when present — used to locate the offending entry. */\n blockType?: string;\n /** Which field the diagnostic concerns. */\n field: \"type\" | \"name\" | \"settings\";\n};\n\nexport type SectionDiagnosticTarget = {\n kind: \"section\";\n /** The referenced section `type` that has no matching section file. */\n sectionType: string;\n /** The `{% section %}` tag's instance id, when the tag included one. */\n tagId?: string;\n};\n\nexport type DiagnosticTarget =\n | SettingDiagnosticTarget\n | BlockDiagnosticTarget\n | SectionDiagnosticTarget;\n\nexport interface Diagnostic {\n severity: \"error\" | \"warning\";\n message: string;\n /**\n * Optional structured locator so position-aware consumers (e.g. the\n * CodeMirror-based theme editor) can map a diagnostic back to a source\n * range. The CLI ignores this and only renders `message`.\n */\n target?: DiagnosticTarget;\n}\n","import { VALID_SETTING_TYPES } from \"./types\";\nimport type { Diagnostic } from \"./types\";\n\n/**\n * Message shown when a setting declares a `type` that is not one of the\n * canonical `VALID_SETTING_TYPES`. Centralized here so the CLI and the editor\n * render identical text. Kept to a single line — the list of valid types is a\n * static set exposed once via the `VALID_SETTING_TYPES` export, so repeating it\n * in every diagnostic only bloats structured output.\n */\nexport function invalidSettingTypeMessage(type: string): string {\n return `Invalid settings type: '${type}'`;\n}\n\nexport function validateSettings(settings: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const ids = new Set<string>();\n\n for (let index = 0; index < settings.length; index++) {\n const raw = settings[index];\n const setting: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const id = typeof setting.id === \"string\" ? setting.id : undefined;\n const type = typeof setting.type === \"string\" ? setting.type : undefined;\n\n if (id !== undefined && id.trim() === \"\") {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in settings: id cannot be empty\",\n target: { kind: \"setting\", index, settingType: type, field: \"id\" },\n });\n } else if (id && ids.has(id)) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in settings: duplicate id '${id}' found`,\n target: { kind: \"setting\", index, settingId: id, field: \"id\" },\n });\n } else if (id) {\n ids.add(id);\n }\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in setting '${id ?? index}': missing required field 'type'`,\n target: { kind: \"setting\", index, settingId: id, field: \"type\" },\n });\n } else if (!VALID_SETTING_TYPES.includes(type)) {\n diagnostics.push({\n severity: \"error\",\n message: invalidSettingTypeMessage(type),\n target: { kind: \"setting\", index, settingType: type, field: \"type\" },\n });\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\n\nexport function validateBlocks(blocks: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const types = new Set<string>();\n\n for (let index = 0; index < blocks.length; index++) {\n const raw = blocks[index];\n const block: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const type = typeof block.type === \"string\" ? block.type : undefined;\n const name = typeof block.name === \"string\" ? block.name : undefined;\n const settings = block.settings;\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in blocks at index ${index}: missing required field 'type'`,\n target: { kind: \"block\", index, field: \"type\" },\n });\n } else if (types.has(type)) {\n diagnostics.push({\n severity: \"warning\",\n message: `Warning in blocks: duplicate type '${type}' found`,\n target: { kind: \"block\", index, blockType: type, field: \"type\" },\n });\n } else {\n types.add(type);\n }\n\n // Named block references (type only, no name or settings) point to\n // standalone block templates — skip the name requirement for those.\n const isNamedBlockRef = !name && !settings;\n if (!name && type !== \"@app\" && type !== \"@theme\" && !isNamedBlockRef) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': missing required field 'name'`,\n target: { kind: \"block\", index, blockType: type, field: \"name\" },\n });\n }\n\n if (settings) {\n if (!Array.isArray(settings)) {\n // e.g. the author wrote `\"settings\": {}` instead of `\"settings\": []`.\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': 'settings' must be an array ([])`,\n target: { kind: \"block\", index, blockType: type, field: \"settings\" },\n });\n } else {\n diagnostics.push(...validateSettings(settings));\n }\n }\n\n // Recurse into nested blocks (max 2 levels enforced by the engine,\n // but we validate whatever is declared)\n if (Array.isArray(block.blocks)) {\n diagnostics.push(...validateBlocks(block.blocks as unknown[]));\n }\n }\n\n return diagnostics;\n}\n","import type { BlocksSchemaType, Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\nimport { validateBlocks } from \"./validate-blocks\";\n\n// Strip Liquid comment blocks so they don't interfere with schema extraction.\nfunction stripLiquidComments(text: string): string {\n return text.replace(\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g,\n \"\",\n );\n}\n\n// Detect duplicate \"blocks\" keys in the same JSON object.\n// Standard JSON.parse silently drops duplicates, so we scan tokens manually.\nfunction findDuplicateBlocksKeys(jsonText: string): number {\n let count = 0;\n const stack: Array<{\n type: \"object\" | \"array\";\n keys: Set<string>;\n expectingKey: boolean;\n }> = [];\n let pendingKey: string | null = null;\n let i = 0;\n\n while (i < jsonText.length) {\n const ch = jsonText.charCodeAt(i);\n\n // Whitespace\n if (ch === 0x20 || ch === 0x0a || ch === 0x0d || ch === 0x09) {\n i++;\n continue;\n }\n\n if (ch === 0x7b) {\n // {\n stack.push({ type: \"object\", keys: new Set(), expectingKey: true });\n pendingKey = null;\n i++;\n } else if (ch === 0x7d) {\n // }\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x5b) {\n // [\n pendingKey = null;\n stack.push({ type: \"array\", keys: new Set(), expectingKey: false });\n i++;\n } else if (ch === 0x5d) {\n // ]\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x3a) {\n // :\n i++;\n } else if (ch === 0x2c) {\n // ,\n const top = stack[stack.length - 1];\n if (top?.type === \"object\") {\n top.expectingKey = true;\n }\n pendingKey = null;\n i++;\n } else if (ch === 0x22) {\n // \"\n let j = i + 1;\n while (j < jsonText.length) {\n if (\n jsonText.charCodeAt(j) === 0x22 &&\n jsonText.charCodeAt(j - 1) !== 0x5c\n ) {\n break;\n }\n j++;\n }\n const str = jsonText.slice(i + 1, j);\n i = j + 1;\n\n const top = stack[stack.length - 1];\n if (top?.type === \"object\" && top.expectingKey) {\n if (str === \"blocks\" && top.keys.has(str)) {\n count++;\n }\n top.keys.add(str);\n top.expectingKey = false;\n pendingKey = str;\n } else {\n pendingKey = null;\n }\n } else {\n pendingKey = null;\n i++;\n }\n }\n\n return count;\n}\n\nexport interface ValidateSchemaOptions {\n blocksSchemaType?: BlocksSchemaType;\n}\n\n// Validate the full Liquid file content containing a {% schema %} block.\n// Returns an array of diagnostics (empty = valid).\nexport function validateSchemaText(\n text: string,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n const stripped = stripLiquidComments(text);\n const match = stripped.match(\n /\\{%-?\\s*schema\\s*-?%\\}([\\s\\S]*?)\\{%-?\\s*endschema\\s*-?%\\}/,\n );\n if (!match) return diagnostics;\n\n const jsonText = match[1] ?? \"\";\n\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(jsonText) as Record<string, unknown>;\n } catch (e) {\n diagnostics.push({\n severity: \"error\",\n message: `Invalid JSON:\\n ${(e as Error).message}`,\n });\n return diagnostics;\n }\n\n // Duplicate \"blocks\" keys\n const dupes = findDuplicateBlocksKeys(jsonText);\n for (let d = 0; d < dupes; d++) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: duplicate 'blocks' key in the same object\",\n });\n }\n\n // Settings\n if (\n schema !== null &&\n typeof schema === \"object\" &&\n Array.isArray(schema.settings)\n ) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n // Blocks\n if (schema !== null && typeof schema === \"object\" && \"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n // \"unknown\" — validate if array, accept if object\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n\n// Validate a parsed schema object directly (when you already have the JSON).\nexport function validateSchema(\n schema: Record<string, unknown>,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n if (Array.isArray(schema.settings)) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n if (\"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\n\n// Liquid comment blocks — stripped so commented-out section tags are not\n// treated as real references.\nconst LIQUID_COMMENT_REGEX =\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g;\n\n// The `{% schema %} … {% endschema %}` block — removed so section types\n// declared inside the schema JSON are not mistaken for `{% section %}` usages.\nconst SCHEMA_BLOCK_REGEX =\n /\\{%-?\\s*schema\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endschema\\s*-?%\\}/;\n\n// Matches a `{% section %}` tag. Supports both the id-bearing form\n// (`{% section 'hero', id: 'abc' %}`) emitted by the visual editor and the\n// bare form (`{% section 'hero' %}`) hand-authored themes use, plus\n// whitespace-control tags (`{%- … -%}`) and single or double quotes.\n// `id` is optional — capture group 2 is undefined for bare tags.\nconst SECTION_TAG_PATTERN =\n \"\\\\{%-?\\\\s*section\\\\s+['\\\"]([^'\\\"]+)['\\\"](?:\\\\s*,\\\\s*id:\\\\s*['\\\"]([^'\\\"]+)['\\\"])?\\\\s*-?%\\\\}\";\n\n// Reserved layout-region section types. `{% section 'navbar' %}` and friends\n// resolve to the theme's navbar/footer/library_navbar template slots rather than\n// a `sections/<name>` definition, and render empty when absent — so they are\n// never \"missing\". Mirrors `LiquidTags::Section::SECTION_TEMPLATES` server-side.\nconst RESERVED_SECTION_TYPES = new Set([\"navbar\", \"library_navbar\", \"footer\"]);\n\n// `fluid://extensions/{id}/{type}/{name}` references resolve against an app\n// extension installed on the company at render time — they cannot be validated\n// against local files, so they are never flagged. Mirrors `Themes::ExtensionUri`.\nconst EXTENSION_URI_PATTERN = /^fluid:\\/\\/extensions\\/[^/]+\\/[^/]+\\/[^/]+$/;\n\n/** Whether a `{% section %}` type resolves to something other than an on-disk `sections/<name>` definition (and so cannot be flagged as missing). */\nexport function isNonLocalSectionType(type: string): boolean {\n return RESERVED_SECTION_TYPES.has(type) || EXTENSION_URI_PATTERN.test(type);\n}\n\nexport interface SectionReference {\n /** The referenced section type/name (group 1). */\n type: string;\n /** The section instance id, when the tag declares one. */\n id?: string;\n /** The full matched tag text. */\n fullTag: string;\n /** 0-based position of the tag within the template body. */\n order: number;\n}\n\n/** A template, identified by `path`, with its raw liquid `content`. */\nexport interface TemplateInput {\n path: string;\n content: string;\n}\n\n// Liquid outside the comment and schema blocks — the rendered template body\n// where `{% section %}` references actually live.\nfunction templateBody(liquid: string): string {\n return liquid\n .replace(LIQUID_COMMENT_REGEX, \"\")\n .replace(SCHEMA_BLOCK_REGEX, \"\");\n}\n\n/**\n * Extract every `{% section %}` reference from a liquid template, ignoring\n * tags inside comments or the `{% schema %}` block. Shared by the editor's\n * section-usage detection and the CLI linter so both parse references\n * identically.\n */\nexport function extractSectionReferences(liquid: string): SectionReference[] {\n const body = templateBody(liquid);\n const pattern = new RegExp(SECTION_TAG_PATTERN, \"g\");\n const references: SectionReference[] = [];\n let order = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(body)) !== null) {\n const type = match[1];\n if (!type) continue;\n references.push({ type, id: match[2], fullTag: match[0], order: order++ });\n }\n return references;\n}\n\n/**\n * Paths of the templates that reference `sectionName`. Used by the editor to\n * warn before deleting a section that is still in use.\n */\nexport function findTemplatesReferencingSection(\n templates: TemplateInput[],\n sectionName: string,\n): string[] {\n const matches: string[] = [];\n for (const template of templates) {\n const references = extractSectionReferences(template.content);\n if (references.some((reference) => reference.type === sectionName)) {\n matches.push(template.path);\n }\n }\n return matches;\n}\n\nexport interface MissingSectionRef {\n templatePath: string;\n sectionType: string;\n diagnostic: Diagnostic;\n}\n\n/**\n * Find `{% section %}` references that point to a section that does not exist\n * in `existingSectionNames` — the static equivalent of \"an in-use section was\n * deleted\". Reserved layout-region types (navbar/footer/library_navbar) and\n * `fluid://` extension URIs are never flagged (see `isNonLocalSectionType`).\n * Emits one `error` diagnostic per missing section type per template.\n */\nexport function findMissingSectionReferences(\n templates: TemplateInput[],\n existingSectionNames: Set<string>,\n): MissingSectionRef[] {\n const missing: MissingSectionRef[] = [];\n for (const template of templates) {\n const reported = new Set<string>();\n for (const reference of extractSectionReferences(template.content)) {\n if (existingSectionNames.has(reference.type)) continue;\n if (isNonLocalSectionType(reference.type)) continue;\n if (reported.has(reference.type)) continue;\n reported.add(reference.type);\n missing.push({\n templatePath: template.path,\n sectionType: reference.type,\n diagnostic: {\n severity: \"error\",\n message: `references missing section '${reference.type}'`,\n target: {\n kind: \"section\",\n sectionType: reference.type,\n tagId: reference.id,\n },\n },\n });\n }\n }\n return missing;\n}\n","import {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n statSync,\n} from \"node:fs\";\nimport { extname, basename, relative, dirname } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { mimeTypeFor, type MimeType } from \"./mime-type.js\";\nimport { normalizeThemeResourceKey } from \"./resource-key.js\";\nimport {\n validateSchemaText,\n type Diagnostic,\n type BlocksSchemaType,\n} from \"@fluid-app/theme-schema\";\n\n// Top-level theme folders that are not page templates. Everything else at the\n// top level (home_page, product, page, footer, navbar, …) is a page template.\nconst NON_TEMPLATE_DIRS = new Set([\n \"sections\",\n \"blocks\",\n \"components\",\n \"layouts\",\n \"config\",\n \"assets\",\n \"locales\",\n]);\n\nexport class ThemeFile {\n readonly absolutePath: string;\n readonly relativePath: string;\n readonly mime: MimeType;\n\n constructor(absolutePath: string, root: string) {\n this.absolutePath = absolutePath;\n this.relativePath = normalizeThemeResourceKey(relative(root, absolutePath));\n this.mime = mimeTypeFor(extname(absolutePath).toLowerCase());\n }\n\n get name(): string {\n return basename(this.absolutePath);\n }\n\n get isText(): boolean {\n return this.mime.isText;\n }\n\n get isLiquid(): boolean {\n return this.absolutePath.endsWith(\".liquid\");\n }\n\n get isJson(): boolean {\n return this.absolutePath.endsWith(\".json\");\n }\n\n get exists(): boolean {\n return existsSync(this.absolutePath);\n }\n\n read(): string {\n return readFileSync(this.absolutePath, \"utf-8\");\n }\n\n readBinary(): Buffer {\n return readFileSync(this.absolutePath);\n }\n\n write(content: string | Buffer): void {\n mkdirSync(dirname(this.absolutePath), { recursive: true });\n if (typeof content === \"string\") {\n writeFileSync(this.absolutePath, content, \"utf-8\");\n } else {\n writeFileSync(this.absolutePath, content);\n }\n }\n\n checksum(): string {\n const content = this.isText ? this.read() : this.readBinary();\n return createHash(\"sha256\").update(content).digest(\"hex\");\n }\n\n size(): number {\n return statSync(this.absolutePath).size;\n }\n\n get isTemplate(): boolean {\n // Page templates (home_page, product, footer, navbar, …) live in top-level\n // page-type folders and expect blocks as objects. The reserved categories\n // below either expect blocks as arrays (sections, blocks, components) or\n // carry no block schema (layouts, config, assets, locales).\n const parts = this.relativePath.split(/[/\\\\]/);\n return parts.length >= 2 && !NON_TEMPLATE_DIRS.has(parts[0]!);\n }\n\n validateSchema(): Diagnostic[] {\n if (!this.isLiquid) return [];\n\n const blocksSchemaType: BlocksSchemaType = this.isTemplate\n ? \"object\"\n : \"array\";\n\n return validateSchemaText(this.read(), { blocksSchemaType });\n }\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\n\nconst IGNORE_FILE = \".fluidignore\";\n\ninterface Pattern {\n negated: boolean;\n pattern: string;\n}\n\nexport class FluidIgnore {\n private patterns: Pattern[];\n\n constructor(root: string) {\n this.patterns = this.parse(join(root, IGNORE_FILE));\n }\n\n ignore(relativePath: string): boolean {\n let result = false;\n for (const { negated, pattern } of this.patterns) {\n if (this.match(pattern, relativePath)) {\n result = !negated;\n }\n }\n return result;\n }\n\n private parse(filePath: string): Pattern[] {\n if (!existsSync(filePath)) return [];\n return readFileSync(filePath, \"utf-8\")\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l && !l.startsWith(\"#\"))\n .map((l) => {\n const negated = l.startsWith(\"!\");\n let pattern = negated ? l.slice(1) : l;\n if (pattern.startsWith(\"/\")) pattern = pattern.slice(1);\n return { negated, pattern };\n });\n }\n\n private match(pattern: string, path: string): boolean {\n if (pattern.endsWith(\"/\")) {\n return path.startsWith(pattern) || path === pattern.slice(0, -1);\n }\n if (pattern.includes(\"/\")) {\n return this.fnmatch(pattern, path);\n }\n return this.fnmatch(pattern, path) || this.fnmatch(pattern, basename(path));\n }\n\n private fnmatch(pattern: string, str: string): boolean {\n const re = pattern\n .split(\"**\")\n .map((p) =>\n p\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\"),\n )\n .join(\".*\");\n return new RegExp(`^${re}$`).test(str);\n }\n}\n","import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { ThemeFile } from \"./file.js\";\nimport { FluidIgnore } from \"./fluid-ignore.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nconst THEME_MARKERS = [\"templates\", \"assets\", \"config\"];\nconst THEME_ASSET_MANIFEST = \".fluid-assets.json\";\n\nexport class ThemeRoot {\n readonly root: string;\n readonly ignore: FluidIgnore;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.ignore = new FluidIgnore(this.root);\n }\n\n isValid(): boolean {\n return (\n existsSync(join(this.root, THEME_ASSET_MANIFEST)) ||\n THEME_MARKERS.some((m) => {\n try {\n return statSync(join(this.root, m)).isDirectory();\n } catch {\n return false;\n }\n })\n );\n }\n\n files(): ThemeFile[] {\n return this.glob(this.root).filter(\n (f) =>\n isThemeResourceKey(f.relativePath) &&\n !this.ignore.ignore(f.relativePath),\n );\n }\n\n isResourcePath(pathOrFile: string | ThemeFile): boolean {\n const file = this.file(pathOrFile);\n return isThemeResourceKey(file.relativePath);\n }\n\n file(pathOrFile: string | ThemeFile): ThemeFile {\n if (pathOrFile instanceof ThemeFile) return pathOrFile;\n const abs = isAbsolute(pathOrFile)\n ? pathOrFile\n : join(this.root, pathOrFile);\n return new ThemeFile(abs, this.root);\n }\n\n private glob(dir: string): ThemeFile[] {\n const results: ThemeFile[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\")) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\") continue;\n results.push(...this.glob(full));\n } else if (entry.isFile()) {\n results.push(new ThemeFile(full, this.root));\n }\n }\n return results;\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The runtime is shipped with this CLI, not taken from a mutable theme/CDN URL. */\nexport function findMemberScreenAssets(): string | undefined {\n let directory = dirname(fileURLToPath(import.meta.url));\n while (true) {\n for (const candidate of [\n join(directory, \"dist-cli-preview\"),\n join(directory, \"dist\", \"dist-cli-preview\"),\n ]) {\n if (existsSync(join(candidate, \"capabilities.json\"))) return candidate;\n }\n const parent = dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\nexport class MemberScreenAssets {\n readonly screens: string[];\n constructor(private readonly directory = findMemberScreenAssets()) {\n this.screens = [];\n if (!directory) return;\n try {\n const value: unknown = JSON.parse(\n readFileSync(join(directory, \"capabilities.json\"), \"utf8\"),\n );\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"version\" in value) ||\n value.version !== 1 ||\n !(\"screens\" in value) ||\n !Array.isArray(value.screens)\n )\n return;\n if (\n !value.screens.every(\n (screen: unknown) =>\n typeof screen === \"string\" && /^[a-z][a-z-]*$/.test(screen),\n )\n )\n return;\n if (!existsSync(join(directory, \"account-screens.js\"))) return;\n this.screens = value.screens;\n } catch {\n /* An incomplete build must not advertise screen support. */\n }\n }\n read(path: string): Buffer | undefined {\n if (\n !this.directory ||\n !/^(?:account-screens\\.js|chunks\\/[a-zA-Z0-9_.-]+\\.js)$/.test(path)\n )\n return undefined;\n try {\n return readFileSync(join(this.directory, path));\n } catch {\n return undefined;\n }\n }\n}\n","export function buildHotReloadScript(mode: \"full-page\" | \"off\"): string {\n return `\n<script>\n(() => {\n window.__FLUID_CLI_ENV__ = ${JSON.stringify({ mode })};\n\n class HotReload {\n static reloadMode() { return window.__FLUID_CLI_ENV__.mode; }\n static isActive() { return HotReload.reloadMode() !== \"off\"; }\n static setHotReloadCookie(files) {\n const expires = new Date(Date.now() + 3000).toUTCString();\n document.cookie = \\`hot_reload_files=\\${files.join(\",\")};expires=\\${expires};path=/\\`;\n }\n static refresh(files) {\n HotReload.setHotReloadCookie(files);\n console.log(\"[HotReload] Refreshing page\");\n window.location.reload();\n }\n }\n\n class SSEClient {\n constructor(url, handler) {\n if (typeof EventSource === \"undefined\") {\n console.error(\"[HotReload] EventSource not supported in this browser.\");\n return;\n }\n console.log(\"[HotReload] Initializing…\");\n this.url = url;\n this.handler = handler;\n }\n connect() {\n const es = new EventSource(this.url);\n es.onopen = () => console.log(\"[HotReload] SSE connected.\");\n es.onerror = () => {\n console.log(\"[HotReload] SSE closed. Reconnecting in 5s…\");\n es.close();\n setTimeout(() => this.connect(), 5000);\n };\n es.onmessage = (msg) => {\n const data = JSON.parse(msg.data);\n if (data.reload_page) { HotReload.refresh([]); return; }\n this.handler(data);\n };\n }\n }\n\n if (HotReload.isActive()) {\n new SSEClient(\"/hot-reload\", (data) => {\n if (data.modified) HotReload.refresh(data.modified);\n }).connect();\n }\n})();\n</script>`;\n}\n\nexport function injectHotReload(\n html: string,\n mode: \"full-page\" | \"off\",\n): string {\n const script = buildHotReloadScript(mode);\n if (html.includes(\"</body>\")) {\n return html.replace(\"</body>\", `${script}\\n</body>`);\n }\n return html + script;\n}\n","import {\n parse,\n parseFragment,\n serialize,\n type DefaultTreeAdapterMap,\n} from \"parse5\";\nimport type {\n MemberPreviewMetadata,\n MemberPreviewSelection,\n} from \"./member-preview-contract.js\";\nimport { memberPreviewUrl, accountPath } from \"./member-preview-contract.js\";\nimport { injectHotReload } from \"./hot-reload.js\";\n\ntype Element = DefaultTreeAdapterMap[\"element\"];\ntype Parent = DefaultTreeAdapterMap[\"parentNode\"];\n\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (char) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[\n char\n ] ?? char,\n );\n}\n/** Use the configured Rails environment, never a browser-supplied upstream. */\nexport function memberOrigin(apiBase: string, company: string): string {\n const url = new URL(apiBase);\n if (!/^[a-z0-9-]+$/i.test(company))\n throw new Error(\"Invalid company subdomain.\");\n if (url.hostname.startsWith(\"api.\"))\n url.hostname = `${company}.account.${url.hostname.slice(4)}`;\n return url.origin;\n}\nexport interface MemberHtmlOptions {\n origin: string;\n localOrigin: string;\n path: string;\n selection: MemberPreviewSelection;\n metadata: MemberPreviewMetadata;\n reloadMode: \"full-page\" | \"off\";\n screenKey?: string;\n}\nexport function renderMemberHtml(\n render: { html: string; styles: string; templateStyles: string },\n opts: MemberHtmlOptions,\n): string {\n const document = parse(render.html);\n const elements: Element[] = [];\n function visit(parent: Parent): void {\n for (const child of parent.childNodes) {\n if (\"tagName\" in child) {\n elements.push(child);\n visit(child);\n }\n }\n }\n visit(document);\n const head = elements.find((el) => el.tagName === \"head\")!;\n // Remove renderer/editor bases, retaining one environment-correct base for\n // scripts, fonts, images, CSS url() and imports. Stylesheet URLs keep their own base.\n for (const el of elements) {\n if (el.tagName === \"base\" && el.parentNode)\n el.parentNode.childNodes.splice(el.parentNode.childNodes.indexOf(el), 1);\n // Do not load a theme's live account runtime in a CLI sample preview.\n if (el.tagName === \"script\") {\n const src = el.attrs.find((attr) => attr.name === \"src\")?.value ?? \"\";\n if (\n /\\/(?:account-screens(?:\\/|\\.)|member-storefront-sdk\\/)/.test(src) &&\n el.parentNode\n )\n el.parentNode.childNodes.splice(\n el.parentNode.childNodes.indexOf(el),\n 1,\n );\n }\n if (\n el.tagName === \"fluid-account-screen\" ||\n el.tagName === \"fluid-messaging\"\n ) {\n el.attrs = el.attrs.filter(\n (attr) => attr.name !== \"preview\" && attr.name !== \"path\",\n );\n el.attrs.push({ name: \"preview\", value: \"true\" });\n if (opts.screenKey)\n el.attrs.push({\n name: \"path\",\n value: new URL(opts.path, opts.origin).pathname.slice(1),\n });\n }\n if (el.tagName === \"form\") {\n el.attrs = el.attrs.filter((attr) => attr.name !== \"action\");\n el.attrs.push({\n name: \"action\",\n value: `${opts.localOrigin}/_members/_actions`,\n });\n }\n const formAction = el.attrs.find((attr) => attr.name === \"formaction\");\n if (formAction) formAction.value = `${opts.localOrigin}/_members/_actions`;\n if (el.tagName === \"a\" || el.tagName === \"area\") {\n const href = el.attrs.find((a) => a.name === \"href\");\n if (href) href.value = previewLink(href.value, opts);\n }\n }\n const runtime = parseFragment(\n `<script type=\"module\" src=\"${escapeHtml(opts.localOrigin)}/_members/_runtime/account-screens.js\"></script>`,\n );\n head.childNodes.unshift(...runtime.childNodes);\n const base = parseFragment(\n `<base href=\"${escapeHtml(new URL(opts.path, opts.origin).href)}\">`,\n );\n head.childNodes.unshift(...base.childNodes);\n // Both strings are trusted output from the authenticated theme renderer. The\n // template stylesheet is raw CSS, whereas page_stylesheet is HTML link/style tags.\n const styles = parseFragment(render.styles);\n head.childNodes.push(...styles.childNodes);\n const style = parseFragment(\"<style></style>\").childNodes[0];\n if (style && \"childNodes\" in style) {\n style.childNodes.push({\n nodeName: \"#text\",\n value: render.templateStyles,\n parentNode: style,\n });\n head.childNodes.push(style);\n }\n // Form submissions are not part of the sample preview contract. In particular\n // do not let a theme's live sign-in/sign-out form inherit the upstream base.\n const guard = parseFragment(\n `<script>document.addEventListener(\"submit\",function(event){event.preventDefault();},true);</script>`,\n );\n head.childNodes.unshift(...guard.childNodes);\n const context = JSON.stringify(opts.selection).replace(/</g, \"\\\\u003c\");\n const navigation = parseFragment(`<script>\n document.addEventListener(\"fluid-account-navigate\",function(event){\n event.preventDefault(); event.stopImmediatePropagation();\n var slug=event.detail && event.detail.slug;\n if(typeof slug!==\"string\" || !/^[a-z][a-z0-9_-]*(?:[/?#]|$)/.test(slug) || slug.includes(\"\\\\\\\\\")) return;\n var next=new URL(\"/_members/\"+slug,window.location.origin);\n if(next.origin!==window.location.origin || !next.pathname.startsWith(\"/_members/\")) return;\n var context=${context};\n [\"members_site_id\",\"country_id\",\"language_id\",\"member_type_id\"].forEach(function(key){\n if(context[key]===undefined) next.searchParams.delete(key); else next.searchParams.set(key,context[key]);\n });\n window.location.assign(next.href);\n },true);\n </script>`);\n head.childNodes.unshift(...navigation.childNodes);\n return injectHotReload(serialize(document), opts.reloadMode);\n}\nexport function previewLink(href: string, opts: MemberHtmlOptions): string {\n if (href.startsWith(\"#\"))\n return (\n opts.localOrigin + memberPreviewUrl(opts.path, opts.selection) + href\n );\n let target: URL;\n try {\n target = new URL(href, new URL(opts.path, opts.origin));\n } catch {\n return href;\n }\n if (![\"http:\", \"https:\"].includes(target.protocol)) return href;\n const absolute = /^[a-z][a-z\\d+.-]*:/i.test(href) || href.startsWith(\"//\");\n const accountLink = absolute && target.origin === opts.origin;\n if (absolute && !accountLink && target.origin !== opts.localOrigin)\n return href;\n target.pathname = accountPath(target.pathname);\n const isMember =\n accountLink ||\n /^\\/(auth(?:\\/|$)|login$|logout$)/.test(target.pathname) ||\n target.pathname.startsWith(\"/_members/\") ||\n opts.metadata.members_sites.some((site) =>\n site.pages.some((p) => p.path === target.pathname),\n ) ||\n opts.metadata.system_screens.some(\n (screen) =>\n target.pathname === `/${screen.key}` ||\n target.pathname.startsWith(`/${screen.key}/`),\n );\n const path = target.pathname + target.search + target.hash;\n return (\n opts.localOrigin +\n (isMember ? memberPreviewUrl(path, opts.selection) : path)\n );\n}\n","import { MemberScreenAssets } from \"./member-screen-assets.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ApiClient } from \"../../api.js\";\nimport { getApiBase } from \"../../api.js\";\nimport {\n MemberPreviewError,\n isRecord,\n parseMemberMetadata,\n parseMemberRender,\n parseSelection,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./member-preview-contract.js\";\nimport {\n escapeHtml,\n memberOrigin,\n renderMemberHtml,\n} from \"./member-preview-html.js\";\n\n/** Deny DNS rebinding and cross-origin access before using Node's CLI credential. */\nexport function isLocalPreviewRequest(\n req: IncomingMessage,\n host: string,\n): boolean {\n const port = req.socket.localPort;\n const hosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n if ((host === \"0.0.0.0\" || host === \"::\") && req.socket.localAddress) {\n const address = req.socket.localAddress.replace(/^::ffff:/, \"\");\n hosts.add(address.includes(\":\") ? `[${address}]` : address);\n }\n let url: URL;\n try {\n url = new URL(`http://${req.headers.host}`);\n } catch {\n return false;\n }\n if (\n !hosts.has(url.hostname) ||\n Number(url.port || 80) !== port ||\n url.username ||\n url.password ||\n url.pathname !== \"/\"\n )\n return false;\n if (req.headers.origin && req.headers.origin !== url.origin) return false;\n return req.headers[\"sec-fetch-site\"] !== \"cross-site\";\n}\n\nexport class MemberPreview {\n private metadata: MemberPreviewMetadata | undefined;\n private loading: Promise<MemberPreviewMetadata> | undefined;\n private readonly origin: string;\n constructor(\n private readonly api: ApiClient,\n private readonly theme: { id: number; company: string },\n private readonly reloadMode: \"full-page\" | \"off\",\n private readonly screenAssets = new MemberScreenAssets(),\n ) {\n this.origin = memberOrigin(getApiBase(), theme.company);\n }\n /** Only call after every upload in a watcher batch has been acknowledged. */\n async refreshAfterSync(): Promise<void> {\n if (!this.metadata && !this.loading) return;\n if (this.loading) await this.loading;\n this.metadata = undefined;\n const next = await this.loadMetadata();\n this.metadata = next;\n }\n private async loadMetadata(): Promise<MemberPreviewMetadata> {\n return parseMemberMetadata(\n await this.api.get(\"/api/company/members_sites\", {\n application_theme_id: this.theme.id,\n }),\n );\n }\n private async getMetadata(): Promise<MemberPreviewMetadata> {\n if (this.metadata) return this.metadata;\n this.loading ??= this.loadMetadata();\n try {\n this.metadata = await this.loading;\n return this.metadata;\n } finally {\n this.loading = undefined;\n }\n }\n async handle(req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n return false;\n res.setHeader(\"Cache-Control\", \"private, no-store\");\n res.setHeader(\"Referrer-Policy\", \"no-referrer\");\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n try {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n res.setHeader(\"Allow\", \"GET, HEAD\");\n throw new MemberPreviewError(\"Member preview is read-only.\", 405);\n }\n if (url.pathname.startsWith(\"/_members/_runtime/\")) {\n const asset = this.screenAssets.read(\n url.pathname.slice(\"/_members/_runtime/\".length),\n );\n if (!asset)\n throw new MemberPreviewError(\n \"Preview runtime asset unavailable. Rebuild or update the CLI.\",\n 404,\n );\n res.setHeader(\"Content-Type\", \"text/javascript; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : asset);\n return true;\n }\n const metadata = await this.getMetadata();\n if (url.pathname === \"/_members/_metadata\") {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : JSON.stringify({\n theme_id: this.theme.id,\n mode: \"sample\",\n ...metadata,\n supported_system_screens: metadata.system_screens\n .filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n )\n .map((screen) => screen.key),\n }),\n );\n return true;\n }\n if (url.pathname === \"/_members\" || url.pathname === \"/_members/\") {\n const screens = metadata.system_screens.filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n );\n const links = metadata.members_sites\n .map((site) => {\n const destinations = [\n ...site.pages.map((page) => ({\n path: page.path,\n title: page.title + (page.published ? \"\" : \" (draft)\"),\n })),\n ...screens.map((screen) => ({\n path: `/${screen.key}`,\n title: screen.key + \" (sample)\",\n })),\n ];\n return `<h2>${escapeHtml(site.name)}</h2><ul>${destinations.map((page) => `<li><a href=\"${escapeHtml(memberPreviewUrl(page.path, { members_site_id: site.id }))}\">${escapeHtml(page.title)}</a></li>`).join(\"\")}</ul>`;\n })\n .join(\"\");\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : `<!doctype html><meta charset=\"utf-8\"><title>Member preview</title><h1>Member preview</h1><p>Sample data. Select a page to preview its site's layout.</p>${links || \"<p>No member pages in this theme.</p>\"}`,\n );\n return true;\n }\n const selection = parseSelection(url.searchParams);\n const site = metadata.members_sites.find(\n (s) => s.id === selection.members_site_id,\n );\n if (!site)\n throw new MemberPreviewError(\n \"This site no longer belongs to the preview theme. Select a page at /_members.\",\n 404,\n );\n const path = url.pathname.slice(\"/_members\".length);\n const page = site.pages.find((p) => p.path === path);\n const screen = page\n ? undefined\n : metadata.system_screens.find(\n (s) => path === `/${s.key}` || path.startsWith(`/${s.key}/`),\n );\n if (!page && !screen)\n throw new MemberPreviewError(\n \"This member page is unavailable in the selected site. Select a page at /_members.\",\n 404,\n );\n if (screen && !this.screenAssets.screens.includes(screen.key))\n throw new MemberPreviewError(\n \"This system screen has no packaged sample preview. Rebuild or update the CLI.\",\n 501,\n );\n const response = await this.api.post(\n `/api/v202604/themes/${this.theme.id}/member-preview`,\n {\n ...selection,\n ...(page\n ? { members_page_id: page.id }\n : { system_screen_key: screen!.key }),\n },\n );\n const render = parseMemberRender(\n response,\n this.theme.id,\n selection,\n page?.id ?? null,\n screen?.key ?? null,\n );\n const html = renderMemberHtml(render, {\n origin: this.origin,\n localOrigin: url.origin,\n path: path + url.search,\n selection,\n metadata,\n reloadMode: this.reloadMode,\n screenKey: screen?.key,\n });\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : html);\n } catch (error) {\n // Never echo upstream envelopes, credentials, or internal diagnostics into HTML.\n const status =\n error instanceof MemberPreviewError\n ? error.status\n : isRecord(error) &&\n typeof error.status === \"number\" &&\n [401, 403, 404, 422].includes(error.status)\n ? error.status\n : 502;\n const message =\n error instanceof MemberPreviewError\n ? error.message\n : status === 401 || status === 403\n ? \"Member preview authorization failed. Check your CLI login and theme permissions.\"\n : \"Member preview could not be rendered. Check the selected page and server preview support.\";\n res.writeHead(status, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(req.method === \"HEAD\" ? undefined : message);\n }\n return true;\n }\n}\n","import type { ServerResponse } from \"node:http\";\n\nexport class SSEStream {\n private responses = new Set<ServerResponse>();\n\n add(res: ServerResponse): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.write(\":\\n\\n\");\n this.responses.add(res);\n res.on(\"close\", () => this.responses.delete(res));\n }\n\n broadcast(data: string): void {\n const payload = `data: ${data}\\n\\n`;\n for (const res of this.responses) {\n try {\n res.write(payload);\n } catch {\n this.responses.delete(res);\n }\n }\n }\n\n close(): void {\n for (const res of this.responses) {\n try {\n res.end();\n } catch {\n // ignore\n }\n }\n this.responses.clear();\n }\n\n get size(): number {\n return this.responses.size;\n }\n}\n","import https from \"node:https\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { injectHotReload } from \"./hot-reload.js\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\n\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"content-security-policy\",\n]);\n\nexport interface ProxyOptions {\n company: string;\n themeId: number;\n reloadMode: \"full-page\" | \"off\";\n pendingFiles?: () => Array<{ relativePath: string; read: () => string }>;\n}\n\nexport async function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: ProxyOptions,\n): Promise<void> {\n const companyHost = `${opts.company}.fluid.app`;\n\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && typeof v === \"string\") {\n headers[k] = v;\n }\n }\n headers[\"host\"] = companyHost;\n headers[\"x-fluid-theme\"] = String(opts.themeId);\n headers[\"user-agent\"] = \"Fluid CLI\";\n headers[\"accept-encoding\"] = \"identity\";\n\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n url.searchParams.set(\"_fd\", \"0\");\n url.searchParams.set(\"pb\", \"0\");\n\n const pending = opts.pendingFiles?.() ?? [];\n const isGet = req.method === \"GET\" || req.method === \"HEAD\";\n let method = req.method ?? \"GET\";\n let body: string | Buffer | undefined;\n\n if (pending.length > 0 && isGet) {\n method = \"POST\";\n const params = new URLSearchParams();\n params.set(\"_method\", req.method ?? \"GET\");\n for (const f of pending) {\n params.set(`replace_templates[${f.relativePath}]`, f.read());\n }\n const token = getAuthToken();\n if (token) headers[\"authorization\"] = `Bearer ${token}`;\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = params.toString();\n headers[\"content-length\"] = String(Buffer.byteLength(body));\n } else if (!isGet) {\n body = await readBody(req);\n if (body.length > 0) {\n headers[\"content-length\"] = String(body.length);\n }\n }\n\n return new Promise((resolve, reject) => {\n const options: https.RequestOptions = {\n hostname: companyHost,\n port: 443,\n path: url.pathname + (url.search || \"\"),\n method,\n headers,\n };\n\n const proxyReq = https.request(options, (proxyRes) => {\n const contentType = proxyRes.headers[\"content-type\"] ?? \"\";\n const isHtml = contentType.includes(\"text/html\");\n\n const responseHeaders: Record<string, string | string[]> = {};\n for (const [k, v] of Object.entries(proxyRes.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && v !== undefined) {\n responseHeaders[k] = v as string | string[];\n }\n }\n\n if (isHtml) {\n const chunks: Buffer[] = [];\n proxyRes.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n proxyRes.on(\"end\", () => {\n let html = Buffer.concat(chunks).toString(\"utf-8\");\n html = injectHotReload(html, opts.reloadMode);\n responseHeaders[\"content-length\"] = String(Buffer.byteLength(html));\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n res.end(html);\n resolve();\n });\n } else {\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n proxyRes.pipe(res);\n proxyRes.on(\"end\", resolve);\n }\n });\n\n proxyReq.on(\"error\", (err) => {\n reject(err);\n });\n\n if (body) proxyReq.write(body);\n proxyReq.end();\n });\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n","import { isApiError } from \"@fluid-app/themes-api-client\";\n\n// Name of the bundled skill that walks the caller through migrating\n// per-template `styles.css` files to theme-level assets. See\n// `skills/template-stylesheet-to-asset-migration/SKILL.md` in this\n// package.\nexport const STYLESHEET_MIGRATION_SKILL =\n \"template-stylesheet-to-asset-migration\";\n\n// Detect the backend's 422 rejection for legacy stylesheet keys\n// (`ApplicationThemeResources::UpdateAction#stylesheet_rejected_response`\n// on the Rails side). Both the message and the errors payload identify\n// the rejection uniquely; match on either so a future wording tweak on\n// one surface does not silently drop the hint.\nfunction isStylesheetKeyRejection(error: {\n status: number;\n message: string;\n data: unknown;\n}): boolean {\n if (error.status !== 422) return false;\n if (/stylesheet.*no longer accepted/i.test(error.message)) return true;\n if (error.data && typeof error.data === \"object\") {\n const resourceErrors = (\n error.data as { application_theme_resource?: { key?: unknown } }\n ).application_theme_resource;\n if (\n resourceErrors &&\n typeof resourceErrors.key === \"string\" &&\n /stylesheet.*no longer accepted/i.test(resourceErrors.key)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Extract a human-readable message from a caught error. ApiError's default\n * `toString` prefixes with `ApiError:` and the class name; this returns the\n * API's own `error_message` verbatim so the CLI surfaces messages like\n * \"This stylesheet key is no longer accepted. Upload stylesheets as\n * theme-level assets.\" directly.\n *\n * When the error is the legacy stylesheet key rejection specifically,\n * a hint is appended pointing to the bundled migration skill — the\n * caller needs to move the per-template `styles.css` bytes to `assets/`\n * and stop pushing the deprecated column key.\n */\nexport function formatError(e: unknown): string {\n if (isApiError(e)) {\n const status = e.status ? ` [${e.status}]` : \"\";\n const hint = isStylesheetKeyRejection(e)\n ? `\\n ↳ Run \\`fluid theme skills install\\` — the bundled \\`${STYLESHEET_MIGRATION_SKILL}\\` skill can help you migrate this to a theme-level asset.`\n : \"\";\n return `${e.message}${status}${hint}`;\n }\n if (e instanceof Error) return e.message;\n return String(e);\n}\n","import { relative, sep } from \"node:path\";\nimport chokidar from \"chokidar\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ThemeFile } from \"../file.js\";\nimport { formatError } from \"../format-error.js\";\n\nexport type FileChangeHandler = (\n modified: ThemeFile[],\n added: ThemeFile[],\n removed: ThemeFile[],\n /** When the filesystem event arrived, not when the handler got to run.\n * Handlers are serialized behind awaited uploads, so a queued event can\n * start long after it happened — anything deciding where one edit ends and\n * the next begins has to use this, or it measures upload duration. */\n arrivedAt: number,\n) => Promise<void>;\n\nfunction relativeThemePath(root: ThemeRoot, filePath: string): string {\n return relative(root.root, filePath).split(sep).join(\"/\");\n}\n\nexport function watchTheme(\n root: ThemeRoot,\n handler: FileChangeHandler,\n): () => Promise<void> {\n const watcher = chokidar.watch(root.root, {\n ignoreInitial: true,\n ignored: (filePath: string) => {\n if (filePath.includes(\"node_modules\")) return true;\n try {\n const rel = relativeThemePath(root, filePath);\n const basename = rel.split(/[\\\\/]/).pop() ?? \"\";\n return basename.startsWith(\".\") || root.ignore.ignore(rel);\n } catch {\n return false;\n }\n },\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },\n });\n\n let pending = Promise.resolve();\n const enqueue = (fn: () => Promise<void>) => {\n // The change handler has its own internal try/catch around\n // per-file uploads/deletes, but anything that throws outside\n // that (e.g. `root.file()`, `validateSchema()`) must still be\n // surfaced — a bare `.catch(() => {})` here previously swallowed\n // it and left watch mode silently stuck.\n pending = pending.then(fn).catch((e) => {\n console.error(` [Watcher] change handling failed: ${formatError(e)}`);\n });\n };\n\n watcher.on(\"change\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([root.file(filePath)], [], [], arrivedAt));\n });\n\n watcher.on(\"add\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [root.file(filePath)], [], arrivedAt));\n });\n\n watcher.on(\"unlink\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [], [root.file(filePath)], arrivedAt));\n });\n\n return () => watcher.close();\n}\n","import { createHash } from \"node:crypto\";\nimport { lstatSync, readFileSync, readdirSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nconst METADATA = [\".fluid-assets.json\", \".fluid-theme.json\", \".fluidignore\"];\n\nexport class BackgroundPullChangedError extends Error {\n constructor() {\n super(\n \"Local files changed during background pull; remaining updates skipped.\",\n );\n }\n}\n\n/** Keep the preflight snapshot across asynchronous download/merge work. Each\n * mutation checks its original bytes synchronously and records only our writes.\n * This prevents an edit made after preflight from becoming a merge input or\n * being overwritten by a deferred write, asset cleanup, or metadata update. */\nexport class BackgroundPullGuard {\n private readonly root: string;\n private readonly expected: Map<string, string | null>;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.expected = this.snapshot();\n }\n\n assertUnchanged(): void {\n const current = this.snapshot();\n for (const key of new Set([...current.keys(), ...this.expected.keys()])) {\n if ((current.get(key) ?? null) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n }\n\n mutate(keys: readonly string[], action: () => void): void {\n for (const key of keys) {\n if (this.fingerprint(key) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n action();\n for (const key of keys) this.expected.set(key, this.fingerprint(key));\n }\n\n private fingerprint(key: string): string | null {\n const path = resolve(this.root, key);\n if (!path.startsWith(this.root + sep))\n throw new BackgroundPullChangedError();\n // Fail closed for a parent replaced by a symlink while awaiting Git/network.\n for (let cursor = path; ; cursor = dirname(cursor)) {\n try {\n if (lstatSync(cursor).isSymbolicLink())\n throw new BackgroundPullChangedError();\n } catch (error) {\n if (\n !(\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n )\n throw error;\n }\n if (cursor === this.root) break;\n }\n try {\n return createHash(\"sha256\").update(readFileSync(path)).digest(\"hex\");\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n return null;\n throw error;\n }\n }\n\n private snapshot(): Map<string, string | null> {\n const result = new Map<string, string | null>();\n const visit = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n continue;\n if (entry.isSymbolicLink()) throw new BackgroundPullChangedError();\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path);\n else {\n const key = relative(this.root, path).split(sep).join(\"/\");\n result.set(key, this.fingerprint(key));\n }\n }\n };\n visit(this.root);\n for (const key of METADATA) result.set(key, this.fingerprint(key));\n return result;\n }\n}\n","import { normalizeThemeResourceKey } from \"./resource-key.js\";\n\nexport class CaseCollisionError extends Error {\n constructor(readonly collisions: readonly (readonly string[])[]) {\n super(\n `Theme contains paths that differ only by letter case and cannot be synchronized safely:\\n${collisions\n .map((paths) => ` ${paths.join(\", \")}`)\n .join(\"\\n\")}`,\n );\n this.name = \"CaseCollisionError\";\n }\n}\n\n/** Refuse paths a case-insensitive checkout cannot represent independently. */\nexport function assertNoCaseCollisions(paths: readonly string[]): void {\n const pathsByFoldedKey = new Map<string, Set<string>>();\n for (const rawPath of paths) {\n const path = normalizeThemeResourceKey(rawPath);\n const foldedKey = path.toLowerCase();\n const matchingPaths = pathsByFoldedKey.get(foldedKey) ?? new Set<string>();\n matchingPaths.add(path);\n pathsByFoldedKey.set(foldedKey, matchingPaths);\n }\n\n const collisions = [...pathsByFoldedKey.values()]\n .filter((matchingPaths) => matchingPaths.size > 1)\n .map((matchingPaths) => [...matchingPaths].sort())\n .sort(([left = \"\"], [right = \"\"]) => left.localeCompare(right));\n\n if (collisions.length > 0) throw new CaseCollisionError(collisions);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nconst MANIFEST_FILE = \".fluid-assets.json\";\nconst MANIFEST_VERSION = 1;\n\n/**\n * A tiny shadow-repo placeholder for a manifest-backed asset. It preserves\n * the path as a deletion baseline without retaining the asset bytes locally.\n */\nexport const MANAGED_ASSET_SHADOW_SENTINEL = \"fluid-managed-asset\\n\";\n\nexport interface ThemeAssetLink {\n /** Theme that last supplied this asset reference. */\n sourceThemeId: number;\n /** SHA-256 checksum of the source resource, when the API provides one. */\n checksum?: string;\n /** ImageKit URL saved into themes that consume this asset. */\n url?: string;\n /** MIME type required to create a URL-backed FileResource. */\n contentType?: string;\n /** File size required to create a URL-backed FileResource. */\n contentSize?: number;\n /** Optional display metadata copied with the ImageKit URL. */\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n /** A dev-only source that must survive pull until a push makes it durable. */\n pending?: boolean;\n /** DAM asset identity retained as provenance for CLI-uploaded assets. */\n damAssetCode?: string;\n}\n\ninterface ThemeAssetManifestDocument {\n version: number;\n assets: Record<string, ThemeAssetLink>;\n}\n\n/**\n * Tracks binary theme assets that deliberately live only on the server.\n *\n * The manifest is a dotfile so it is excluded from theme uploads and file\n * watching. It is written before the corresponding local file is removed,\n * which prevents `delete: true` from mistaking the removed byte source for a\n * request to delete its remote FileResource.\n */\nexport class ThemeAssetManifest {\n private assets: Record<string, ThemeAssetLink>;\n private readonly path: string;\n\n constructor(themeRoot: string) {\n this.path = join(themeRoot, MANIFEST_FILE);\n this.assets = readDocument(this.path).assets;\n }\n\n reload(): void {\n this.assets = readDocument(this.path).assets;\n }\n\n keys(): string[] {\n return Object.keys(this.assets);\n }\n\n entries(): Array<[string, ThemeAssetLink]> {\n return Object.entries(this.assets).map(([key, link]) => [\n key,\n copyLink(link),\n ]);\n }\n\n /**\n * Stable digest of the URL references represented by this manifest.\n * Pull baselines exclude pending entries because those belong only to an\n * existing dev target and are deliberately absent from the pulled source.\n */\n fingerprint(opts: { excludePending?: boolean } = {}): string {\n const entries = this.entries()\n .filter(([, link]) => !opts.excludePending || !link.pending)\n .toSorted(([left], [right]) =>\n left < right ? -1 : left > right ? 1 : 0,\n );\n return createHash(\"sha256\").update(JSON.stringify(entries)).digest(\"hex\");\n }\n\n has(key: string): boolean {\n return this.assets[key] !== undefined;\n }\n\n get(key: string): ThemeAssetLink | undefined {\n const link = this.assets[key];\n return link ? copyLink(link) : undefined;\n }\n\n set(key: string, link: ThemeAssetLink): void {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(link)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n this.assets[key] = copyLink(link);\n }\n\n delete(key: string): void {\n delete this.assets[key];\n }\n\n write(): void {\n const document: ThemeAssetManifestDocument = {\n version: MANIFEST_VERSION,\n assets: copyAssets(this.assets),\n };\n const tempPath = `${this.path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\n try {\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(tempPath, JSON.stringify(document, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, this.path);\n } catch (error) {\n try {\n unlinkSync(tempPath);\n } catch {\n // The temporary file may not have been written yet.\n }\n throw error;\n }\n }\n}\n\nfunction readDocument(path: string): ThemeAssetManifestDocument {\n if (!existsSync(path)) return emptyDocument();\n\n try {\n return parseDocument(JSON.parse(readFileSync(path, \"utf-8\")));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Could not read ${MANIFEST_FILE}: ${message}`);\n }\n}\n\nfunction parseDocument(value: unknown): ThemeAssetManifestDocument {\n if (!isRecord(value) || value[\"version\"] !== MANIFEST_VERSION) {\n throw new Error(`expected version ${MANIFEST_VERSION}`);\n }\n\n const rawAssets = value[\"assets\"];\n if (!isRecord(rawAssets)) throw new Error(\"expected an assets object\");\n\n const assets: Record<string, ThemeAssetLink> = {};\n for (const [key, rawLink] of Object.entries(rawAssets)) {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(rawLink)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n assets[key] = copyLink(rawLink);\n }\n\n return { version: MANIFEST_VERSION, assets };\n}\n\nfunction emptyDocument(): ThemeAssetManifestDocument {\n return { version: MANIFEST_VERSION, assets: {} };\n}\n\nfunction copyAssets(\n assets: Record<string, ThemeAssetLink>,\n): Record<string, ThemeAssetLink> {\n return Object.fromEntries(\n Object.entries(assets).map(([key, link]) => [key, copyLink(link)]),\n );\n}\n\nfunction copyLink(link: ThemeAssetLink): ThemeAssetLink {\n return {\n sourceThemeId: link.sourceThemeId,\n ...(typeof link.checksum === \"string\" ? { checksum: link.checksum } : {}),\n ...(typeof link.url === \"string\" ? { url: link.url } : {}),\n ...(typeof link.contentType === \"string\"\n ? { contentType: link.contentType }\n : {}),\n ...(typeof link.contentSize === \"number\"\n ? { contentSize: link.contentSize }\n : {}),\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n ...(link.pending === true ? { pending: true } : {}),\n ...(typeof link.damAssetCode === \"string\"\n ? { damAssetCode: link.damAssetCode }\n : {}),\n };\n}\n\nfunction isThemeAssetLink(value: unknown): value is ThemeAssetLink {\n return (\n isRecord(value) &&\n typeof value[\"sourceThemeId\"] === \"number\" &&\n Number.isInteger(value[\"sourceThemeId\"]) &&\n value[\"sourceThemeId\"] > 0 &&\n (value[\"checksum\"] === undefined ||\n (typeof value[\"checksum\"] === \"string\" &&\n value[\"checksum\"].length > 0)) &&\n (value[\"url\"] === undefined ||\n (typeof value[\"url\"] === \"string\" && value[\"url\"].length > 0)) &&\n (value[\"contentType\"] === undefined ||\n (typeof value[\"contentType\"] === \"string\" &&\n value[\"contentType\"].length > 0)) &&\n (value[\"contentSize\"] === undefined ||\n (typeof value[\"contentSize\"] === \"number\" &&\n Number.isInteger(value[\"contentSize\"]) &&\n value[\"contentSize\"] > 0)) &&\n (value[\"previewImageUrl\"] === undefined ||\n (typeof value[\"previewImageUrl\"] === \"string\" &&\n value[\"previewImageUrl\"].length > 0)) &&\n (value[\"altText\"] === undefined || typeof value[\"altText\"] === \"string\") &&\n (value[\"handle\"] === undefined ||\n (typeof value[\"handle\"] === \"string\" && value[\"handle\"].length > 0)) &&\n (value[\"pending\"] === undefined || typeof value[\"pending\"] === \"boolean\") &&\n (value[\"damAssetCode\"] === undefined ||\n (typeof value[\"damAssetCode\"] === \"string\" &&\n value[\"damAssetCode\"].length > 0))\n );\n}\n\nexport function isThemeAssetKey(key: string): boolean {\n if (key.includes(\"\\\\\") || key.includes(\"\\0\")) return false;\n\n const segments = key.split(\"/\");\n return (\n segments[0] === \"assets\" &&\n segments.length === 2 &&\n segments[1] !== undefined &&\n segments[1].length > 0 &&\n segments[1] !== \".\" &&\n segments[1] !== \"..\"\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","// Resource keys that the backend can hide from the resources index\n// when `STYLESHEET_STRICT_INPUT` is enabled on the owning company —\n// theme-level `styles.css` / `global_styles.css` and per-template\n// composite `{type}/{name}/styles.css`. See `Themes::Resource.find_by`\n// + `Themes::Theme#theme_stylesheet_columns_hidden?` /\n// `Themes::Template#stylesheet_column_hidden?` on the Rails side.\n//\n// A pull that used the \"delete files missing on remote\" behavior would\n// otherwise wipe a merchant's local stylesheets the first time the\n// flag flips on — the resource just stops appearing in the index, not\n// the file itself. Skip these keys from the delete pass so local\n// content stays intact regardless of the flag state.\n\nconst STYLESHEET_KEY_PATTERN =\n /^(styles\\.css|global_styles\\.css|[^/]+\\/[^/]+\\/styles\\.css)$/;\n\nexport function isStylesheetKey(key: string): boolean {\n return STYLESHEET_KEY_PATTERN.test(key);\n}\n","/** The same file graph is used for uploads and incoming omission protection.\n * Rails remains responsible for resolving company-specific roles and handles. */\nexport function memberDependencyPaths(\n document: Record<string, unknown>,\n availablePaths: ReadonlySet<string>,\n deletedPaths: readonly string[] = [],\n changedPaths: ReadonlySet<string> = availablePaths,\n): Set<string> {\n const dependencies = new Set<string>();\n for (const entry of [\n ...records(document.templates),\n ...records(document.menu_references),\n ]) {\n if (typeof entry.path === \"string\") dependencies.add(entry.path);\n }\n for (const site of records(document.sites)) {\n for (const page of records(site.pages)) {\n if (typeof page.template_path === \"string\")\n dependencies.add(page.template_path);\n }\n const layout = site.layout_name;\n if (layout === \"none\") continue;\n if (\n typeof layout === \"string\" &&\n layout &&\n ![\"member\", \"theme\"].includes(layout)\n ) {\n dependencies.add(`layouts/${layout}.liquid`);\n continue;\n }\n const fallback = [layout, \"member\", \"theme\"]\n .filter(\n (name): name is string => typeof name === \"string\" && name.length > 0,\n )\n .map((name) => `layouts/${name}.liquid`)\n .find((path) => availablePaths.has(path) || deletedPaths.includes(path));\n if (fallback) dependencies.add(fallback);\n }\n\n // Variables are a separate resource, but their menu selectors belong to the\n // same template and must not replace the previous reference before import.\n for (const path of dependencies) {\n if (!path.endsWith(\"/index.liquid\")) continue;\n for (const suffix of [\"variables.json\", \"styles.css\"]) {\n const sibling = path.replace(/index\\.liquid$/, suffix);\n const candidates =\n suffix === \"styles.css\" ? changedPaths : availablePaths;\n if (candidates.has(sibling) || deletedPaths.includes(sibling))\n dependencies.add(sibling);\n }\n }\n\n return dependencies;\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n const candidates: unknown[] = Array.isArray(entries) ? entries : [];\n return candidates.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === \"object\" && entry !== null && !Array.isArray(entry),\n );\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\n\nexport const MEMBER_CONFIGURATION_KEY = \"members_sites.json\";\n\nexport interface ThemeUpload {\n file: ThemeFile;\n memberConfiguration?: {\n content: string;\n files: ThemeFile[];\n };\n}\n\n/** Apply member selectors and their document together, before losing old references. */\nexport function planThemeUploads(\n themeRoot: ThemeRoot,\n changedFiles: readonly ThemeFile[],\n deletedPaths: readonly string[] = [],\n): ThemeUpload[] {\n const localFiles = themeRoot.files();\n const manifest = localFiles.find(\n (file) => file.relativePath === MEMBER_CONFIGURATION_KEY,\n );\n if (!manifest || themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY)) {\n return individualUploads(changedFiles);\n }\n\n const content = manifest.read();\n const document: unknown = JSON.parse(content);\n // Legacy arrays do not carry the V2 dependency graph. The server's existing\n // importer still accepts them through the ordinary manifest resource.\n if (Array.isArray(document)) return individualUploads(changedFiles);\n if (!isRecord(document)) {\n throw new Error(\"members_sites.json must contain a JSON object.\");\n }\n\n const dependencies = memberDependencyPaths(\n document,\n new Set(localFiles.map((file) => file.relativePath)),\n deletedPaths,\n new Set(changedFiles.map((file) => file.relativePath)),\n );\n\n for (const path of deletedPaths) {\n if (dependencies.has(path)) {\n throw new Error(\n `Member dependency is still declared and cannot be deleted: ${path}`,\n );\n }\n }\n\n const needsBundle = changedFiles.some(\n (file) =>\n file.relativePath === MEMBER_CONFIGURATION_KEY ||\n dependencies.has(file.relativePath),\n );\n if (!needsBundle) return changedFiles.map((file) => ({ file }));\n\n const availableFiles = new Map(\n localFiles.map((file) => [file.relativePath, file]),\n );\n const memberFiles = [...dependencies].map((path) => {\n const file = availableFiles.get(path);\n if (!file || !file.isText || themeRoot.ignore.ignore(path)) {\n throw new Error(\n `Member dependency must be an available text file: ${path}`,\n );\n }\n return file;\n });\n const uploads: ThemeUpload[] = changedFiles\n .filter(\n (file) =>\n file.relativePath !== MEMBER_CONFIGURATION_KEY &&\n !dependencies.has(file.relativePath),\n )\n .map((file) => ({ file }));\n uploads.push({\n file: manifest,\n memberConfiguration: { content, files: memberFiles },\n });\n return uploads;\n}\n\nfunction individualUploads(files: readonly ThemeFile[]): ThemeUpload[] {\n return [...files]\n .sort(\n (left, right) =>\n Number(left.relativePath === MEMBER_CONFIGURATION_KEY) -\n Number(right.relativePath === MEMBER_CONFIGURATION_KEY),\n )\n .map((file) => ({ file }));\n}\n\nfunction isRecord(entry: unknown): entry is Record<string, unknown> {\n return typeof entry === \"object\" && entry !== null && !Array.isArray(entry);\n}\n\n/** Removing an older file is not an instruction to erase a member site. */\nexport function assertMemberConfigurationRetained(\n deletedPaths: readonly string[],\n): void {\n if (deletedPaths.includes(MEMBER_CONFIGURATION_KEY)) {\n throw new Error(\n \"Keep members_sites.json. To remove member configuration, upload an explicit empty versioned document instead of deleting or renaming the file.\",\n );\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport {\n isApiError,\n themes,\n type components,\n} from \"@fluid-app/themes-api-client\";\nimport type { ApiClient } from \"../api.js\";\nimport { formatError } from \"./format-error.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport {\n isThemeAssetKey,\n ThemeAssetManifest,\n type ThemeAssetLink,\n} from \"./asset-manifest.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n MEMBER_CONFIGURATION_KEY,\n} from \"./member-configuration.js\";\nimport type {\n DevRemoteState,\n RemoteResourceState,\n} from \"./dev-remote-baseline.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\ninterface RemoteAssetMetadata {\n url: string;\n contentType: string;\n contentSize: number;\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n}\n\ninterface ManagedAssetPlan {\n key: string;\n link: ThemeAssetLink;\n targetResource?: RemoteResource;\n metadata?: RemoteAssetMetadata;\n}\n\ntype UploadedBinaryResource = RemoteResource & {\n damAssetCode?: string;\n assetMetadata?: RemoteAssetMetadata;\n};\n\nconst ASSET_REFERENCE_CONCURRENCY = 6;\n\nexport interface SyncResult {\n uploaded: number;\n downloaded: number;\n linked: number;\n deleted: number;\n errors: string[];\n validationFailed: boolean;\n}\n\n/**\n * Server rejected the push because the CLI's `base_sha` no longer\n * matches the theme's current `content_version_sha` (someone else\n * wrote to the theme since our last pull). Callers surface a\n * \"pull first\" message; no partial state has been written when this\n * throws from the preflight, and the per-file variant preserves the\n * atomicity guarantee mid-loop by short-circuiting the remaining\n * files on the first conflicting response.\n */\nexport class PushConflictError extends Error {\n constructor(public readonly remoteSha: string | null) {\n super(\n remoteSha\n ? `Your local is behind the server. Server is at ${remoteSha}; local is stale.`\n : \"Your local is behind the server.\",\n );\n this.name = \"PushConflictError\";\n }\n}\n\nexport class Syncer {\n private checksumIndex = new Map<string, string>();\n private memberAcknowledgments = new Map<string, string>();\n private rawRemoteResources = new Map<string, RemoteResource>();\n private remoteResourceGroups = new Map<string, RemoteResource[]>();\n private remoteResourceIndex = new Map<string, RemoteResource>();\n private remoteIndexesDirty = false;\n private remoteResourcesLoaded = false;\n private lastKnownRemoteSha: string | null = null;\n private assetManifestInstance: ThemeAssetManifest | undefined;\n\n constructor(\n private api: ApiClient,\n private themeId: number,\n private themeRoot: ThemeRoot,\n assetManifest?: ThemeAssetManifest,\n ) {\n this.assetManifestInstance = assetManifest;\n }\n\n private get assetManifest(): ThemeAssetManifest {\n this.assetManifestInstance ??= new ThemeAssetManifest(this.themeRoot.root);\n return this.assetManifestInstance;\n }\n\n // ─── Checksum Management ──────────────────────────────────────────────────\n\n async fetchChecksums(): Promise<void> {\n // `content_version_sha` on the resources index is a Phase 003a\n // server addition — older servers don't emit it. Cast through\n // `unknown` because the typed API client hasn't been regenerated\n // against the new OpenAPI spec yet; regeneration is a follow-up.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n this.updateChecksums(body.application_theme_resources ?? []);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n }\n\n /**\n * Server's `content_version_sha` captured on the last `fetchChecksums()`\n * or `downloadAll()`. `null` when talking to a pre-003a server.\n */\n remoteSha(): string | null {\n return this.lastKnownRemoteSha;\n }\n\n private updateChecksums(resources: RemoteResource[]): void {\n assertNoCaseCollisions(\n resources.flatMap((resource) => (resource.key ? [resource.key] : [])),\n );\n this.rawRemoteResources.clear();\n this.remoteResourceGroups.clear();\n for (const resource of resources) {\n if (!resource.key) continue;\n\n this.rawRemoteResources.set(resource.key, resource);\n const group = this.remoteResourceGroups.get(resource.key) ?? [];\n group.push(resource);\n this.remoteResourceGroups.set(resource.key, group);\n }\n this.remoteIndexesDirty = true;\n }\n\n private setRemoteResource(resource: RemoteResource): void {\n if (!resource.key) return;\n this.rawRemoteResources.set(resource.key, resource);\n this.remoteResourceGroups.set(resource.key, [resource]);\n this.remoteIndexesDirty = true;\n }\n\n private removeRemoteResource(relativePath: string): void {\n this.rawRemoteResources.delete(relativePath);\n this.remoteResourceGroups.delete(relativePath);\n this.remoteIndexesDirty = true;\n }\n\n // Rebuilding on every setRemoteResource/removeRemoteResource made bulk\n // upload loops O(n²) over the remote resource map, so mutations only mark\n // the indexes dirty and the next read rebuilds once.\n private get checksums(): Map<string, string> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.checksumIndex;\n }\n\n private get remoteResources(): Map<string, RemoteResource> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.remoteResourceIndex;\n }\n\n private rebuildRemoteIndexes(): void {\n this.remoteIndexesDirty = false;\n this.checksumIndex.clear();\n this.remoteResourceIndex.clear();\n\n for (const [key, resource] of this.rawRemoteResources) {\n // The backend may return both a legacy key and its .liquid equivalent.\n // Preserve the explicit .liquid resource when both are present.\n if (this.rawRemoteResources.has(`${key}.liquid`)) continue;\n\n this.remoteResourceIndex.set(key, resource);\n if (resource.checksum) this.checksumIndex.set(key, resource.checksum);\n }\n }\n\n hasChanged(file: ThemeFile): boolean {\n return file.checksum() !== this.checksums.get(file.relativePath);\n }\n\n remoteKeys(): string[] {\n return [...this.remoteResources.keys()];\n }\n\n /** A null-content resource has no possible working-tree counterpart. */\n private canDeleteRemoteResource(key: string): boolean {\n const resource = this.remoteResources.get(key);\n return resource?.content != null || isManagedAssetResource(resource);\n }\n\n /** Snapshot of remote checksums (key → sha256). Available after fetchChecksums() or downloadAll(). */\n remoteChecksums(): Record<string, string> {\n return Object.fromEntries(this.checksums);\n }\n\n /** URL-backed assets keyed by their exact theme resource path. */\n remoteAssetUrls(): Record<string, string> {\n const urls: Record<string, string> = {};\n for (const [key, resource] of this.remoteResources) {\n if (!isManagedAssetResource(resource)) continue;\n const url = resource.url;\n if (typeof url === \"string\" && url.length > 0) urls[key] = url;\n }\n return urls;\n }\n\n /** Compact, complete resource state paired with its acknowledged dev SHA. */\n devRemoteState(assetManifestSha: string): DevRemoteState | null {\n if (!this.remoteResourcesLoaded || !this.lastKnownRemoteSha) return null;\n\n return {\n themeId: this.themeId,\n remoteSha: this.lastKnownRemoteSha,\n assetManifestSha,\n resources: [...this.remoteResourceGroups.values()]\n .flat()\n .map(remoteResourceState),\n };\n }\n\n useDevRemoteState(state: DevRemoteState): void {\n if (state.themeId !== this.themeId) {\n throw new Error(\n `Dev remote state belongs to theme #${state.themeId}, not #${this.themeId}`,\n );\n }\n this.updateChecksums(state.resources.map(remoteResourceFromState));\n this.lastKnownRemoteSha = state.remoteSha;\n this.remoteResourcesLoaded = true;\n }\n\n private async ensureRemoteResourcesLoaded(): Promise<void> {\n if (!this.remoteResourcesLoaded) await this.fetchChecksums();\n }\n\n /**\n * Adds URL-backed FileResources for manifest assets without transferring\n * their bytes. The target stores the source asset's ImageKit URL.\n */\n async linkManagedAssets(opts: { replace?: boolean } = {}): Promise<number> {\n this.assetManifest.reload();\n await this.ensureRemoteResourcesLoaded();\n\n const plans: ManagedAssetPlan[] = [];\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n // A local binary is the recovery source after a failed metadata pull or\n // an intentional replacement. Let the normal upload path handle it;\n // never recreate the older manifest URL over those bytes first.\n if (this.themeRoot.file(key).exists) continue;\n\n const targetResource =\n this.managedAssetResourceForLink(key, link) ??\n this.remoteResources.get(key);\n if (\n targetResource &&\n !this.managedAssetNeedsRefresh(targetResource, link)\n ) {\n continue;\n }\n if (targetResource && !opts.replace) continue;\n\n const metadata = assetMetadataFromLink(link);\n plans.push({\n key,\n link,\n ...(targetResource ? { targetResource } : {}),\n ...(metadata ? { metadata } : {}),\n });\n }\n\n if (plans.length > 0) {\n await this.resolveAssetMetadata(plans);\n await this.createAssetReferences(plans);\n await this.fetchChecksums();\n }\n if (opts.replace && (await this.pruneDuplicateManagedAssetReferences())) {\n await this.fetchChecksums();\n }\n this.ensureManagedAssetsAreResolved();\n\n return plans.length;\n }\n\n private async resolveAssetMetadata(plans: ManagedAssetPlan[]): Promise<void> {\n const bySourceTheme = new Map<number, ManagedAssetPlan[]>();\n for (const plan of plans) {\n if (plan.metadata) continue;\n const sourcePlans = bySourceTheme.get(plan.link.sourceThemeId) ?? [];\n sourcePlans.push(plan);\n bySourceTheme.set(plan.link.sourceThemeId, sourcePlans);\n }\n\n let manifestChanged = false;\n for (const [sourceThemeId, sourcePlans] of bySourceTheme) {\n let sourceAssets: Map<string, RemoteAssetMetadata>;\n try {\n sourceAssets = await this.fetchThemeAssetMetadata(sourceThemeId);\n } catch (error) {\n throw new Error(\n `Could not read asset metadata from theme #${sourceThemeId}: ${formatError(error)}`,\n );\n }\n\n for (const plan of sourcePlans) {\n const sourceMetadata = sourceAssets.get(assetFilename(plan.key));\n if (!sourceMetadata) {\n throw new Error(\n `Could not find usable metadata for ${plan.key} in theme #${sourceThemeId}`,\n );\n }\n\n // Preserve a pulled URL while enriching an older manifest. A remote\n // source update must not silently change this checkout's asset URL.\n const metadata: RemoteAssetMetadata = {\n ...sourceMetadata,\n ...(typeof plan.link.url === \"string\" ? { url: plan.link.url } : {}),\n };\n plan.metadata = metadata;\n this.assetManifest.set(plan.key, {\n ...plan.link,\n ...metadata,\n });\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) this.assetManifest.write();\n }\n\n private async fetchThemeAssetMetadata(\n sourceThemeId: number,\n ): Promise<Map<string, RemoteAssetMetadata>> {\n const body = await themes.getThemeAssets(this.api, sourceThemeId);\n if (!isRecord(body) || !Array.isArray(body[\"file_resources\"])) {\n throw new Error(\"Theme assets response did not include file_resources\");\n }\n\n const assets = new Map<string, RemoteAssetMetadata>();\n for (const value of body[\"file_resources\"]) {\n const asset = parseThemeAssetMetadata(value);\n if (asset) assets.set(asset.filename, asset.metadata);\n }\n return assets;\n }\n\n private async createAssetReferences(\n plans: ManagedAssetPlan[],\n ): Promise<void> {\n const errors: string[] = [];\n let nextPlan = 0;\n\n const worker = async () => {\n while (nextPlan < plans.length) {\n const plan = plans[nextPlan];\n nextPlan += 1;\n if (!plan || !plan.metadata) continue;\n\n try {\n await this.createAssetReference(plan);\n } catch (error) {\n errors.push(`${plan.key}: ${formatError(error)}`);\n }\n }\n };\n\n await Promise.all(\n Array.from(\n { length: Math.min(ASSET_REFERENCE_CONCURRENCY, plans.length) },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not save ${errors.length} ImageKit URL reference(s) (this requires File Resources update access): ${errors.join(\"; \")}`,\n );\n }\n }\n\n private async createAssetReference(plan: ManagedAssetPlan): Promise<void> {\n const metadata = plan.metadata;\n if (!metadata) throw new Error(\"asset metadata was not resolved\");\n\n const targetResourceId = plan.targetResource?.resource_id;\n if (plan.targetResource && typeof targetResourceId !== \"number\") {\n throw new Error(\"existing target asset has no resource ID\");\n }\n\n const body = await themes.createFileResource(this.api, {\n file_resource: {\n url: metadata.url,\n filename: assetFilename(plan.key),\n content_type: metadata.contentType,\n content_size: metadata.contentSize,\n ...(metadata.previewImageUrl\n ? { preview_image_url: metadata.previewImageUrl }\n : {}),\n ...(metadata.altText !== undefined\n ? { alt_text: metadata.altText }\n : {}),\n ...(metadata.handle ? { handle: metadata.handle } : {}),\n relateable_id: this.themeId,\n relateable_type: \"ApplicationTheme\",\n },\n });\n\n const createdResourceId = createdFileResourceId(body);\n if (!createdResourceId) {\n throw new Error(\"create response did not include a FileResource ID\");\n }\n\n if (typeof targetResourceId !== \"number\") return;\n\n try {\n await themes.destroyFileResource(this.api, targetResourceId);\n } catch (error) {\n if (isNotFoundError(error)) return;\n try {\n await themes.destroyFileResource(this.api, createdResourceId);\n } catch {\n // Keep the original error; a later sync can repair any duplicate.\n }\n throw error;\n }\n }\n\n /**\n * Returns every resource for an exact logical key. A legacy `foo` resource\n * is hidden when `foo.liquid` exists, matching the normal remote index.\n */\n private resourcesForLogicalKey(key: string): RemoteResource[] {\n if (this.rawRemoteResources.has(`${key}.liquid`)) return [];\n return this.remoteResourceGroups.get(key) ?? [];\n }\n\n private managedAssetResourceForLink(\n key: string,\n link: ThemeAssetLink,\n ): RemoteResource | undefined {\n return this.resourcesForLogicalKey(key).find(\n (resource) =>\n isManagedAssetResource(resource) &&\n !this.managedAssetNeedsRefresh(resource, link),\n );\n }\n\n /** Make interrupted reference replacement converge to one FileResource. */\n private async pruneDuplicateManagedAssetReferences(): Promise<boolean> {\n const resourcesToDelete: Array<{ key: string; resourceId: number }> = [];\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key) || !link.url) continue;\n\n const resources = this.resourcesForLogicalKey(key).filter(\n isManagedAssetResource,\n );\n if (resources.length < 2) continue;\n\n const matchingResources = resources.filter(\n (resource) => resource.url === link.url,\n );\n if (matchingResources.length === 0) continue;\n\n const keeper = this.lowestResourceId(matchingResources, key);\n const keeperId = this.resourceIdForAssetReference(keeper, key);\n for (const resource of resources) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId !== keeperId)\n resourcesToDelete.push({ key, resourceId });\n }\n }\n\n if (resourcesToDelete.length === 0) return false;\n\n const errors: string[] = [];\n let nextResource = 0;\n const worker = async () => {\n while (nextResource < resourcesToDelete.length) {\n const resource = resourcesToDelete[nextResource];\n nextResource += 1;\n if (!resource) continue;\n\n try {\n await themes.destroyFileResource(this.api, resource.resourceId);\n } catch (error) {\n if (!isNotFoundError(error)) {\n errors.push(`${resource.key}: ${formatError(error)}`);\n }\n }\n }\n };\n\n await Promise.all(\n Array.from(\n {\n length: Math.min(\n ASSET_REFERENCE_CONCURRENCY,\n resourcesToDelete.length,\n ),\n },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not remove ${errors.length} duplicate ImageKit URL reference(s): ${errors.join(\"; \")}`,\n );\n }\n\n return true;\n }\n\n private lowestResourceId(\n resources: RemoteResource[],\n key: string,\n ): RemoteResource {\n let lowest = resources[0];\n if (!lowest) throw new Error(`No asset resources found for ${key}`);\n\n let lowestId = this.resourceIdForAssetReference(lowest, key);\n for (const resource of resources.slice(1)) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId < lowestId) {\n lowest = resource;\n lowestId = resourceId;\n }\n }\n return lowest;\n }\n\n private resourceIdForAssetReference(\n resource: RemoteResource,\n key: string,\n ): number {\n const resourceId = positiveInteger(resource.resource_id);\n if (!resourceId) {\n throw new Error(`Existing target asset has no resource ID: ${key}`);\n }\n return resourceId;\n }\n\n /** Makes this target the provenance source for assets it now resolves. */\n repointManagedAssetsToCurrentTheme(): void {\n this.assetManifest.reload();\n let changed = false;\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n if (this.themeRoot.file(key).exists) continue;\n\n const resource = this.remoteResources.get(key);\n if (!isManagedAssetResource(resource)) {\n throw new Error(`Managed asset is missing from target theme: ${key}`);\n }\n\n const nextLink: ThemeAssetLink = { ...link, sourceThemeId: this.themeId };\n delete nextLink.pending;\n delete nextLink.checksum;\n Object.assign(nextLink, resourceLink(resource));\n this.assetManifest.set(key, nextLink);\n changed = true;\n }\n\n if (changed) this.assetManifest.write();\n }\n\n // ─── Upload ───────────────────────────────────────────────────────────────\n\n /**\n * Uploads one file. Resolves with the exact text content that was sent to\n * the server (null for binary files) so callers can run diagnostics against\n * the same bytes instead of re-reading a file that may have changed on disk\n * while the request was in flight.\n */\n async uploadFile(\n file: ThemeFile,\n baseSha?: string | null,\n opts: { pendingAsset?: boolean } = {},\n ): Promise<string | null> {\n if (file.isText) {\n const content = file.read();\n const resource = await this.putResource(\n { key: file.relativePath, content },\n baseSha,\n );\n this.setRemoteResource({\n ...resource,\n key: file.relativePath,\n content,\n checksum: resource.checksum ?? file.checksum(),\n });\n return content;\n }\n\n if (isNestedBinaryThemeAsset(file)) {\n throw new Error(\n `Binary assets must be directly inside assets/: ${file.relativePath}`,\n );\n }\n\n const resource = await this.uploadBinaryFile(file, baseSha);\n this.setRemoteResource(resource);\n if (isThemeAssetKey(file.relativePath)) {\n this.externalizeBinaryFile(file, resource, opts.pendingAsset);\n }\n return null;\n }\n\n /** Canonical member bytes accepted by the server during this transfer. */\n acknowledgedMemberContents(): ReadonlyMap<string, string> {\n return this.memberAcknowledgments;\n }\n\n /** Upload one member document and its changed dependencies as one transaction. */\n async uploadMemberConfiguration(\n upload: ThemeUpload,\n baseSha?: string | null,\n ): Promise<Array<{ file: ThemeFile; content: string }>> {\n if (!upload.memberConfiguration) {\n throw new Error(\"A member configuration upload is required.\");\n }\n const { content, files } = upload.memberConfiguration;\n const capturedFiles = files.map((file) => ({ file, content: file.read() }));\n const resource = await this.putResource(\n {\n key: MEMBER_CONFIGURATION_KEY,\n content,\n member_files: capturedFiles.map(\n ({ file, content: capturedContent }) => ({\n key: file.relativePath,\n content: capturedContent,\n }),\n ),\n },\n baseSha,\n capturedFiles.map(({ file }) => file.relativePath),\n );\n // The server may allocate new menu handles and rewrite selectors. Keep its\n // acknowledged contents/checksums, never claim that the local bytes landed.\n this.setRemoteResource(resource);\n if (resource.key && resource.content != null) {\n this.memberAcknowledgments.set(\n resource.key,\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content),\n );\n }\n return [{ file: upload.file, content }, ...capturedFiles];\n }\n\n /**\n * Wraps the generated `updateThemeResource` client with the two\n * merge-aware Phase 003 additions: sending `base_sha` on the request\n * and reading the server's fresh `content_version_sha` off the\n * response so the caller can thread it forward on the next PUT.\n *\n * Accepts any `application_theme_resource` shape — text uploads pass\n * `{ key, content }`; binary uploads (after DAM + ImageKit\n * orchestration) pass `{ key, dam_asset: { ... } }`. Both must route\n * through here so `lastKnownRemoteSha` stays in lockstep with every\n * write the server has ack'd, mixed text/binary pushes included.\n *\n * The typed client hasn't been regenerated against the new OpenAPI\n * spec yet, so `base_sha` is threaded through as an extra property\n * (server accepts unknown fields on this endpoint) and the response\n * is cast to read the extra `content_version_sha`. Regeneration is\n * a follow-up; that PR will drop these casts.\n *\n * `PushConflictError` is thrown on a 409 so callers can distinguish\n * \"server rejected because of stale base\" from generic upload\n * failures.\n */\n private async putResource(\n resource: Record<string, unknown>,\n baseSha: string | null | undefined,\n expectedMemberKeys?: readonly string[],\n ): Promise<RemoteResource> {\n const body: Record<string, unknown> = {\n application_theme_resource: resource,\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.updateThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as {\n application_theme_resource?: RemoteResource;\n member_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n if (expectedMemberKeys) {\n const acknowledged = new Map(\n (response.member_resources ?? []).map((entry) => [entry.key, entry]),\n );\n const manifest = response.application_theme_resource;\n if (\n manifest?.key !== MEMBER_CONFIGURATION_KEY ||\n manifest.content == null ||\n !manifest.checksum ||\n expectedMemberKeys.some(\n (key) =>\n acknowledged.get(key)?.content == null ||\n !acknowledged.get(key)?.checksum,\n )\n ) {\n throw new Error(\n \"Fluid did not acknowledge every bundled member file. Update the server before retrying this push.\",\n );\n }\n }\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n for (const memberResource of response.member_resources ?? []) {\n this.setRemoteResource(memberResource);\n if (memberResource.key && memberResource.content != null) {\n this.memberAcknowledgments.set(\n memberResource.key,\n typeof memberResource.content === \"string\"\n ? memberResource.content\n : JSON.stringify(memberResource.content),\n );\n }\n }\n return (\n response.application_theme_resource ?? {\n key: typeof resource[\"key\"] === \"string\" ? resource[\"key\"] : \"\",\n checksum: null,\n }\n );\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n /**\n * Server-side push preflight (Phase 003a). Runs once at the start of\n * a push loop. On 200 the server's fresh `remote_sha` is stashed on\n * the syncer so subsequent per-file PUTs can carry it. On 409 a\n * `PushConflictError` is thrown carrying the server's `remote_sha`\n * from the `meta` payload — the caller renders \"pull first\".\n *\n * Skipped when `baseSha` is null/undefined so old-behavior pushes\n * (no stored `baseSha` in `.fluid-theme.json`) and `--force` pushes\n * short-circuit past the check.\n */\n /**\n * Tell Fluid the push is finished, so it commits the theme's current state\n * as one version.\n *\n * The other half of `preflightPush`. That one runs once before the file\n * loop to reject a stale base; this runs once after it, and is the only\n * thing that turns a push into a commit — the per-file writes just mark the\n * theme changed.\n *\n * It exists because the server cannot see where an operation ends. A push\n * arrives as a hundred-odd independent requests, and every way of inferring\n * \"these belong together\" either merges two publishes that happened to land\n * close together or splits one push across several commits. The client\n * knows; this says so.\n *\n * Returns false rather than throwing when the request fails. Callers must\n * retain their previous baseline until finalization succeeds; the server\n * can reject an incomplete member graph even after individual files land.\n *\n * Returns whether Fluid took the request, because a caller tracking edit\n * boundaries needs to know. A failed ask leaves the previous edit\n * uncommitted, and uploading the next one over it merges the two into\n * whichever commit eventually lands.\n */\n async requestSync(): Promise<boolean> {\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/sync`,\n {},\n )) as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n return true;\n } catch (e) {\n console.warn(\n ` ⚠ couldn't ask Fluid to commit this push (${formatError(e)}). Retry the push to finalize it.`,\n );\n return false;\n }\n }\n\n async preflightPush(baseSha: string | null | undefined): Promise<void> {\n if (!baseSha) return;\n\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/check_push`,\n { base_sha: baseSha },\n )) as { remote_sha?: string };\n if (response.remote_sha) this.lastKnownRemoteSha = response.remote_sha;\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n private rethrowIfConflict(e: unknown): unknown {\n const status =\n (e as { status?: number; response?: { status?: number } })?.status ??\n (e as { response?: { status?: number } })?.response?.status;\n if (status !== 409) return e;\n\n // `ApiError` from api-client-core stores the parsed response body\n // under `.data` (not `.body`). Reading the wrong key silently\n // dropped `remote_sha` in production so the \"server is at X\"\n // hint in the CLI output went missing.\n const meta = (e as { data?: { meta?: { remote_sha?: string } } })?.data\n ?.meta;\n return new PushConflictError(meta?.remote_sha ?? null);\n }\n\n private async uploadBinaryFile(\n file: ThemeFile,\n baseSha: string | null | undefined,\n ): Promise<UploadedBinaryResource> {\n // Step 1: Create DAM placeholder\n const placeholderBody = await this.api.post<{\n asset: { id: number; canonical_path: string };\n }>(\"/api/dam/assets\", {\n placeholder_asset: {\n description: `Uploaded via Fluid CLI: ${file.name}`,\n mime_type: file.mime.name,\n name: file.name,\n },\n });\n const asset = placeholderBody.asset;\n\n // Step 2: Get ImageKit auth token\n const authBody = await this.api.post<{\n token: string;\n signature: string;\n expire: number;\n }>(\"/api/dam/assets/imagekit_auth\", {});\n\n // Step 3: Upload to ImageKit via multipart\n const folder = this.canonicalPathToImageKitFolder(asset.canonical_path);\n const formData = new FormData();\n const blob = new Blob([file.readBinary() as unknown as ArrayBuffer], {\n type: file.mime.name,\n });\n formData.append(\"file\", blob, file.name);\n formData.append(\"token\", authBody.token);\n formData.append(\"signature\", authBody.signature);\n formData.append(\"expire\", String(authBody.expire));\n formData.append(\"folder\", folder);\n formData.append(\"fileName\", file.name);\n formData.append(\"publicKey\", \"public_j7s4Ih9ETh/OCp41mVQH7tlXBdU=\");\n\n const ikResp = await fetch(\n \"https://upload.imagekit.io/api/v1/files/upload\",\n {\n method: \"POST\",\n body: formData,\n },\n );\n if (!ikResp.ok) throw new Error(`ImageKit upload failed: ${ikResp.status}`);\n const ikBody = (await ikResp.json()) as {\n fileId: string;\n url: string;\n thumbnailUrl: string;\n size: number;\n height?: number;\n width?: number;\n };\n\n // Step 4: Backfill DAM asset\n const backfillPayload: Record<string, unknown> = {\n asset: {\n id: asset.id,\n imagekit_file_id: ikBody.fileId,\n imagekit_url: ikBody.url,\n mime_type: file.mime.name,\n name: file.name,\n file_size: ikBody.size,\n expected_path: asset.canonical_path,\n },\n };\n if (ikBody.height)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"height\"] =\n ikBody.height;\n if (ikBody.width)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"width\"] =\n ikBody.width;\n\n const backfillBody = await this.api.post<{\n asset: { code: string; default_variant_url: string };\n }>(\"/api/dam/assets/backfill_imagekit\", backfillPayload);\n\n // Step 5: Associate with theme resource. Route through `putResource`\n // so `base_sha` gets sent and `lastKnownRemoteSha` advances just\n // like text-file writes — a mixed text+binary push must keep them\n // in a single monotonic chain.\n const update = await this.putResource(\n {\n key: file.relativePath,\n dam_asset: {\n dam_asset_code: backfillBody.asset.code,\n content_type: file.mime.name,\n content_size: ikBody.size,\n filename: file.name,\n handle: backfillBody.asset.code,\n url: backfillBody.asset.default_variant_url,\n preview_image_url: ikBody.thumbnailUrl,\n },\n },\n baseSha,\n );\n\n // Older API responses can omit URL fields. Preserve the known DAM URL so\n // the manifest stays usable after a successful remote write.\n return {\n ...update,\n key: update.key || file.relativePath,\n url: update.url ?? backfillBody.asset.default_variant_url,\n damAssetCode: backfillBody.asset.code,\n assetMetadata: {\n url: backfillBody.asset.default_variant_url,\n contentType: file.mime.name,\n contentSize: ikBody.size,\n previewImageUrl: ikBody.thumbnailUrl,\n altText: file.name,\n handle: backfillBody.asset.code,\n },\n };\n }\n\n private externalizeBinaryFile(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n pending: boolean | undefined,\n ): void {\n // Persist before unlinking. If persistence fails, retain the local bytes so\n // the developer can retry instead of losing the only provenance record.\n this.assetManifest.reload();\n this.assetManifest.set(file.relativePath, {\n sourceThemeId: this.themeId,\n ...resourceLink(resource),\n ...uploadedAssetMetadata(file, resource),\n ...(pending ? { pending: true } : {}),\n ...(typeof resource.damAssetCode === \"string\"\n ? { damAssetCode: resource.damAssetCode }\n : {}),\n });\n this.assetManifest.write();\n unlinkSync(file.absolutePath);\n }\n\n private canonicalPathToImageKitFolder(canonicalPath: string): string {\n const parts = canonicalPath.split(\".\");\n const companyId = parts[0] ?? \"unknown\";\n const category = parts[1] ?? \"files\";\n const assetCode = parts[2] ?? \"unknown\";\n const folderMap: Record<string, string> = {\n images: \"images\",\n videos: \"videos\",\n audio: \"audio\",\n documents: \"documents\",\n files: \"files\",\n };\n return `${companyId}/${folderMap[category] ?? \"files\"}/${assetCode}`;\n }\n\n // ─── Delete ───────────────────────────────────────────────────────────────\n\n async deleteRemoteFile(\n relativePath: string,\n baseSha?: string | null,\n ): Promise<void> {\n assertMemberConfigurationRetained([relativePath]);\n // Chokidar observes the intentional unlink after a binary has been\n // externalized. That unlink must never delete the server-side DAM link.\n this.assetManifest.reload();\n if (this.assetManifest.has(relativePath)) return;\n\n const body: Record<string, unknown> = {\n application_theme_resource: { key: relativePath },\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.deleteThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n } catch (e) {\n if (!isNotFoundError(e)) throw this.rethrowIfConflict(e);\n }\n this.removeRemoteResource(relativePath);\n }\n\n // ─── Download ─────────────────────────────────────────────────────────────\n\n async downloadAll(): Promise<RemoteResource[]> {\n // Same cast rationale as `fetchChecksums`. The additional\n // `content_version_sha` field is stashed on the syncer so callers\n // (pull command) can persist it as the new `baseSha`.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n const resources = body.application_theme_resources ?? [];\n this.updateChecksums(resources);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n return resources;\n }\n\n async downloadBinaryAsset(url: string): Promise<Buffer> {\n const resp = await fetch(url);\n if (!resp.ok) throw new Error(`Failed to download asset: ${resp.status}`);\n return Buffer.from(await resp.arrayBuffer());\n }\n\n /**\n * Move directly-addressable binary `assets/*` resources into the local\n * manifest before merge-pull sees them. This prevents a byte download and\n * keeps those paths out of the shadow repository; their canonical state is\n * the ImageKit URL, not a local file.\n */\n async externalizePulledAssets(\n resources: RemoteResource[],\n opts: { delete: boolean; backgroundGuard?: BackgroundPullGuard },\n ): Promise<{ managedKeys: Set<string>; linked: number; errors: string[] }> {\n this.assetManifest.reload();\n\n const managedKeys = new Set<string>();\n const changedManifestKeys = new Set<string>();\n const remoteManagedKeys = new Set<string>();\n const preservedManifestKeys = new Set<string>();\n const filesToRemove = new Map<string, ThemeFile>();\n const errors: string[] = [];\n let manifestChanged = false;\n\n // ApplicationThemeResource deliberately exposes only the URL/checksum.\n // Capture FileResource metadata during the pull too, so a later push can\n // recreate the reference even if this source/development theme has since\n // been deleted. This is one small JSON request, never a binary download.\n const resourcesNeedingMetadata = resources.filter((resource) => {\n const key = resource.key;\n if (!key) return false;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) return false;\n if (!isLinkableBinaryResource(resource, key, file)) return false;\n\n const existing = this.assetManifest.get(key);\n if (this.themeRoot.ignore.ignore(key) || existing?.pending) return false;\n return (\n !existing ||\n existing.url !== resource.url ||\n !assetMetadataFromLink(existing)\n );\n });\n let assetMetadata = new Map<string, RemoteAssetMetadata>();\n const unresolvedMetadataKeys = new Set<string>();\n if (resourcesNeedingMetadata.length > 0) {\n try {\n assetMetadata = await this.fetchThemeAssetMetadata(this.themeId);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Read remote asset metadata: ${formatError(error)}`);\n for (const resource of resourcesNeedingMetadata) {\n if (resource.key) unresolvedMetadataKeys.add(resource.key);\n }\n }\n\n if (unresolvedMetadataKeys.size === 0) {\n for (const resource of resourcesNeedingMetadata) {\n const key = resource.key;\n if (!key || assetMetadata.has(assetFilename(key))) continue;\n unresolvedMetadataKeys.add(key);\n errors.push(\n `Could not find usable metadata for ${key} in theme #${this.themeId}`,\n );\n }\n }\n }\n\n opts.backgroundGuard?.assertUnchanged();\n for (const resource of resources) {\n const key = resource.key;\n if (!key) continue;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) continue;\n if (!isLinkableBinaryResource(resource, key, file)) continue;\n\n remoteManagedKeys.add(key);\n if (this.themeRoot.ignore.ignore(key)) {\n if (this.assetManifest.has(key)) preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // A newly added dev asset has not necessarily been promoted to the\n // pulled source yet. It remains authoritative until a push makes it\n // durable on the selected target.\n if (this.assetManifest.get(key)?.pending) {\n preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // Do not create an incomplete URL-only reference: a later push may need\n // content type and size after this source theme no longer exists. Let\n // merge-pull retain/download just this binary instead.\n if (unresolvedMetadataKeys.has(key)) {\n // The fallback bytes are now the only trustworthy copy. A manifest\n // entry pointing at a URL the remote no longer serves is stale —\n // keeping it would let linkManagedAssets restore the older asset if\n // the local file later disappears.\n const stale = this.assetManifest.get(key);\n if (stale && stale.url !== resource.url) {\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n continue;\n }\n\n try {\n const existing = this.assetManifest.get(key);\n const metadata = assetMetadata.get(assetFilename(key));\n this.assetManifest.set(key, {\n ...existing,\n sourceThemeId: this.themeId,\n ...metadata,\n ...resourceLink(resource),\n });\n manifestChanged = true;\n changedManifestKeys.add(key);\n managedKeys.add(key);\n filesToRemove.set(key, file);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n }\n }\n\n if (opts.delete) {\n for (const [key, link] of this.assetManifest.entries()) {\n if (\n remoteManagedKeys.has(key) ||\n preservedManifestKeys.has(key) ||\n this.themeRoot.ignore.ignore(key) ||\n link.pending\n ) {\n continue;\n }\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) {\n try {\n // Persist all links first. If this fails, preserve the local bytes and\n // let merge-pull materialize the affected resources normally.\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([\".fluid-assets.json\"], () =>\n this.assetManifest.write(),\n );\n else this.assetManifest.write();\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Persist remote asset manifest: ${formatError(error)}`);\n for (const key of changedManifestKeys) managedKeys.delete(key);\n return { managedKeys, linked: 0, errors };\n }\n }\n\n let linked = 0;\n for (const [key, file] of filesToRemove) {\n try {\n if (file.exists) {\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([key], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n }\n linked++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n // The manifest link is safely on disk; retain the resource outside the\n // shadow even if an operating-system lock delays local cleanup.\n }\n }\n\n return { managedKeys, linked, errors };\n }\n\n private isSafeThemeFile(key: string, file: ThemeFile): boolean {\n return (\n !key.includes(\"\\0\") &&\n !key.split(/[\\\\/]/).includes(\"..\") &&\n (file.absolutePath === this.themeRoot.root ||\n file.absolutePath.startsWith(this.themeRoot.root + sep))\n );\n }\n\n private managedAssetNeedsRefresh(\n resource: RemoteResource,\n link: ThemeAssetLink,\n ): boolean {\n if (!isManagedAssetResource(resource)) return true;\n if (link.url !== undefined) return resource.url !== link.url;\n if (link.checksum !== undefined) return resource.checksum !== link.checksum;\n return true;\n }\n\n private ensureManagedAssetsAreResolved(): void {\n const unresolved = this.assetManifest\n .entries()\n .filter(\n ([key]) =>\n !this.themeRoot.ignore.ignore(key) &&\n !this.themeRoot.file(key).exists,\n )\n .map(([key]) => key)\n .filter((key) => !isManagedAssetResource(this.remoteResources.get(key)));\n if (unresolved.length > 0) {\n throw new Error(\n `Managed asset(s) could not be linked: ${unresolved.join(\", \")}`,\n );\n }\n }\n\n // ─── Full Upload ──────────────────────────────────────────────────────────\n\n async uploadTheme(\n opts: {\n delete?: boolean;\n validate?: boolean;\n linkManagedAssets?: { replace?: boolean };\n pendingBinaryAssets?: boolean;\n onProgress?: (done: number, total: number) => void;\n // The SHA `.fluid-theme.json` stored on the last pull. Sent to the\n // server on every PUT/DELETE so a stale local aborts the push\n // instead of clobbering. Null / undefined skips the check entirely\n // (force-push and pre-Phase-003 servers).\n baseSha?: string | null;\n /** The caller already ran the stale-base preflight. */\n skipPreflight?: boolean;\n /** Last acknowledged dev resource index, validated by `baseSha`. */\n remoteState?: DevRemoteState;\n } = {},\n ): Promise<SyncResult> {\n const localFiles = this.themeRoot.files();\n assertNoCaseCollisions(localFiles.map((file) => file.relativePath));\n const result: SyncResult = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: 0,\n errors: [],\n validationFailed: false,\n };\n\n // Schema validation pass\n if (opts.validate) {\n for (const file of localFiles) {\n if (!file.isLiquid) continue;\n const diagnostics = file.validateSchema();\n const errors = diagnostics.filter((d) => d.severity === \"error\");\n for (const d of errors) {\n result.errors.push(`${file.relativePath}: ${d.message}`);\n }\n }\n if (result.errors.length > 0) {\n result.validationFailed = true;\n return result;\n }\n }\n\n if (opts.remoteState) {\n this.useDevRemoteState(opts.remoteState);\n } else {\n await this.fetchChecksums();\n }\n\n // Preflight before any state change so a stale local aborts with\n // zero writes. On 409, `PushConflictError` propagates to the CLI\n // and the per-file loop below never runs.\n if (!opts.skipPreflight) await this.preflightPush(opts.baseSha);\n\n // Roll the base_sha forward as the server bumps its version on\n // each successful write. Start from the caller's stored SHA (from\n // pull) and update from every response — the next PUT carries the\n // freshest SHA the server has given us so a concurrent third-party\n // write mid-loop 409s cleanly instead of the CLI overwriting.\n let baseSha = opts.baseSha ?? null;\n\n const localPaths = new Set(localFiles.map((file) => file.relativePath));\n if (opts.delete) {\n for (const key of this.assetManifest.keys()) localPaths.add(key);\n }\n const toDelete = opts.delete\n ? this.remoteKeys().filter(\n (key) =>\n key !== MEMBER_CONFIGURATION_KEY &&\n this.canDeleteRemoteResource(key) &&\n !localPaths.has(key) &&\n !this.themeRoot.ignore.ignore(key),\n )\n : [];\n\n const plannedUploads = planThemeUploads(\n this.themeRoot,\n localFiles.filter((f) => f.exists && this.hasChanged(f)),\n toDelete,\n );\n\n if (opts.linkManagedAssets) {\n result.linked = await this.linkManagedAssets(opts.linkManagedAssets);\n // FileResource writes can change the server's content version. The list\n // after reference creation gives the next normal resource write a fresh\n // base when the server supports Phase 003a.\n baseSha = this.lastKnownRemoteSha ?? baseSha;\n } else if (this.assetManifestInstance) {\n this.assetManifest.reload();\n this.ensureManagedAssetsAreResolved();\n }\n\n // Linking managed assets can acknowledge or externalize local binaries.\n // Recheck ordinary files after that step before attempting an upload.\n const toUpload = plannedUploads.filter(\n ({ file, memberConfiguration }) =>\n memberConfiguration || (file.exists && this.hasChanged(file)),\n );\n const uploadCount = toUpload.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n );\n let done = 0;\n for (const upload of toUpload) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n result.errors.length > 0\n ) {\n result.errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await this.uploadMemberConfiguration(upload, baseSha);\n } else {\n await this.uploadFile(file, baseSha, {\n pendingAsset: opts.pendingBinaryAssets,\n });\n }\n // `putResource` updates `lastKnownRemoteSha` from the server's\n // response. Thread it forward so the next iteration carries\n // the current SHA — this is how mid-push races surface.\n baseSha = this.lastKnownRemoteSha;\n result.uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n done += 1 + (upload.memberConfiguration?.files.length ?? 0);\n opts.onProgress?.(done, uploadCount);\n }\n\n if (opts.delete && result.errors.length === 0) {\n for (const key of toDelete) {\n try {\n await this.deleteRemoteFile(key, baseSha);\n baseSha = this.lastKnownRemoteSha;\n result.deleted++;\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n }\n }\n\n return result;\n }\n\n // ─── Full Download ────────────────────────────────────────────────────────\n\n async downloadTheme(\n opts: {\n delete?: boolean;\n skip?: Set<string>;\n onProgress?: (done: number, total: number) => void;\n } = {},\n ): Promise<SyncResult & { skipped: number }> {\n const resources = await this.downloadAll();\n const externalizedAssets = await this.externalizePulledAssets(resources, {\n delete: opts.delete ?? false,\n });\n const result: SyncResult & { skipped: number } = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: externalizedAssets.linked,\n skipped: 0,\n errors: [...externalizedAssets.errors],\n validationFailed: false,\n };\n\n let done = 0;\n for (const resource of resources) {\n if (externalizedAssets.managedKeys.has(resource.key)) {\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n if (opts.skip?.has(resource.key)) {\n result.skipped++;\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n const file = this.themeRoot.file(resource.key);\n\n // Guard against path traversal from malicious API responses\n if (!this.isSafeThemeFile(resource.key, file)) {\n result.errors.push(`Download ${resource.key}: path traversal detected`);\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n try {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n const buf = await this.downloadBinaryAsset(resource.url);\n file.write(buf);\n } else if (\n resource.content !== undefined &&\n resource.content !== null\n ) {\n const content =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n file.write(content);\n }\n result.downloaded++;\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${formatError(e)}`);\n }\n opts.onProgress?.(++done, resources.length);\n }\n\n if (opts.delete) {\n const remoteKeys = new Set(resources.map((r) => r.key));\n for (const file of this.themeRoot.files()) {\n if (remoteKeys.has(file.relativePath)) continue;\n // Preserve local stylesheets that the backend hides under\n // STYLESHEET_STRICT_INPUT — their absence from the resources\n // index is intentional (theme-level styles.css /\n // global_styles.css and per-template composite\n // {type}/{name}/styles.css), not a signal that the merchant\n // deleted them.\n if (isStylesheetKey(file.relativePath)) continue;\n\n try {\n unlinkSync(file.absolutePath);\n result.deleted++;\n } catch {\n // ignore\n }\n }\n }\n\n return result;\n }\n}\n\nfunction isLinkableBinaryResource(\n resource: RemoteResource,\n key: string,\n file: ThemeFile,\n): boolean {\n return (\n isThemeAssetKey(key) && isManagedAssetResource(resource) && !file.isText\n );\n}\n\nfunction isNestedBinaryThemeAsset(file: ThemeFile): boolean {\n return (\n !file.isText &&\n file.relativePath.startsWith(\"assets/\") &&\n !isThemeAssetKey(file.relativePath)\n );\n}\n\nfunction isManagedAssetResource(\n resource: RemoteResource | undefined,\n): resource is RemoteResource {\n return (\n resource?.resource_type === \"FileResource\" &&\n typeof resource.url === \"string\" &&\n resource.url.length > 0\n );\n}\n\nfunction remoteResourceFromState(state: RemoteResourceState): RemoteResource {\n return {\n key: state.key,\n checksum: state.checksum,\n content: state.contentPresent ? \"\" : null,\n resource_type: state.resourceType,\n resource_id: state.resourceId,\n url: state.url,\n };\n}\n\nfunction remoteResourceState(resource: RemoteResource): RemoteResourceState {\n return {\n key: resource.key,\n checksum: resource.checksum,\n contentPresent: resource.content != null,\n resourceType: resource.resource_type,\n resourceId: resource.resource_id,\n url: resource.url,\n };\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return isApiError(error) && error.status === 404;\n}\n\nfunction resourceLink(\n resource: RemoteResource,\n): Pick<ThemeAssetLink, \"checksum\" | \"url\"> {\n return {\n ...(typeof resource.checksum === \"string\" && resource.checksum.length > 0\n ? { checksum: resource.checksum }\n : {}),\n ...(typeof resource.url === \"string\" && resource.url.length > 0\n ? { url: resource.url }\n : {}),\n };\n}\n\nfunction assetFilename(key: string): string {\n return key.slice(\"assets/\".length);\n}\n\nfunction assetMetadataFromLink(\n link: ThemeAssetLink,\n): RemoteAssetMetadata | undefined {\n if (\n typeof link.url !== \"string\" ||\n link.url.length === 0 ||\n typeof link.contentType !== \"string\" ||\n link.contentType.length === 0 ||\n typeof link.contentSize !== \"number\" ||\n !Number.isInteger(link.contentSize) ||\n link.contentSize <= 0\n ) {\n return undefined;\n }\n\n return {\n url: link.url,\n contentType: link.contentType,\n contentSize: link.contentSize,\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n };\n}\n\nfunction uploadedAssetMetadata(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n): RemoteAssetMetadata {\n if (resource.assetMetadata) return resource.assetMetadata;\n if (!resource.url) {\n throw new Error(`Uploaded asset has no URL: ${file.relativePath}`);\n }\n\n return {\n url: resource.url,\n contentType: file.mime.name,\n contentSize: file.size(),\n altText: file.name,\n ...(typeof resource.damAssetCode === \"string\"\n ? { handle: resource.damAssetCode }\n : {}),\n };\n}\n\nfunction parseThemeAssetMetadata(\n value: unknown,\n): { filename: string; metadata: RemoteAssetMetadata } | undefined {\n if (!isRecord(value)) return undefined;\n\n const filename = nonEmptyString(value[\"filename\"]);\n const url = nonEmptyString(value[\"url\"]);\n const contentType = nonEmptyString(value[\"content_type\"]);\n const contentSize = positiveInteger(value[\"content_size\"]);\n if (!filename || !url || !contentType || !contentSize) return undefined;\n\n const previewImageUrl = nonEmptyString(value[\"preview_image_url\"]);\n const altText = optionalString(value[\"alt_text\"]);\n const handle = nonEmptyString(value[\"handle\"]);\n return {\n filename,\n metadata: {\n url,\n contentType,\n contentSize,\n ...(previewImageUrl ? { previewImageUrl } : {}),\n ...(altText !== undefined ? { altText } : {}),\n ...(handle ? { handle } : {}),\n },\n };\n}\n\nfunction createdFileResourceId(value: unknown): number | undefined {\n if (!isRecord(value) || !isRecord(value[\"file_resource\"])) {\n return undefined;\n }\n return positiveInteger(value[\"file_resource\"][\"id\"]);\n}\n\nfunction positiveInteger(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isInteger(value) && value > 0) {\n return value;\n }\n if (typeof value !== \"string\" || !/^\\d+$/.test(value)) return undefined;\n\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * Heuristic-only check for obviously unbalanced liquid delimiters\n * (`{% %}` and `{{ }}`). This is NOT a liquid parser — it only tracks\n * opening delimiters until they are closed. It exists to give watch-mode users a signal\n * when a save is liquid-syntax-broken: the server accepts\n * syntax-broken liquid silently on upload, and the storefront\n * renderer then serves stale content for that section with no error\n * anywhere else in the pipeline.\n *\n * Closing-looking tokens without a preceding Liquid opener are intentionally\n * ignored. Liquid files commonly contain CSS such as `width:100%}` or adjacent\n * block braces (`}}`), so treating every close token as Liquid creates noisy\n * false warnings on valid theme files.\n *\n * Known false positive: delimiters written literally inside a\n * `{% raw %}...{% endraw %}` block are still counted and can trip\n * this check even though the liquid is valid. Acceptable for a\n * warn-only heuristic — a real fix requires parsing liquid, which is\n * out of scope here (see the server-side validation note in the PR).\n */\nexport function hasUnbalancedLiquidDelimiters(content: string): boolean {\n let unclosedTags = 0;\n let unclosedOutputs = 0;\n\n for (const token of content.matchAll(/\\{%|%\\}|\\{\\{|\\}\\}/g)) {\n switch (token[0]) {\n case \"{%\":\n unclosedTags += 1;\n break;\n case \"%}\":\n if (unclosedTags > 0) unclosedTags -= 1;\n break;\n case \"{{\":\n unclosedOutputs += 1;\n break;\n case \"}}\":\n if (unclosedOutputs > 0) unclosedOutputs -= 1;\n break;\n }\n }\n\n return unclosedTags > 0 || unclosedOutputs > 0;\n}\n\nexport interface LiquidBlockTagDiagnostic {\n severity: \"error\";\n message: string;\n}\n\nconst BLOCK_TAGS = new Map([\n [\"capture\", \"endcapture\"],\n [\"case\", \"endcase\"],\n [\"comment\", \"endcomment\"],\n [\"for\", \"endfor\"],\n [\"form\", \"endform\"],\n [\"if\", \"endif\"],\n [\"ifchanged\", \"endifchanged\"],\n [\"javascript\", \"endjavascript\"],\n [\"paginate\", \"endpaginate\"],\n [\"raw\", \"endraw\"],\n [\"schema\", \"endschema\"],\n [\"style\", \"endstyle\"],\n [\"stylesheet\", \"endstylesheet\"],\n [\"tablerow\", \"endtablerow\"],\n [\"unless\", \"endunless\"],\n]);\n\nconst CLOSING_TAGS = new Set(BLOCK_TAGS.values());\nconst OPAQUE_BLOCK_TAGS = new Set([\n \"comment\",\n \"javascript\",\n \"raw\",\n \"schema\",\n \"style\",\n \"stylesheet\",\n]);\n\ninterface OpenBlock {\n name: string;\n expectedClose: string;\n line: number;\n}\n\n/**\n * Find structurally unbalanced Liquid block tags such as an `{% if %}` with\n * no `{% endif %}`. This intentionally recognizes only established paired\n * tags; custom and inline tags are ignored rather than guessed at.\n *\n * Content inside raw/comment/schema/style/javascript blocks is opaque to\n * Liquid and therefore skipped until that block's matching close tag. This\n * prevents CSS, JSON, and examples containing Liquid-looking text from\n * producing false errors.\n */\nexport function findLiquidBlockTagDiagnostics(\n content: string,\n): LiquidBlockTagDiagnostic[] {\n const stack: OpenBlock[] = [];\n const diagnostics: LiquidBlockTagDiagnostic[] = [];\n let line = 1;\n let previousTagIndex = 0;\n\n const processTag = (name: string, tagLine: number): void => {\n const open = stack.at(-1);\n\n if (open && OPAQUE_BLOCK_TAGS.has(open.name)) {\n if (name === open.expectedClose) stack.pop();\n return;\n }\n\n const expectedClose = BLOCK_TAGS.get(name);\n if (expectedClose) {\n stack.push({ name, expectedClose, line: tagLine });\n return;\n }\n\n if (!CLOSING_TAGS.has(name)) return;\n\n if (!open) {\n diagnostics.push({\n severity: \"error\",\n message: `Unexpected Liquid tag '{% ${name} %}' on line ${tagLine}; there is no open block to close.`,\n });\n return;\n }\n\n if (name !== open.expectedClose) {\n diagnostics.push({\n severity: \"error\",\n message: `Mismatched Liquid tag '{% ${name} %}' on line ${tagLine}; '{% ${open.name} %}' from line ${open.line} must close with '{% ${open.expectedClose} %}'.`,\n });\n return;\n }\n\n stack.pop();\n };\n\n for (const match of content.matchAll(\n /\\{%-?\\s*([a-zA-Z_][\\w-]*)\\b(?:(?!\\{%)[\\s\\S])*?-?%\\}/g,\n )) {\n const name = match[1]?.toLowerCase();\n if (!name) continue;\n\n const index = match.index ?? 0;\n // Count each character at most once across the scan. Re-slicing from the\n // start for every tag turns large generated templates into O(n²) work.\n for (let cursor = previousTagIndex; cursor < index; cursor++) {\n if (content.charCodeAt(cursor) === 10) line += 1;\n }\n previousTagIndex = index;\n\n const open = stack.at(-1);\n if (name === \"liquid\" && !(open && OPAQUE_BLOCK_TAGS.has(open.name))) {\n // `{% liquid %}` places one delimiter-free statement on each line. Feed\n // those statements through the same stack so this supported syntax\n // cannot bypass lint, push, or watch-mode validation.\n const statements = match[0]\n .replace(/^\\{%-?\\s*liquid\\b/i, \"\")\n .replace(/-?%\\}$/, \"\")\n .split(\"\\n\");\n for (const [offset, statement] of statements.entries()) {\n const statementName = /^\\s*([a-zA-Z_][\\w-]*)\\b/.exec(statement)?.[1];\n if (!statementName) continue;\n processTag(statementName.toLowerCase(), line + offset);\n }\n } else {\n processTag(name, line);\n }\n }\n\n for (const open of stack.reverse()) {\n diagnostics.push({\n severity: \"error\",\n message: `Unclosed Liquid tag '{% ${open.name} %}' on line ${open.line}; expected '{% ${open.expectedClose} %}'.`,\n });\n }\n\n return diagnostics;\n}\n","import net from \"node:net\";\n\n/**\n * The dev command does real work before it ever binds a port: it resolves\n * (or creates) a server-side dev theme and runs a full initial sync, which\n * can take minutes on a large theme. If the requested port is already taken\n * — most commonly by another `fluid theme dev` or the Mist Desktop preview,\n * which both default to 9292 — all of that work is wasted and the process\n * used to die with a raw `EADDRINUSE` stack trace. Call this before any of\n * that work starts so we fail fast with a clear message instead.\n */\nexport class PortInUseError extends Error {\n constructor(\n public readonly host: string,\n public readonly port: number,\n ) {\n super(formatPortConflictMessage(host, port));\n this.name = \"PortInUseError\";\n }\n}\n\nexport function formatPortConflictMessage(host: string, port: number): string {\n return (\n `Port ${port} on ${host} is already in use — likely another ` +\n \"`fluid theme dev` or the Mist Desktop preview. Stop the other \" +\n \"server or pass --port <number>.\"\n );\n}\n\n/**\n * Attempt to bind `host:port`, then immediately release it. Resolves if the\n * port is free; rejects with `PortInUseError` on `EADDRINUSE`/`EACCES`, or\n * the raw error for anything else unexpected.\n */\nexport function checkPortAvailable(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n reject(new PortInUseError(host, port));\n } else {\n reject(err);\n }\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve());\n });\n\n server.listen(port, host);\n });\n}\n","import http from \"node:http\";\nimport { MemberPreview, isLocalPreviewRequest } from \"./member-preview.js\";\nimport { SSEStream } from \"./sse.js\";\nimport { proxyRequest } from \"./proxy.js\";\nimport { watchTheme } from \"./watcher.js\";\nimport { PushConflictError, Syncer } from \"../syncer.js\";\nimport {\n findLiquidBlockTagDiagnostics,\n hasUnbalancedLiquidDelimiters,\n} from \"../liquid-delimiters.js\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ApiClient } from \"../../api.js\";\nimport { formatPortConflictMessage } from \"./port-preflight.js\";\nimport { assertNoCaseCollisions } from \"../case-collisions.js\";\nimport { isStylesheetKey } from \"../stylesheet-keys.js\";\nimport { ThemeAssetManifest } from \"../asset-manifest.js\";\nimport type { DevRemoteState } from \"../dev-remote-baseline.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n} from \"../member-configuration.js\";\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString(\"en-US\", { hour12: false });\n}\n\nexport interface DevServerOptions {\n host: string;\n port: number;\n reloadMode: \"full-page\" | \"off\";\n /** Cached dev resource index paired with its acknowledged remote version. */\n initialSync?: DevRemoteState;\n /** Persist the complete dev resource baseline and its version atomically. */\n onRemoteState?: (state: DevRemoteState) => void;\n /** Discard persisted trust after the server rejects a watched write. */\n onRemoteStateInvalidated?: () => void;\n}\n\nexport interface DevServerTheme {\n id: number;\n name: string;\n company: string;\n editorUrl?: string;\n}\n\nexport async function startDevServer(\n api: ApiClient,\n theme: DevServerTheme,\n themeRoot: ThemeRoot,\n opts: DevServerOptions & { validate?: boolean },\n onReady?: (address: string) => void,\n): Promise<() => void> {\n const sse = new SSEStream();\n const memberPreview = new MemberPreview(api, theme, opts.reloadMode);\n const syncer = new Syncer(api, theme.id, themeRoot);\n let remoteStateSafe = true;\n const invalidateRemoteState = () => {\n if (!remoteStateSafe) return;\n remoteStateSafe = false;\n try {\n opts.onRemoteStateInvalidated?.();\n } catch {\n // The stale version will fail preflight on the next startup.\n }\n };\n const recordRemoteState = () => {\n if (!remoteStateSafe) return;\n if (opts.onRemoteState) {\n const state = syncer.devRemoteState(\n new ThemeAssetManifest(themeRoot.root).fingerprint(),\n );\n if (state) {\n try {\n opts.onRemoteState(state);\n } catch {\n invalidateRemoteState();\n }\n }\n }\n };\n\n const pendingUpdates = new Set<string>();\n\n // ── Initial sync ─────────────────────────────────────────────────────────\n console.log(`\\nSyncing theme ${theme.name} (#${theme.id})…`);\n const progress = (done: number, total: number) => {\n process.stdout.write(`\\r Uploading ${done}/${total} files…`);\n };\n const uploadFromRemoteIndex = () =>\n syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n onProgress: progress,\n });\n let syncResult;\n if (opts.initialSync) {\n let remoteStateIsTrusted = false;\n try {\n syncer.useDevRemoteState(opts.initialSync);\n await syncer.preflightPush(opts.initialSync.remoteSha);\n remoteStateIsTrusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n\n syncResult = remoteStateIsTrusted\n ? await syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n baseSha: syncer.remoteSha(),\n remoteState: opts.initialSync,\n skipPreflight: true,\n onProgress: progress,\n })\n : await uploadFromRemoteIndex();\n } else {\n syncResult = await uploadFromRemoteIndex();\n }\n process.stdout.write(\"\\n\");\n if (syncResult.linked > 0) {\n console.log(` Saved ${syncResult.linked} remote asset reference(s).`);\n }\n if (syncResult.validationFailed) {\n console.error(\n `\\nSchema validation failed (${syncResult.errors.length} error(s)). Use --force to skip.\\n`,\n );\n for (const e of syncResult.errors) console.error(` ${e}`);\n process.exit(1);\n } else if (syncResult.errors.length > 0) {\n invalidateRemoteState();\n for (const e of syncResult.errors) console.error(` ${e}`);\n if (syncResult.uploaded + syncResult.deleted === 0) process.exit(1);\n }\n if (syncResult.errors.length === 0) {\n recordRemoteState();\n }\n\n // ── File watcher ─────────────────────────────────────────────────────────\n //\n // Uploading a file no longer commits anything — Fluid only records that the\n // theme changed, and something has to ask for the commit. `theme push` asks\n // once when its loop ends; the watcher has no such moment, since chokidar\n // reports one event per file and nothing in it says whether this is a lone\n // save or the 200th write of a restore.\n //\n // So ask once the writes stop — but decide where \"the writes\" end by when\n // events ARRIVED, not by when their handlers ran. Handlers are serialized\n // behind awaited uploads, so a genuinely separate edit can queue behind a\n // slow one and start immediately after it; timing the handlers would fold\n // the two into a single commit and erase the boundary between them.\n //\n // An event that arrived within the window continues the operation already\n // in flight, so its pending ask is cancelled and rescheduled. One that\n // arrived after a real pause ends that operation, so the ask goes out NOW\n // rather than being cancelled — otherwise the earlier edit would be\n // swallowed into this one's commit.\n //\n // Asks are also chained against the uploads. A fire-and-forget request can\n // overlap the next batch, committing a half-written operation or leaving\n // the rest of it pending; awaiting the outstanding one before uploading\n // keeps each commit to a settled state.\n //\n // If the server stops with an ask still pending, Fluid's sweeper picks the\n // change up; the cost is a delay, never a lost commit.\n const SYNC_IDLE_MS = 2_000;\n let lastArrivedAt = 0;\n let pendingSync: ReturnType<typeof setTimeout> | null = null;\n let syncInFlight: Promise<void> = Promise.resolve();\n\n // Tracks an ask Fluid did not take. Until it lands the previous edit is\n // uncommitted, and uploading the next one over it merges the two into\n // whichever commit eventually arrives — so the boundary is retried before\n // any further writes go out. A retry that also fails has done what a client\n // can: the content is safe on the server and the sweeper commits it, but\n // the two edits will share a version.\n let askOwed = false;\n let remoteWritesBlocked = false;\n const blockRemoteWrites = (error: PushConflictError): void => {\n if (remoteWritesBlocked) return;\n remoteWritesBlocked = true;\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Remote theme changed outside this dev session (${error.message}). Restart theme dev to compare the current remote state before writing again.`,\n );\n };\n const sendSync = (): void => {\n syncInFlight = syncInFlight.then(async () => {\n const accepted = await syncer.requestSync();\n askOwed = !accepted;\n if (accepted) recordRemoteState();\n });\n };\n const flushSyncNow = (): void => {\n if (!pendingSync) return;\n clearTimeout(pendingSync);\n pendingSync = null;\n sendSync();\n };\n const scheduleSync = (): void => {\n if (pendingSync) clearTimeout(pendingSync);\n pendingSync = setTimeout(() => {\n pendingSync = null;\n sendSync();\n }, SYNC_IDLE_MS);\n };\n\n const stopWatcher = watchTheme(\n themeRoot,\n async (modified, added, removed, arrivedAt) => {\n if (arrivedAt - lastArrivedAt > SYNC_IDLE_MS) {\n // A real pause: whatever was pending belongs to the previous edit.\n flushSyncNow();\n } else if (pendingSync) {\n // Same operation still arriving — hold the ask.\n clearTimeout(pendingSync);\n pendingSync = null;\n }\n lastArrivedAt = arrivedAt;\n // Never upload across an outstanding ask, or it commits a half-written\n // operation.\n await syncInFlight;\n // An ask Fluid refused still owes the edit before this one its own\n // commit. Retry before writing over it.\n if (askOwed) {\n sendSync();\n await syncInFlight;\n }\n if (remoteWritesBlocked) return;\n\n let uploads: ThemeUpload[];\n try {\n assertNoCaseCollisions(\n themeRoot.files().map((file) => file.relativePath),\n );\n assertMemberConfigurationRetained(\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n uploads = planThemeUploads(\n themeRoot,\n [...modified, ...added],\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n } catch (error) {\n console.error(`\\n[Watcher] Sync blocked: ${String(error)}`);\n return;\n }\n\n let wroteRemote = false;\n const memberTransfer = uploads.some(\n (upload) =>\n upload.memberConfiguration ||\n upload.file.relativePath === \"members_sites.json\",\n );\n\n for (const upload of uploads) {\n const { file } = upload;\n const uploadFiles = [\n file,\n ...(upload.memberConfiguration?.files ?? []),\n ];\n // Validate schema on liquid files during dev (warn, don't block)\n if (opts.validate) {\n for (const candidate of uploadFiles) {\n if (!candidate.isLiquid) continue;\n for (const diagnostic of candidate.validateSchema()) {\n const prefix =\n diagnostic.severity === \"error\"\n ? \"Schema error\"\n : \"Schema warning\";\n console.warn(\n `\\n[${prefix}] ${candidate.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n\n for (const pendingFile of uploadFiles)\n pendingUpdates.add(pendingFile.relativePath);\n try {\n // uploadFile() resolves with the exact bytes it sent, so the\n // diagnostic below always describes the uploaded content —\n // never a re-read of a file an editor may have changed,\n // replaced, or deleted while the request was in flight.\n const uploadedFiles = upload.memberConfiguration\n ? await syncer.uploadMemberConfiguration(upload, syncer.remoteSha())\n : [\n {\n file,\n content: await syncer.uploadFile(file, syncer.remoteSha(), {\n pendingAsset: true,\n }),\n },\n ];\n wroteRemote = true;\n for (const {\n file: uploadedFile,\n content: uploadedContent,\n } of uploadedFiles) {\n console.log(\n ` ✓ synced ${uploadedFile.relativePath} (${timestamp()})`,\n );\n // \"synced\" is true of the upload and false of the page. The\n // storefront inlines a stylesheet from a Themes::FileResource that a\n // background job rewrites after the publish commits, so\n // `inline_asset_content` keeps emitting the previous bytes for a\n // moment after the server accepts this write. Say so, or the ✓ reads\n // as \"the rendered CSS is up to date\" (CURRENT-3899).\n if (isStylesheetKey(uploadedFile.relativePath)) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: the storefront inlines this CSS from a published asset, so it serves the previous bytes until the stylesheet asset sync finishes`,\n );\n }\n // Cheap warn-only heuristic: the server accepts syntax-broken\n // liquid silently, and the storefront then serves stale\n // content for the section with no error anywhere. This does\n // not parse liquid — it can false-positive on delimiters\n // written literally inside {% raw %} — but it's the only\n // client-side signal a save is likely broken.\n if (\n uploadedFile.isLiquid &&\n uploadedContent !== null &&\n hasUnbalancedLiquidDelimiters(uploadedContent)\n ) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: unbalanced liquid delimiters — the storefront may silently serve stale content for this section`,\n );\n }\n if (uploadedFile.isLiquid && uploadedContent !== null) {\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n uploadedContent,\n )) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n } catch (e) {\n if (e instanceof PushConflictError) {\n blockRemoteWrites(e);\n break;\n }\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Upload failed: ${file.relativePath}: ${e}`,\n );\n // Keep safe first-write failures retryable, but never finalize a partial batch.\n remoteWritesBlocked = wroteRemote || memberTransfer;\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n } finally {\n for (const pendingFile of uploadFiles)\n pendingUpdates.delete(pendingFile.relativePath);\n }\n }\n\n if (remoteWritesBlocked) return;\n\n for (const file of removed) {\n if (themeRoot.ignore.ignore(file.relativePath)) continue;\n try {\n await syncer.deleteRemoteFile(file.relativePath, syncer.remoteSha());\n wroteRemote = true;\n console.log(` ✓ removed ${file.relativePath}`);\n } catch (error) {\n if (error instanceof PushConflictError) {\n blockRemoteWrites(error);\n break;\n }\n invalidateRemoteState();\n remoteWritesBlocked = wroteRemote || memberTransfer;\n console.error(\n `[Watcher] Delete failed: ${file.relativePath}: ${error}`,\n );\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n }\n }\n\n if (remoteWritesBlocked) return;\n\n if (wroteRemote) {\n recordRemoteState();\n try {\n await memberPreview.refreshAfterSync();\n } catch {\n console.error(\n \"[Member preview] Could not refresh routes after sync. The next member request will retry.\",\n );\n }\n }\n\n if (removed.length > 0) {\n sse.broadcast(JSON.stringify({ reload_page: true }));\n } else if (uploads.length > 0) {\n sse.broadcast(\n JSON.stringify({\n modified: uploads.flatMap(({ file, memberConfiguration }) =>\n [file, ...(memberConfiguration?.files ?? [])].map(\n (updatedFile) => updatedFile.relativePath,\n ),\n ),\n }),\n );\n }\n\n scheduleSync();\n },\n );\n\n // ── HTTP server ───────────────────────────────────────────────────────────\n const server = http.createServer(async (req, res) => {\n if (!isLocalPreviewRequest(req, opts.host)) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Local preview requests only.\");\n return;\n }\n if (await memberPreview.handle(req, res)) return;\n if (req.url === \"/hot-reload\") {\n sse.add(res);\n return;\n }\n\n try {\n await proxyRequest(req, res, {\n company: theme.company,\n themeId: theme.id,\n reloadMode: opts.reloadMode,\n pendingFiles: () =>\n [...pendingUpdates]\n .map((p) => themeRoot.file(p))\n .filter((f) => f.isText)\n .map((f) => ({\n relativePath: f.relativePath,\n read: () => f.read(),\n })),\n });\n } catch (e) {\n console.error(`[Proxy] ${req.method} ${req.url} → ${e}`);\n if (!res.headersSent) {\n // Surface the real upstream failure instead of a bare 502.\n // Every local render round-trips through <company>.fluid.app,\n // so this error is usually environmental — a TLS-inspecting\n // security agent whose root CA the browser trusts via the OS\n // keychain but Node does not, DNS, or a dropped connection —\n // and a naked \"Bad Gateway\" sends humans and QA agents off\n // chasing the theme instead of the network. Diagnosed live:\n // four workflow page steps went needs-review over one masked\n // cert error.\n const message = e instanceof Error ? e.message : String(e);\n res.writeHead(502, { \"content-type\": \"text/plain; charset=utf-8\" });\n res.end(\n `Bad Gateway — the local preview could not reach ${theme.company}.fluid.app: ${message}\\n` +\n \"This is the dev machine's network path to Fluid, not the theme. \" +\n \"Common causes: TLS-inspecting security software (its root CA is in the OS keychain, which Node does not read — set NODE_EXTRA_CA_CERTS to its certificate), DNS, or a proxy. \" +\n \"The same error is logged by the theme dev server process.\",\n );\n }\n }\n });\n\n // The preflight check in `dev.ts` runs before the (potentially minutes-long)\n // initial sync above, but the port could still be grabbed by another\n // process in the window between that check and this `listen()` call.\n // Handle that race the same way: a friendly message and a clean exit\n // instead of an unhandled `EADDRINUSE`/`EACCES` stack trace.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n console.error(formatPortConflictMessage(opts.host, opts.port));\n process.exit(1);\n }\n reject(err);\n });\n server.listen(opts.port, opts.host, () => resolve());\n });\n\n const address = `http://${opts.host}:${opts.port}`;\n onReady?.(address);\n\n // ── Teardown ──────────────────────────────────────────────────────────────\n return function stop() {\n sse.close();\n stopWatcher();\n server.close();\n };\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\n\nconst BASELINE_VERSION = 1;\nconst BASELINE_FILE = join(\".fluid-theme\", \"dev-baseline.json\");\n\nexport interface RemoteResourceState {\n key: string;\n checksum: string | null;\n contentPresent: boolean;\n resourceType?: string | null;\n resourceId?: number | null;\n url?: string | null;\n}\n\nexport interface DevRemoteState {\n themeId: number;\n remoteSha: string;\n assetManifestSha: string;\n resources: RemoteResourceState[];\n}\n\nexport function readDevRemoteBaseline(\n themeRoot: string,\n themeId: number,\n assetManifestSha: string,\n): DevRemoteState | null {\n try {\n const parsed = JSON.parse(\n readFileSync(join(themeRoot, BASELINE_FILE), \"utf-8\"),\n );\n const state = parseDocument(parsed);\n if (state.themeId !== themeId) return null;\n if (state.assetManifestSha !== assetManifestSha) return null;\n return state;\n } catch {\n return null;\n }\n}\n\nexport function writeDevRemoteBaseline(\n themeRoot: string,\n state: DevRemoteState,\n): void {\n const document = { version: BASELINE_VERSION, ...state };\n parseDocument(document);\n\n const path = join(themeRoot, BASELINE_FILE);\n const tempPath = `${path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(tempPath, `${JSON.stringify(document)}\\n`, {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, path);\n } catch (error) {\n rmSync(tempPath, { force: true });\n throw error;\n }\n}\n\nexport function removeDevRemoteBaseline(themeRoot: string): void {\n rmSync(join(themeRoot, BASELINE_FILE), { force: true });\n}\n\nexport async function devRemoteStateFromSourceShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n themeId: number,\n remoteSha: string,\n): Promise<DevRemoteState | null> {\n try {\n if (!(await shadow.hasHead())) return null;\n\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const resources: RemoteResourceState[] = [];\n for (const key of await shadow.headPaths()) {\n const content = await shadow.blobAtHead(key);\n if (!content) return null;\n const asset = assetManifest.get(key);\n if (asset) {\n if (\n asset.pending ||\n !asset.url ||\n !content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL))\n ) {\n return null;\n }\n resources.push({\n key,\n checksum: asset.checksum ?? null,\n contentPresent: false,\n resourceType: \"FileResource\",\n url: asset.url,\n });\n continue;\n }\n\n if (content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL)))\n return null;\n resources.push({\n key,\n checksum: createHash(\"sha256\").update(content).digest(\"hex\"),\n contentPresent: true,\n });\n }\n\n return {\n themeId,\n remoteSha,\n assetManifestSha: assetManifest.fingerprint(),\n resources,\n };\n } catch {\n return null;\n }\n}\n\nfunction parseDocument(document: unknown): DevRemoteState {\n if (!isRecord(document) || document[\"version\"] !== BASELINE_VERSION) {\n throw new Error(`expected version ${BASELINE_VERSION}`);\n }\n\n const themeId = document[\"themeId\"];\n const remoteSha = document[\"remoteSha\"];\n const assetManifestSha = document[\"assetManifestSha\"];\n const rawResources = document[\"resources\"];\n if (!isPositiveInteger(themeId)) throw new Error(\"invalid theme id\");\n if (!isNonEmptyString(remoteSha)) throw new Error(\"invalid remote sha\");\n if (!isNonEmptyString(assetManifestSha)) {\n throw new Error(\"invalid asset manifest sha\");\n }\n if (!Array.isArray(rawResources)) throw new Error(\"invalid resources\");\n\n const resources = rawResources.map(parseResource);\n assertNoCaseCollisions(resources.map((resource) => resource.key));\n return { themeId, remoteSha, assetManifestSha, resources };\n}\n\nfunction parseResource(resource: unknown): RemoteResourceState {\n if (!isRecord(resource)) throw new Error(\"invalid resource\");\n\n const key = resource[\"key\"];\n const checksum = resource[\"checksum\"];\n const contentPresent = resource[\"contentPresent\"];\n const resourceType = resource[\"resourceType\"];\n const resourceId = resource[\"resourceId\"];\n const url = resource[\"url\"];\n if (!isNonEmptyString(key) || key.includes(\"\\0\")) {\n throw new Error(\"invalid resource key\");\n }\n if (checksum !== null && !isNonEmptyString(checksum)) {\n throw new Error(\"invalid resource checksum\");\n }\n if (typeof contentPresent !== \"boolean\") {\n throw new Error(\"invalid resource content marker\");\n }\n if (\n resourceType !== undefined &&\n resourceType !== null &&\n !isNonEmptyString(resourceType)\n ) {\n throw new Error(\"invalid resource type\");\n }\n if (\n resourceId !== undefined &&\n resourceId !== null &&\n !isPositiveInteger(resourceId)\n ) {\n throw new Error(\"invalid resource id\");\n }\n if (url !== undefined && url !== null && !isNonEmptyString(url)) {\n throw new Error(\"invalid resource url\");\n }\n\n return {\n key,\n checksum,\n contentPresent,\n resourceType,\n resourceId,\n url,\n };\n}\n\nfunction isRecord(candidate: unknown): candidate is Record<string, unknown> {\n return typeof candidate === \"object\" && candidate !== null;\n}\n\nfunction isNonEmptyString(candidate: unknown): candidate is string {\n return typeof candidate === \"string\" && candidate.length > 0;\n}\n\nfunction isPositiveInteger(candidate: unknown): candidate is number {\n return (\n typeof candidate === \"number\" &&\n Number.isInteger(candidate) &&\n candidate > 0\n );\n}\n","import { spawn } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * A bare git repo hidden under `.fluid-theme/repo` that stands in for\n * git's index+HEAD when talking to the Fluid server. Every pull commits\n * the incoming remote state onto a single branch (`refs/heads/main`);\n * every successful push commits the outgoing local state onto the same\n * branch. HEAD therefore represents \"the last state the CLI and server\n * agreed on\", and its tree is the natural three-way-merge base for the\n * next pull.\n *\n * We stay in git's plumbing layer — no working tree, no index file\n * next to the theme content — so the shadow repo cannot interfere\n * with the user's own git repo (if they have one) around the theme\n * dir. All state lives inside `.fluid-theme/`.\n *\n * The class only wraps the small handful of plumbing commands we\n * actually need: hash-object, cat-file, write-tree (via a temp index),\n * commit-tree, update-ref, and merge-file. Everything else stays out.\n */\nexport class ShadowRepo {\n // Cached \"does HEAD exist\" result. `pull`/`push` call `blobAtHead`\n // per file (potentially hundreds of times per invocation), and\n // spawning a `git rev-parse` subprocess each time is measurable\n // overhead. HEAD presence only flips one way per instance —\n // `commitState` sets it true after a successful `update-ref` — so\n // the cache stays correct without invalidation.\n private headExists: boolean | undefined = undefined;\n\n private constructor(\n private readonly themeRoot: string,\n private readonly gitDir: string,\n ) {}\n\n /**\n * Return a ShadowRepo bound to `themeId` for the given theme root.\n * The shadow is initialized on first use and re-initialized when\n * the caller passes a themeId different from the one previously\n * recorded — the merge base is only meaningful for the theme it\n * was captured against, so a cross-theme operation (pull A → push\n * B, or two pulls of different themes into one dir) starts from a\n * clean slate rather than pretending B's state matches A's HEAD.\n */\n static async open(themeRoot: string, themeId: number): Promise<ShadowRepo> {\n const shadowDir = join(themeRoot, \".fluid-theme\");\n const gitDir = join(shadowDir, \"repo\");\n const themeIdFile = join(shadowDir, \"theme-id\");\n\n // The stored themeId is written on every commit, so a shadow with\n // a HEAD but no theme-id file is one written by an older CLI —\n // treat it as \"unknown\" and wipe rather than trust it against\n // whatever theme the caller is now operating on.\n if (existsSync(gitDir)) {\n const stored = readStoredThemeId(themeIdFile);\n if (stored !== themeId) {\n rmSync(gitDir, { recursive: true, force: true });\n }\n }\n\n const repo = new ShadowRepo(themeRoot, gitDir);\n\n if (!existsSync(gitDir)) {\n mkdirSync(shadowDir, { recursive: true });\n await repo.git([\"init\", \"--bare\", \"-b\", \"main\", gitDir], {\n cwd: themeRoot,\n });\n }\n\n // Rewrite even when the dir already existed — a fresh init above\n // won't have this file yet, and a preserved dir may have missed a\n // previous write if the process crashed mid-way. Idempotent.\n writeFileSync(themeIdFile, `${themeId}\\n`, \"utf-8\");\n\n // Idempotent: keep `.fluid-theme/` invisible to the user's own git\n // repo. The nested `.gitignore` handles `git add .` inside the\n // shadow dir; the root-level entry handles `git status` on the\n // theme root, which otherwise still lists the untracked shadow\n // directory itself (only its *contents* are ignored by the nested\n // file).\n const shadowIgnore = join(shadowDir, \".gitignore\");\n if (!existsSync(shadowIgnore)) {\n writeFileSync(\n shadowIgnore,\n \"# Fluid CLI shadow repo — internal state, not for version control.\\n*\\n\",\n );\n }\n await ensureRootGitignoreHidesShadow(themeRoot);\n\n return repo;\n }\n\n /** True when the repo has at least one commit on `refs/heads/main`. */\n async hasHead(): Promise<boolean> {\n if (this.headExists !== undefined) return this.headExists;\n try {\n await this.git([\"rev-parse\", \"--verify\", \"HEAD\"]);\n this.headExists = true;\n } catch {\n this.headExists = false;\n }\n return this.headExists;\n }\n\n /** Desktop snapshots deliberately use a different author. They are local\n * recovery points, not proof that those bytes were synced to the server. */\n async hasSyncedHead(): Promise<boolean> {\n if (!(await this.hasHead())) return false;\n const { stdout } = await this.git([\"log\", \"-1\", \"--format=%ae\"]);\n return stdout.toString(\"utf8\").trim() === \"cli@fluid.app\";\n }\n\n /**\n * Every path recorded under HEAD's tree, recursively. Callers use\n * this to detect local deletions (paths in HEAD, absent from the\n * working tree). Returns [] when HEAD has never been committed.\n */\n async headPaths(): Promise<string[]> {\n if (!(await this.hasHead())) return [];\n // -z is required, not cosmetic. Without it git honours core.quotePath\n // (default true) and renders any path containing a non-ASCII byte in\n // its C-quoted display form — `\"assets/caf\\303\\251.css\"`, quotes\n // included. That string matches nothing in the working tree, so\n // diffAgainstShadow reports the file as a local deletion and push\n // sends a DELETE for a key the server has never had. The failed\n // delete then blocks commitPushedState, so shadow HEAD never\n // advances and the phantom delete repeats on every later push.\n // -z also covers the rarer newline-in-path case; splitting on \"\\n\"\n // could not.\n const { stdout } = await this.git([\n \"ls-tree\",\n \"-r\",\n \"-z\",\n \"HEAD\",\n \"--name-only\",\n ]);\n return stdout\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter((line) => line.length > 0);\n }\n\n /**\n * The content of `path` in HEAD's tree, or null when the path does\n * not exist there. Callers use this as the merge base for pull\n * conflict resolution.\n */\n async blobAtHead(path: string): Promise<Buffer | null> {\n if (!(await this.hasHead())) return null;\n try {\n const { stdout } = await this.git([\"cat-file\", \"-p\", `HEAD:${path}`]);\n return stdout;\n } catch {\n return null;\n }\n }\n\n /**\n * Write `content` as a blob in the shadow repo and return its sha.\n * Used by `commitState` to stage each file's content before the\n * `write-tree` call.\n */\n async writeBlob(content: string | Buffer): Promise<string> {\n const buf = typeof content === \"string\" ? Buffer.from(content) : content;\n const { stdout } = await this.git([\"hash-object\", \"-w\", \"--stdin\"], {\n input: buf,\n });\n return stdout.toString(\"utf8\").trim();\n }\n\n /**\n * Commit `files` as HEAD's new tree, threaded onto the current HEAD\n * as the parent. Uses a per-call temp index so a partial run can't\n * corrupt anything reachable from HEAD; the previous commit stays\n * intact until `update-ref` at the end.\n *\n * Returns the new commit sha.\n */\n async commitState(\n files: Array<{ path: string; sha: string }>,\n message: string,\n ): Promise<string> {\n const indexPath = mkdtempSync(join(tmpdir(), \"fluid-shadow-\")) + \"/index\";\n\n try {\n if (files.length > 0) {\n // Keep file lists off argv: large themes exceed Windows' process\n // command-line limit. NUL delimiters preserve paths verbatim.\n await this.git([\"update-index\", \"-z\", \"--index-info\"], {\n input: Buffer.from(\n files.map(({ path, sha }) => `100644 ${sha}\\t${path}\\0`).join(\"\"),\n ),\n env: { GIT_INDEX_FILE: indexPath },\n });\n }\n\n const treeSha = (\n await this.git([\"write-tree\"], { env: { GIT_INDEX_FILE: indexPath } })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n const parent = (await this.hasHead())\n ? (await this.git([\"rev-parse\", \"HEAD\"])).stdout.toString(\"utf8\").trim()\n : null;\n\n const commitArgs = [\"commit-tree\", treeSha, \"-m\", message];\n if (parent) commitArgs.push(\"-p\", parent);\n\n const commitSha = (\n await this.git(commitArgs, {\n env: {\n GIT_AUTHOR_NAME: \"Fluid CLI\",\n GIT_AUTHOR_EMAIL: \"cli@fluid.app\",\n GIT_COMMITTER_NAME: \"Fluid CLI\",\n GIT_COMMITTER_EMAIL: \"cli@fluid.app\",\n },\n })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n await this.git([\"update-ref\", \"refs/heads/main\", commitSha]);\n this.headExists = true;\n return commitSha;\n } finally {\n // Best-effort — a stale index in tmp is harmless.\n try {\n rmSync(indexPath, { force: true });\n rmSync(indexPath.substring(0, indexPath.length - \"/index\".length), {\n recursive: true,\n force: true,\n });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Three-way merge of `local` against `remote` with `base` as the\n * common ancestor. Returns the merged bytes and a flag when\n * `git merge-file` reported unresolved conflicts (i.e. the output\n * contains `<<<<<<<` markers for the reader to resolve).\n *\n * `base` is null when HEAD has never seen this path; we merge\n * against an empty base, which is what git itself does for a new\n * file added on both sides.\n *\n * `favor` maps to `git merge-file`'s `--ours` / `--theirs`: instead\n * of emitting `<<<<<<<` markers, conflicting hunks are resolved to\n * the local (`\"local\"` → `--ours`, local is file1) or remote\n * (`\"remote\"` → `--theirs`) side. The output then never contains\n * markers, so the result is reported conflict-free even when\n * merge-file's exit code still counts the auto-resolved hunks.\n */\n async merge3(\n base: Buffer | null,\n local: Buffer,\n remote: Buffer,\n favor?: \"local\" | \"remote\",\n ): Promise<{ merged: Buffer; hasConflicts: boolean }> {\n const dir = mkdtempSync(join(tmpdir(), \"fluid-merge-\"));\n const localPath = join(dir, \"local\");\n const basePath = join(dir, \"base\");\n const remotePath = join(dir, \"remote\");\n\n try {\n writeFileSync(localPath, local);\n writeFileSync(basePath, base ?? Buffer.alloc(0));\n writeFileSync(remotePath, remote);\n\n // `git merge-file -p` writes the merged result to stdout instead\n // of mutating `localPath` in place, and exits non-zero (= number\n // of unresolved conflicts) when it couldn't reconcile everything.\n // We treat any non-zero exit as \"conflicts present\" as long as\n // stdout is populated — the caller writes it to disk with markers.\n try {\n const { stdout } = await this.git([\n \"merge-file\",\n \"-p\",\n ...(favor === \"local\"\n ? [\"--ours\"]\n : favor === \"remote\"\n ? [\"--theirs\"]\n : []),\n \"-L\",\n \"local\",\n \"-L\",\n \"base\",\n \"-L\",\n \"remote\",\n localPath,\n basePath,\n remotePath,\n ]);\n return { merged: stdout, hasConflicts: false };\n } catch (err) {\n const e = err as {\n code?: number;\n stdout?: Buffer | string;\n stderr?: Buffer | string;\n };\n // `git merge-file` exit codes:\n // 0 clean merge (handled in the try branch above)\n // 1..127 count of unresolved conflict hunks; stdout is the\n // merged content with markers — treat as conflict.\n // >=128 git itself crashed (SIGSEGV, out-of-memory) —\n // stdout is empty, don't clobber the user's file.\n // negative usage error (bad args) — same, don't clobber.\n // Requiring both the conflict-range code AND a non-empty\n // stdout matches the comment above and stops a crash from\n // silently writing 0 bytes to disk.\n const merged =\n e.stdout instanceof Buffer\n ? e.stdout\n : e.stdout != null\n ? Buffer.from(e.stdout)\n : Buffer.alloc(0);\n const inConflictRange =\n typeof e.code === \"number\" && e.code >= 1 && e.code <= 127;\n // With a favor side, merge-file resolved every hunk itself —\n // the exit code still counts them, but the output carries no\n // markers, so it's a finished merge from the caller's view.\n // The stdout-length guard is dropped here: an auto-resolved\n // result can be legitimately empty (favoring a side that\n // deleted all content).\n if (inConflictRange && favor) {\n return { merged, hasConflicts: false };\n }\n if (inConflictRange && merged.length > 0) {\n return { merged, hasConflicts: true };\n }\n throw err;\n }\n } finally {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Snapshot the working-tree copy of `paths` into HEAD as a single\n * commit. Intended for the migration path: on the first pull with\n * the new CLI (no shadow repo yet, but a `.fluid-theme.json` with\n * checksums exists) we seed HEAD with whatever is on disk before\n * running the merge, so unmodified files fast-forward cleanly and\n * modified files show a diff.\n */\n async seedFromWorkingTree(\n files: Array<{ path: string; content: string | Buffer }>,\n message: string,\n ): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n for (const { path, content } of files) {\n entries.push({ path, sha: await this.writeBlob(content) });\n }\n await this.commitState(entries, message);\n }\n\n private async git(\n args: string[],\n opts: {\n cwd?: string;\n input?: Buffer;\n env?: Record<string, string>;\n } = {},\n ): Promise<{ stdout: Buffer; stderr: Buffer }> {\n const fullArgs =\n args[0] === \"init\" ? args : [\"--git-dir\", this.gitDir, ...args];\n const child = spawn(\"git\", fullArgs, {\n cwd: opts.cwd ?? this.themeRoot,\n env: { ...process.env, ...opts.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n return new Promise((resolve, reject) => {\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.on(\"error\", reject);\n child.stdin.on(\"error\", reject);\n child.on(\"close\", (code) => {\n const out = Buffer.concat(stdout);\n const err = Buffer.concat(stderr);\n if (code === 0) {\n resolve({ stdout: out, stderr: err });\n } else {\n const e = new Error(\n `git ${args.join(\" \")} exited with ${code}: ${err.toString(\"utf8\")}`,\n ) as Error & { code: number; stdout: Buffer; stderr: Buffer };\n e.code = code ?? -1;\n e.stdout = out;\n e.stderr = err;\n reject(e);\n }\n });\n child.stdin.end(opts.input);\n });\n }\n}\n\n/**\n * Content-type check the pull command uses to decide whether a file\n * is safe to run through `merge3` (line-based) or must fall back to\n * whole-file \"either/or\" resolution (binary).\n */\nexport function looksBinary(content: Buffer): boolean {\n // Same heuristic git uses for `core.autocrlf` detection: any NUL in\n // the first 8000 bytes counts as binary.\n const scan = content.subarray(0, Math.min(content.length, 8000));\n return scan.includes(0);\n}\n\n/** Best-effort readFile that returns null when the file does not exist. */\nexport function readIfExists(path: string): Buffer | null {\n try {\n return readFileSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the numeric theme id from `.fluid-theme/theme-id`. Returns\n * null when the file is missing or the contents don't parse cleanly;\n * `open` treats that as \"unknown theme\" and rebuilds the shadow.\n */\nfunction readStoredThemeId(themeIdFile: string): number | null {\n try {\n const stored = parseInt(readFileSync(themeIdFile, \"utf-8\").trim(), 10);\n return Number.isFinite(stored) ? stored : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Append `.fluid-theme/` to the theme root's `.gitignore` when the\n * theme dir sits inside a git working tree and the entry isn't\n * already there. Skipped when the user isn't in a git repo — no\n * point manufacturing a `.gitignore` for someone who doesn't use\n * git. Idempotent — a second call is a no-op.\n */\nasync function ensureRootGitignoreHidesShadow(\n themeRoot: string,\n): Promise<void> {\n const insideGit = await new Promise<boolean>((resolve) => {\n const child = spawn(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], {\n cwd: themeRoot,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n child.on(\"close\", (code) => resolve(code === 0));\n child.on(\"error\", () => resolve(false));\n });\n if (!insideGit) return;\n\n const gitignorePath = join(themeRoot, \".gitignore\");\n let existing = \"\";\n try {\n existing = readFileSync(gitignorePath, \"utf-8\");\n } catch {\n existing = \"\";\n }\n\n const lines = existing.split(\"\\n\").map((line) => line.trim());\n // Match both `.fluid-theme/` and `.fluid-theme` — either shape hides\n // the dir from `git status`.\n if (lines.includes(\".fluid-theme/\") || lines.includes(\".fluid-theme\")) {\n return;\n }\n\n const separator =\n existing.length === 0 || existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n writeFileSync(\n gitignorePath,\n `${existing}${separator}.fluid-theme/\\n`,\n \"utf-8\",\n );\n}\n","import chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport type { createApiClient } from \"./api.js\";\nimport { themes, type components } from \"@fluid-app/themes-api-client\";\n\nexport type ApplicationTheme = components[\"schemas\"][\"ApplicationTheme\"];\n\nconst PAGE_SIZE = 50;\nconst LOAD_MORE_VALUE = -1;\n\nfunction themeLabel(t: ApplicationTheme): string {\n const active = t.status === \"active\" ? ` ${chalk.green(\"[active]\")}` : \"\";\n return `${t.name} (#${t.id})${active}`;\n}\n\nfunction themeChoices(\n themeList: ApplicationTheme[],\n hasMore: boolean,\n): prompts.Choice[] {\n const choices: prompts.Choice[] = themeList.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n if (hasMore) {\n choices.push({\n title: chalk.dim(`── Load more themes ──`),\n value: LOAD_MORE_VALUE,\n });\n }\n return choices;\n}\n\nasync function fetchThemesPage(\n api: ReturnType<typeof createApiClient>,\n page: number,\n searchQuery?: string,\n): Promise<{\n themes: ApplicationTheme[];\n hasMore: boolean;\n}> {\n const body = await themes.listApplicationThemes(api, {\n per_page: PAGE_SIZE,\n page,\n ...(searchQuery ? { search_query: searchQuery } : {}),\n });\n const list = body.application_themes ?? [];\n const totalPages = body.meta?.total_pages ?? 1;\n return { themes: list, hasMore: page < totalPages };\n}\n\nexport async function selectTheme(\n api: ReturnType<typeof createApiClient>,\n message: string,\n): Promise<ApplicationTheme> {\n const allThemes: ApplicationTheme[] = [];\n let page = 1;\n let hasMore = true;\n let initialIndex = 0;\n\n // Search cache — persists across suggest calls\n let searchQuery = \"\";\n let searchResults: ApplicationTheme[] = [];\n\n while (true) {\n if (hasMore && allThemes.length < page * PAGE_SIZE) {\n const result = await fetchThemesPage(api, page);\n allThemes.push(...result.themes);\n hasMore = result.hasMore;\n }\n\n if (!allThemes.length) {\n console.error(\"No themes found.\");\n process.exit(1);\n }\n\n const choices = themeChoices(allThemes, hasMore);\n\n const { id } = await prompts(\n {\n type: \"autocomplete\",\n name: \"id\",\n message,\n initial: initialIndex,\n choices,\n suggest: async (input: string, choices: prompts.Choice[]) => {\n if (!input) {\n searchQuery = \"\";\n searchResults = [];\n return choices;\n }\n\n if (input !== searchQuery) {\n searchQuery = input;\n try {\n const result = await fetchThemesPage(api, 1, input);\n searchResults = result.themes;\n } catch {\n searchResults = [];\n }\n }\n\n return searchResults.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n },\n },\n { onCancel: () => process.exit(130) },\n );\n\n if (id === LOAD_MORE_VALUE) {\n initialIndex = allThemes.length;\n page++;\n continue;\n }\n\n if (!id) {\n console.error(\"No theme selected.\");\n process.exit(1);\n }\n\n // Check loaded themes first, then search results\n const found =\n allThemes.find((t) => t.id === id) ??\n searchResults.find((t) => t.id === id);\n if (found) return found;\n\n // Fetch directly by ID as fallback\n const body = await themes.getApplicationTheme(api, id);\n return body.application_theme;\n }\n}\n\nexport async function findTheme(\n api: ReturnType<typeof createApiClient>,\n identifier: string,\n): Promise<ApplicationTheme> {\n // Try ID lookup first\n const idNum = Number(identifier);\n if (Number.isInteger(idNum) && idNum > 0) {\n try {\n const body = await themes.getApplicationTheme(api, idNum);\n if (body.application_theme) return body.application_theme;\n } catch {\n // Not found by ID, fall through to search\n }\n }\n\n // Search by name via API with pagination\n let page = 1;\n let hasMore = true;\n while (hasMore) {\n const result = await fetchThemesPage(api, page, identifier);\n const found = result.themes.find(\n (t) => t.name.toLowerCase() === identifier.toLowerCase(),\n );\n if (found) return found;\n hasMore = result.hasMore;\n page++;\n }\n\n console.error(`No theme found with identifier: ${identifier}`);\n process.exit(1);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nexport interface FluidWorkspace {\n /** Absolute path to the workspace root (where .fluid-workspace.json lives) */\n root: string;\n /** Parsed workspace config */\n config: WorkspaceConfig;\n}\n\ninterface WorkspaceConfig {\n type: string;\n version: number;\n}\n\nconst WORKSPACE_FILE = \".fluid-workspace.json\";\n\n/**\n * Walk up from `startDir` looking for `.fluid-workspace.json`.\n * Returns the workspace info if found, or `null` if not in a workspace.\n */\nexport function findWorkspace(startDir?: string): FluidWorkspace | null {\n let dir = resolve(startDir ?? process.cwd());\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const candidate = join(dir, WORKSPACE_FILE);\n if (existsSync(candidate)) {\n try {\n const raw = readFileSync(candidate, \"utf-8\");\n const config = JSON.parse(raw) as WorkspaceConfig;\n return { root: dir, config };\n } catch {\n return null;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n return null;\n}\n\n/**\n * If cwd is already inside `{workspace}/local/{company}/...`, return that\n * theme root directory. Otherwise return null.\n *\n * Examples (workspace root = /code/fluid-theme-dev):\n * cwd = /code/fluid-theme-dev/local/acme-co → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev/local/acme-co/templates → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev → null\n * cwd = /code/fluid-theme-dev/local → null\n */\nexport function resolveThemeRootFromCwd(\n workspace: FluidWorkspace,\n): string | null {\n const cwd = resolve(process.cwd());\n const localDir = join(workspace.root, \"local\");\n const rel = relative(localDir, cwd);\n\n // Not under local/ at all, or exactly at local/\n if (rel.startsWith(\"..\") || rel === \".\") return null;\n\n // rel is like \"acme-co\" or \"acme-co/templates/subfolder\"\n // The theme root is the first segment: local/{company}\n const firstSegment = rel.split(sep)[0];\n if (!firstSegment) return null;\n\n return join(localDir, firstSegment);\n}\n","import { Command } from \"commander\";\nimport { navigateTheme } from \"./navigate.js\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport {\n devThemeKey,\n getDevTheme,\n setDevTheme,\n setLastDevThemeId,\n clearDevTheme,\n type DevThemeRef,\n} from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { startDevServer } from \"../theme/dev-server/index.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport {\n devRemoteStateFromSourceShadow,\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"../theme/dev-remote-baseline.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n checkPortAvailable,\n PortInUseError,\n} from \"../theme/dev-server/port-preflight.js\";\nimport { isApiError, themes } from \"@fluid-app/themes-api-client\";\nimport { findTheme, type ApplicationTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\n/** Whether this invocation may read and persist the dev remote baseline. */\nexport function devRemoteBaselineEnabled(explicitTheme: boolean): boolean {\n return (\n !explicitTheme && process.env[\"FLUID_THEME_DEV_DISABLE_SHADOW_SYNC\"] !== \"1\"\n );\n}\n\n/**\n * Create the isolated theme used by `theme dev`.\n *\n * A checkout from `theme pull` has a source theme id. New servers clone that\n * source by reference, preserving its DAM/ImageKit assets without moving\n * bytes. A 404/405 keeps older deployments compatible with the established\n * empty-theme flow; other failures must remain visible to the developer.\n */\nexport async function createDevelopmentTheme(\n api: ReturnType<typeof createApiClient>,\n sourceThemeId: number | undefined,\n name: string,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (sourceThemeId !== undefined) {\n try {\n const body = await themes.cloneApplicationThemeForDevelopment(\n api,\n sourceThemeId,\n { application_theme: { name } },\n );\n return { theme: body.application_theme, referenceCloned: true };\n } catch (error) {\n if (\n !isApiError(error) ||\n (error.status !== 404 && error.status !== 405)\n ) {\n throw error;\n }\n\n console.warn(\n \"Server-side theme cloning is unavailable; falling back to an empty development theme. The first sync may take longer.\",\n );\n }\n }\n\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"development\" },\n });\n return { theme: body.application_theme, referenceCloned: false };\n}\n\nasync function ensureDevTheme(\n api: ReturnType<typeof createApiClient>,\n projectKey: string,\n identifier?: string,\n sourceThemeId?: number,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (identifier) {\n const theme = await findTheme(api, identifier);\n // Keep `navigate` pointed at whatever the dev server is actually serving.\n setLastDevThemeId(theme.id);\n return { theme, referenceCloned: false };\n }\n\n // Reuse this project's stored dev theme if it still exists and is still a\n // development theme (a published/promoted theme must not be edited in place).\n const stored = getDevTheme(projectKey);\n // A checkout pulled from a different source needs an isolated sandbox of\n // its own. Older stored entries have no source id, so they are retained only\n // for worktrees that were not pulled from a remote theme.\n if (stored && stored.sourceThemeId === sourceThemeId) {\n try {\n const body = await themes.getApplicationTheme(api, stored.id);\n const existing = body.application_theme;\n if (existing && existing.status === \"development\") {\n console.log(`Using existing dev theme #${existing.id}`);\n // Refresh the stored name and mark it most-recent for `navigate`.\n setDevTheme(projectKey, {\n ...stored,\n id: existing.id,\n name: existing.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n });\n return { theme: existing, referenceCloned: false };\n }\n } catch {\n // Theme no longer exists — fall through to create a new one.\n }\n // Stored theme is gone or no longer a dev theme; forget it.\n clearDevTheme(projectKey);\n }\n\n // Create a new development theme\n const { hostname } = await import(\"node:os\");\n const host = hostname().split(\".\")[0] ?? \"dev\";\n const name =\n `Development (${host}-${Math.random().toString(36).slice(2, 8)})`.slice(\n 0,\n 50,\n );\n\n const creation = await createDevelopmentTheme(api, sourceThemeId, name);\n const { theme } = creation;\n const devTheme: DevThemeRef = {\n id: theme.id,\n name: theme.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n };\n setDevTheme(projectKey, devTheme);\n console.log(`Created dev theme: ${theme.name} (#${theme.id})`);\n return creation;\n}\n\nexport function createDevCommand(): Command {\n return new Command(\"dev\")\n .description(\"Start the theme dev server with hot reload\")\n .option(\"--host <host>\", \"Local server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Local server port\", \"9292\")\n .option(\n \"-t, --theme <name-or-id>\",\n \"Use an existing theme instead of dev theme\",\n )\n .option(\"-f, --force\", \"Skip schema validation on upload\")\n .option(\"--live-reload <mode>\", \"Reload mode: full-page | off\", \"full-page\")\n .option(\"--navigate\", \"Open browser navigator after server starts\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n host: string;\n port: string;\n theme?: string;\n force?: boolean;\n liveReload: string;\n navigate?: boolean;\n root: string;\n }) => {\n requireToken();\n\n // If no explicit --root and we're inside a workspace, resolve to the theme root\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const port = Number(opts.port);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n console.error(\n `Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`,\n );\n process.exit(1);\n }\n\n // Fail fast if the port is already taken — everything below this\n // point (resolving/creating the dev theme, the initial sync) is\n // expensive and would otherwise run to completion only to crash\n // with a raw EADDRINUSE stack trace when the server finally binds.\n try {\n await checkPortAvailable(opts.host, port);\n } catch (e) {\n if (e instanceof PortInUseError) {\n console.error(e.message);\n } else {\n console.error(`Failed to check port availability: ${e}`);\n }\n process.exit(1);\n }\n\n const reloadMode = opts.liveReload === \"off\" ? \"off\" : \"full-page\";\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n\n // Use company from .fluid-theme.json if available, otherwise fetch\n let company: string;\n if (config?.company) {\n company = config.company;\n } else {\n const companyRes = await api.get<CompanyMe>(\n \"/api/company/v1/companies/me\",\n );\n company = companyRes.data?.company?.subdomain ?? \"\";\n if (!company) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n }\n\n // Always iterate on an isolated dev theme: reuse the stored one or\n // create a fresh `development` theme. `--theme` is the explicit\n // escape hatch for targeting an existing theme. A pulled theme id is\n // never a sync target: it only seeds a new isolated reference clone.\n const projectKey = devThemeKey(company, themeRoot.root);\n const devTarget = opts.theme\n ? await ensureDevTheme(api, projectKey, opts.theme)\n : await ensureDevTheme(api, projectKey, undefined, config?.themeId);\n const { theme } = devTarget;\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const baselineEnabled = devRemoteBaselineEnabled(Boolean(opts.theme));\n let initialRemoteState = baselineEnabled\n ? readDevRemoteBaseline(\n themeRoot.root,\n theme.id,\n assetManifest.fingerprint(),\n )\n : null;\n if (\n !initialRemoteState &&\n baselineEnabled &&\n devTarget.referenceCloned &&\n config?.themeId &&\n config.baseSha\n ) {\n try {\n const sourceShadow = await ShadowRepo.open(\n themeRoot.root,\n config.themeId,\n );\n initialRemoteState = await devRemoteStateFromSourceShadow(\n themeRoot,\n sourceShadow,\n theme.id,\n config.baseSha,\n );\n if (initialRemoteState) {\n writeDevRemoteBaseline(themeRoot.root, initialRemoteState);\n }\n } catch {\n initialRemoteState = null;\n }\n }\n const editorUrl = `https://admin.fluid.app/themes/${theme.id}/editor`;\n\n let stop: (() => void) | undefined;\n\n const cleanup = () => {\n stop?.();\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n stop = await startDevServer(\n api,\n {\n id: theme.id,\n name: theme.name,\n company,\n editorUrl,\n },\n themeRoot,\n {\n host: opts.host,\n port,\n reloadMode,\n validate: !opts.force,\n ...(initialRemoteState ? { initialSync: initialRemoteState } : {}),\n ...(baselineEnabled\n ? {\n onRemoteState: (state) =>\n writeDevRemoteBaseline(themeRoot.root, state),\n onRemoteStateInvalidated: () =>\n removeDevRemoteBaseline(themeRoot.root),\n }\n : {}),\n },\n (address) => {\n console.log(`\\n Dev server: ${address}`);\n console.log(` Web editor: ${editorUrl}`);\n console.log(\"\\n Watching for file changes…\\n\");\n\n if (opts.navigate) {\n void navigateTheme(api, theme.id, address).catch(\n (error: unknown) => {\n console.error(\n error instanceof Error\n ? error.message\n : \"Could not open the preview navigator.\",\n );\n },\n );\n }\n },\n );\n\n // Keep process alive\n await new Promise(() => {});\n },\n );\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, readIfExists } from \"./shadow-repo.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\n\nexport interface DiffSet {\n /** Files whose local content differs from shadow HEAD — the ones to send. */\n changed: ThemeFile[];\n /** Paths present in shadow HEAD but no longer on disk — deletions to send. */\n deleted: string[];\n}\n\n/**\n * Compute what changed locally since the last time the shadow repo\n * committed a state. Replaces the sha256 `checksums` map: shadow HEAD\n * is the source of truth for \"what the CLI last saw the server have\".\n *\n * Files whose local bytes are byte-identical to their HEAD blob are\n * skipped; anything else — new, modified, or a locally-deleted path\n * that HEAD still has — is included.\n */\nexport async function diffAgainstShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<DiffSet> {\n const changed: ThemeFile[] = [];\n const deleted: string[] = [];\n\n const localFiles = themeRoot.files();\n const localByKey = new Map<string, ThemeFile>();\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let headPaths: string[];\n try {\n headPaths = await shadow.headPaths();\n } catch (error) {\n throw new Error(\"Could not read the local theme shadow\", { cause: error });\n }\n\n assertNoCaseCollisions([\n ...localFiles.map((file) => file.relativePath),\n ...headPaths,\n ]);\n const headPathSet = new Set(headPaths);\n\n for (const file of localFiles) {\n if (!file.exists) continue;\n localByKey.set(file.relativePath, file);\n\n const headBlob = await shadow.blobAtHead(file.relativePath);\n if (!headBlob && headPathSet.has(file.relativePath)) {\n throw new Error(\n `Could not read the local theme shadow: ${file.relativePath}`,\n );\n }\n const localBuf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n if (headBlob && headBlob.equals(localBuf)) continue;\n\n changed.push(file);\n }\n\n for (const key of headPaths) {\n if (localByKey.has(key)) continue;\n if (themeRoot.ignore.ignore(key)) continue;\n // A manifest-backed asset is deliberately absent from disk. Its URL is\n // still authoritative and must never be emitted as a remote deletion.\n if (assetManifest.has(key)) continue;\n if (isStylesheetKey(key)) continue; // hidden from the API surface\n deleted.push(key);\n }\n\n return { changed, deleted };\n}\n\n/**\n * Refuse a push when any working file still contains a conflict marker\n * from a previous pull. Mirrors git's \"you have unresolved conflicts;\n * fix them and re-run\" behavior — the whole point of writing markers\n * on pull was to hand resolution to the user, so we can't send them\n * upstream.\n */\nexport function findUnresolvedConflicts(files: ThemeFile[]): string[] {\n const flagged: string[] = [];\n for (const file of files) {\n if (!file.isText) continue;\n const buf = readIfExists(file.absolutePath);\n if (!buf) continue;\n if (containsConflictMarker(buf)) flagged.push(file.relativePath);\n }\n return flagged;\n}\n\n/**\n * Stable, machine-readable one-liner for non-interactive callers\n * (Mist Desktop's publish flow parses push output). Uploading marker-\n * bearing files to a live theme is never acceptable, so `--auto-\n * baseline` pushes still refuse — but they emit this line so the\n * desktop can surface WHICH files block the publish instead of a\n * dead-end wall of prose. Format:\n *\n * FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=a.liquid,b.json\n */\nexport function conflictMarkerBlockLine(files: string[]): string {\n return `FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=${files.join(\",\")}`;\n}\n\nconst CONFLICT_START = Buffer.from(\"<<<<<<<\");\nconst CONFLICT_MID = Buffer.from(\"=======\");\nconst CONFLICT_END = Buffer.from(\">>>>>>>\");\n\n/**\n * A file counts as unresolved when it contains all three marker\n * shapes: `<<<<<<<`, `=======`, and `>>>>>>>`. Requiring all three\n * avoids false positives — a line of equals signs alone (e.g. inside\n * an ASCII table in a template comment) doesn't trip the guard.\n */\nfunction containsConflictMarker(buf: Buffer): boolean {\n return (\n buf.includes(CONFLICT_START) &&\n buf.includes(CONFLICT_MID) &&\n buf.includes(CONFLICT_END)\n );\n}\n\n/**\n * Commit the pushed state to shadow HEAD. Server-acknowledged contents\n * override local bytes for normalized member files, including files edited\n * or deleted locally during upload. Local files remain untouched so those\n * later changes still differ from the next pull's merge base.\n */\nexport async function commitPushedState(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n message: string,\n acknowledgedContents?: ReadonlyMap<string, string>,\n): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const recordedKeys = new Set<string>();\n for (const [path, content] of new Map(acknowledgedContents)) {\n entries.push({ path, sha: await shadow.writeBlob(content) });\n recordedKeys.add(path);\n }\n for (const file of themeRoot.files()) {\n if (!file.exists || recordedKeys.has(file.relativePath)) continue;\n recordedKeys.add(file.relativePath);\n // A manifest entry normally has no file at all. If bytes are present (for\n // example after a metadata fallback), they remain authoritative until a\n // successful upload can externalize them again.\n const buf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n entries.push({ path: file.relativePath, sha: await shadow.writeBlob(buf) });\n }\n let managedAssetSentinelSha: string | undefined;\n for (const key of assetManifest.keys()) {\n if (recordedKeys.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n entries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // Commit an empty tree too when HEAD exists: a successful final deletion\n // must clear its old path/sentinel so later pushes do not repeat it.\n if (entries.length > 0 || (await shadow.hasHead())) {\n await shadow.commitState(entries, message);\n }\n}\n\n/**\n * A target can already have every URL-backed FileResource (for example from a\n * reference clone), while this checkout's manifest still names its old source\n * theme. Adopt the target and clear any legacy binary from shadow even though\n * no remote write was necessary.\n */\nexport async function finalizeManifestOnlyPush(\n syncer: { repointManagedAssetsToCurrentTheme(): void },\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<void> {\n syncer.repointManagedAssetsToCurrentTheme();\n await commitPushedState(\n themeRoot,\n shadow,\n `push @ ${new Date().toISOString()}`,\n );\n}\n","import { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { FetchBinary } from \"./merge-pull.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\nexport interface SeedBaselineResult {\n /** False when a baseline already existed (HEAD present) — e.g. the\n * legacy checksum-era migration already seeded it. The migration\n * always wins over server seeding: a migrated HEAD carries the\n * local \"unmodified since last sync\" blobs, which is a better\n * merge base than the server's current state. */\n seeded: boolean;\n /** Paths recorded into the baseline commit. */\n recorded: string[];\n /** Server paths with no local counterpart, deliberately left OUT of\n * the baseline (see invariant below). */\n serverOnly: string[];\n /** Non-fatal per-file problems (e.g. a binary download failed). */\n errors: string[];\n}\n\n/**\n * `fluid theme push --auto-baseline`: when a theme directory has no\n * shadow baseline (scaffold that never pulled, or a dir last synced\n * by the checksum-era CLI whose migration had nothing to seed),\n * record the server's CURRENT state as the baseline commit so the\n * normal push diff (local vs baseline) can run.\n *\n * Invariants:\n *\n * 1. **The working tree is never touched.** Baseline recording writes\n * blobs into the bare shadow repo only; not a single byte on disk\n * changes. Local files identical to the server simply won't diff;\n * files that differ (or exist only locally) will push.\n *\n * 2. **Server-only files are never deleted.** Paths that exist on the\n * server but not locally are deliberately excluded from the\n * baseline commit. `diffAgainstShadow` reports deletions as \"in\n * HEAD but not on disk\", so putting server-only paths into HEAD\n * would mark them for remote deletion — on THIS push (or worse, a\n * later one) — for files the user never had. Excluding them makes\n * the diff structurally unable to delete them; the next `pull`\n * materializes them locally and records them for real.\n *\n * Bandwidth note: a server file whose sha256 checksum matches the\n * local file's is recorded from the LOCAL bytes (identical by\n * definition), so binary assets that are already in sync are never\n * downloaded just to seed the baseline.\n *\n * No-op (`seeded: false`) when HEAD already exists.\n */\nexport async function seedBaselineFromServer(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n message: string;\n}): Promise<SeedBaselineResult> {\n const { shadow, themeRoot, remote, fetchBinary, message } = input;\n const result: SeedBaselineResult = {\n seeded: false,\n recorded: [],\n serverOnly: [],\n errors: [],\n };\n\n if (await shadow.hasHead()) return result;\n\n const entries: Array<{ path: string; sha: string }> = [];\n\n for (const resource of remote) {\n const file = themeRoot.file(resource.key);\n\n // Same traversal guard as mergePull — never trust remote keys.\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Baseline ${resource.key}: path traversal detected`);\n continue;\n }\n\n if (!file.exists) {\n result.serverOnly.push(resource.key);\n continue;\n }\n\n let content: Buffer | null;\n if (resource.checksum && file.checksum() === resource.checksum) {\n // Identical to the server — local bytes ARE the server bytes.\n content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n } else {\n try {\n content = await materializeRemote(resource, fetchBinary);\n } catch (e) {\n result.errors.push(\n `Baseline ${resource.key}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n }\n if (content == null) {\n // Nothing to record (empty resource) — the local file will be\n // treated as new and pushed, which is the honest outcome.\n continue;\n }\n\n entries.push({ path: resource.key, sha: await shadow.writeBlob(content) });\n result.recorded.push(resource.key);\n }\n\n if (entries.length > 0) {\n await shadow.commitState(entries, message);\n result.seeded = true;\n }\n\n return result;\n}\n\nasync function materializeRemote(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n","import { readLegacyThemeConfig } from \"../theme-config.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\n\n/**\n * On first pull after upgrading from a checksum-era CLI, the shadow\n * repo starts with no HEAD. `mergePull` would then run every diverged\n * file through a null-base merge — even files the user never touched\n * locally — producing spurious `<<<<<<<` markers for every file the\n * server updated since the last pull.\n *\n * Recover a real merge base by trusting the legacy sha256 checksums:\n * any local file whose content still matches its stored checksum is\n * \"unmodified since last pull\" and can be committed as HEAD. Files\n * whose local sha256 diverges from the stored checksum stay\n * unseeded — we don't have their pre-modification content, so a\n * null-base merge (marker-first UX) is the honest fallback for them.\n *\n * Idempotent: no-op when HEAD already exists, when there is no\n * legacy config, when the config is for a different theme, or when\n * the checksums map is empty. Runs before `mergePull` so its base\n * lookups see the seeded tree.\n */\nexport async function migrateLegacyChecksumsIntoShadow(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n absoluteRoot: string;\n themeId: number;\n}): Promise<void> {\n const { shadow, themeRoot, absoluteRoot, themeId } = input;\n\n if (await shadow.hasHead()) return;\n\n const legacy = readLegacyThemeConfig(absoluteRoot);\n if (!legacy) return;\n if (legacy.themeId !== themeId) return;\n if (!legacy.checksums || Object.keys(legacy.checksums).length === 0) return;\n\n const seed: Array<{ path: string; content: Buffer }> = [];\n for (const file of themeRoot.files()) {\n if (!file.exists) continue;\n const stored = legacy.checksums[file.relativePath];\n if (!stored) continue;\n if (file.checksum() !== stored) continue;\n\n const content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n seed.push({ path: file.relativePath, content });\n }\n\n if (seed.length === 0) return;\n\n await shadow.seedFromWorkingTree(\n seed,\n `migrate from checksum-era CLI @ ${new Date().toISOString()}`,\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport type { ChangeEntry, GitSyncActor } from \"@fluid-app/fluid-cli\";\nimport {\n gitSyncActorFromMe,\n gitSyncCommitSubject,\n resolveGitSyncActor,\n summarizeChanges,\n} from \"@fluid-app/fluid-cli\";\n\n/**\n * User-stamped shadow-repo commit subjects, in the canonical GitSync\n * format shared with the mist / portal / widget CLIs and the Ruby\n * adapters (see `@fluid-app/fluid-cli`'s git-sync/commit-subject.ts):\n *\n * Mike Tingey: Push theme Aurora · 2026-07-22 05:10:33 (0182d21e-…)\n *\n * When the actor is unknown (offline, /api/me down, token weirdness) the\n * commit is attributed to `Fluid` in the same shape, so consumers need\n * one grammar rather than two:\n *\n * Fluid: Push theme Aurora · 2026-07-22 05:10:33\n *\n * Shadow repos written by older CLIs still hold the pre-format machine\n * wording (`push @ <iso>`). We no longer emit it, but Mist Desktop's\n * History popover still parses it so those repos keep rendering.\n */\n\nexport type SyncVerb =\n | \"Push\"\n | \"Pull\"\n | \"Baseline from server\"\n | \"Snapshot before pull\";\n\n/** Human action per verb, used as the canonical subject's action. */\nconst BASE_MESSAGE: Record<SyncVerb, string> = {\n Push: \"Push from Fluid CLI\",\n Pull: \"Pull from Fluid CLI\",\n \"Baseline from server\": \"Baseline from server\",\n \"Snapshot before pull\": \"Snapshot before pull\",\n};\n\n/** Re-exported under the local name the theme CLI has always used. */\nexport type SyncActor = GitSyncActor;\n\n/**\n * Build a shadow-repo commit subject.\n *\n * `changes` (when supplied) replaces the per-verb constant with a\n * description of what the sync actually carried, so the shadow log reads\n * `Update 3 files in templates` rather than `Push from Fluid CLI` on\n * every entry. Falls back to the constant for an empty change set — a\n * baseline or a no-op sync still deserves a sensible subject.\n */\nexport function syncCommitSubject(\n verb: SyncVerb,\n actor: SyncActor | null,\n when: Date = new Date(),\n changes: readonly ChangeEntry[] = [],\n): string {\n const action = summarizeChanges(changes) ?? BASE_MESSAGE[verb];\n return gitSyncCommitSubject(action, actor, when);\n}\n\n/** Resolve a `/api/me` body to a commit actor. Exported for tests.\n * Delegates to the shared resolver so the name fallback chain\n * (full_name → first+last → email → `user-<id>`) can't drift between\n * CLIs. Null when the body carries no usable identity — the caller then\n * writes a `Fluid:` system commit. */\nexport function actorFromMe(\n raw: Parameters<typeof gitSyncActorFromMe>[0],\n): SyncActor | null {\n return gitSyncActorFromMe(raw);\n}\n\n/**\n * Best-effort fetch of the signed-in user for commit stamping. Races\n * `/api/me` against a short timeout and NEVER rejects — a sync must\n * not block or fail because the identity lookup did. Callers kick\n * this off early and await it only at commit time.\n */\nexport async function fetchSyncActor(\n api: ApiClient,\n timeoutMs = 5_000,\n): Promise<SyncActor | null> {\n // Served from the profile cache after the first sync; /api/me only\n // fires on a miss.\n return resolveGitSyncActor(() => requestSyncActor(api, timeoutMs));\n}\n\nasync function requestSyncActor(\n api: ApiClient,\n timeoutMs: number,\n): Promise<SyncActor | null> {\n try {\n const raw = await Promise.race([\n api.get<Record<string, unknown>>(\"/api/me\"),\n new Promise<never>((_, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n // Don't keep the process alive just for the stamp timeout.\n timer.unref?.();\n }),\n ]);\n return actorFromMe(raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(\n ` (skipping user-stamp on sync commit — couldn't fetch /api/me: ${reason})`,\n );\n return null;\n }\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { readThemeConfig, writeThemeConfig } from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { PushConflictError, Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n commitPushedState,\n conflictMarkerBlockLine,\n diffAgainstShadow,\n finalizeManifestOnlyPush,\n findUnresolvedConflicts,\n} from \"../theme/merge-push.js\";\nimport { seedBaselineFromServer } from \"../theme/auto-baseline.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport {\n fetchSyncActor,\n syncCommitSubject,\n type SyncActor,\n} from \"../theme/sync-identity.js\";\nimport type { ChangeEntry } from \"@fluid-app/fluid-cli\";\nimport { themes } from \"@fluid-app/themes-api-client\";\nimport {\n selectTheme,\n findTheme,\n type ApplicationTheme,\n} from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { formatError } from \"../theme/format-error.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n MEMBER_CONFIGURATION_KEY,\n} from \"../theme/member-configuration.js\";\n\nexport function createPushCommand(): Command {\n return new Command(\"push\")\n .description(\"Push local theme files to a remote theme\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to push to\")\n .option(\"-n, --nodelete\", \"Do not delete remote files missing locally\")\n .option(\n \"-f, --force\",\n \"Skip local Liquid validation and the server-side merge check\",\n )\n .option(\"-p, --publish\", \"Publish the theme after pushing\")\n .option(\n \"-u, --unpublished\",\n \"Create a new unpublished theme and push to it\",\n )\n .option(\n \"--auto-baseline\",\n \"When no local baseline exists, record the server's current state \" +\n \"as the baseline and push only what differs locally (never \" +\n \"modifies local files, never deletes server-only files)\",\n )\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n force?: boolean;\n publish?: boolean;\n unpublished?: boolean;\n autoBaseline?: boolean;\n root: string;\n }) => {\n requireToken();\n\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n let theme: ApplicationTheme;\n\n if (opts.unpublished) {\n const { name } = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Name for the new theme\",\n },\n { onCancel: () => process.exit(130) },\n );\n if (!name) {\n console.error(\"Theme name is required.\");\n process.exit(1);\n }\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"draft\" },\n });\n theme = body.application_theme;\n console.log(\n `Created unpublished theme: ${theme.name} (#${theme.id})`,\n );\n } else if (opts.theme) {\n theme = await findTheme(api, opts.theme);\n } else if (config) {\n console.log(\n ` Using theme from .fluid-theme.json: ${chalk.bold(config.themeName)} (#${config.themeId})`,\n );\n const body = await themes.getApplicationTheme(api, config.themeId);\n theme = body.application_theme;\n } else {\n theme = await selectTheme(api, \"Select a theme to push to\");\n }\n\n const shadow = await ShadowRepo.open(themeRoot.root, theme.id);\n\n // Legacy checksum-era dirs (pre-shadow CLI) migrate here — on\n // push as well as pull, so a dir that never pulled with the\n // new CLI still recovers its merge base instead of dead-ending\n // on the baseline check below. Idempotent; the migration wins\n // over `--auto-baseline` server seeding because a migrated\n // HEAD holds the local \"unmodified since last sync\" blobs.\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot: themeRoot.root,\n themeId: theme.id,\n });\n\n const localFiles = themeRoot.files().filter((f) => f.exists);\n\n // Lazy, memoized identity lookup for commit stamping — only\n // fired when we actually commit, never blocks or fails a push.\n let actorPromise: Promise<SyncActor | null> | null = null;\n const getActor = (): Promise<SyncActor | null> =>\n (actorPromise ??= fetchSyncActor(api));\n\n // Refuse to push files that still contain `<<<<<<< / >>>>>>>`\n // markers from a previous merge — the whole point of writing\n // them was to hand resolution to the user; sending them\n // upstream would ship broken content. This guard is absolute:\n // `--auto-baseline` (non-interactive) refuses too, but emits a\n // machine-readable line the desktop can surface well.\n const unresolved = findUnresolvedConflicts(localFiles);\n if (unresolved.length > 0) {\n console.log();\n console.log(\n chalk.red(\n `✗ ${unresolved.length} file(s) still contain unresolved conflict markers:`,\n ),\n );\n for (const key of unresolved) console.log(` ${key}`);\n console.log();\n console.log(\n ` Edit each file to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} sections,`,\n );\n console.log(` then re-run ${chalk.cyan(\"fluid theme push\")}.`);\n console.log();\n if (opts.autoBaseline) {\n console.error(conflictMarkerBlockLine(unresolved));\n }\n process.exit(1);\n }\n\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // baseSha — server-side merge check on every write. Skipped\n // when `--force`, when the config was written by an older CLI\n // that didn't yet capture the pull's `content_version_sha`,\n // or when we're pushing to a different theme than the one the\n // config was written for (a `--unpublished` create, or\n // `--theme` pointing somewhere else). `baseSha` is per-theme —\n // reusing one theme's sha as the base of another theme's push\n // always 409s.\n const configMatchesTheme = config?.themeId === theme.id;\n let baseSha =\n opts.force || !configMatchesTheme ? null : (config?.baseSha ?? null);\n\n // A baseline exists when either half is present: shadow HEAD\n // (the local diff base — written by pull, a legacy migration,\n // or a previous auto-baseline) or a stored `baseSha` (the\n // server-side race check). HEAD alone is enough to diff\n // honestly; a missing baseSha only skips the server preflight.\n const hasBaseline = baseSha != null || (await shadow.hasHead());\n\n // No baseline at all: without `--auto-baseline`, refuse —\n // otherwise the CLI silently clobbers whatever the server\n // currently has, with no way for the user to see the\n // divergence first. `--force` opts out (explicit clobber);\n // `--unpublished` skips because the theme was created empty\n // in this same command and has nothing to protect.\n if (!opts.force && !opts.unpublished && !hasBaseline) {\n if (!opts.autoBaseline) {\n console.error();\n console.error(\n chalk.red(\n `No local baseline for theme \"${theme.name}\" (#${theme.id}).`,\n ),\n );\n console.error();\n console.error(\n ` Run ${chalk.cyan(`fluid theme pull -t ${theme.id}`)} first to sync down the current server state,`,\n );\n console.error(\n ` then push — this way you see what would change before it goes live.`,\n );\n console.error();\n console.error(\n ` Or re-run with ${chalk.cyan(\"--auto-baseline\")} to record the server's current state`,\n );\n console.error(\n ` as the baseline and push only what differs locally (local files are never modified).`,\n );\n console.error();\n console.error(\n ` Or, if you know what you're doing and want to overwrite the server's current`,\n );\n console.error(\n ` contents wholesale, re-run with ${chalk.cyan(\"--force\")}.`,\n );\n console.error();\n process.exit(1);\n }\n\n // `--auto-baseline`: record the server's current state as\n // the baseline WITHOUT touching a single local file, then\n // fall through to the normal diff. Server-only files are\n // deliberately kept out of the baseline so they can never\n // be reported as local deletions (see auto-baseline.ts).\n const baselineSpinner = ora(\n `No local baseline — recording current server state for ${theme.name} (#${theme.id})…`,\n ).start();\n try {\n const resources = await syncer.downloadAll();\n const seedResult = await seedBaselineFromServer({\n shadow,\n themeRoot,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n message: syncCommitSubject(\n \"Baseline from server\",\n await getActor(),\n ),\n });\n baseSha = syncer.remoteSha() ?? null;\n const parts = [`recorded ${seedResult.recorded.length} file(s)`];\n if (seedResult.serverOnly.length > 0) {\n parts.push(\n `left ${seedResult.serverOnly.length} server-only file(s) untouched`,\n );\n }\n baselineSpinner.succeed(`Baseline recorded — ${parts.join(\", \")}.`);\n for (const err of seedResult.errors) {\n console.warn(` ${chalk.yellow(\"warn\")} ${err}`);\n }\n } catch (e) {\n baselineSpinner.fail(\n `Could not record a baseline from the server: ${formatError(e)}`,\n );\n process.exit(1);\n }\n }\n\n // Diff against shadow HEAD is the source of truth for \"what\n // changed since last sync\" — replaces the sha256 checksum map.\n const { changed, deleted } = await diffAgainstShadow(themeRoot, shadow);\n if (!opts.nodelete) assertMemberConfigurationRetained(deleted);\n const managedAssetCount = new ThemeAssetManifest(themeRoot.root).keys()\n .length;\n\n if (\n changed.length === 0 &&\n deleted.length === 0 &&\n managedAssetCount === 0\n ) {\n console.log(\"Nothing to push — local matches the last synced state.\");\n await persistConfig();\n return;\n }\n\n const spinner = ora(`Pushing to ${theme.name} (#${theme.id})…`).start();\n\n // Validate before linking URL references so a schema failure cannot\n // mutate the target theme at all.\n if (!opts.force) {\n const validationErrors: string[] = [];\n for (const file of changed) {\n if (!file.isLiquid) continue;\n for (const diagnostic of file.validateSchema()) {\n if (diagnostic.severity === \"error\") {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n file.read(),\n )) {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n if (validationErrors.length > 0) {\n spinner.fail(\n `Liquid validation failed (${validationErrors.length} error(s)). Use --force to skip.`,\n );\n for (const error of validationErrors) console.error(` ${error}`);\n process.exit(1);\n }\n }\n try {\n await syncer.preflightPush(baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (e) {\n if (e instanceof PushConflictError) {\n renderPullFirst(spinner);\n process.exit(1);\n }\n throw e;\n }\n\n const uploads = planThemeUploads(\n themeRoot,\n changed,\n opts.nodelete ? [] : deleted,\n );\n let linked = 0;\n try {\n linked = await syncer.linkManagedAssets({ replace: true });\n // Reference writes are outside the ordinary resource PUT path. The\n // syncer refreshes its remote SHA afterwards so following PUTs keep\n // the existing Phase-003a conflict chain intact.\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (error) {\n spinner.fail(\n `Could not save remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n\n if (changed.length === 0 && deleted.length === 0 && linked === 0) {\n try {\n await finalizeManifestOnlyPush(syncer, themeRoot, shadow);\n } catch (error) {\n spinner.fail(\n `Could not finalize remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n spinner.succeed(\"Nothing to push — local matches the remote theme.\");\n await persistConfig();\n return;\n }\n\n let uploaded = 0;\n let deletedCount = 0;\n const errors: string[] = [];\n let progress = 0;\n const total =\n uploads.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n ) + (opts.nodelete ? 0 : deleted.length);\n\n for (const upload of uploads) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n errors.length > 0\n ) {\n errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await syncer.uploadMemberConfiguration(upload, baseSha);\n } else {\n await syncer.uploadFile(file, baseSha);\n }\n baseSha = syncer.remoteSha() ?? baseSha;\n uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n progress += 1 + (upload.memberConfiguration?.files.length ?? 0);\n spinner.text = `Pushing ${progress}/${total} files…`;\n }\n\n if (!opts.nodelete && errors.length === 0) {\n for (const key of deleted) {\n try {\n await syncer.deleteRemoteFile(key, baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n deletedCount++;\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n spinner.text = `Pushing ${++progress}/${total} files…`;\n }\n }\n\n if (errors.length === 0) {\n try {\n // The selected target now owns every manifest reference, so a\n // later deleted dev sandbox cannot force a byte upload again.\n syncer.repointManagedAssetsToCurrentTheme();\n } catch (error) {\n errors.push(`Save asset provenance: ${formatError(error)}`);\n }\n }\n\n // Every write is on the server; ask Fluid to commit them as one\n // version. The writes themselves only mark the theme changed — this\n // one call is what makes a 120-file push a single commit instead of\n // 120, and it is the reason the server no longer has to guess where\n // an operation ended.\n //\n // A partial transfer may contain unresolved member dependencies.\n // Managed-asset-only pushes still finalize once every write succeeds.\n if (\n errors.length === 0 &&\n (uploaded > 0 || deletedCount > 0 || linked > 0)\n ) {\n if (!(await syncer.requestSync())) {\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push before publishing.\",\n );\n }\n }\n\n if (errors.length) {\n spinner.warn(`Pushed with ${errors.length} error(s).`);\n for (const err of errors) console.error(` ${err}`);\n // The live theme is missing every rejected file, so this is a\n // failure. Mist's lifecycle retry runs `fluid theme push\n // --auto-baseline` and keys off the exit code, and every other\n // failure in this command already exits 1 — exiting 0 here\n // reported a publish that did not happen.\n //\n // exitCode rather than process.exit(): the command still has\n // output to flush, and an immediate exit can truncate it.\n process.exitCode = 1;\n } else {\n spinner.succeed(\n `Pushed ${uploaded} file(s), saved ${linked} remote asset reference(s)` +\n (deletedCount > 0\n ? `, deleted ${deletedCount} remote file(s).`\n : \".\"),\n );\n }\n\n // Roll shadow HEAD forward only when every write succeeded —\n // committing the working tree on a partial push would make\n // `diffAgainstShadow` treat the failed files as \"already\n // synced\" on the next push, silently swallowing the retry.\n // Leave HEAD stale on failure and let the next push re-diff\n // and re-attempt.\n if (errors.length === 0) {\n // Describe what the push carried rather than stamping every\n // entry \"Push from Fluid CLI\". `changed` is the uploaded set\n // and `deleted` the removed keys — exactly what landed, since\n // we only get here when no write failed.\n const pushedChanges: ChangeEntry[] = [\n ...changed.map((file) => ({\n status: \"modified\" as const,\n path: file.relativePath,\n })),\n ...(opts.nodelete\n ? []\n : deleted.map((key) => ({\n status: \"deleted\" as const,\n path: key,\n }))),\n ];\n try {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\n \"Push\",\n await getActor(),\n new Date(),\n pushedChanges,\n ),\n syncer.acknowledgedMemberContents(),\n );\n } catch (error) {\n console.error(\n `Theme content was uploaded to the server, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Do not repeat the push with --force. Review the remote theme and run a normal pull to recover the baseline.`,\n );\n if (opts.publish) console.error(\"Publishing was not attempted.\");\n process.exitCode = 1;\n return;\n }\n }\n\n await persistConfig();\n\n /**\n * Persist `.fluid-theme.json`. When a config already exists,\n * this is the pre-existing baseSha refresh. When it doesn't\n * (a scaffold's first `--auto-baseline` push), bind the dir\n * to the theme now — best-effort, since the company subdomain\n * needs one more API call — so the next push/pull needs no\n * interactive picker.\n */\n async function persistConfig(): Promise<void> {\n if (config) {\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: config.company,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n return;\n }\n if (!opts.autoBaseline) return;\n try {\n const res = await api.get<{\n data?: { company?: { subdomain?: string } };\n }>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) return;\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n } catch {\n // Best-effort — the shadow baseline alone is enough for\n // the next push to diff correctly.\n }\n }\n\n if (opts.publish && errors.length === 0) {\n const pubSpinner = ora(\"Publishing theme…\").start();\n try {\n await themes.publishApplicationTheme(api, theme.id);\n pubSpinner.succeed(\"Theme published.\");\n } catch (e) {\n pubSpinner.fail(`Publish failed: ${e}`);\n // Content uploaded but the theme is still a draft. Without\n // this the caller is told the publish succeeded.\n process.exitCode = 1;\n }\n }\n },\n );\n}\n\nfunction renderPullFirst(spinner: ReturnType<typeof ora>): void {\n spinner.fail(\"Server has changed since your last pull. Push aborted.\");\n console.log();\n console.log(\n ` ${chalk.cyan(\"Run `fluid theme pull` first\")} to merge the remote changes,`,\n );\n console.log(\n ` then re-run push. Use ${chalk.cyan(\"fluid theme push --force\")} to overwrite anyway.`,\n );\n console.log();\n}\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport { diffAgainstShadow } from \"./merge-push.js\";\nimport { ThemeRoot } from \"./root.js\";\nimport { ShadowRepo } from \"./shadow-repo.js\";\n\nfunction containsSymlink(directory: string): boolean {\n return readdirSync(directory, { withFileTypes: true }).some((entry) => {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n return false;\n return (\n entry.isSymbolicLink() ||\n (entry.isDirectory() && containsSymlink(join(directory, entry.name)))\n );\n });\n}\n\n/** Fail closed for unattended updates. Desktop snapshots can advance shadow\n * HEAD with local work, so only a CLI sync baseline is eligible. */\nexport async function canPullThemeInBackground(\n root: string,\n themeId: number,\n): Promise<boolean> {\n try {\n if (containsSymlink(root)) return false;\n const config = readThemeConfig(root);\n if (\n !config ||\n config.themeId !== themeId ||\n !existsSync(join(root, \".fluid-theme\", \"repo\", \"HEAD\"))\n )\n return false;\n const storedId = readFileSync(\n join(root, \".fluid-theme\", \"theme-id\"),\n \"utf8\",\n ).trim();\n if (storedId !== String(themeId)) return false;\n const manifestPath = join(root, \".fluid-assets.json\");\n if (existsSync(manifestPath)) {\n const raw: unknown = JSON.parse(readFileSync(manifestPath, \"utf8\"));\n if (\n !raw ||\n typeof raw !== \"object\" ||\n !(\"version\" in raw) ||\n raw.version !== 1 ||\n !(\"assets\" in raw) ||\n !raw.assets ||\n typeof raw.assets !== \"object\" ||\n Array.isArray(raw.assets)\n )\n return false;\n const manifest = new ThemeAssetManifest(root);\n if (manifest.entries().some(([, link]) => link.pending)) return false;\n if (\n !config.assetManifestSha ||\n config.assetManifestSha !== manifest.fingerprint()\n )\n return false;\n } else if (\n config.assetManifestSha &&\n config.assetManifestSha !== new ThemeAssetManifest(root).fingerprint()\n )\n return false;\n const shadow = await ShadowRepo.open(root, themeId);\n if (!(await shadow.hasSyncedHead())) return false;\n const diff = await diffAgainstShadow(new ThemeRoot(root), shadow);\n return diff.changed.length === 0 && diff.deleted.length === 0;\n } catch {\n return false;\n }\n}\n","import { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nexport interface MemberOmissionInput {\n /** Both accepted and edited documents: omission must not erase either graph. */\n previousDocuments: readonly (Buffer | null)[];\n availablePaths: ReadonlySet<string>;\n incomingPaths: ReadonlySet<string>;\n ignored: (path: string) => boolean;\n}\n\n/** Missing/ignored is not empty. Return paths to retain without acknowledging\n * their local bytes as remotely accepted. Used before pull writes or pruning. */\nexport function planOmittedMemberFiles(\n input: MemberOmissionInput,\n): Set<string> {\n if (\n input.incomingPaths.has(MEMBER_CONFIGURATION_KEY) &&\n !input.ignored(MEMBER_CONFIGURATION_KEY)\n )\n return new Set();\n\n const protectedPaths = new Set<string>();\n for (const content of input.previousDocuments) {\n if (content === null) continue;\n protectedPaths.add(MEMBER_CONFIGURATION_KEY);\n if (content.byteLength > 2 * 1024 * 1024) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json exceeds 2 MB.\",\n );\n }\n const document: unknown = JSON.parse(content.toString(\"utf8\"));\n if (!isRecord(document) || document.version !== 2) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json must be a version 2 document.\",\n );\n }\n for (const field of [\"sites\", \"menus\", \"menu_references\"]) {\n if (!Array.isArray(document[field])) {\n throw new Error(\n `Cannot preserve member files: members_sites.json ${field} must be an array.`,\n );\n }\n }\n assertDependencyShape(document);\n // An omitted manifest cannot authorize pruning shared composition files.\n // Their dependency relationships live in the template contents, not the manifest.\n if ((document.sites as unknown[]).length > 0) {\n for (const path of input.availablePaths) {\n if (/^(sections|blocks|components|snippets|navbar|footer)\\//.test(path))\n protectedPaths.add(path);\n }\n }\n for (const path of memberDependencyPaths(document, input.availablePaths)) {\n if (\n !isThemeResourceKey(path) ||\n path.includes(\"\\\\\") ||\n [...path].some((character) => character.charCodeAt(0) < 32) ||\n path === \"config/members_sites.json\"\n ) {\n throw new Error(\n `Cannot preserve member files: invalid dependency path ${path}.`,\n );\n }\n protectedPaths.add(path);\n }\n }\n return new Set(\n [...protectedPaths].filter(\n (path) => !input.incomingPaths.has(path) || input.ignored(path),\n ),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Reject incomplete dependency declarations rather than silently pruning files. */\nfunction assertDependencyShape(document: Record<string, unknown>): void {\n const entries = (value: unknown): Record<string, unknown>[] => {\n if (!Array.isArray(value) || !value.every(isRecord)) {\n throw new Error(\n \"Cannot preserve member files: malformed dependency declaration.\",\n );\n }\n return value;\n };\n for (const entry of [\n ...entries(document.templates ?? []),\n ...entries(document.menu_references),\n ]) {\n if (typeof entry.path !== \"string\" || !entry.path) {\n throw new Error(\n \"Cannot preserve member files: dependency path is missing.\",\n );\n }\n }\n for (const site of entries(document.sites)) {\n if (site.layout_name !== null && typeof site.layout_name !== \"string\") {\n throw new Error(\"Cannot preserve member files: invalid layout name.\");\n }\n for (const page of entries(site.pages)) {\n if (\n page.template_path !== null &&\n typeof page.template_path !== \"string\"\n ) {\n throw new Error(\n \"Cannot preserve member files: invalid page template path.\",\n );\n }\n }\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, looksBinary, readIfExists } from \"./shadow-repo.js\";\nimport { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { planOmittedMemberFiles } from \"./member-transfer.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { commitPushedState } from \"./merge-push.js\";\nimport { syncCommitSubject, type SyncActor } from \"./sync-identity.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\n/** `write(..., RESOLVES_CONFLICT)`: this write is the chosen side of a conflict. */\nconst RESOLVES_CONFLICT = true;\n\n/**\n * Appended to every per-file write failure. Resource keys are verbatim\n * relative paths, so a template named `Sale 11/14/2025, 12:30 PM` asks\n * the filesystem for directories that Windows (and any path already\n * occupied by a file) refuses. Renaming the template in the admin is\n * the only fix the user owns.\n */\nconst RENAME_REMEDY =\n 'Rename the template in the admin visual builder to remove / \\\\ : * ? \" < > | from its name, then pull again.';\n\nexport interface MergePullResult {\n /** Files written from the remote without any local content to merge. */\n written: number;\n /** Files that went through a clean three-way merge with no markers. */\n merged: number;\n /** Files where markers remain — the caller must surface these. */\n conflicts: string[];\n /** Conflicting files auto-resolved to one side via `resolve`. */\n autoResolved: string[];\n /** Files removed locally because remote no longer emitted them. */\n deleted: number;\n /** Files that needed no change (already in sync). */\n skipped: number;\n /** Non-fatal per-file errors. */\n errors: string[];\n /** The remote revision must not be accepted as the new sync baseline. */\n baselinePreserved?: boolean;\n}\n\nexport type FetchBinary = (url: string) => Promise<Buffer>;\n\nexport interface MergePullInput {\n themeRoot: ThemeRoot;\n backgroundGuard?: BackgroundPullGuard;\n shadow: ShadowRepo;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n delete: boolean;\n /**\n * When true, skip the merge and take remote wholesale for every\n * file. Used by `fluid theme pull --force` when the user wants a\n * clean slate. HEAD still advances to reflect the state we wrote.\n */\n force?: boolean;\n /**\n * Remote resources already represented by `.fluid-assets.json`. They are\n * intentionally not materialized; ShadowRepo retains only a tiny path\n * sentinel, never their binary bytes.\n */\n skipRemoteKeys?: ReadonlySet<string>;\n /**\n * Non-interactive conflict resolution (`fluid theme pull --resolve\n * <local|remote>`): conflicting hunks are resolved to the chosen\n * side via `git merge-file --ours/--theirs` instead of writing\n * `<<<<<<<` markers; binary conflicts keep the whole chosen side.\n * Because \"remote\" resolution discards local hunks that live\n * NOWHERE else (shadow HEAD only holds the last-synced base, not\n * the user's edits), the pre-merge working tree is committed to the\n * shadow repo first — so the discarded state stays restorable.\n * Undefined = current behavior (markers).\n */\n resolve?: \"local\" | \"remote\";\n /**\n * Signed-in user for commit stamping (best-effort — null falls back\n * to the legacy machine-flavored subjects).\n */\n actor?: SyncActor | null;\n onProgress?: (done: number, total: number) => void;\n}\n\n/**\n * Reconcile the just-downloaded remote tree against the working tree,\n * using the shadow repo's HEAD as the merge base. Text files that\n * diverge on both sides get run through `git merge-file`; the result\n * — clean or with `<<<<<<<` markers — is written to disk. Binary\n * files fall back to \"take remote\" because there is no principled\n * three-way merge for them.\n *\n * HEAD advances to reflect the remote state we just materialized,\n * even when unresolved conflict markers remain in the working tree.\n * The user resolves markers in their editor and re-runs push; push\n * refuses to send files whose content still starts with a marker,\n * so a half-resolved push cannot silently ship broken content.\n */\nexport async function mergePull(\n input: MergePullInput,\n): Promise<MergePullResult> {\n const { themeRoot, shadow, remote, fetchBinary, onProgress } = input;\n const doDelete = input.delete;\n const result: MergePullResult = {\n written: 0,\n merged: 0,\n conflicts: [],\n autoResolved: [],\n deleted: 0,\n skipped: 0,\n errors: [],\n };\n\n // In `resolve` mode, working-tree writes are DEFERRED until after\n // the merge loop: if any conflict got auto-resolved, the pre-merge\n // working tree (still untouched at that point) is committed to the\n // shadow repo first, so the side we discard remains restorable.\n const pendingWrites: Array<{\n key: string;\n file: ThemeFile;\n content: Buffer;\n record: () => void;\n resolvesConflict: boolean;\n }> = [];\n // Keys whose bytes never reached disk — refused by the traversal\n // guard, or failed on the write. They must be kept out of the shadow\n // HEAD commit below, exactly like a failed download: HEAD is what\n // push diffs against, so recording content we did not write would\n // turn a tolerated failure into a remote DELETE. Refused keys have a\n // second reason — git rejects paths like `../x` outright, and the\n // commit takes the whole pull down with it.\n const unwrittenKeys = new Set<string>();\n const tryWrite = (key: string, file: ThemeFile, content: Buffer): boolean => {\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([key], () => file.write(content));\n else file.write(content);\n return true;\n } catch (e) {\n if (e instanceof BackgroundPullChangedError) throw e;\n unwrittenKeys.add(key);\n result.errors.push(`Reconcile ${key}: ${errMsg(e)}. ${RENAME_REMEDY}`);\n return false;\n }\n };\n /**\n * Write `content`, then apply `record` — the bookkeeping that says\n * which bucket of the summary this file counted as. The two travel\n * together, including into the deferred flush: bookkeeping applied\n * when the write was merely QUEUED would report a file the flush\n * never managed to write as both written and errored.\n */\n const write = (\n key: string,\n file: ThemeFile,\n content: Buffer,\n record: () => void,\n resolvesConflict = false,\n ): void => {\n if (input.resolve) {\n pendingWrites.push({ key, file, content, record, resolvesConflict });\n return;\n }\n if (tryWrite(key, file, content)) record();\n };\n\n const remoteContent = new Map<string, Buffer>();\n const remoteKeys = new Set<string>();\n let done = 0;\n for (const resource of remote) {\n remoteKeys.add(resource.key);\n if (input.skipRemoteKeys?.has(resource.key)) {\n onProgress?.(++done, remote.length);\n continue;\n }\n try {\n const buf = await materialize(resource, fetchBinary);\n if (buf) remoteContent.set(resource.key, buf);\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${errMsg(e)}`);\n }\n onProgress?.(++done, remote.length);\n }\n\n input.backgroundGuard?.assertUnchanged();\n let retainedMemberPaths: Set<string>;\n try {\n if (\n remoteKeys.has(MEMBER_CONFIGURATION_KEY) &&\n !themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY) &&\n !remoteContent.has(MEMBER_CONFIGURATION_KEY)\n ) {\n throw new Error(\n \"members_sites.json was listed but could not be downloaded; previous member state retained.\",\n );\n }\n retainedMemberPaths = planOmittedMemberFiles({\n previousDocuments: [\n readIfExists(themeRoot.file(MEMBER_CONFIGURATION_KEY).absolutePath),\n await shadow.blobAtHead(MEMBER_CONFIGURATION_KEY),\n ],\n availablePaths: new Set([\n ...themeRoot.files().map((file) => file.relativePath),\n ...(await shadow.headPaths()),\n ]),\n incomingPaths: remoteKeys,\n ignored: (path) => themeRoot.ignore.ignore(path),\n });\n } catch (error) {\n result.errors.push(errMsg(error));\n result.baselinePreserved = true;\n return result;\n }\n for (const path of retainedMemberPaths) remoteContent.delete(path);\n\n for (const [key, remoteBuf] of remoteContent) {\n const file = themeRoot.file(key);\n\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Reconcile ${key}: path traversal detected`);\n unwrittenKeys.add(key);\n continue;\n }\n\n if (input.force) {\n // `--force`: clobber everything with the remote content. HEAD\n // still advances below so the next pull's merge base is right.\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n const localBuf = readIfExists(file.absolutePath);\n const baseBuf = await shadow.blobAtHead(key);\n\n if (localBuf == null) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (localBuf.equals(remoteBuf)) {\n result.skipped++;\n continue;\n }\n\n if (baseBuf && localBuf.equals(baseBuf)) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (baseBuf && remoteBuf.equals(baseBuf)) {\n // A prior unresolved pull advances shadow HEAD to the clean remote\n // state while leaving markers in the working tree. A later\n // `--resolve remote` must be able to finish that interrupted clone;\n // treating the marker-filled file as an ordinary local edit traps\n // every retry in the same broken state.\n if (\n input.resolve === \"remote\" &&\n !looksBinary(localBuf) &&\n hasGeneratedConflictMarkers(localBuf)\n ) {\n write(\n key,\n file,\n remoteBuf,\n () => result.autoResolved.push(`${key} (cleared conflict markers)`),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n // Server didn't move; keep the user's local edits.\n result.skipped++;\n continue;\n }\n\n const isBinary =\n looksBinary(localBuf) ||\n looksBinary(remoteBuf) ||\n (baseBuf ? looksBinary(baseBuf) : false);\n\n if (isBinary) {\n // No textual merge for binary files: whole-file either/or.\n if (input.resolve === \"local\") {\n // Keep the local side untouched — nothing to write.\n result.autoResolved.push(`${key} (binary — kept local)`);\n continue;\n }\n // Take remote. In `--resolve remote` that's the chosen side;\n // otherwise flag it so the user can compare against the shadow\n // repo (`git show HEAD:...`) and reapply local intent by hand.\n write(\n key,\n file,\n remoteBuf,\n () => {\n if (input.resolve === \"remote\") {\n result.autoResolved.push(`${key} (binary — kept remote)`);\n } else {\n result.conflicts.push(`${key} (binary — kept remote)`);\n }\n },\n input.resolve === \"remote\",\n );\n continue;\n }\n\n // First merge WITHOUT a favor side so we know whether this file\n // actually conflicts — that drives honest bookkeeping (merged vs\n // autoResolved) and the pre-merge snapshot decision.\n const { merged, hasConflicts } = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n );\n if (hasConflicts && input.resolve) {\n const favored = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n input.resolve,\n );\n write(\n key,\n file,\n favored.merged,\n () => result.autoResolved.push(key),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n write(key, file, merged, () => {\n if (hasConflicts) result.conflicts.push(key);\n else result.merged++;\n });\n }\n\n // Flush deferred writes (resolve mode only). When any conflict was\n // auto-resolved, snapshot the pre-merge working tree first — it is\n // the ONLY place the losing side of each resolution still exists.\n if (input.resolve) {\n if (pendingWrites.some((w) => w.resolvesConflict)) {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\"Snapshot before pull\", input.actor ?? null),\n );\n }\n input.backgroundGuard?.assertUnchanged();\n for (const { key, file, content, record } of pendingWrites) {\n if (tryWrite(key, file, content)) record();\n }\n }\n\n if (doDelete && (await shadow.hasHead())) {\n for (const file of themeRoot.files()) {\n if (\n remoteKeys.has(file.relativePath) ||\n retainedMemberPaths.has(file.relativePath)\n )\n continue;\n // Stylesheet-hidden files are intentionally absent from the\n // resources index — deleting them locally would fight the\n // STYLESHEET_STRICT_INPUT feature, not honor a real deletion.\n if (isStylesheetKey(file.relativePath)) continue;\n\n const baseBuf = await shadow.blobAtHead(file.relativePath);\n if (!baseBuf) continue; // never seen by shadow — leave alone\n const localBuf = readIfExists(file.absolutePath);\n if (!localBuf) continue;\n if (!localBuf.equals(baseBuf)) continue; // user has local edits\n\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([file.relativePath], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n result.deleted++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n // ignore best-effort deletion\n }\n }\n }\n\n // Advance HEAD to the remote state we just downloaded. Even when\n // some files carry unresolved conflict markers, HEAD reflects the\n // server's clean bytes — the next push's diff-against-HEAD is what\n // surfaces the markers as \"unresolved\".\n //\n // Preserve failed-download paths from the previous HEAD so a\n // transient network error doesn't quietly drop them from the merge\n // base. If a file was in HEAD before the pull and its download\n // just failed, the next pull needs the previous blob as the base\n // for its merge — otherwise `blobAtHead` returns null next time and\n // a server-side change fires a spurious null-base conflict on a\n // file the user never touched.\n //\n // Keys whose write failed take the same route: their bytes are on\n // the server but not on disk, so recording the remote content here\n // would claim a state this checkout never had.\n try {\n const commitEntries: Array<{ path: string; sha: string }> = [];\n for (const [key, buf] of remoteContent) {\n if (unwrittenKeys.has(key)) continue;\n commitEntries.push({ path: key, sha: await shadow.writeBlob(buf) });\n }\n for (const key of new Set([...remoteKeys, ...retainedMemberPaths])) {\n if (input.skipRemoteKeys?.has(key) && !retainedMemberPaths.has(key))\n continue;\n // Downloaded AND written — already added above.\n if (remoteContent.has(key) && !unwrittenKeys.has(key)) continue;\n const prevBlob = await shadow.blobAtHead(key);\n if (prevBlob == null) continue; // wasn't in HEAD to begin with\n commitEntries.push({ path: key, sha: await shadow.writeBlob(prevBlob) });\n }\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let managedAssetSentinelSha: string | undefined;\n for (const key of input.skipRemoteKeys ?? []) {\n // Ignored resources without a manifest link remain outside the shadow\n // entirely. Otherwise removing an ignore rule could make a skipped file\n // look like a local deletion on the next push.\n if (!remoteKeys.has(key) || !assetManifest.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n commitEntries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // A pull containing only manifest-backed assets still needs a shadow commit:\n // sentinel entries replace any legacy binary blobs while keeping a baseline\n // for an intentional manifest-entry removal on a later push.\n // When the server becomes empty, record an empty tree rather than retaining\n // stale paths from HEAD as a future merge/delete baseline — but only when\n // the server really is empty. Empty entries with a non-empty remote mean\n // every write failed, and HEAD must survive as the next pull's merge base.\n if (\n commitEntries.length > 0 ||\n (remoteKeys.size === 0 && (await shadow.hasHead()))\n ) {\n input.backgroundGuard?.assertUnchanged();\n await shadow.commitState(\n commitEntries,\n syncCommitSubject(\"Pull\", input.actor ?? null),\n );\n }\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n result.baselinePreserved = true;\n result.errors.push(\n `Downloaded changes have already been applied locally, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Review the files and retry a normal pull before pushing; do not use --force to bypass the missing baseline.`,\n );\n }\n\n return result;\n}\n\nfunction hasGeneratedConflictMarkers(content: Buffer): boolean {\n const text = content.toString(\"utf8\");\n return (\n /^<<<<<<< local\\r?$/m.test(text) &&\n /^=======\\r?$/m.test(text) &&\n /^>>>>>>> remote\\r?$/m.test(text)\n );\n}\n\nasync function materialize(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","import {\n BackgroundPullChangedError,\n BackgroundPullGuard,\n} from \"../theme/background-pull-guard.js\";\nimport { canPullThemeInBackground } from \"../theme/background-pull.js\";\nimport { join, resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { readThemeConfig, writeThemeConfig } from \"../theme-config.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport { mergePull } from \"../theme/merge-pull.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport { fetchSyncActor } from \"../theme/sync-identity.js\";\nimport { selectTheme, findTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { CaseCollisionError } from \"../theme/case-collisions.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\nasync function fetchCompanySubdomain(\n api: ReturnType<typeof createApiClient>,\n): Promise<string> {\n const res = await api.get<CompanyMe>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n return subdomain;\n}\n\nexport function createPullCommand(): Command {\n return new Command(\"pull\")\n .description(\"Pull a remote theme to your local directory\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to pull\")\n .option(\"-n, --nodelete\", \"Do not delete local files missing on remote\")\n .option(\"--root <path>\", \"Theme root directory\")\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .option(\n \"--only-if-clean\",\n \"Skip with exit code 3 when local work cannot be proven synced\",\n )\n .option(\n \"-f, --force\",\n \"Overwrite local without merging (skip conflict markers)\",\n )\n .option(\n \"--resolve <side>\",\n \"Auto-resolve merge conflicts to one side instead of writing \" +\n \"conflict markers: 'local' keeps your files' hunks, 'remote' \" +\n \"takes the server's (for non-interactive use)\",\n )\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n root?: string;\n yes?: boolean;\n force?: boolean;\n resolve?: string;\n onlyIfClean?: boolean;\n }) => {\n let spinner: ReturnType<typeof ora> | undefined;\n try {\n requireToken();\n\n if (\n opts.resolve !== undefined &&\n opts.resolve !== \"local\" &&\n opts.resolve !== \"remote\"\n ) {\n console.error(\n `Invalid --resolve value \"${opts.resolve}\" — use \"local\" or \"remote\".`,\n );\n process.exit(1);\n }\n const resolveSide = opts.resolve as \"local\" | \"remote\" | undefined;\n\n const api = createApiClient();\n const workspace = findWorkspace();\n\n const theme = opts.theme\n ? await findTheme(api, opts.theme)\n : await selectTheme(api, \"Select a theme to pull\");\n\n const subdomain = await fetchCompanySubdomain(api);\n let root: string;\n if (opts.root) {\n root = opts.root;\n } else if (workspace) {\n root =\n resolveThemeRootFromCwd(workspace) ??\n join(workspace.root, \"local\", subdomain);\n } else {\n root = `.`;\n }\n\n const absoluteRoot = resolve(root);\n const existingConfig = readThemeConfig(absoluteRoot);\n const backgroundGuard = opts.onlyIfClean\n ? new BackgroundPullGuard(absoluteRoot)\n : undefined;\n\n console.log();\n console.log(` Theme: ${chalk.bold(theme.name)} (#${theme.id})`);\n console.log(` Company: ${chalk.bold(subdomain)}`);\n console.log(` Target: ${chalk.bold(absoluteRoot)}`);\n console.log();\n\n if (!opts.yes) {\n const { confirmed } = await prompts(\n {\n type: \"confirm\",\n name: \"confirmed\",\n message: \"Pull theme to this directory?\",\n initial: true,\n },\n { onCancel: () => process.exit(130) },\n );\n if (!confirmed) {\n console.log(\"Aborted.\");\n process.exit(0);\n }\n }\n\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n console.log(\"Skipped: local changes or an unknown sync baseline.\");\n process.exit(3);\n }\n backgroundGuard?.assertUnchanged();\n const themeRoot = new ThemeRoot(root);\n const shadow = await ShadowRepo.open(absoluteRoot, theme.id);\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot,\n themeId: theme.id,\n });\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // Fire the identity lookup alongside the download — it never\n // rejects, and mergePull only awaits it at commit time.\n const actorPromise = fetchSyncActor(api);\n\n spinner = ora(`Pulling ${theme.name} (#${theme.id})…`).start();\n const resources = await syncer\n .downloadAll()\n .catch((error: unknown) => {\n if (!(error instanceof CaseCollisionError)) throw error;\n if (spinner) spinner.fail(error.message);\n process.exit(1);\n });\n // Recheck after network I/O and before any asset or content writes.\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n spinner.stop();\n console.log(\"Skipped: local files changed while downloading.\");\n process.exit(3);\n }\n const externalizedAssets = await syncer.externalizePulledAssets(\n resources,\n {\n delete: !opts.nodelete,\n backgroundGuard,\n },\n );\n\n const result = await mergePull({\n themeRoot,\n shadow,\n backgroundGuard,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n delete: !opts.nodelete,\n force: opts.force ?? false,\n skipRemoteKeys: externalizedAssets.managedKeys,\n resolve: resolveSide,\n actor: await actorPromise,\n onProgress: (done, total) => {\n if (spinner) spinner.text = `Downloading ${done}/${total} files…`;\n },\n });\n result.errors.push(...externalizedAssets.errors);\n\n const parts: string[] = [];\n if (result.written > 0) parts.push(`wrote ${result.written} file(s)`);\n if (result.merged > 0)\n parts.push(`merged ${result.merged} file(s) cleanly`);\n if (externalizedAssets.linked > 0) {\n parts.push(\n `kept ${externalizedAssets.linked} binary asset(s) remote`,\n );\n }\n if (result.autoResolved.length > 0)\n parts.push(\n `auto-resolved ${result.autoResolved.length} conflict(s) (kept ${resolveSide})`,\n );\n if (result.deleted > 0)\n parts.push(`deleted ${result.deleted} local file(s)`);\n if (result.skipped > 0)\n parts.push(`${result.skipped} already in sync`);\n\n if (result.errors.length) {\n spinner.warn(\n `Pulled with ${result.errors.length} error(s): ${parts.join(\", \")}.`,\n );\n for (const e of result.errors) console.error(` ${e}`);\n // A failed file or restore point leaves the checkout incomplete\n // or without a safe sync baseline. Conflicts already exit 1\n // below; this branch used to fall through at 0.\n //\n // Mist spawns `fluid theme pull -t <id> --root <path> --yes` to\n // clone a theme, and only records a lifecycle failure when the\n // exit code is non-zero — so a partial clone never reached the\n // agent's context and it went on to edit a checkout that was\n // missing files.\n //\n // exitCode rather than an immediate exit: the per-file error\n // list is printed right here and `.fluid-theme.json` is written\n // below, both of which still need to happen.\n process.exitCode = 1;\n }\n // Conflicts still need to be reported when snapshot saving failed;\n // Mist parses these paths to guide the user through resolution.\n if (result.conflicts.length > 0) {\n spinner.warn(\n `${result.conflicts.length} conflict(s) — resolve markers before pushing: ${parts.join(\", \")}.`,\n );\n console.log();\n for (const c of result.conflicts) {\n console.log(` ${chalk.yellow(\"CONFLICT\")} ${c}`);\n }\n console.log();\n console.log(\n ` Edit each file above to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} markers,`,\n );\n console.log(\n ` then run ${chalk.cyan(\"fluid theme push\")} once your resolution is in place.`,\n );\n console.log();\n } else if (result.errors.length === 0) {\n spinner.succeed(parts.join(\", \") || \"Already up to date.\");\n }\n\n // A rejected transfer or failed snapshot did not accept the revision.\n if (result.baselinePreserved) return;\n\n // Update .fluid-theme.json — baseSha is the server-side handle\n // the push preflight uses; the merge base itself lives in the\n // shadow repo.\n const remoteSha = syncer.remoteSha();\n backgroundGuard?.assertUnchanged();\n const updateConfig = () =>\n writeThemeConfig(absoluteRoot, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: remoteSha ?? existingConfig?.baseSha,\n assetManifestSha: new ThemeAssetManifest(\n absoluteRoot,\n ).fingerprint({\n excludePending: true,\n }),\n });\n\n if (backgroundGuard)\n backgroundGuard.mutate([\".fluid-theme.json\"], updateConfig);\n else updateConfig();\n if (result.conflicts.length > 0) process.exit(1);\n } catch (error) {\n if (!(error instanceof BackgroundPullChangedError)) throw error;\n spinner?.stop();\n console.log(error.message);\n process.exitCode = 3;\n }\n },\n );\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport {\n findMissingSectionReferences,\n validateSchemaText,\n VALID_SETTING_TYPES,\n type BlocksSchemaType,\n type Diagnostic,\n type TemplateInput,\n} from \"@fluid-app/theme-schema\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface FileDiagnostics {\n path: string;\n diagnostics: Diagnostic[];\n}\n\n// A theme section is defined by a liquid file under the top-level `sections/`\n// directory. Returns the section name a `{% section %}` tag would reference, or\n// null if the file is not a section definition. Handles both the flat layout\n// (`sections/hero.liquid`) and the nested one (`sections/hero/index.liquid`).\nfunction sectionNameOf(relativePath: string): string | null {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts[0] === \"sections\" && parts.length >= 2) {\n return parts[1]!.replace(/\\.liquid$/, \"\");\n }\n return null;\n}\n\nexport function createLintCommand(): Command {\n return new Command(\"lint\")\n .description(\"Validate theme files locally (read-only — no upload)\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .option(\"--json\", \"Output results as compact JSON\")\n .action(async (opts: { root: string; json?: boolean }) => {\n // Resolve the theme root the same way push/dev do: when left at the\n // default, prefer the workspace's theme root if we're inside one.\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n const message = `'${rootPath}' does not look like a theme directory.`;\n if (opts.json) {\n console.log(JSON.stringify({ ok: false, error: message }));\n } else {\n console.error(message);\n }\n process.exit(1);\n }\n\n const files = themeRoot.files();\n // Read each liquid file once and reuse the content for both passes\n // (validateSchemaText and the section scan) to avoid a double disk read.\n const liquidFiles = files\n .filter((f) => f.isLiquid)\n .map((f) => ({ file: f, content: f.read() }));\n\n const byFile = new Map<string, Diagnostic[]>();\n const record = (path: string, diagnostic: Diagnostic): void => {\n const existing = byFile.get(path);\n if (existing) existing.push(diagnostic);\n else byFile.set(path, [diagnostic]);\n };\n\n // ── Schema pass — the same {% schema %} validation `fluid theme push`\n // runs. blocksSchemaType mirrors ThemeFile.validateSchema: page/layout\n // templates use object blocks, sections use array blocks.\n for (const { file, content } of liquidFiles) {\n const blocksSchemaType: BlocksSchemaType = file.isTemplate\n ? \"object\"\n : \"array\";\n for (const diagnostic of validateSchemaText(content, {\n blocksSchemaType,\n })) {\n record(file.relativePath, diagnostic);\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(content)) {\n record(file.relativePath, diagnostic);\n }\n }\n\n // ── Section pass — flag `{% section 'x' %}` references to a section\n // that has no definition on disk. Section definitions and assets are\n // not themselves referrers, so they are excluded from the scan.\n const existingSectionNames = new Set<string>();\n for (const { file } of liquidFiles) {\n const name = sectionNameOf(file.relativePath);\n if (name) existingSectionNames.add(name);\n }\n const referrers: TemplateInput[] = liquidFiles\n .filter(({ file }) => sectionNameOf(file.relativePath) === null)\n .map(({ file, content }) => ({ path: file.relativePath, content }));\n for (const missing of findMissingSectionReferences(\n referrers,\n existingSectionNames,\n )) {\n record(missing.templatePath, missing.diagnostic);\n }\n\n const results: FileDiagnostics[] = [...byFile.entries()]\n .map(([path, diagnostics]) => ({ path, diagnostics }))\n .sort((a, b) => a.path.localeCompare(b.path));\n\n let errors = 0;\n let warnings = 0;\n for (const { diagnostics } of results) {\n for (const d of diagnostics) {\n if (d.severity === \"error\") errors++;\n else warnings++;\n }\n }\n\n // Surface the canonical setting types once (not in every diagnostic) so a\n // consumer fixing an \"Invalid settings type\" error has the valid set to\n // hand without it bloating each message.\n const hasInvalidSettingType = results.some(({ diagnostics }) =>\n diagnostics.some(\n (d) =>\n d.target?.kind === \"setting\" &&\n d.target.field === \"type\" &&\n d.target.settingType !== undefined,\n ),\n );\n\n if (opts.json) {\n console.log(\n JSON.stringify({\n ok: errors === 0,\n errors,\n warnings,\n filesChecked: liquidFiles.length,\n ...(hasInvalidSettingType\n ? { validSettingTypes: VALID_SETTING_TYPES }\n : {}),\n files: results,\n }),\n );\n } else {\n printText(results, errors, warnings, liquidFiles.length);\n }\n\n process.exit(errors > 0 ? 1 : 0);\n });\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction printText(\n results: FileDiagnostics[],\n errors: number,\n warnings: number,\n filesChecked: number,\n): void {\n for (const { path, diagnostics } of results) {\n console.log(chalk.bold(path));\n for (const d of diagnostics) {\n const label =\n d.severity === \"error\"\n ? chalk.red(\"error\".padEnd(7))\n : chalk.yellow(\"warning\".padEnd(7));\n // Only the first line — a few messages (e.g. the `Invalid JSON:` parse\n // error) carry a multi-line body that `--json` preserves in full.\n const message = d.message.split(\"\\n\")[0];\n console.log(` ${label} ${message}`);\n }\n }\n\n const suffix = `(${plural(filesChecked, \"file\")} checked)`;\n if (errors > 0) {\n console.log(\n `\\n${chalk.red(`✖ ${plural(errors, \"error\")}, ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else if (warnings > 0) {\n console.log(\n `\\n${chalk.yellow(`⚠ ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else {\n console.log(`${chalk.green(\"✓ No problems found\")} ${suffix}`);\n }\n}\n","import { Command } from \"commander\";\nimport { execFileSync } from \"node:child_process\";\nimport { rmSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport prompts from \"prompts\";\n\nconst DEFAULT_CLONE_URL = \"git@github.com:fluid-commerce/base-theme.git\";\n\nconst SAFE_NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/;\n\nexport function createInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Initialize a new theme by cloning the base theme\")\n .argument(\"[name]\", \"Directory name for the new theme\")\n .option(\"-u, --clone-url <url>\", \"Git URL to clone from\", DEFAULT_CLONE_URL)\n .action(async (name: string | undefined, opts: { cloneUrl: string }) => {\n if (!name) {\n const res = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Theme name\",\n },\n { onCancel: () => process.exit(130) },\n );\n name = res.name as string;\n if (!name) {\n console.error(\"No name provided.\");\n process.exit(1);\n }\n }\n\n if (!SAFE_NAME_RE.test(name)) {\n console.error(\n `Invalid theme name: '${name}'. Use only letters, numbers, hyphens, underscores, and dots.`,\n );\n process.exit(1);\n }\n\n console.log(`Cloning theme from ${opts.cloneUrl} into ${name}…`);\n execFileSync(\"git\", [\"clone\", opts.cloneUrl, name], { stdio: \"inherit\" });\n\n for (const dir of [\".git\", \".github\"]) {\n const path = join(name, dir);\n if (existsSync(path)) rmSync(path, { recursive: true, force: true });\n }\n\n console.log(`\\nTheme initialized in ./${name}`);\n console.log(`Next steps:\\n cd ${name}\\n fluid theme push`);\n });\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\n// A skill is a directory containing a SKILL.md. The bundled skills directory\n// holds one such directory per skill (e.g. `themes-review/`).\nexport function listSkillNames(skillsDir: string): string[] {\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(skillsDir, name, \"SKILL.md\")))\n .sort();\n}\n\nexport interface InstallSkillsOptions {\n /** Directory holding the bundled skills (one sub-directory per skill). */\n sourceDir: string;\n /** Directory the skills are copied into (one sub-directory per skill). */\n targetRoot: string;\n /** Overwrite existing skills without asking. */\n force: boolean;\n /**\n * Asked once per skill that already exists when not forcing. Return true to\n * overwrite, false to leave the existing copy untouched.\n */\n confirmOverwrite: (name: string) => Promise<boolean>;\n /**\n * Called immediately for each install that left a temporary backup directory\n * behind (it could not be removed). Reported as it happens — not via the\n * return value — so the warning isn't lost if a later skill throws mid-loop.\n */\n onLeftover: (path: string) => void;\n}\n\nexport interface InstallSkillsResult {\n readonly installed: readonly string[];\n readonly skipped: readonly string[];\n}\n\n// Copy each bundled skill into `targetRoot/<name>`. Existing skills are only\n// replaced with `force` or an affirmative `confirmOverwrite`; everything else is\n// reported as skipped so the caller can summarize what happened.\nexport async function installSkills(\n options: InstallSkillsOptions,\n): Promise<InstallSkillsResult> {\n const { sourceDir, targetRoot, force, confirmOverwrite, onLeftover } =\n options;\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n mkdirSync(targetRoot, { recursive: true });\n\n for (const name of listSkillNames(sourceDir)) {\n const from = join(sourceDir, name);\n const to = join(targetRoot, name);\n const exists = existsSync(to);\n\n if (exists && !force && !(await confirmOverwrite(name))) {\n skipped.push(name);\n continue;\n }\n\n // Atomic copy + swap: a failed copy never destroys an existing install, and\n // the whole-directory replace prunes files removed from the bundled skill.\n // Report any leftover backup immediately so the warning isn't lost if a\n // later skill throws mid-loop.\n const leftover = replaceDirectory(from, to);\n if (leftover !== null) onLeftover(leftover);\n installed.push(name);\n }\n\n return { installed, skipped };\n}\n\n/**\n * Replace `target` with a fresh copy of `source` without ever leaving `target`\n * missing or partially written.\n *\n * Filesystem copies are not atomic, so a naive \"delete then copy\" loses the\n * original if the copy fails (permissions, no disk space, an interrupted\n * process). This stages the copy in a sibling directory and only swaps it into\n * place once it has fully succeeded; an existing `target` is moved aside to a\n * sibling backup first and restored if the swap fails. Because the whole\n * directory is replaced, files removed or renamed in `source` do not linger.\n *\n * Staging and backup directories live beside `target`, so its parent must\n * already exist and be on the same filesystem — that keeps the swap a cheap,\n * atomic rename rather than a cross-device copy.\n *\n * Not safe against a second process racing on the same `target`; intended for\n * single-process CLI use.\n *\n * @returns the path of a leftover backup directory that could not be removed\n * after an otherwise-successful replace (the previous contents are retained\n * there for manual cleanup), or `null` when nothing was left behind. The caller\n * should surface a non-null result so the leftover isn't silently hidden.\n */\nexport function replaceDirectory(\n source: string,\n target: string,\n): string | null {\n const staging = reserveSiblingPath(target, \"staging\");\n try {\n cpSync(source, staging, { recursive: true });\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n\n // No existing target: a single rename moves the staged copy into place.\n if (!existsSync(target)) {\n return swapIntoPlace(staging, target, null);\n }\n\n // Existing target: move it aside first so the swap stays reversible.\n const backup = reserveSiblingPath(target, \"backup\");\n try {\n renameSync(target, backup);\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n return swapIntoPlace(staging, target, backup);\n}\n\n// Move the staged copy into `target`. If the move fails, restore the original\n// from `backup` (when there was one) *before* any cleanup, so a failed staging\n// removal can never leave the caller without a directory at `target`; then\n// best-effort discard the staged copy and rethrow. On success, returns the\n// backup path if it could not be removed (a leftover the caller should report),\n// or `null`.\nfunction swapIntoPlace(\n staging: string,\n target: string,\n backup: string | null,\n): string | null {\n try {\n renameSync(staging, target);\n } catch (error) {\n if (backup !== null) restoreBackup(backup, target, error);\n removeQuietly(staging);\n throw error;\n }\n return removeQuietly(backup);\n}\n\n// Best-effort restore of the original directory. If even this fails, surface an\n// error pointing at the backup so the user can recover their data by hand.\nfunction restoreBackup(backup: string, target: string, cause: unknown): void {\n try {\n renameSync(backup, target);\n } catch {\n throw new Error(\n `Failed to replace ${target}; its previous contents are preserved at ${backup}.`,\n { cause },\n );\n }\n}\n\n// Best-effort removal of a temporary staging/backup directory. Cleanup must\n// never throw: a failure here should not mask the real outcome or strand the\n// caller. Returns the path when removal failed (so the caller can surface the\n// leftover), or `null` when the directory was removed or there was none.\nfunction removeQuietly(path: string | null): string | null {\n if (path === null) return null;\n try {\n rmSync(path, { recursive: true, force: true });\n return null;\n } catch {\n return path;\n }\n}\n\n// Pick a sibling path of `basePath` that does not exist yet, so renaming onto it\n// is a clean create on every platform (Windows rejects a rename onto an existing\n// directory). Deterministic — no randomness.\nfunction reserveSiblingPath(basePath: string, label: string): string {\n let candidate = `${basePath}.${label}`;\n for (let n = 1; existsSync(candidate); n += 1) {\n candidate = `${basePath}.${label}.${n}`;\n }\n return candidate;\n}\n","import { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { installSkills, listSkillNames } from \"../skills/install.js\";\n\n// Where skills land by default — `.agents/skills/` is the tool-neutral\n// convention for agent skills.\nconst DEFAULT_TARGET_DIR = \".agents/skills\";\n\n// The bundled skills ship beside the compiled CLI at `dist/skills/` and live at\n// the package root `skills/` in source. Resolve relative to this module so the\n// lookup works whether the code runs from the published bundle or from source.\nfunction resolveBundledSkillsDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, \"skills\"), // dist/skills (bundled build output)\n join(here, \"..\", \"skills\"), // package root from dist/index.mjs\n join(here, \"..\", \"..\", \"skills\"), // package root from src/commands/\n ];\n for (const dir of candidates) {\n if (listSkillNames(dir).length > 0) return dir;\n }\n // Last resort: walk up looking for a skills/ dir that holds a skill.\n let dir = here;\n for (let depth = 0; depth < 6; depth++) {\n const candidate = join(dir, \"skills\");\n if (listSkillNames(candidate).length > 0) return candidate;\n dir = dirname(dir);\n }\n throw new Error(\n \"Could not locate the bundled theme skills — this is a packaging bug.\",\n );\n}\n\n// Skills stay out of `fluid theme push/pull/dev/lint` only when some path\n// segment is a dot-directory — that is the sole thing ThemeRoot.glob() skips.\n// Returns true when installing into a *visible* dir inside a theme, where the\n// files would be uploaded as theme content instead of kept local.\nfunction skillsWouldShipWithTheme(cwd: string, targetRoot: string): boolean {\n if (!new ThemeRoot(cwd).isValid()) return false; // not a theme dir — irrelevant\n const rel = relative(cwd, targetRoot);\n if (rel.startsWith(\"..\")) return false; // outside the theme root — never scanned\n return !rel.split(sep).some((segment) => segment.startsWith(\".\"));\n}\n\nexport function createSkillsCommand(): Command {\n const skills = new Command(\"skills\").description(\n \"Manage the bundled Fluid theme AI skills\",\n );\n\n skills\n .command(\"install\")\n .description(\n \"Copy the bundled theme skills into the current directory (default: .agents/skills/)\",\n )\n .option(\"-d, --dir <path>\", \"Directory to install into\", DEFAULT_TARGET_DIR)\n .option(\"-f, --force\", \"Overwrite existing skills without prompting\")\n .action(async (opts: { dir: string; force?: boolean }) => {\n const sourceDir = resolveBundledSkillsDir();\n if (listSkillNames(sourceDir).length === 0) {\n console.error(\"No bundled skills found to install.\");\n process.exit(1);\n }\n\n const targetRoot = resolve(process.cwd(), opts.dir);\n if (skillsWouldShipWithTheme(process.cwd(), targetRoot)) {\n console.log(\n `${chalk.yellow(\"⚠\")} ${chalk.bold(opts.dir)} is not a hidden directory — ` +\n `'fluid theme push' will upload these skills as theme files. ` +\n `Install into a dot-directory like ${chalk.cyan(DEFAULT_TARGET_DIR)} to keep them local.`,\n );\n }\n\n const { installed, skipped } = await installSkills({\n sourceDir,\n targetRoot,\n force: Boolean(opts.force),\n confirmOverwrite: async (name) => {\n const res = await prompts(\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `${chalk.yellow(name)} already exists in ${opts.dir}. Overwrite?`,\n initial: false,\n },\n { onCancel: () => process.exit(130) },\n );\n return Boolean(res.overwrite);\n },\n // Warn as soon as a leftover is found, so it's reported even if a later\n // skill fails before the install finishes.\n onLeftover: (path) => {\n console.log(\n `${chalk.yellow(\"⚠\")} kept the previous copy at ${path} (couldn't remove it — delete it manually)`,\n );\n },\n });\n\n for (const name of installed) {\n console.log(`${chalk.green(\"✓\")} ${name} → ${join(opts.dir, name)}`);\n }\n for (const name of skipped) {\n console.log(`${chalk.dim(`· skipped ${name} (kept existing)`)}`);\n }\n\n const parts = [\n installed.length > 0 ? `${installed.length} installed` : null,\n skipped.length > 0 ? `${skipped.length} skipped` : null,\n ].filter(Boolean);\n console.log(\n `\\n${chalk.bold(parts.join(\", \") || \"Nothing to do\")} in ${targetRoot}`,\n );\n if (installed.length > 0) {\n console.log(\n chalk.dim(\"Restart your agent session to pick up the new skills.\"),\n );\n }\n });\n\n return skills;\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createDevCommand } from \"./dev.js\";\nimport { createPushCommand } from \"./push.js\";\nimport { createPullCommand } from \"./pull.js\";\nimport { createLintCommand } from \"./lint.js\";\nimport { createInitCommand } from \"./init.js\";\nimport { createNavigateCommand } from \"./navigate.js\";\nimport { createSkillsCommand } from \"./skills.js\";\n\nexport function registerThemeCommand(ctx: PluginContext): void {\n const cmd = new Command(\"theme\").description(\n \"Theme developer workflow — dev server, push, pull, lint, init, skills\",\n );\n\n cmd.addCommand(createDevCommand());\n cmd.addCommand(createPushCommand());\n cmd.addCommand(createPullCommand());\n cmd.addCommand(createLintCommand());\n cmd.addCommand(createInitCommand());\n cmd.addCommand(createNavigateCommand());\n cmd.addCommand(createSkillsCommand());\n\n ctx.program.addCommand(cmd);\n}\n","import type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerThemeCommand } from \"./commands/theme.js\";\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-theme-dev\",\n version: \"0.1.0\",\n register(ctx: PluginContext) {\n registerThemeCommand(ctx);\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["delay","isRecord","isRecord","themes.getApplicationThemeAvailableThemeables","settingTypesJson.types","escapeHtml","escapeHtml","isRecord","parseDocument","isRecord","records","isRecord","themes.listThemeResources","themes.getThemeAssets","isRecord","themes.createFileResource","themes.destroyFileResource","themes.updateThemeResource","themes.deleteThemeResource","isRecord","themes.listApplicationThemes","themes.getApplicationTheme","themes.cloneApplicationThemeForDevelopment","themes.createApplicationTheme","themes.getApplicationTheme","themes.createApplicationTheme","themes.getApplicationTheme","themes.publishApplicationTheme"],"sources":["../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/api.ts","../src/plugin-state.ts","../src/theme/dev-server/member-preview-contract.ts","../src/theme/navigation-members.ts","../../../api-clients/themes/src/namespaces/v0.ts","../src/commands/navigate.ts","../src/theme-config.ts","../src/theme/mime-type.ts","../src/theme/resource-key.ts","../../../platform/theme-schema/src/setting-types.json","../../../platform/theme-schema/src/types.ts","../../../platform/theme-schema/src/validate-settings.ts","../../../platform/theme-schema/src/validate-blocks.ts","../../../platform/theme-schema/src/validate.ts","../../../platform/theme-schema/src/sections.ts","../src/theme/file.ts","../src/theme/fluid-ignore.ts","../src/theme/root.ts","../src/theme/dev-server/member-screen-assets.ts","../src/theme/dev-server/hot-reload.ts","../src/theme/dev-server/member-preview-html.ts","../src/theme/dev-server/member-preview.ts","../src/theme/dev-server/sse.ts","../src/theme/dev-server/template-overrides.ts","../src/theme/dev-server/proxy.ts","../src/theme/format-error.ts","../src/theme/dev-server/watcher.ts","../src/theme/background-pull-guard.ts","../src/theme/case-collisions.ts","../src/theme/asset-manifest.ts","../src/theme/stylesheet-keys.ts","../src/theme/member-dependencies.ts","../src/theme/member-configuration.ts","../src/theme/syncer.ts","../src/theme/liquid-delimiters.ts","../src/theme/dev-server/port-preflight.ts","../src/theme/dev-server/index.ts","../src/theme/dev-remote-baseline.ts","../src/theme/shadow-repo.ts","../src/theme-picker.ts","../src/workspace.ts","../src/commands/dev.ts","../src/theme/development-push.ts","../src/theme/merge-push.ts","../src/theme/auto-baseline.ts","../src/theme/legacy-migration.ts","../src/theme/sync-identity.ts","../src/commands/push.ts","../src/theme/background-pull.ts","../src/theme/member-transfer.ts","../src/theme/merge-pull.ts","../src/commands/pull.ts","../src/commands/lint.ts","../src/commands/init.ts","../src/skills/install.ts","../src/commands/skills.ts","../src/commands/theme.ts","../src/index.ts"],"sourcesContent":["/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * `??` rather than `||` so an explicitly empty `errors` (`\"\"`, `0`, `false`)\n * is preserved instead of silently falling back to the whole envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n return (body.errors ?? body) as ApiErrorData;\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\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 /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\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 priority?: RequestInit[\"priority\"];\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\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\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(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\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 cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\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[]. The OpenAPI-declared\n // param name is sometimes already bracketed (e.g. \"product_ids[]\",\n // \"filters[integration_class][]\") to match Rails' own convention;\n // appending another \"[]\" in that case would double-bracket the wire\n // format (key[][]=1), which Rails does not parse as the array.\n const arrayKey = key.endsWith(\"[]\") ? key : `${key}[]`;\n value.forEach((item) => queryString.append(arrayKey, 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 const headerRequestId = getRequestIdFromHeaders(response.headers);\n\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 // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = 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 headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\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 const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\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 priority,\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 if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\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 fetchWithNetworkRetry(url, fetchOptions, signal);\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 {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = 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 (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\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","import {\n ApiError,\n createFetchClient,\n type FetchClient,\n type RequestOptions,\n} from \"@fluid-app/api-client-core\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nexport type ApiClient = FetchClient;\n\n/** Base URL for all API calls. Set FLUID_API_BASE to route through a BFF. */\nexport function getApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\n// Only the desktop's unattended worker opts in. Gate auth resolution to pace\n// even concurrent identity/resource requests within a background theme pull.\nlet backgroundGate = Promise.resolve();\nasync function backgroundToken(tokenOverride?: string): Promise<string | null> {\n backgroundGate = backgroundGate.then(\n () => new Promise((resolve) => setTimeout(resolve, 1_000)),\n );\n await backgroundGate;\n return tokenOverride ?? getAuthToken() ?? null;\n}\n\nexport function createApiClient(tokenOverride?: string): ApiClient {\n const client = createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () =>\n process.env[\"FLUID_BACKGROUND_SYNC\"] === \"1\"\n ? backgroundToken(tokenOverride)\n : (tokenOverride ?? getAuthToken() ?? null),\n });\n const request = async <TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> => {\n for (let attempt = 0; ; attempt++) {\n options.signal?.throwIfAborted();\n try {\n return await client.request<TResponse>(endpoint, options);\n } catch (error) {\n // Only retry reads: a failed write response may follow a successful\n // server mutation. Preserve the final API error and request ID.\n if (\n (options.method ?? \"GET\") !== \"GET\" ||\n !(error instanceof ApiError) ||\n ![502, 503, 504].includes(error.status) ||\n attempt >= 2 ||\n options.signal?.aborted\n ) {\n throw error;\n }\n await delay(500 * 2 ** attempt, undefined, { signal: options.signal });\n }\n }\n };\n return {\n ...client,\n request,\n get: (endpoint, params, options) =>\n request(endpoint, { ...options, method: \"GET\", params }),\n };\n}\n\nexport function requireToken(): string {\n const token = getAuthToken();\n if (!token) {\n console.error(\"Not logged in. Run `fluid login` first.\");\n process.exit(1);\n }\n return token;\n}\n","import { existsSync } from \"node:fs\";\nimport { readConfig, updateConfig } from \"@fluid-app/fluid-cli\";\n\nexport interface DevThemeRef {\n id: number;\n name: string;\n /** Theme that was pulled when this isolated dev target was created. */\n sourceThemeId?: number;\n}\n\ninterface ThemeDevState {\n /**\n * Dev themes keyed per project, so `theme dev` in one working copy never\n * reuses (and clobbers) another project's sandbox theme. See `devThemeKey`.\n * Entries are pruned once their theme directory no longer exists, so the map\n * can't grow without bound as projects (and one-off/temp dirs) come and go.\n */\n devThemes?: Record<string, DevThemeRef>;\n /** Most recently started dev theme — `navigate`'s default target. */\n lastDevThemeId?: number;\n /**\n * Legacy single global dev theme id. Older CLI versions stored one dev theme\n * here regardless of project. Read once for migration (see `getDevTheme`),\n * then dropped in favour of `devThemes`.\n */\n devThemeId?: number;\n /** Legacy companion to `devThemeId`. */\n devThemeName?: string;\n}\n\nconst PLUGIN_KEY = \"theme-dev\";\n\nfunction getState(): ThemeDevState {\n const config = readConfig();\n return (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n}\n\n/** Extract the absolute theme root from a `company:themeRoot` key. */\nfunction themeRootFromKey(key: string): string {\n const sep = key.indexOf(\":\");\n return sep === -1 ? key : key.slice(sep + 1);\n}\n\n/**\n * Set `key` to `theme`, dropping any entries whose theme directory no longer\n * exists. Tying an entry's lifetime to its directory keeps the map bounded —\n * abandoned/deleted projects fall out the next time `theme dev` runs anywhere.\n */\nfunction withDevTheme(\n existing: Record<string, DevThemeRef> | undefined,\n key: string,\n theme: DevThemeRef,\n): Record<string, DevThemeRef> {\n const next: Record<string, DevThemeRef> = {};\n for (const [k, v] of Object.entries(existing ?? {})) {\n if (existsSync(themeRootFromKey(k))) next[k] = v;\n }\n next[key] = theme;\n return next;\n}\n\n/**\n * Stable key identifying a dev theme's owning project: the Fluid company\n * (subdomains are globally unique) plus the absolute theme root. Two working\n * copies — or the same copy pulled from two companies — get distinct keys.\n */\nexport function devThemeKey(\n company: string | undefined,\n themeRoot: string,\n): string {\n return `${company ?? \"default\"}:${themeRoot}`;\n}\n\n/**\n * The dev theme stored for a project key, if any. Falls back once to the legacy\n * global `devThemeId` (older CLI versions) and adopts it for this key — clearing\n * the legacy fields so a second project can't adopt the same theme and collide.\n */\nexport function getDevTheme(key: string): DevThemeRef | undefined {\n const state = getState();\n const existing = state.devThemes?.[key];\n if (existing) return existing;\n\n if (state.devThemeId) {\n const migrated: DevThemeRef = {\n id: state.devThemeId,\n name: state.devThemeName ?? `Development #${state.devThemeId}`,\n };\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const { devThemeId: _id, devThemeName: _name, ...rest } = current;\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...rest,\n devThemes: withDevTheme(rest.devThemes, key, migrated),\n lastDevThemeId: migrated.id,\n },\n },\n };\n });\n return migrated;\n }\n\n return undefined;\n}\n\n/** Store (or refresh) the dev theme for a project key and mark it most-recent. */\nexport function setDevTheme(key: string, theme: DevThemeRef): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...current,\n devThemes: withDevTheme(current.devThemes, key, theme),\n lastDevThemeId: theme.id,\n },\n },\n };\n });\n}\n\n/** Forget a project's dev theme (it was deleted remotely or is no longer a dev theme). */\nexport function clearDevTheme(key: string): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const removed = current.devThemes?.[key];\n if (!removed) return config;\n const { [key]: _removed, ...rest } = current.devThemes ?? {};\n const next: ThemeDevState = { ...current, devThemes: rest };\n // Don't leave `navigate` pointing at a theme we just forgot.\n if (current.lastDevThemeId === removed.id) {\n next.lastDevThemeId = undefined;\n }\n return {\n ...config,\n plugins: { ...config.plugins, [PLUGIN_KEY]: next },\n };\n });\n}\n\n/**\n * Mark a theme as the most recently started dev server (`navigate`'s default)\n * without recording it as a project's dev theme — used for the `--theme`\n * escape hatch, which may target an arbitrary (non-dev) theme.\n */\nexport function setLastDevThemeId(id: number): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: { ...current, lastDevThemeId: id },\n },\n };\n });\n}\n\n/**\n * The dev theme to target by default in `navigate` — the most recently started\n * dev server. Falls back to the legacy global id for users who haven't yet run\n * the per-project `theme dev`.\n */\nexport function getLastDevThemeId(): number | undefined {\n const state = getState();\n return state.lastDevThemeId ?? state.devThemeId;\n}\n","/** A deliberately small projection of the Rails metadata and render contracts. */\nexport interface MemberPreviewPage {\n id: number;\n title: string;\n path: string;\n published: boolean;\n}\nexport interface MemberPreviewSite {\n id: number;\n name: string;\n pages: MemberPreviewPage[];\n}\nexport interface MemberPreviewMetadata {\n members_sites: MemberPreviewSite[];\n system_screens: Array<{ key: string }>;\n}\nexport interface MemberPreviewSelection {\n members_site_id: number;\n country_id?: number;\n language_id?: number;\n member_type_id?: string;\n}\nexport class MemberPreviewError extends Error {\n constructor(\n message: string,\n readonly status: number = 502,\n ) {\n super(message);\n }\n}\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nexport function isId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value > 0;\n}\n/** The metadata API still exposes storefront-era /accounts paths. Account hosts omit it. */\nexport function accountPath(path: string): string {\n return path === \"/accounts\"\n ? \"/\"\n : path.startsWith(\"/accounts/\")\n ? path.slice(\"/accounts\".length)\n : path;\n}\nexport function isProductionPath(value: unknown): value is string {\n if (\n typeof value !== \"string\" ||\n !value.startsWith(\"/\") ||\n value.startsWith(\"//\") ||\n value.includes(\"\\\\\")\n )\n return false;\n const parsed = new URL(value, \"http://preview.invalid\");\n return parsed.pathname === value && !value.startsWith(\"/_members\");\n}\nexport function parseMemberMetadata(value: unknown): MemberPreviewMetadata {\n if (\n !isRecord(value) ||\n !Array.isArray(value.members_sites) ||\n !Array.isArray(value.system_screens)\n ) {\n throw new MemberPreviewError(\n \"This server does not provide compatible member preview metadata.\",\n );\n }\n const sites = value.members_sites.map((site): MemberPreviewSite => {\n if (\n !isRecord(site) ||\n !isId(site.id) ||\n typeof site.name !== \"string\" ||\n !Array.isArray(site.pages)\n )\n throw new MemberPreviewError(\"Invalid member site metadata.\");\n const pages = site.pages.map((page): MemberPreviewPage => {\n if (\n !isRecord(page) ||\n !isId(page.id) ||\n typeof page.title !== \"string\" ||\n !isProductionPath(page.path) ||\n typeof page.published !== \"boolean\"\n )\n throw new MemberPreviewError(\"Invalid member page metadata.\");\n return {\n id: page.id,\n title: page.title,\n path: accountPath(page.path),\n published: page.published,\n };\n });\n if (new Set(pages.map((p) => p.path)).size !== pages.length)\n throw new MemberPreviewError(\"Ambiguous member page paths.\");\n return { id: site.id, name: site.name, pages };\n });\n if (new Set(sites.map((s) => s.id)).size !== sites.length)\n throw new MemberPreviewError(\"Duplicate member site identifiers.\");\n const screens = value.system_screens.map((screen): { key: string } => {\n if (\n !isRecord(screen) ||\n typeof screen.key !== \"string\" ||\n !/^[a-z][a-z0-9_-]*$/.test(screen.key)\n )\n throw new MemberPreviewError(\"Invalid system screen metadata.\");\n return { key: screen.key };\n });\n return { members_sites: sites, system_screens: screens };\n}\nexport function parseSelection(\n params: URLSearchParams,\n): MemberPreviewSelection {\n const site = numericParam(params, \"members_site_id\");\n if (!site)\n throw new MemberPreviewError(\n \"Select a member site at /_members first.\",\n 400,\n );\n const memberType = params.get(\"member_type_id\");\n if (\n params.getAll(\"member_type_id\").length > 1 ||\n (memberType !== null && !/^[\\w-]{1,100}$/.test(memberType))\n )\n throw new MemberPreviewError(\"Invalid member type selection.\", 400);\n return {\n members_site_id: site,\n country_id: numericParam(params, \"country_id\"),\n language_id: numericParam(params, \"language_id\"),\n ...(memberType ? { member_type_id: memberType } : {}),\n };\n}\nfunction numericParam(\n params: URLSearchParams,\n key: string,\n): number | undefined {\n const value = params.get(key);\n if (value === null) return undefined;\n if (\n params.getAll(key).length !== 1 ||\n !/^[1-9]\\d*$/.test(value) ||\n !isId(Number(value))\n )\n throw new MemberPreviewError(`Invalid ${key}.`, 400);\n return Number(value);\n}\nexport function memberPreviewUrl(\n path: string,\n selection: MemberPreviewSelection,\n): string {\n const url = new URL(path, \"http://preview.invalid\");\n if (url.origin !== \"http://preview.invalid\")\n throw new MemberPreviewError(\"Expected a local member path.\", 400);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n url.pathname = `/_members${url.pathname}`;\n for (const key of [\n \"members_site_id\",\n \"country_id\",\n \"language_id\",\n \"member_type_id\",\n ] as const) {\n const value = selection[key];\n if (value === undefined) url.searchParams.delete(key);\n else url.searchParams.set(key, String(value));\n }\n return url.pathname + url.search + url.hash;\n}\nexport function parseMemberRender(\n value: unknown,\n themeId: number,\n selection: MemberPreviewSelection,\n pageId: number | null,\n screenKey: string | null = null,\n): { html: string; styles: string; templateStyles: string } {\n const preview = isRecord(value) ? value.preview : undefined;\n if (\n !isRecord(preview) ||\n preview.contract_version !== 1 ||\n preview.mode !== \"sample\" ||\n preview.theme_id !== themeId ||\n preview.members_site_id !== selection.members_site_id ||\n preview.members_page_id !== pageId ||\n preview.system_screen_key !== screenKey ||\n typeof preview.html_content !== \"string\" ||\n !(\n preview.page_stylesheet === null ||\n typeof preview.page_stylesheet === \"string\"\n ) ||\n !(\n preview.template_stylesheet === null ||\n typeof preview.template_stylesheet === \"string\"\n )\n ) {\n throw new MemberPreviewError(\n \"Incompatible member preview response. Update the server and CLI together.\",\n );\n }\n return {\n html: preview.html_content,\n styles: preview.page_stylesheet ?? \"\",\n templateStyles: preview.template_stylesheet ?? \"\",\n };\n}\n","import {\n isRecord,\n parseMemberMetadata,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./dev-server/member-preview-contract.js\";\n\ninterface NavigatorMetadata extends MemberPreviewMetadata {\n supported_system_screens: string[];\n}\n\n/** Read the running server's acknowledged route map, without sending credentials. */\nexport async function readPreviewMetadata(\n address: string,\n themeId: number,\n): Promise<NavigatorMetadata | undefined> {\n let value: unknown;\n try {\n const response = await fetch(`${address}/_members/_metadata`, {\n redirect: \"error\",\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) throw new Error(\"Member metadata unavailable\");\n value = await response.json();\n } catch {\n console.warn(\n \"Member preview is unavailable. Start an updated theme dev server to list member pages.\",\n );\n return undefined;\n }\n if (!isRecord(value) || value.mode !== \"sample\") {\n throw new Error(\n \"The running server does not provide compatible preview metadata.\",\n );\n }\n if (value.theme_id !== themeId) {\n throw new Error(\n `The running server is not serving theme #${themeId}. Use its theme ID or restart theme dev.`,\n );\n }\n if (\n !Array.isArray(value.supported_system_screens) ||\n !value.supported_system_screens.every(\n (key): key is string => typeof key === \"string\",\n )\n ) {\n throw new Error(\n \"The running server does not provide preview capabilities.\",\n );\n }\n return {\n ...parseMemberMetadata(value),\n supported_system_screens: value.supported_system_screens,\n };\n}\n\nexport function memberChoices(\n metadata: NavigatorMetadata | undefined,\n): Array<{ title: string; value: string }> {\n if (!metadata) return [];\n return metadata.members_sites.flatMap((site) => {\n const prefix = `Member · ${site.name} (#${site.id}) · `;\n const selection = { members_site_id: site.id };\n return [\n ...site.pages.map((page) => ({\n title: `${prefix}${page.title}${page.published ? \"\" : \" (draft)\"} — ${page.path}`,\n value: memberPreviewUrl(page.path, selection),\n })),\n ...metadata.system_screens\n .filter((screen) =>\n metadata.supported_system_screens.includes(screen.key),\n )\n .map((screen) => {\n return {\n title: `${prefix}${screen.key}`,\n value: memberPreviewUrl(`/${screen.key}`, selection),\n };\n }),\n ];\n });\n}\n\n/** A watcher update may remove a page while the prompt is open. */\nexport async function validateMemberDestination(\n address: string,\n themeId: number,\n path: string,\n): Promise<void> {\n const metadata = await readPreviewMetadata(address, themeId);\n if (!memberChoices(metadata).some((choice) => choice.value === path)) {\n throw new Error(\n \"This member page is no longer available. Run the navigator again to refresh the choices.\",\n );\n }\n}\n","/**\n * Generated API client functions for v0\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/v0\";\n\n// ============================================================================\n// applicationthemetemplates\n// ============================================================================\n\n/**\n * Lists all theme templates\n * Lists the templates available to a theme: the theme's own templates, global templates it has not overridden, and — when you filter by `themeable_type` — active templates from installed droplets. Requires a company admin user or company API token with the `application_themes` view permission. The list is not paginated.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeTemplates(\n client: FetchClient,\n params?: operations[\"listThemeTemplates\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeTemplates\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates`, params);\n}\n\n/**\n * Creates a theme template\n * Creates a template in a theme and publishes it immediately. Pass the theme in the `application_theme_id` query parameter. Requires the `application_themes` update permission. Page types managed by a dedicated endpoint, such as `members_page`, are refused with 422. When the company has moved template stylesheets to theme-level assets, a request that includes `stylesheet` is also refused with 422.\n *\n * @param client - Fetch client instance\n * @param body - body\n * @param params - params\n */\nexport async function createThemeTemplate(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n params: operations[\"createThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"createThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates`, body, { params });\n}\n\n/**\n * List all mysite themes\n * List all mysite themes\n *\n * @param client - Fetch client instance\n\n */\nexport async function listMysiteThemes(\n client: FetchClient,\n): Promise<\n operations[\"listMysiteThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/mysite_themes`);\n}\n\n/**\n * Retrieves a theme template\n * Returns a theme template with details. For section templates whose schema\ndeclares `@theme` or named standalone block references, the response\nincludes an `available_theme_blocks` array with the resolved block schemas\n(name, settings, presets). Private blocks (underscore-prefixed) are excluded\nfrom `@theme` results but included when explicitly referenced by name.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Updates a theme template\n * Updates a template's attributes. Send `publish: true` to publish the saved changes in the same call. Requires the `application_themes` update permission. Global templates and templates provided by a droplet cannot be edited (403). Changing a template into or out of a page type managed by a dedicated endpoint, such as `members_page`, is refused with 422.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplate(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_theme_templates/${id}`, body);\n}\n\n/**\n * Deletes a theme template\n * Deletes a template, along with any region rules that point at it. If it was the default for its type, another template is promoted to default. Requires the `application_themes` update permission. Global templates, droplet-provided templates, and page types managed by a dedicated endpoint are refused with 403; a template still referenced elsewhere returns 409.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Returns all available themeables for theme template\n * Lists the company's records of the template's type (for example products or pages) that can be assigned to this template, with the template currently assigned to each. Records already using this template are listed first, then by title. Requires the `application_themes` view permission. Paginated with `page` and `per_page` (default 20); only shareable template types are supported (400 otherwise).\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getThemeTemplateAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getThemeTemplateAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getThemeTemplateAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Get available variables for a theme template\n * Get available variables that can be used in the theme template\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getThemeTemplateAvailableVariables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getThemeTemplateAvailableVariables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getThemeTemplateAvailableVariables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_variables`,\n params,\n );\n}\n\n/**\n * Clones a theme template\n * Copies a template within its theme. The copy is named after the original with a ` (Clone)` suffix, is not the default, and gets its own copy of the template's translations. Requires the `application_themes` update permission. Page types managed by a dedicated endpoint, such as `members_page`, cannot be cloned (422).\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/clone`);\n}\n\n/**\n * Publishes the template\n * Publishes the template's current version, or the version given in `version`, so the storefront renders it. Requires the `application_themes` update permission. Global templates cannot be published (403). If the theme is connected to a Git repository, the change is also pushed there in the background.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function publishThemeTemplate(\n client: FetchClient,\n id: string | number,\n params?: operations[\"publishThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"publishThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/publish`, {\n params,\n });\n}\n\n/**\n * Render a system screen in the selected member site's theme\n * Uses the same layout, settings, localization, and GlobalEmbeds pipeline as template previews. The screen body is sealed and never persisted. Unsaved shared settings and layout overrides are accepted; screen body, stylesheet, variables, resource and version overrides are rejected. The real SDK element uses read-only sample data in the builder without member credentials or live member API calls. The builder displays the response in its preview canvas.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function renderThemeSystemScreenPage(\n client: FetchClient,\n body: NonNullable<\n operations[\"renderThemeSystemScreenPage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeSystemScreenPage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/render_page`, body);\n}\n\n/**\n * Renders a page for a theme template\n * Renders the template to HTML for the theme builder's preview, optionally with unsaved content, a specific version, sample resources, a country and language, and a preview audience. Nothing is saved. Requires the `application_themes` view permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function renderThemeTemplatePage(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"renderThemeTemplatePage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeTemplatePage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_theme_templates/${id}/render_page`,\n body,\n );\n}\n\n/**\n * Renders a section template\n * Renders a section template on its own, inside the theme's layout, and returns the HTML for the theme builder's preview. The `id` must be a template of type `sections`. Results are cached briefly per section version. Requires the `application_themes` view permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function renderThemeTemplateSection(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"renderThemeTemplateSection\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/render_section`);\n}\n\n/**\n * Sets a theme template as default\n * Makes this template the default for its type in its theme, so resources without an assigned template render with it. Requires the `application_themes` update permission. Returns the template without a wrapping key.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function setDefaultThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"setDefaultThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/set_default`);\n}\n\n/**\n * Updates themeable records to be used by the specified template\n * Assigns records of the template's type to this template (`added_ids`) and unassigns others (`removed_ids`) in one transaction. Supported for product, medium, enrollment pack, library, page, collection, post, category, and member page templates. Send at least one of the two lists. Requires the `application_themes` update permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplateThemeables(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplateThemeables\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplateThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_theme_templates/${id}/themeables_update`,\n body,\n );\n}\n\n// ============================================================================\n// application-themes\n// ============================================================================\n\n/**\n * List application themes\n * Get all application themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listApplicationThemes(\n client: FetchClient,\n params?: operations[\"listApplicationThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes`, params);\n}\n\n/**\n * Create an application theme\n * Creates a new theme for your company. Requires a company Bearer token with permission to update themes. The theme starts in `draft` status unless you pass another `status`. Pass `cover_image_url` to set the cover image. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422. A Git repository is provisioned for the theme in the background when Git sync is available.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createApplicationTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createApplicationTheme\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes`, body);\n}\n\n/**\n * Get current active application theme\n * Returns your company's currently active theme with its templates, versions, images, and Git repositories. Requires a company Bearer token with permission to view themes. Returns 404 when no theme is active.\n *\n * @param client - Fetch client instance\n\n */\nexport async function getActiveApplicationTheme(\n client: FetchClient,\n): Promise<\n operations[\"getActiveApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/active`);\n}\n\n/**\n * Import an application theme from zip file\n * Starts importing a theme from a zip archive at a URL you provide. Requires a company Bearer token with permission to update themes. A placeholder theme is created immediately with status `importing`, and the archive is processed in the background; the theme moves to `draft` when the import succeeds or to `error` when it fails. `theme_type` defaults to `fluid`; pass `shopify` to import a Shopify theme archive. The response only confirms that the import started.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function importApplicationThemeFromZip(\n client: FetchClient,\n body: NonNullable<\n operations[\"importApplicationThemeFromZip\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"importApplicationThemeFromZip\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/import_zip`, body);\n}\n\n/**\n * Get an application theme\n * Returns one of your company's themes with its templates (including full Liquid content), versions, images, and Git repositories. Pass `version` to read the theme as it was at a past version. Requires a company Bearer token with permission to view themes. Themes belonging to another company return 404.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationTheme(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationTheme\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}`, params);\n}\n\n/**\n * Update an application theme\n * Updates a theme's attributes. Requires a company Bearer token with permission to update themes. Setting `active` to true makes the theme `active`; false sets it to `draft`. Send `publish` as `\"true\"` to publish the theme and sync it to its Git repository in the same request. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateApplicationTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_themes/${id}`, body);\n}\n\n/**\n * Delete an application theme\n * Permanently deletes one of your company's themes along with its templates and region rules. Any Git repositories connected to the theme are archived afterwards. Requires a company Bearer token with permission to update themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_themes/${id}`);\n}\n\n/**\n * Returns available themeables for a given type scoped to the theme's company\n * Lists the company resources of one type (for example `product`, `page`, `collection`, or `enrollment_pack`) that templates in this theme can be assigned to, sorted by title. Pass `themeable` to choose the type; unsupported types return 400. Pass `search` to filter by title. Results are paginated, with page details in `meta.pagination`, 20 per page by default. Requires a company Bearer token with permission to view themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationThemeAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationThemeAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Clone an application theme\n * Creates a copy of one of your company's themes, including its templates, images, assets, and member configuration. The copy keeps the source theme's name and starts in `draft` status. Requires a company Bearer token with permission to update themes. Returns 422 when the source theme's member configuration cannot be copied.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/clone`);\n}\n\n/**\n * Create a development reference clone of an application theme\n * Creates an isolated development theme while preserving existing DAM and ImageKit references without transferring asset bytes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function cloneApplicationThemeForDevelopment(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"cloneApplicationThemeForDevelopment\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"cloneApplicationThemeForDevelopment\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${id}/clone_for_development`,\n body,\n );\n}\n\n/**\n * Get a short-lived download URL for the theme at a past version\n * Returns a pre-signed URL for a tarball of the theme's connected\nrepository at one commit. The URL is fetched **directly from the Git\nprovider by the client** and needs no authentication of its own — Fluid\nmints it and never proxies the bytes, so repository credentials stay\nserver-side.\n\nOne request returns the entire tree, binary assets included as real\nbytes. That matters for restoring a past version: an asset's current\nURL points at its current content, so anything built from per-file\nlinks would silently fail to revert images.\n\nRead-only. This exposes a version; it does not apply one. Clients with\na local checkout restore by writing the archive into their theme\nfolder, then pushing — the same local-then-publish flow as the Mist and\nPortal rollbacks. A client without one (the admin editor) uses the\ngit_restore operation instead, which applies a version to the theme's\ndraft records server-side.\n\nThe URL is short-lived. Re-request it rather than caching it.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param params - params\n */\nexport async function getApplicationThemeGitArchiveUrl(\n client: FetchClient,\n id: string | number,\n params: operations[\"getApplicationThemeGitArchiveUrl\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeGitArchiveUrl\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_archive_url`, params);\n}\n\n/**\n * Connect the theme to a Git repository, provisioning one if needed\n * Ensures the theme has a connected Git repository, creating and\nprovisioning one when it doesn't already have it.\n\nIdempotent — an already-connected theme returns its repository\nunchanged, and one whose earlier provisioning failed has it retried.\nSafe to call twice.\n\nProvisioning the upstream repository happens in the background, so a\nfreshly created connection reports `status: provisioning` and becomes\n`connected` shortly after. Poll the git_versions operation, or retry\nthis one, to observe the change.\n\nThis connects themes that automatic provisioning deliberately skips —\ndevelopment themes among them — because an explicit request is a\ndifferent intent from \"provision every theme on creation\".\n\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function connectApplicationThemeToGit(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"connectApplicationThemeToGit\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_connect`);\n}\n\n/**\n * Restore the theme's draft content to a past published version\n * Applies one published version back to the theme, for a client that has\nno local checkout to stage it in. The git_archive_url operation is the\nequivalent for one that does.\n\nPublishes as it writes: every record goes through the theme's own write\nseam, which publishes it. Publishing is per record and there is no\nwhole-theme publish, so a restore that only touched drafts would leave\nthe caller publishing every template by hand. For an **active** theme\nthis therefore goes live.\n\nIt remains undoable: the version being left behind is still in the\nhistory this restored from.\n\nThe version is read as a single archive rather than file by file, and\napplied in one transaction: a tree that cannot be written leaves the\ntheme as it was rather than half-restored.\n\nFiles the target version does not contain are removed, which is what\nmakes this a restore rather than a merge — a template added after that\nversion would otherwise survive into a tree that never had it.\n\nUndoing a restore is another restore: the version you were on is still\nin the history you restored from.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function restoreApplicationThemeGitVersion(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"restoreApplicationThemeGitVersion\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"restoreApplicationThemeGitVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_restore`, body);\n}\n\n/**\n * List a theme's published version history from its connected Git repo\n * Returns the recent commits on the theme's connected Git repository,\nnewest first. Themes are DB-canonical: Fluid writes one commit per\npublish, so this log is the theme's published version history.\n\n`author_name` is the committing identity, which for anything Fluid\npushed is the Fluid GitHub App. The person who published is named in\n`message`.\n\nRead-only — this endpoint never provisions or repairs a repository.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function listApplicationThemeGitVersions(\n client: FetchClient,\n id: string | number,\n params?: operations[\"listApplicationThemeGitVersions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemeGitVersions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_versions`, params);\n}\n\n/**\n * Import an application theme\n * Copies a theme from the Fluid theme catalog into your company. The copy is made in the background, so the response only confirms that the import started; the new theme appears in your theme list when it finishes. Requires a company Bearer token with permission to update themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function importApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.post(`/api/application_themes/${id}/import`);\n}\n\n/**\n * Publishes the theme\n * Publishes the theme so the storefront serves its latest content, then syncs it to any connected Git repository. Pass the optional `version` query parameter to publish a specific past version instead; an unknown version returns 404. Requires a company Bearer token with permission to update themes. Returns 422 when the theme's member configuration references a template or navigation item that does not exist.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function publishApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"publishApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/publish`);\n}\n\n/**\n * Get theme assets\n * Lists the files (images, stylesheets, scripts, and other assets) uploaded to one of your company's themes. Requires a company Bearer token with permission to view themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeAssets(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeAssets\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/theme_assets`);\n}\n\n// ============================================================================\n// applicationthemeresources\n// ============================================================================\n\n/**\n * Lists all theme resources\n * Lists resources for a theme owned by the current company, including the canonical members_sites.json snapshot. Themes without member sites receive an explicit empty version 2 document. Companyless root themes are not accessible through this endpoint. The content field remains JSON-encoded text.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function listThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"listThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${application_theme_id}/resources`,\n );\n}\n\n/**\n * Updates a theme resource\n * Submit member template dependencies together with members_sites.json using member_files so navigation is captured before any file changes. The files and member graph apply atomically; a rejected dependency rolls back the bundle. Existing single-resource writes remain supported. Missing manifests preserve existing configuration; deleting the file is refused.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function updateThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"updateThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_themes/${application_theme_id}/resources`,\n body,\n );\n}\n\n/**\n * Deletes a theme resource\n * Deletes one theme file, identified by its `key`, as the theme CLI does when a local file is removed. Send the `base_sha` from your last pull to guard against overwriting newer server changes (409 when stale). The member site manifest `members_sites.json` cannot be deleted this way. Requires a company admin token with the `application_themes` update permission.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function deleteThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"deleteThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"deleteThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/application_themes/${application_theme_id}/resources`,\n { body },\n );\n}\n\n/**\n * Commits the theme's current state as one version\n * Called once by the CLI AFTER its per-file push loop, and the only thing that turns a push into a commit — the per-file writes themselves just mark the theme changed. This is the other half of check_push, which runs once before the loop. Omitting it is safe: the server sweeps anything left unsynced, so an older CLI lags by at most a sweep rather than going unsynced.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function syncThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"syncThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/sync`,\n );\n}\n\n/**\n * Check a theme push before uploading\n * Merge-aware push preflight: CLI checks its stored base_sha matches the server's current content_version_sha before starting the per-file push loop. Enforcement lives on the per-file PUT/DELETE too; this endpoint is UX so the CLI can fail fast before touching any state.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function checkPushThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"checkPushThemeResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"checkPushThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/check_push`,\n body,\n );\n}\n\n// ============================================================================\n// file-resources\n// ============================================================================\n\n/**\n * Returns a list of file resources\n * Lists the company's uploaded files (images, stylesheets, scripts, and other theme assets), newest first by default. Available to company admins with the `file_resources` view permission and to reps. Supports `search_query`, `sorted_by`, `with_types` (partial content-type match, such as `image` or `css`), and `size_greater_than` / `size_less_than` in megabytes. Paginated with `page` and `per_page` (default 10); totals are returned in `meta`.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listFileResources(\n client: FetchClient,\n params?: operations[\"listFileResources\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources`, params);\n}\n\n/**\n * Creates a file resource\n * Uploads a file to the company's asset library. Provide exactly one source: `content` for a text file, a multipart `file` upload, or `dam_asset_code` to link an existing digital asset; otherwise supply `url` with `filename`, `content_type`, and `content_size`. To attach the file to a theme or template, send both `relateable_type` (`ApplicationTheme` or `ApplicationThemeTemplate`) and `relateable_id`. Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createFileResource(\n client: FetchClient,\n body: NonNullable<\n operations[\"createFileResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFileResource\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources`, body);\n}\n\n/**\n * Creates multiple file resources\n * Creates several files in one transaction; if any file fails, none are created (422). Each entry accepts the same source fields as creating a single file (`content`, `file`, or `url` with its metadata). Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkCreateFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkCreateFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkCreateFileResources\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources/bulk_create`, body);\n}\n\n/**\n * Deletes multiple file resources\n * Deletes the listed files that belong to the company. IDs that do not match a company file are ignored; `meta.destroyed_count` reports how many were deleted, and `ids` echoes the request. Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkDestroyFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkDestroyFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkDestroyFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/bulk_destroy`, { body });\n}\n\n/**\n * Shows a file resource\n * Returns one of the company's uploaded files. Available to company admins with the `file_resources` view permission and to reps. Returns 404 when the file does not belong to the company.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function showFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"showFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources/${id}`);\n}\n\n/**\n * Deletes a file resource\n * Deletes one of the company's uploaded files and returns its ID. Requires the `file_resources` update permission. Returns 404 when the file does not belong to the company.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function destroyFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"destroyFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/${id}`);\n}\n\n// ============================================================================\n// root-themes\n// ============================================================================\n\n/**\n * List the company's root themes\n * Get all root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listRootThemes(\n client: FetchClient,\n params?: operations[\"listRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes`, params);\n}\n\n/**\n * Create a root theme\n * Submits a new theme to the Fluid theme library for review. The theme is created\nunder your company with an initial `1.0` version in `draft` status, built from the\nzip archive at `zip_file_url`. The version stays in draft until a Fluid platform\nadministrator approves or rejects it.\n\nRequires a company admin Bearer token with the `application_themes` update\npermission. The response carries only a confirmation message; call\n`GET /api/root_themes/my` to see the new theme and its versions. A failure while\ncreating the theme returns `422`.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createRootTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes`, body);\n}\n\n/**\n * List company root themes\n * Get all company root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listCompanyRootThemes(\n client: FetchClient,\n params?: operations[\"listCompanyRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listCompanyRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes/my`, params);\n}\n\n/**\n * Update a root theme\n * Updates a theme your company submitted to the Fluid theme library. You can change\nits `name`, `description`, `demo_url`, and `status`. When you pass a\n`zip_file_url` that differs from the latest version's archive, any pending\n`draft` versions are marked `superseded` and a new draft version with the next\nmajor version number (for example `2.0`) is created for review, using\n`changelog` as its changelog. Passing the same archive URL again does not create\na new version.\n\nRequires a company admin Bearer token with the `application_themes` update\npermission. You can only update themes that belong to your company; any other\n`id` returns `404`. The response carries only a confirmation message.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/root_themes/${id}`, body);\n}\n\n/**\n * Delete a root theme\n * Permanently deletes a theme your company submitted to the Fluid theme library,\ntogether with its versions and any theme region rules attached to it. Requires a\ncompany admin Bearer token with the `application_themes` update permission. You\ncan only delete themes that belong to your company; any other `id` returns\n`404`. If the theme cannot be deleted, the request returns `422`.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteRootTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/root_themes/${id}`);\n}\n\n/**\n * Update a root theme status\n * Approves or rejects one version of a theme in the Fluid theme library. Restricted\nto Fluid platform administrators; any other caller receives `403`.\n\nSet `approved` to `true` to publish a `draft` version, or to re-publish a\npreviously `approved` version (for example, to roll back). The version moves to\n`publishing` and is imported in the background. Set `approved` to `false` to\nreject a `draft` version. Any other transition, such as rejecting an approved\nversion, returns `422`. An unknown theme `id` or a `theme_version_id` that does\nnot belong to the theme returns `404`.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootThemeStatus(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootThemeStatus\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootThemeStatus\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes/${id}/status`, body);\n}\n\n// ============================================================================\n// theme-region-rules\n// ============================================================================\n\n/**\n * List theme region rules\n * Retrieve a list of theme region rules for the current company\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeRegionRules(\n client: FetchClient,\n params?: operations[\"listThemeRegionRules\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeRegionRules\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules`, params);\n}\n\n/**\n * Create theme region rule\n * Create a new theme region rule\n\nA region rule controls what visitors from one region see at a storefront route:\na specific theme template (`redirect_type: template`), a redirect to\n`redirect_url` (`redirect`), or no page at all (`disabled`). Optionally point the\nrule at a specific resource with `resource_type` and `resource_id`.\n\nIf `application_theme_id` is omitted, the rule is attached to your company's\nactive theme. Requires a company admin or rep Bearer token with the\n`application_themes` update permission.\n\nThis operation is an upsert keyed on theme, `route_path`, and `region_code`. When\nno matching rule exists it creates one and returns `201`. When one exists, the\nrequest body replaces its state and the response is `200`; omitted fields fall\nback to their defaults (`priority` 0, `active` true, `redirect_type`\n`template`). To change only some fields of an existing rule, use the update\noperation instead.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createThemeRegionRule(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/theme_region_rules`, body);\n}\n\n/**\n * Show theme region rule\n * Retrieve a specific theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules/${id}`);\n}\n\n/**\n * Update theme region rule\n * Update an existing theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeRegionRule(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/theme_region_rules/${id}`, body);\n}\n\n/**\n * Delete theme region rule\n * Delete a theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.delete(`/api/theme_region_rules/${id}`);\n}\n","import { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { getLastDevThemeId } from \"../plugin-state.js\";\nimport {\n memberChoices,\n readPreviewMetadata,\n validateMemberDestination,\n} from \"../theme/navigation-members.js\";\nimport { themes } from \"@fluid-app/themes-api-client\";\n\nfunction localSuggest(\n input: string,\n choices: prompts.Choice[],\n): prompts.Choice[] {\n if (!input) return choices;\n const lower = input.toLowerCase();\n return choices.filter((c) => c.title.toLowerCase().includes(lower));\n}\n\ninterface ThemeTemplate {\n id: number;\n name: string;\n themeable_type: string;\n default: boolean;\n}\n\ninterface TemplatesResponse {\n templates: ThemeTemplate[];\n}\n\nconst THEMEABLE_TYPE_MAP: Record<string, string> = {\n \"/home\": \"home_page\",\n \"/home/shop\": \"shop_page\",\n \"/home/join\": \"join_page\",\n \"/cart\": \"cart_page\",\n \"/home/blog\": \"post_page\",\n \"/home/categories\": \"category_page\",\n \"/home/collections\": \"collection_page\",\n};\n\nconst STATIC_ROUTES = [\n { label: \"Home\", path: \"/home\" },\n { label: \"Shop\", path: \"/home/shop\" },\n { label: \"Join / Sign Up\", path: \"/home/join\" },\n { label: \"Cart\", path: \"/cart\" },\n { label: \"Blog\", path: \"/home/blog\" },\n { label: \"Categories (all)\", path: \"/home/categories\" },\n { label: \"Collections (all)\", path: \"/home/collections\" },\n] as const;\n\nconst RESOURCE_ROUTES = [\n {\n label: \"Category\",\n type: \"category\",\n template: \"/home/categories/%s\",\n fallback: \"/home/categories\",\n },\n {\n label: \"Collection\",\n type: \"collection\",\n template: \"/home/collections/%s\",\n fallback: \"/home/collections\",\n },\n {\n label: \"Product\",\n type: \"product\",\n template: \"/home/products/%s\",\n fallback: \"/home/shop\",\n },\n {\n label: \"Library\",\n type: \"library\",\n template: \"/home/libraries/%s\",\n fallback: \"/home/libraries\",\n },\n {\n label: \"Post\",\n type: \"post\",\n template: \"/home/posts/%s\",\n fallback: \"/home/blog\",\n },\n {\n label: \"Media\",\n type: \"medium\",\n template: \"/home/media/%s\",\n fallback: \"/home/media\",\n },\n {\n label: \"Enrollment Pack\",\n type: \"enrollment_pack\",\n template: \"/home/enrollments/%s\",\n fallback: \"/home/join\",\n },\n {\n label: \"Page\",\n type: \"page\",\n template: \"/home/pages/%s\",\n fallback: \"/home/pages\",\n },\n] as const;\n\nasync function fetchTemplatesForType(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n): Promise<ThemeTemplate[]> {\n const params = new URLSearchParams({\n application_theme_id: String(themeId),\n themeable_type: themeableType,\n published: \"true\",\n });\n const body = await api.get<TemplatesResponse>(\n `/api/application_theme_templates?${params}`,\n );\n return body.templates ?? [];\n}\n\nasync function selectTemplate(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n onCancel: () => void,\n): Promise<number | null> {\n const templates = await fetchTemplatesForType(api, themeId, themeableType);\n if (templates.length <= 1) return null;\n\n const templateChoices = templates.map((t) => ({\n title: `${t.name}${t.default ? \" (default)\" : \"\"}`,\n value: t.id,\n }));\n const { templateId } = await prompts(\n {\n type: \"autocomplete\",\n name: \"templateId\",\n message: \"Select a template\",\n choices: templateChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n return templateId ?? null;\n}\n\nexport function createNavigateCommand(): Command {\n return new Command(\"navigate\")\n .description(\"Interactively navigate to a route in the dev server browser\")\n .option(\"--host <host>\", \"Dev server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Dev server port\", \"9292\")\n .option(\"-t, --theme <id>\", \"Theme ID (defaults to active dev theme)\")\n .action(async (opts: { host: string; port: string; theme?: string }) => {\n requireToken();\n\n const themeId = opts.theme ? Number(opts.theme) : getLastDevThemeId();\n\n if (!themeId) {\n console.error(\n \"No active dev theme. Run `fluid theme dev` first, or pass --theme <id>.\",\n );\n process.exit(1);\n }\n\n const address = `http://${opts.host}:${opts.port}`;\n\n await navigateTheme(createApiClient(), themeId, address);\n });\n}\n\n/** Shared by theme navigate and theme dev --navigate. */\nexport async function navigateTheme(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n address: string,\n): Promise<void> {\n type Choice = {\n title: string;\n value:\n | string\n | {\n resourceType: string;\n template: string;\n fallback: string;\n label: string;\n };\n };\n const choices: Choice[] = [\n ...STATIC_ROUTES.map((r) => ({ title: r.label, value: r.path })),\n ...RESOURCE_ROUTES.map((r) => ({\n title: `${r.label} (select specific)`,\n value: {\n resourceType: r.type,\n template: r.template,\n fallback: r.fallback,\n label: r.label,\n },\n })),\n ];\n\n let cancelled = false;\n const onCancel = () => {\n cancelled = true;\n return false;\n };\n\n const metadata = await readPreviewMetadata(address, themeId);\n choices.push(...memberChoices(metadata));\n const unavailable = metadata?.system_screens.filter(\n (screen) => !metadata.supported_system_screens.includes(screen.key),\n );\n if (unavailable?.length)\n console.log(\n `System-screen preview unavailable: ${unavailable.map((screen) => screen.key).join(\", \")}.`,\n );\n\n const { dest } = await prompts(\n {\n type: \"autocomplete\",\n name: \"dest\",\n message: \"Select a route\",\n choices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n if (!dest) return;\n\n let path: string;\n let themeableType: string | undefined;\n\n if (typeof dest === \"string\") {\n if (dest.startsWith(\"/_members/\")) {\n await validateMemberDestination(address, themeId, dest);\n }\n path = dest;\n themeableType = THEMEABLE_TYPE_MAP[dest];\n } else {\n themeableType = dest.resourceType;\n const body = await themes.getApplicationThemeAvailableThemeables(\n api,\n themeId,\n { themeable: dest.resourceType, per_page: 50 },\n );\n const resources = body.available_themeables ?? [];\n\n if (!resources.length) {\n console.log(`No ${dest.label} resources found, using listing page.`);\n path = dest.fallback;\n } else {\n const resourceChoices = resources.map((r) => ({\n title: r.title ?? r.slug ?? \"Untitled\",\n value: r.slug,\n }));\n const { slug } = await prompts(\n {\n type: \"autocomplete\",\n name: \"slug\",\n message: `Select a ${dest.label.toLowerCase()}`,\n choices: resourceChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n if (cancelled || !slug) return;\n path = dest.template.replace(\"%s\", encodeURIComponent(slug as string));\n }\n }\n\n let templateParam = \"\";\n if (themeableType) {\n const templateId = await selectTemplate(\n api,\n themeId,\n themeableType,\n onCancel,\n );\n if (templateId) {\n templateParam = `?theme_template_id=${templateId}`;\n }\n }\n\n if (cancelled) return;\n const url = `${address}${path}${templateParam}`;\n console.log(`\\nNavigating to: ${url}\\n`);\n const open = (await import(\"open\")).default;\n await open(url);\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface ThemeConfig {\n themeId: number;\n themeName: string;\n company: string;\n /**\n * Server's `content_version_sha` at the moment of the last pull.\n * Sent back to the server as `base_sha` on push — the server rejects\n * with 409 if it no longer matches, so a push against stale data\n * can't silently clobber. Optional so a `.fluid-theme.json` written\n * by an older CLI (checksums-era) still deserializes; when absent,\n * the CLI falls back to the pre-merge-aware behavior (no server-side\n * check).\n */\n baseSha?: string;\n /** Digest of the managed ImageKit references captured with this baseline. */\n assetManifestSha?: string;\n}\n\n/**\n * Shape of a `.fluid-theme.json` written by the pre-shadow-repo CLI.\n * We read it only to migrate the shadow repo forward on first\n * new-CLI use; we never write this shape back.\n */\nexport interface LegacyThemeConfig extends ThemeConfig {\n lastPulledAt?: string | null;\n checksums?: Record<string, string>;\n}\n\nconst CONFIG_FILE = \".fluid-theme.json\";\n\n/**\n * `company` must be the bare subdomain slug — the dev server builds\n * `<company>.fluid.app` from it, so a full domain here produces an\n * unreachable `<slug>.fluid.app.fluid.app` host. Some external tooling\n * writes the full domain (or a URL); accept those and reduce them to\n * the subdomain.\n */\nfunction normalizeCompany(company: string): string {\n const host = company\n .replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\")\n .replace(/[/?#].*$/, \"\");\n return host.replace(/\\.fluid\\.app$/i, \"\");\n}\n\nfunction configPath(themeRoot: string): string {\n return join(themeRoot, CONFIG_FILE);\n}\n\n/** Read `.fluid-theme.json` from a theme directory, or null if it doesn't exist. */\nexport function readThemeConfig(themeRoot: string): ThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n const config = JSON.parse(raw) as ThemeConfig;\n if (typeof config.company === \"string\") {\n config.company = normalizeCompany(config.company);\n }\n return config;\n } catch {\n return null;\n }\n}\n\n/**\n * Read a legacy config (pre-shadow-repo). Used only by the migration\n * path on first new-CLI pull to seed shadow HEAD from files whose\n * local content still matches their stored sha256 checksum.\n */\nexport function readLegacyThemeConfig(\n themeRoot: string,\n): LegacyThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n return JSON.parse(raw) as LegacyThemeConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a push or pull to `theme` may use this directory's own sync state\n * and rebind it to `theme`.\n *\n * Development themes are scratch copies `fluid theme dev` creates and syncs.\n * Using one here would move the project's sync baseline to the scratch copy\n * and rebind the project, so a later Publish would update the copy instead of\n * the theme the project was pulled from. Push syncs such a theme separately;\n * pull refuses. An explicit `--theme` to any other theme still rebinds.\n */\nexport function mayBindTheme(\n existing: ThemeConfig | null,\n theme: { id: number; status?: string | null },\n): boolean {\n return (\n !existing || existing.themeId === theme.id || theme.status !== \"development\"\n );\n}\n\n/** Why `mayBindTheme` refused, with the way forward. */\nexport function developmentThemeRefusal(\n existing: ThemeConfig,\n theme: { id: number; name: string },\n): string {\n return (\n `${theme.name} (#${theme.id}) is a development theme, and this project is bound to ${existing.themeName} (#${existing.themeId}). ` +\n \"Pulling it here would replace this project's sync state. Use `fluid theme dev` or `fluid theme push -t` to update the development theme, or pull it into a separate directory with --root.\"\n );\n}\n\n/** Write `.fluid-theme.json` to a theme directory. */\nexport function writeThemeConfig(themeRoot: string, config: ThemeConfig): void {\n const path = configPath(themeRoot);\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n","const TEXT_TYPES: Record<string, string> = {\n \".liquid\": \"text/x-liquid\",\n \".json\": \"application/json\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".html\": \"text/html\",\n \".txt\": \"text/plain\",\n \".md\": \"text/markdown\",\n \".svg\": \"image/svg+xml\",\n};\n\nconst BINARY_TYPES: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".otf\": \"font/otf\",\n \".pdf\": \"application/pdf\",\n \".zip\": \"application/zip\",\n \".mp4\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n};\n\nexport interface MimeType {\n name: string;\n isText: boolean;\n}\n\nexport function mimeTypeFor(ext: string): MimeType {\n const text = TEXT_TYPES[ext];\n if (text) return { name: text, isText: true };\n\n const binary = BINARY_TYPES[ext];\n if (binary) return { name: binary, isText: false };\n\n return { name: \"application/octet-stream\", isText: false };\n}\n","const THEME_LEVEL_RESOURCE_KEYS = new Set([\n \"global_styles.css\",\n \"styles.css\",\n \"variables.json\",\n \"members_sites.json\",\n]);\n\n/**\n * Template types Fluid has retired. The resources API answers 404 for them, so\n * a theme that still carries their folders must not try to upload them: member\n * home pages are `members_page` templates on the `/` route, sign-in is hosted,\n * and account pages became member pages.\n */\nexport const RETIRED_TEMPLATE_TYPES: ReadonlySet<string> = new Set([\n \"account_page\",\n \"members_dashboard\",\n \"members_login\",\n]);\n\nexport function isRetiredTemplateKey(relativePath: string): boolean {\n const [prefix] = normalizeThemeResourceKey(relativePath).split(\"/\");\n return prefix !== undefined && RETIRED_TEMPLATE_TYPES.has(prefix);\n}\n\nconst COMPOSITE_RESOURCE_FILE_NAMES = new Set([\n \"index.liquid\",\n \"styles.css\",\n \"variables.json\",\n]);\n\nfunction hasSafeSegments(key: string): boolean {\n const segments = key.split(\"/\");\n return segments.every(\n (segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\",\n );\n}\n\nexport function normalizeThemeResourceKey(value: string): string {\n return value.replaceAll(\"\\\\\", \"/\");\n}\n\n/**\n * Whether a relative file path is a resource key accepted by the Fluid themes\n * resource API. Local project files (package manifests, QA evidence, scripts,\n * source baselines, and similar agent artifacts) must never be uploaded.\n */\nexport function isThemeResourceKey(relativePath: string): boolean {\n const key = normalizeThemeResourceKey(relativePath);\n if (!hasSafeSegments(key)) return false;\n if (THEME_LEVEL_RESOURCE_KEYS.has(key)) return true;\n if (isRetiredTemplateKey(key)) return false;\n\n const segments = key.split(\"/\");\n const prefix = segments[0];\n const fileName = segments.at(-1);\n if (!prefix || !fileName || segments.length < 2) return false;\n\n if (\n prefix === \"assets\" ||\n prefix === \"config\" ||\n prefix === \"locales\" ||\n prefix === \"layouts\"\n ) {\n return true;\n }\n\n return segments.length >= 3 && COMPOSITE_RESOURCE_FILE_NAMES.has(fileName);\n}\n","","import settingTypesJson from \"./setting-types.json\" with { type: \"json\" };\n\nexport type SettingType =\n | \"text\"\n | \"plaintext\"\n | \"rich_text\"\n | \"richtext\"\n | \"textarea\"\n | \"html\"\n | \"html_textarea\"\n | \"url\"\n | \"range\"\n | \"number\"\n | \"select\"\n | \"radio\"\n | \"checkbox\"\n | \"color\"\n | \"color_background\"\n | \"font\"\n | \"font_picker\"\n | \"image\"\n | \"image_picker\"\n | \"video_picker\"\n | \"media_picker\"\n | \"text_alignment\"\n | \"media_fit\"\n | \"corner_radius\"\n | \"padding\"\n | \"border\"\n | \"gradient_overlay\"\n | \"header\"\n | \"product\"\n | \"products\"\n | \"collection\"\n | \"collections\"\n | \"category\"\n | \"categories\"\n | \"blog\"\n | \"posts\"\n | \"post\"\n | \"enrollment\"\n | \"enrollments\"\n | \"enrollment_pack\"\n | \"forms\"\n | \"media\"\n | \"variant\"\n | \"link_list\"\n | \"product_list\"\n | \"products_list\"\n | \"collection_list\"\n | \"collections_list\"\n | \"category_list\"\n | \"categories_list\"\n | \"posts_list\"\n | \"enrollment_list\"\n | \"enrollments_list\"\n | \"blog_list\"\n | \"blogs_list\"\n | \"post_list\"\n | \"enrollment_packs_list\";\n\n// Runtime list loaded from the canonical JSON — used for validation.\nexport const VALID_SETTING_TYPES: readonly string[] = Object.values(\n settingTypesJson.types as Record<string, string[]>,\n).flat();\n\n// Compile-time drift guard: if a type exists in the SettingType union but\n// not in setting-types.json, this object literal will error on the missing key.\n// When adding types to setting-types.json, also add them to SettingType above.\nconst _settingTypeCheck: Record<SettingType, true> = {\n text: true,\n plaintext: true,\n rich_text: true,\n richtext: true,\n textarea: true,\n html: true,\n html_textarea: true,\n url: true,\n range: true,\n number: true,\n select: true,\n radio: true,\n checkbox: true,\n color: true,\n color_background: true,\n font: true,\n font_picker: true,\n image: true,\n image_picker: true,\n video_picker: true,\n media_picker: true,\n text_alignment: true,\n media_fit: true,\n corner_radius: true,\n padding: true,\n border: true,\n gradient_overlay: true,\n header: true,\n product: true,\n products: true,\n collection: true,\n collections: true,\n category: true,\n categories: true,\n blog: true,\n posts: true,\n post: true,\n enrollment: true,\n enrollments: true,\n enrollment_pack: true,\n forms: true,\n media: true,\n variant: true,\n link_list: true,\n product_list: true,\n products_list: true,\n collection_list: true,\n collections_list: true,\n category_list: true,\n categories_list: true,\n posts_list: true,\n enrollment_list: true,\n enrollments_list: true,\n blog_list: true,\n blogs_list: true,\n post_list: true,\n enrollment_packs_list: true,\n} satisfies Record<SettingType, true>;\nvoid _settingTypeCheck;\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\nexport interface SchemaSetting {\n type: SettingType;\n id: string;\n default?: string | number | boolean | null;\n label?: string;\n options?: SelectOption[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n content?: string;\n visible_if?: Record<string, unknown>;\n}\n\nexport interface SchemaBlock {\n type: string;\n name?: string;\n limit?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[];\n}\n\nexport interface SchemaPreset {\n name?: string;\n category?: string;\n settings?: Record<string, unknown>;\n blocks?: Array<{ type: string; settings?: Record<string, unknown> }>;\n}\n\nexport interface SectionSchema {\n name?: string;\n tag?: string;\n class?: string;\n enabled_on?: { templates?: string[] };\n disabled_on?: { templates?: string[] };\n max_blocks?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[] | Record<string, unknown>;\n presets?: SchemaPreset[];\n}\n\nexport type BlocksSchemaType = \"array\" | \"object\" | \"unknown\";\n\n/**\n * Structured locator describing the schema element a diagnostic refers to.\n *\n * The rule logic (this package) is intentionally position-agnostic so it can\n * be shared by the CLI (`fluid theme push`) and the CodeMirror-based theme /\n * visual editor alike. Position-aware consumers use `target` to map a\n * diagnostic back to a source range without re-deriving the validation rules.\n */\nexport type SettingDiagnosticTarget = {\n kind: \"setting\";\n /** Index of the setting within its `settings` array. */\n index: number;\n /** The setting's `id`, when present — used to locate the offending entry. */\n settingId?: string;\n /** The setting's `type`, when present — used to locate the offending entry. */\n settingType?: string;\n /** Which field the diagnostic concerns. */\n field: \"id\" | \"type\";\n};\n\nexport type BlockDiagnosticTarget = {\n kind: \"block\";\n /** Index of the block within its `blocks` array. */\n index: number;\n /** The block's `type`, when present — used to locate the offending entry. */\n blockType?: string;\n /** Which field the diagnostic concerns. */\n field: \"type\" | \"name\" | \"settings\";\n};\n\nexport type SectionDiagnosticTarget = {\n kind: \"section\";\n /** The referenced section `type` that has no matching section file. */\n sectionType: string;\n /** The `{% section %}` tag's instance id, when the tag included one. */\n tagId?: string;\n};\n\nexport type DiagnosticTarget =\n | SettingDiagnosticTarget\n | BlockDiagnosticTarget\n | SectionDiagnosticTarget;\n\nexport interface Diagnostic {\n severity: \"error\" | \"warning\";\n message: string;\n /**\n * Optional structured locator so position-aware consumers (e.g. the\n * CodeMirror-based theme editor) can map a diagnostic back to a source\n * range. The CLI ignores this and only renders `message`.\n */\n target?: DiagnosticTarget;\n}\n","import { VALID_SETTING_TYPES } from \"./types\";\nimport type { Diagnostic } from \"./types\";\n\n/**\n * Message shown when a setting declares a `type` that is not one of the\n * canonical `VALID_SETTING_TYPES`. Centralized here so the CLI and the editor\n * render identical text. Kept to a single line — the list of valid types is a\n * static set exposed once via the `VALID_SETTING_TYPES` export, so repeating it\n * in every diagnostic only bloats structured output.\n */\nexport function invalidSettingTypeMessage(type: string): string {\n return `Invalid settings type: '${type}'`;\n}\n\nexport function validateSettings(settings: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const ids = new Set<string>();\n\n for (let index = 0; index < settings.length; index++) {\n const raw = settings[index];\n const setting: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const id = typeof setting.id === \"string\" ? setting.id : undefined;\n const type = typeof setting.type === \"string\" ? setting.type : undefined;\n\n if (id !== undefined && id.trim() === \"\") {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in settings: id cannot be empty\",\n target: { kind: \"setting\", index, settingType: type, field: \"id\" },\n });\n } else if (id && ids.has(id)) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in settings: duplicate id '${id}' found`,\n target: { kind: \"setting\", index, settingId: id, field: \"id\" },\n });\n } else if (id) {\n ids.add(id);\n }\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in setting '${id ?? index}': missing required field 'type'`,\n target: { kind: \"setting\", index, settingId: id, field: \"type\" },\n });\n } else if (!VALID_SETTING_TYPES.includes(type)) {\n diagnostics.push({\n severity: \"error\",\n message: invalidSettingTypeMessage(type),\n target: { kind: \"setting\", index, settingType: type, field: \"type\" },\n });\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\n\nexport function validateBlocks(blocks: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const types = new Set<string>();\n\n for (let index = 0; index < blocks.length; index++) {\n const raw = blocks[index];\n const block: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const type = typeof block.type === \"string\" ? block.type : undefined;\n const name = typeof block.name === \"string\" ? block.name : undefined;\n const settings = block.settings;\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in blocks at index ${index}: missing required field 'type'`,\n target: { kind: \"block\", index, field: \"type\" },\n });\n } else if (types.has(type)) {\n diagnostics.push({\n severity: \"warning\",\n message: `Warning in blocks: duplicate type '${type}' found`,\n target: { kind: \"block\", index, blockType: type, field: \"type\" },\n });\n } else {\n types.add(type);\n }\n\n // Named block references (type only, no name or settings) point to\n // standalone block templates — skip the name requirement for those.\n const isNamedBlockRef = !name && !settings;\n if (!name && type !== \"@app\" && type !== \"@theme\" && !isNamedBlockRef) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': missing required field 'name'`,\n target: { kind: \"block\", index, blockType: type, field: \"name\" },\n });\n }\n\n if (settings) {\n if (!Array.isArray(settings)) {\n // e.g. the author wrote `\"settings\": {}` instead of `\"settings\": []`.\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': 'settings' must be an array ([])`,\n target: { kind: \"block\", index, blockType: type, field: \"settings\" },\n });\n } else {\n diagnostics.push(...validateSettings(settings));\n }\n }\n\n // Recurse into nested blocks (max 2 levels enforced by the engine,\n // but we validate whatever is declared)\n if (Array.isArray(block.blocks)) {\n diagnostics.push(...validateBlocks(block.blocks as unknown[]));\n }\n }\n\n return diagnostics;\n}\n","import type { BlocksSchemaType, Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\nimport { validateBlocks } from \"./validate-blocks\";\n\n// Strip Liquid comment blocks so they don't interfere with schema extraction.\nfunction stripLiquidComments(text: string): string {\n return text.replace(\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g,\n \"\",\n );\n}\n\n// Detect duplicate \"blocks\" keys in the same JSON object.\n// Standard JSON.parse silently drops duplicates, so we scan tokens manually.\nfunction findDuplicateBlocksKeys(jsonText: string): number {\n let count = 0;\n const stack: Array<{\n type: \"object\" | \"array\";\n keys: Set<string>;\n expectingKey: boolean;\n }> = [];\n let pendingKey: string | null = null;\n let i = 0;\n\n while (i < jsonText.length) {\n const ch = jsonText.charCodeAt(i);\n\n // Whitespace\n if (ch === 0x20 || ch === 0x0a || ch === 0x0d || ch === 0x09) {\n i++;\n continue;\n }\n\n if (ch === 0x7b) {\n // {\n stack.push({ type: \"object\", keys: new Set(), expectingKey: true });\n pendingKey = null;\n i++;\n } else if (ch === 0x7d) {\n // }\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x5b) {\n // [\n pendingKey = null;\n stack.push({ type: \"array\", keys: new Set(), expectingKey: false });\n i++;\n } else if (ch === 0x5d) {\n // ]\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x3a) {\n // :\n i++;\n } else if (ch === 0x2c) {\n // ,\n const top = stack[stack.length - 1];\n if (top?.type === \"object\") {\n top.expectingKey = true;\n }\n pendingKey = null;\n i++;\n } else if (ch === 0x22) {\n // \"\n let j = i + 1;\n while (j < jsonText.length) {\n if (\n jsonText.charCodeAt(j) === 0x22 &&\n jsonText.charCodeAt(j - 1) !== 0x5c\n ) {\n break;\n }\n j++;\n }\n const str = jsonText.slice(i + 1, j);\n i = j + 1;\n\n const top = stack[stack.length - 1];\n if (top?.type === \"object\" && top.expectingKey) {\n if (str === \"blocks\" && top.keys.has(str)) {\n count++;\n }\n top.keys.add(str);\n top.expectingKey = false;\n pendingKey = str;\n } else {\n pendingKey = null;\n }\n } else {\n pendingKey = null;\n i++;\n }\n }\n\n return count;\n}\n\nexport interface ValidateSchemaOptions {\n blocksSchemaType?: BlocksSchemaType;\n}\n\n// Validate the full Liquid file content containing a {% schema %} block.\n// Returns an array of diagnostics (empty = valid).\nexport function validateSchemaText(\n text: string,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n const stripped = stripLiquidComments(text);\n const match = stripped.match(\n /\\{%-?\\s*schema\\s*-?%\\}([\\s\\S]*?)\\{%-?\\s*endschema\\s*-?%\\}/,\n );\n if (!match) return diagnostics;\n\n const jsonText = match[1] ?? \"\";\n\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(jsonText) as Record<string, unknown>;\n } catch (e) {\n diagnostics.push({\n severity: \"error\",\n message: `Invalid JSON:\\n ${(e as Error).message}`,\n });\n return diagnostics;\n }\n\n // Duplicate \"blocks\" keys\n const dupes = findDuplicateBlocksKeys(jsonText);\n for (let d = 0; d < dupes; d++) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: duplicate 'blocks' key in the same object\",\n });\n }\n\n // Settings\n if (\n schema !== null &&\n typeof schema === \"object\" &&\n Array.isArray(schema.settings)\n ) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n // Blocks\n if (schema !== null && typeof schema === \"object\" && \"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n // \"unknown\" — validate if array, accept if object\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n\n// Validate a parsed schema object directly (when you already have the JSON).\nexport function validateSchema(\n schema: Record<string, unknown>,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n if (Array.isArray(schema.settings)) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n if (\"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\n\n// Liquid comment blocks — stripped so commented-out section tags are not\n// treated as real references.\nconst LIQUID_COMMENT_REGEX =\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g;\n\n// The `{% schema %} … {% endschema %}` block — removed so section types\n// declared inside the schema JSON are not mistaken for `{% section %}` usages.\nconst SCHEMA_BLOCK_REGEX =\n /\\{%-?\\s*schema\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endschema\\s*-?%\\}/;\n\n// Matches a `{% section %}` tag. Supports both the id-bearing form\n// (`{% section 'hero', id: 'abc' %}`) emitted by the visual editor and the\n// bare form (`{% section 'hero' %}`) hand-authored themes use, plus\n// whitespace-control tags (`{%- … -%}`) and single or double quotes.\n// `id` is optional — capture group 2 is undefined for bare tags.\nconst SECTION_TAG_PATTERN =\n \"\\\\{%-?\\\\s*section\\\\s+['\\\"]([^'\\\"]+)['\\\"](?:\\\\s*,\\\\s*id:\\\\s*['\\\"]([^'\\\"]+)['\\\"])?\\\\s*-?%\\\\}\";\n\n// Reserved layout-region section types. `{% section 'navbar' %}` and friends\n// resolve to the theme's navbar/footer/library_navbar template slots rather than\n// a `sections/<name>` definition, and render empty when absent — so they are\n// never \"missing\". Mirrors `LiquidTags::Section::SECTION_TEMPLATES` server-side.\nconst RESERVED_SECTION_TYPES = new Set([\"navbar\", \"library_navbar\", \"footer\"]);\n\n// `fluid://extensions/{id}/{type}/{name}` references resolve against an app\n// extension installed on the company at render time — they cannot be validated\n// against local files, so they are never flagged. Mirrors `Themes::ExtensionUri`.\nconst EXTENSION_URI_PATTERN = /^fluid:\\/\\/extensions\\/[^/]+\\/[^/]+\\/[^/]+$/;\n\n/** Whether a `{% section %}` type resolves to something other than an on-disk `sections/<name>` definition (and so cannot be flagged as missing). */\nexport function isNonLocalSectionType(type: string): boolean {\n return RESERVED_SECTION_TYPES.has(type) || EXTENSION_URI_PATTERN.test(type);\n}\n\nexport interface SectionReference {\n /** The referenced section type/name (group 1). */\n type: string;\n /** The section instance id, when the tag declares one. */\n id?: string;\n /** The full matched tag text. */\n fullTag: string;\n /** 0-based position of the tag within the template body. */\n order: number;\n}\n\n/** A template, identified by `path`, with its raw liquid `content`. */\nexport interface TemplateInput {\n path: string;\n content: string;\n}\n\n// Liquid outside the comment and schema blocks — the rendered template body\n// where `{% section %}` references actually live.\nfunction templateBody(liquid: string): string {\n return liquid\n .replace(LIQUID_COMMENT_REGEX, \"\")\n .replace(SCHEMA_BLOCK_REGEX, \"\");\n}\n\n/**\n * Extract every `{% section %}` reference from a liquid template, ignoring\n * tags inside comments or the `{% schema %}` block. Shared by the editor's\n * section-usage detection and the CLI linter so both parse references\n * identically.\n */\nexport function extractSectionReferences(liquid: string): SectionReference[] {\n const body = templateBody(liquid);\n const pattern = new RegExp(SECTION_TAG_PATTERN, \"g\");\n const references: SectionReference[] = [];\n let order = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(body)) !== null) {\n const type = match[1];\n if (!type) continue;\n references.push({ type, id: match[2], fullTag: match[0], order: order++ });\n }\n return references;\n}\n\n/**\n * Paths of the templates that reference `sectionName`. Used by the editor to\n * warn before deleting a section that is still in use.\n */\nexport function findTemplatesReferencingSection(\n templates: TemplateInput[],\n sectionName: string,\n): string[] {\n const matches: string[] = [];\n for (const template of templates) {\n const references = extractSectionReferences(template.content);\n if (references.some((reference) => reference.type === sectionName)) {\n matches.push(template.path);\n }\n }\n return matches;\n}\n\nexport interface MissingSectionRef {\n templatePath: string;\n sectionType: string;\n diagnostic: Diagnostic;\n}\n\n/**\n * Find `{% section %}` references that point to a section that does not exist\n * in `existingSectionNames` — the static equivalent of \"an in-use section was\n * deleted\". Reserved layout-region types (navbar/footer/library_navbar) and\n * `fluid://` extension URIs are never flagged (see `isNonLocalSectionType`).\n * Emits one `error` diagnostic per missing section type per template.\n */\nexport function findMissingSectionReferences(\n templates: TemplateInput[],\n existingSectionNames: Set<string>,\n): MissingSectionRef[] {\n const missing: MissingSectionRef[] = [];\n for (const template of templates) {\n const reported = new Set<string>();\n for (const reference of extractSectionReferences(template.content)) {\n if (existingSectionNames.has(reference.type)) continue;\n if (isNonLocalSectionType(reference.type)) continue;\n if (reported.has(reference.type)) continue;\n reported.add(reference.type);\n missing.push({\n templatePath: template.path,\n sectionType: reference.type,\n diagnostic: {\n severity: \"error\",\n message: `references missing section '${reference.type}'`,\n target: {\n kind: \"section\",\n sectionType: reference.type,\n tagId: reference.id,\n },\n },\n });\n }\n }\n return missing;\n}\n","import {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n statSync,\n} from \"node:fs\";\nimport { extname, basename, relative, dirname } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { mimeTypeFor, type MimeType } from \"./mime-type.js\";\nimport { normalizeThemeResourceKey } from \"./resource-key.js\";\nimport {\n validateSchemaText,\n type Diagnostic,\n type BlocksSchemaType,\n} from \"@fluid-app/theme-schema\";\n\n// Top-level theme folders that are not page templates. Everything else at the\n// top level (home_page, product, page, footer, navbar, …) is a page template.\nconst NON_TEMPLATE_DIRS = new Set([\n \"sections\",\n \"blocks\",\n \"components\",\n \"layouts\",\n \"config\",\n \"assets\",\n \"locales\",\n]);\n\nexport class ThemeFile {\n readonly absolutePath: string;\n readonly relativePath: string;\n readonly mime: MimeType;\n\n constructor(absolutePath: string, root: string) {\n this.absolutePath = absolutePath;\n this.relativePath = normalizeThemeResourceKey(relative(root, absolutePath));\n this.mime = mimeTypeFor(extname(absolutePath).toLowerCase());\n }\n\n get name(): string {\n return basename(this.absolutePath);\n }\n\n get isText(): boolean {\n return this.mime.isText;\n }\n\n get isLiquid(): boolean {\n return this.absolutePath.endsWith(\".liquid\");\n }\n\n get isJson(): boolean {\n return this.absolutePath.endsWith(\".json\");\n }\n\n get exists(): boolean {\n return existsSync(this.absolutePath);\n }\n\n read(): string {\n return readFileSync(this.absolutePath, \"utf-8\");\n }\n\n readBinary(): Buffer {\n return readFileSync(this.absolutePath);\n }\n\n write(content: string | Buffer): void {\n mkdirSync(dirname(this.absolutePath), { recursive: true });\n if (typeof content === \"string\") {\n writeFileSync(this.absolutePath, content, \"utf-8\");\n } else {\n writeFileSync(this.absolutePath, content);\n }\n }\n\n checksum(): string {\n const content = this.isText ? this.read() : this.readBinary();\n return createHash(\"sha256\").update(content).digest(\"hex\");\n }\n\n size(): number {\n return statSync(this.absolutePath).size;\n }\n\n get isTemplate(): boolean {\n // Page templates (home_page, product, footer, navbar, …) live in top-level\n // page-type folders and expect blocks as objects. The reserved categories\n // below either expect blocks as arrays (sections, blocks, components) or\n // carry no block schema (layouts, config, assets, locales).\n const parts = this.relativePath.split(/[/\\\\]/);\n return parts.length >= 2 && !NON_TEMPLATE_DIRS.has(parts[0]!);\n }\n\n validateSchema(): Diagnostic[] {\n if (!this.isLiquid) return [];\n\n const blocksSchemaType: BlocksSchemaType = this.isTemplate\n ? \"object\"\n : \"array\";\n\n return validateSchemaText(this.read(), { blocksSchemaType });\n }\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\n\nconst IGNORE_FILE = \".fluidignore\";\n\ninterface Pattern {\n negated: boolean;\n pattern: string;\n}\n\nexport class FluidIgnore {\n private patterns: Pattern[];\n\n constructor(root: string) {\n this.patterns = this.parse(join(root, IGNORE_FILE));\n }\n\n ignore(relativePath: string): boolean {\n let result = false;\n for (const { negated, pattern } of this.patterns) {\n if (this.match(pattern, relativePath)) {\n result = !negated;\n }\n }\n return result;\n }\n\n private parse(filePath: string): Pattern[] {\n if (!existsSync(filePath)) return [];\n return readFileSync(filePath, \"utf-8\")\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l && !l.startsWith(\"#\"))\n .map((l) => {\n const negated = l.startsWith(\"!\");\n let pattern = negated ? l.slice(1) : l;\n if (pattern.startsWith(\"/\")) pattern = pattern.slice(1);\n return { negated, pattern };\n });\n }\n\n private match(pattern: string, path: string): boolean {\n if (pattern.endsWith(\"/\")) {\n return path.startsWith(pattern) || path === pattern.slice(0, -1);\n }\n if (pattern.includes(\"/\")) {\n return this.fnmatch(pattern, path);\n }\n return this.fnmatch(pattern, path) || this.fnmatch(pattern, basename(path));\n }\n\n private fnmatch(pattern: string, str: string): boolean {\n const re = pattern\n .split(\"**\")\n .map((p) =>\n p\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\"),\n )\n .join(\".*\");\n return new RegExp(`^${re}$`).test(str);\n }\n}\n","import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { ThemeFile } from \"./file.js\";\nimport { FluidIgnore } from \"./fluid-ignore.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nconst THEME_MARKERS = [\"templates\", \"assets\", \"config\"];\nconst THEME_ASSET_MANIFEST = \".fluid-assets.json\";\n\nexport class ThemeRoot {\n readonly root: string;\n readonly ignore: FluidIgnore;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.ignore = new FluidIgnore(this.root);\n }\n\n isValid(): boolean {\n return (\n existsSync(join(this.root, THEME_ASSET_MANIFEST)) ||\n THEME_MARKERS.some((m) => {\n try {\n return statSync(join(this.root, m)).isDirectory();\n } catch {\n return false;\n }\n })\n );\n }\n\n files(): ThemeFile[] {\n return this.glob(this.root).filter(\n (f) =>\n isThemeResourceKey(f.relativePath) &&\n !this.ignore.ignore(f.relativePath),\n );\n }\n\n isResourcePath(pathOrFile: string | ThemeFile): boolean {\n const file = this.file(pathOrFile);\n return isThemeResourceKey(file.relativePath);\n }\n\n file(pathOrFile: string | ThemeFile): ThemeFile {\n if (pathOrFile instanceof ThemeFile) return pathOrFile;\n const abs = isAbsolute(pathOrFile)\n ? pathOrFile\n : join(this.root, pathOrFile);\n return new ThemeFile(abs, this.root);\n }\n\n private glob(dir: string): ThemeFile[] {\n const results: ThemeFile[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\")) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\") continue;\n results.push(...this.glob(full));\n } else if (entry.isFile()) {\n results.push(new ThemeFile(full, this.root));\n }\n }\n return results;\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The runtime is shipped with this CLI, not taken from a mutable theme/CDN URL. */\nexport function findMemberScreenAssets(): string | undefined {\n let directory = dirname(fileURLToPath(import.meta.url));\n while (true) {\n for (const candidate of [\n join(directory, \"dist-cli-preview\"),\n join(directory, \"dist\", \"dist-cli-preview\"),\n ]) {\n if (existsSync(join(candidate, \"capabilities.json\"))) return candidate;\n }\n const parent = dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\nexport class MemberScreenAssets {\n readonly screens: string[];\n constructor(private readonly directory = findMemberScreenAssets()) {\n this.screens = [];\n if (!directory) return;\n try {\n const value: unknown = JSON.parse(\n readFileSync(join(directory, \"capabilities.json\"), \"utf8\"),\n );\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"version\" in value) ||\n value.version !== 1 ||\n !(\"screens\" in value) ||\n !Array.isArray(value.screens)\n )\n return;\n if (\n !value.screens.every(\n (screen: unknown) =>\n typeof screen === \"string\" && /^[a-z][a-z-]*$/.test(screen),\n )\n )\n return;\n if (!existsSync(join(directory, \"account-screens.js\"))) return;\n this.screens = value.screens;\n } catch {\n /* An incomplete build must not advertise screen support. */\n }\n }\n read(path: string): Buffer | undefined {\n if (\n !this.directory ||\n !/^(?:account-screens\\.js|chunks\\/[a-zA-Z0-9_.-]+\\.js)$/.test(path)\n )\n return undefined;\n try {\n return readFileSync(join(this.directory, path));\n } catch {\n return undefined;\n }\n }\n}\n","export function buildHotReloadScript(mode: \"full-page\" | \"off\"): string {\n return `\n<script>\n(() => {\n window.__FLUID_CLI_ENV__ = ${JSON.stringify({ mode })};\n\n class HotReload {\n static reloadMode() { return window.__FLUID_CLI_ENV__.mode; }\n static isActive() { return HotReload.reloadMode() !== \"off\"; }\n static setHotReloadCookie(files) {\n const expires = new Date(Date.now() + 3000).toUTCString();\n document.cookie = \\`hot_reload_files=\\${files.join(\",\")};expires=\\${expires};path=/\\`;\n }\n static refresh(files) {\n HotReload.setHotReloadCookie(files);\n console.log(\"[HotReload] Refreshing page\");\n window.location.reload();\n }\n }\n\n class SSEClient {\n constructor(url, handler) {\n if (typeof EventSource === \"undefined\") {\n console.error(\"[HotReload] EventSource not supported in this browser.\");\n return;\n }\n console.log(\"[HotReload] Initializing…\");\n this.url = url;\n this.handler = handler;\n }\n connect() {\n const es = new EventSource(this.url);\n es.onopen = () => console.log(\"[HotReload] SSE connected.\");\n es.onerror = () => {\n console.log(\"[HotReload] SSE closed. Reconnecting in 5s…\");\n es.close();\n setTimeout(() => this.connect(), 5000);\n };\n es.onmessage = (msg) => {\n const data = JSON.parse(msg.data);\n if (data.reload_page) { HotReload.refresh([]); return; }\n this.handler(data);\n };\n }\n }\n\n if (HotReload.isActive()) {\n new SSEClient(\"/hot-reload\", (data) => {\n if (data.modified) HotReload.refresh(data.modified);\n }).connect();\n }\n})();\n</script>`;\n}\n\nexport function injectHotReload(\n html: string,\n mode: \"full-page\" | \"off\",\n): string {\n const script = buildHotReloadScript(mode);\n if (html.includes(\"</body>\")) {\n return html.replace(\"</body>\", `${script}\\n</body>`);\n }\n return html + script;\n}\n","import {\n parse,\n parseFragment,\n serialize,\n type DefaultTreeAdapterMap,\n} from \"parse5\";\nimport type {\n MemberPreviewMetadata,\n MemberPreviewSelection,\n} from \"./member-preview-contract.js\";\nimport { memberPreviewUrl, accountPath } from \"./member-preview-contract.js\";\nimport { injectHotReload } from \"./hot-reload.js\";\n\ntype Element = DefaultTreeAdapterMap[\"element\"];\ntype Parent = DefaultTreeAdapterMap[\"parentNode\"];\n\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (char) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[\n char\n ] ?? char,\n );\n}\n/** Use the configured Rails environment, never a browser-supplied upstream. */\nexport function memberOrigin(apiBase: string, company: string): string {\n const url = new URL(apiBase);\n if (!/^[a-z0-9-]+$/i.test(company))\n throw new Error(\"Invalid company subdomain.\");\n if (url.hostname.startsWith(\"api.\"))\n url.hostname = `${company}.account.${url.hostname.slice(4)}`;\n return url.origin;\n}\nexport interface MemberHtmlOptions {\n origin: string;\n localOrigin: string;\n path: string;\n selection: MemberPreviewSelection;\n metadata: MemberPreviewMetadata;\n reloadMode: \"full-page\" | \"off\";\n screenKey?: string;\n}\nexport function renderMemberHtml(\n render: { html: string; styles: string; templateStyles: string },\n opts: MemberHtmlOptions,\n): string {\n const document = parse(render.html);\n const elements: Element[] = [];\n function visit(parent: Parent): void {\n for (const child of parent.childNodes) {\n if (\"tagName\" in child) {\n elements.push(child);\n visit(child);\n }\n }\n }\n visit(document);\n const head = elements.find((el) => el.tagName === \"head\")!;\n // Remove renderer/editor bases, retaining one environment-correct base for\n // scripts, fonts, images, CSS url() and imports. Stylesheet URLs keep their own base.\n for (const el of elements) {\n if (el.tagName === \"base\" && el.parentNode)\n el.parentNode.childNodes.splice(el.parentNode.childNodes.indexOf(el), 1);\n // Do not load a theme's live account runtime in a CLI sample preview.\n if (el.tagName === \"script\") {\n const src = el.attrs.find((attr) => attr.name === \"src\")?.value ?? \"\";\n if (\n /\\/(?:account-screens(?:\\/|\\.)|member-storefront-sdk\\/)/.test(src) &&\n el.parentNode\n )\n el.parentNode.childNodes.splice(\n el.parentNode.childNodes.indexOf(el),\n 1,\n );\n }\n if (\n el.tagName === \"fluid-account-screen\" ||\n el.tagName === \"fluid-messaging\"\n ) {\n el.attrs = el.attrs.filter(\n (attr) => attr.name !== \"preview\" && attr.name !== \"path\",\n );\n el.attrs.push({ name: \"preview\", value: \"true\" });\n if (opts.screenKey)\n el.attrs.push({\n name: \"path\",\n value: new URL(opts.path, opts.origin).pathname.slice(1),\n });\n }\n if (el.tagName === \"form\") {\n el.attrs = el.attrs.filter((attr) => attr.name !== \"action\");\n el.attrs.push({\n name: \"action\",\n value: `${opts.localOrigin}/_members/_actions`,\n });\n }\n const formAction = el.attrs.find((attr) => attr.name === \"formaction\");\n if (formAction) formAction.value = `${opts.localOrigin}/_members/_actions`;\n if (el.tagName === \"a\" || el.tagName === \"area\") {\n const href = el.attrs.find((a) => a.name === \"href\");\n if (href) href.value = previewLink(href.value, opts);\n }\n }\n const runtime = parseFragment(\n `<script type=\"module\" src=\"${escapeHtml(opts.localOrigin)}/_members/_runtime/account-screens.js\"></script>`,\n );\n head.childNodes.unshift(...runtime.childNodes);\n const base = parseFragment(\n `<base href=\"${escapeHtml(new URL(opts.path, opts.origin).href)}\">`,\n );\n head.childNodes.unshift(...base.childNodes);\n // Both strings are trusted output from the authenticated theme renderer. The\n // template stylesheet is raw CSS, whereas page_stylesheet is HTML link/style tags.\n const styles = parseFragment(render.styles);\n head.childNodes.push(...styles.childNodes);\n const style = parseFragment(\"<style></style>\").childNodes[0];\n if (style && \"childNodes\" in style) {\n style.childNodes.push({\n nodeName: \"#text\",\n value: render.templateStyles,\n parentNode: style,\n });\n head.childNodes.push(style);\n }\n // Form submissions are not part of the sample preview contract. In particular\n // do not let a theme's live sign-in/sign-out form inherit the upstream base.\n const guard = parseFragment(\n `<script>document.addEventListener(\"submit\",function(event){event.preventDefault();},true);</script>`,\n );\n head.childNodes.unshift(...guard.childNodes);\n const context = JSON.stringify(opts.selection).replace(/</g, \"\\\\u003c\");\n const navigation = parseFragment(`<script>\n document.addEventListener(\"fluid-account-navigate\",function(event){\n event.preventDefault(); event.stopImmediatePropagation();\n var slug=event.detail && event.detail.slug;\n if(typeof slug!==\"string\" || !/^[a-z][a-z0-9_-]*(?:[/?#]|$)/.test(slug) || slug.includes(\"\\\\\\\\\")) return;\n var next=new URL(\"/_members/\"+slug,window.location.origin);\n if(next.origin!==window.location.origin || !next.pathname.startsWith(\"/_members/\")) return;\n var context=${context};\n [\"members_site_id\",\"country_id\",\"language_id\",\"member_type_id\"].forEach(function(key){\n if(context[key]===undefined) next.searchParams.delete(key); else next.searchParams.set(key,context[key]);\n });\n window.location.assign(next.href);\n },true);\n </script>`);\n head.childNodes.unshift(...navigation.childNodes);\n return injectHotReload(serialize(document), opts.reloadMode);\n}\nexport function previewLink(href: string, opts: MemberHtmlOptions): string {\n if (href.startsWith(\"#\"))\n return (\n opts.localOrigin + memberPreviewUrl(opts.path, opts.selection) + href\n );\n let target: URL;\n try {\n target = new URL(href, new URL(opts.path, opts.origin));\n } catch {\n return href;\n }\n if (![\"http:\", \"https:\"].includes(target.protocol)) return href;\n const absolute = /^[a-z][a-z\\d+.-]*:/i.test(href) || href.startsWith(\"//\");\n const accountLink = absolute && target.origin === opts.origin;\n if (absolute && !accountLink && target.origin !== opts.localOrigin)\n return href;\n target.pathname = accountPath(target.pathname);\n const isMember =\n accountLink ||\n /^\\/(auth(?:\\/|$)|login$|logout$)/.test(target.pathname) ||\n target.pathname.startsWith(\"/_members/\") ||\n opts.metadata.members_sites.some((site) =>\n site.pages.some((p) => p.path === target.pathname),\n ) ||\n opts.metadata.system_screens.some(\n (screen) =>\n target.pathname === `/${screen.key}` ||\n target.pathname.startsWith(`/${screen.key}/`),\n );\n const path = target.pathname + target.search + target.hash;\n return (\n opts.localOrigin +\n (isMember ? memberPreviewUrl(path, opts.selection) : path)\n );\n}\n","import { MemberScreenAssets } from \"./member-screen-assets.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ApiClient } from \"../../api.js\";\nimport { getApiBase } from \"../../api.js\";\nimport {\n MemberPreviewError,\n isRecord,\n parseMemberMetadata,\n parseMemberRender,\n parseSelection,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./member-preview-contract.js\";\nimport {\n escapeHtml,\n memberOrigin,\n renderMemberHtml,\n} from \"./member-preview-html.js\";\n\n/** Deny DNS rebinding and cross-origin access before using Node's CLI credential. */\nexport function isLocalPreviewRequest(\n req: IncomingMessage,\n host: string,\n): boolean {\n const port = req.socket.localPort;\n const hosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n if ((host === \"0.0.0.0\" || host === \"::\") && req.socket.localAddress) {\n const address = req.socket.localAddress.replace(/^::ffff:/, \"\");\n hosts.add(address.includes(\":\") ? `[${address}]` : address);\n }\n let url: URL;\n try {\n url = new URL(`http://${req.headers.host}`);\n } catch {\n return false;\n }\n if (\n !hosts.has(url.hostname) ||\n Number(url.port || 80) !== port ||\n url.username ||\n url.password ||\n url.pathname !== \"/\"\n )\n return false;\n if (req.headers.origin && req.headers.origin !== url.origin) return false;\n return req.headers[\"sec-fetch-site\"] !== \"cross-site\";\n}\n\nexport class MemberPreview {\n private metadata: MemberPreviewMetadata | undefined;\n private loading: Promise<MemberPreviewMetadata> | undefined;\n private readonly origin: string;\n constructor(\n private readonly api: ApiClient,\n private readonly theme: { id: number; company: string },\n private readonly reloadMode: \"full-page\" | \"off\",\n private readonly screenAssets = new MemberScreenAssets(),\n ) {\n this.origin = memberOrigin(getApiBase(), theme.company);\n }\n /** Only call after every upload in a watcher batch has been acknowledged. */\n async refreshAfterSync(): Promise<void> {\n if (!this.metadata && !this.loading) return;\n if (this.loading) await this.loading;\n this.metadata = undefined;\n const next = await this.loadMetadata();\n this.metadata = next;\n }\n private async loadMetadata(): Promise<MemberPreviewMetadata> {\n return parseMemberMetadata(\n await this.api.get(\"/api/company/members_sites\", {\n application_theme_id: this.theme.id,\n }),\n );\n }\n private async getMetadata(): Promise<MemberPreviewMetadata> {\n if (this.metadata) return this.metadata;\n this.loading ??= this.loadMetadata();\n try {\n this.metadata = await this.loading;\n return this.metadata;\n } finally {\n this.loading = undefined;\n }\n }\n async handle(req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n return false;\n res.setHeader(\"Cache-Control\", \"private, no-store\");\n res.setHeader(\"Referrer-Policy\", \"no-referrer\");\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n try {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n res.setHeader(\"Allow\", \"GET, HEAD\");\n throw new MemberPreviewError(\"Member preview is read-only.\", 405);\n }\n if (url.pathname.startsWith(\"/_members/_runtime/\")) {\n const asset = this.screenAssets.read(\n url.pathname.slice(\"/_members/_runtime/\".length),\n );\n if (!asset)\n throw new MemberPreviewError(\n \"Preview runtime asset unavailable. Rebuild or update the CLI.\",\n 404,\n );\n res.setHeader(\"Content-Type\", \"text/javascript; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : asset);\n return true;\n }\n const metadata = await this.getMetadata();\n if (url.pathname === \"/_members/_metadata\") {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : JSON.stringify({\n theme_id: this.theme.id,\n mode: \"sample\",\n ...metadata,\n supported_system_screens: metadata.system_screens\n .filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n )\n .map((screen) => screen.key),\n }),\n );\n return true;\n }\n if (\n (url.pathname === \"/_members\" || url.pathname === \"/_members/\") &&\n !url.searchParams.has(\"members_site_id\")\n ) {\n const screens = metadata.system_screens.filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n );\n const links = metadata.members_sites\n .map((site) => {\n const destinations = [\n ...site.pages.map((page) => ({\n path: page.path,\n title: page.title + (page.published ? \"\" : \" (draft)\"),\n })),\n ...screens.map((screen) => ({\n path: `/${screen.key}`,\n title: screen.key + \" (sample)\",\n })),\n ];\n return `<h2>${escapeHtml(site.name)}</h2><ul>${destinations.map((page) => `<li><a href=\"${escapeHtml(memberPreviewUrl(page.path, { members_site_id: site.id }))}\">${escapeHtml(page.title)}</a></li>`).join(\"\")}</ul>`;\n })\n .join(\"\");\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : `<!doctype html><meta charset=\"utf-8\"><title>Member preview</title><h1>Member preview</h1><p>Sample data. Select a page to preview its site's layout.</p>${links || \"<p>No member pages in this theme.</p>\"}`,\n );\n return true;\n }\n const selection = parseSelection(url.searchParams);\n const site = metadata.members_sites.find(\n (s) => s.id === selection.members_site_id,\n );\n if (!site)\n throw new MemberPreviewError(\n \"This site no longer belongs to the preview theme. Select a page at /_members.\",\n 404,\n );\n const path = url.pathname.slice(\"/_members\".length) || \"/\";\n const screen = metadata.system_screens.find(\n (s) => path === `/${s.key}` || path.startsWith(`/${s.key}/`),\n );\n const page = screen ? undefined : site.pages.find((p) => p.path === path);\n if (!page && !screen)\n throw new MemberPreviewError(\n \"This member page is unavailable in the selected site. Select a page at /_members.\",\n 404,\n );\n if (screen && !this.screenAssets.screens.includes(screen.key))\n throw new MemberPreviewError(\n \"This system screen has no packaged sample preview. Rebuild or update the CLI.\",\n 501,\n );\n const response = await this.api.post(\n `/api/v202604/themes/${this.theme.id}/member-preview`,\n {\n ...selection,\n ...(page\n ? { members_page_id: page.id }\n : { system_screen_key: screen!.key }),\n },\n );\n const render = parseMemberRender(\n response,\n this.theme.id,\n selection,\n page?.id ?? null,\n screen?.key ?? null,\n );\n const html = renderMemberHtml(render, {\n origin: this.origin,\n localOrigin: url.origin,\n path: path + url.search,\n selection,\n metadata,\n reloadMode: this.reloadMode,\n screenKey: screen?.key,\n });\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : html);\n } catch (error) {\n // Never echo upstream envelopes, credentials, or internal diagnostics into HTML.\n const status =\n error instanceof MemberPreviewError\n ? error.status\n : isRecord(error) &&\n typeof error.status === \"number\" &&\n [401, 403, 404, 422].includes(error.status)\n ? error.status\n : 502;\n const message =\n error instanceof MemberPreviewError\n ? error.message\n : status === 401 || status === 403\n ? \"Member preview authorization failed. Check your CLI login and theme permissions.\"\n : \"Member preview could not be rendered. Check the selected page and server preview support.\";\n res.writeHead(status, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(req.method === \"HEAD\" ? undefined : message);\n }\n return true;\n }\n}\n","import type { ServerResponse } from \"node:http\";\n\nexport class SSEStream {\n private responses = new Set<ServerResponse>();\n\n add(res: ServerResponse): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.write(\":\\n\\n\");\n this.responses.add(res);\n res.on(\"close\", () => this.responses.delete(res));\n }\n\n broadcast(data: string): void {\n const payload = `data: ${data}\\n\\n`;\n for (const res of this.responses) {\n try {\n res.write(payload);\n } catch {\n this.responses.delete(res);\n }\n }\n }\n\n close(): void {\n for (const res of this.responses) {\n try {\n res.end();\n } catch {\n // ignore\n }\n }\n this.responses.clear();\n }\n\n get size(): number {\n return this.responses.size;\n }\n}\n","import type { ApiClient } from \"../../api.js\";\n\nexport const TEMPLATE_OVERRIDE_PARAM = \"theme_template_id\";\n\ninterface TemplateRecord {\n id: number;\n name: string;\n themeable_type: string;\n application_theme_id: number | null;\n}\n\nexport type TemplateOverride =\n | { kind: \"use\"; id: string }\n | {\n kind: \"unresolved\";\n /** No dev template matches, or several do and none can be preferred. */\n reason: \"missing\" | \"ambiguous\";\n requestedId: string;\n themeableType: string;\n name: string;\n candidateIds: number[];\n };\n\nexport interface TemplateOverrideResolver {\n resolve(requestedId: string): Promise<TemplateOverride>;\n /** Forget cached mappings — templates were added to or removed from the dev theme. */\n clear(): void;\n}\n\n/**\n * Maps an explicit `theme_template_id` onto the dev theme the server renders.\n *\n * Mist, `theme navigate` and bookmarked URLs carry template ids from the\n * theme the project was pulled from. Forwarded unchanged next to\n * `x-fluid-theme: <dev theme>`, the storefront renders that original\n * template and the local edits never show. Resolve such an id to the dev\n * theme's template with the same themeable type and name instead. Ids that\n * already belong to the dev theme and global and extension templates (no\n * owning theme) pass through untouched. A failed lookup also passes through,\n * with a warning, since the page may then show the original template.\n */\nexport function createTemplateOverrideResolver(\n api: ApiClient,\n devThemeId: number,\n): TemplateOverrideResolver {\n // Only successful mappings are cached: a missing counterpart may appear on\n // the next sync, and a failed lookup should be retried.\n const cache = new Map<string, string>();\n\n const warnLookupFailed = (requestedId: string, error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(\n `[Templates] Could not map template #${requestedId} to dev theme #${devThemeId} (${message}); rendering it unchanged, which may show the original theme's template.`,\n );\n };\n\n /** `null` means the lookup failed: forward the id as-is, uncached. */\n const lookup = async (\n requestedId: string,\n ): Promise<TemplateOverride | null> => {\n let requested: TemplateRecord | undefined;\n try {\n const body = await api.get<{\n application_theme_template?: TemplateRecord;\n }>(`/api/application_theme_templates/${encodeURIComponent(requestedId)}`);\n requested = body.application_theme_template;\n } catch (error) {\n warnLookupFailed(requestedId, error);\n return null;\n }\n if (!requested) return null;\n const { name, themeable_type: themeableType } = requested;\n const sourceThemeId = requested.application_theme_id;\n if (!sourceThemeId || sourceThemeId === devThemeId) {\n return { kind: \"use\", id: requestedId };\n }\n\n const params = new URLSearchParams({\n application_theme_id: String(devThemeId),\n themeable_type: themeableType,\n });\n let candidates: TemplateRecord[];\n try {\n const body = await api.get<{ templates?: TemplateRecord[] }>(\n `/api/application_theme_templates?${params}`,\n );\n candidates = body.templates ?? [];\n } catch (error) {\n warnLookupFailed(requestedId, error);\n return null;\n }\n\n const matches = candidates.filter(\n (t) =>\n t.application_theme_id === devThemeId &&\n t.themeable_type === themeableType &&\n t.name === name,\n );\n const [counterpart] = matches;\n if (matches.length === 1 && counterpart) {\n return { kind: \"use\", id: String(counterpart.id) };\n }\n // Templates carry no link back to the one they were cloned from, so a\n // duplicated type and name has no right answer — picking one would let\n // an unrelated edit change what the preview renders.\n return {\n kind: \"unresolved\",\n reason: matches.length === 0 ? \"missing\" : \"ambiguous\",\n requestedId,\n themeableType,\n name,\n candidateIds: matches.map((t) => t.id),\n };\n };\n\n return {\n async resolve(requestedId) {\n const cached = cache.get(requestedId);\n if (cached) return { kind: \"use\", id: cached };\n const result = await lookup(requestedId);\n if (!result) return { kind: \"use\", id: requestedId };\n if (result.kind === \"use\") cache.set(requestedId, result.id);\n return result;\n },\n clear() {\n cache.clear();\n },\n };\n}\n","import https from \"node:https\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { injectHotReload } from \"./hot-reload.js\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport {\n TEMPLATE_OVERRIDE_PARAM,\n type TemplateOverride,\n type TemplateOverrideResolver,\n} from \"./template-overrides.js\";\n\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"content-security-policy\",\n]);\n\nexport interface ProxyOptions {\n company: string;\n themeId: number;\n reloadMode: \"full-page\" | \"off\";\n pendingFiles?: () => Array<{ relativePath: string; read: () => string }>;\n /** Maps explicit template overrides onto the dev theme's copies. */\n templateOverrides?: TemplateOverrideResolver;\n}\n\nexport async function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: ProxyOptions,\n): Promise<void> {\n const companyHost = `${opts.company}.fluid.app`;\n\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && typeof v === \"string\") {\n headers[k] = v;\n }\n }\n headers[\"host\"] = companyHost;\n headers[\"x-fluid-theme\"] = String(opts.themeId);\n headers[\"user-agent\"] = \"Fluid CLI\";\n headers[\"accept-encoding\"] = \"identity\";\n\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n url.searchParams.set(\"_fd\", \"0\");\n url.searchParams.set(\"pb\", \"0\");\n\n const requestedTemplateId = url.searchParams.get(TEMPLATE_OVERRIDE_PARAM);\n if (requestedTemplateId && opts.templateOverrides) {\n const override = await opts.templateOverrides.resolve(requestedTemplateId);\n if (override.kind === \"use\") {\n url.searchParams.set(TEMPLATE_OVERRIDE_PARAM, override.id);\n } else if (isPageRequest(req)) {\n // Rendering the original theme's template here would silently show\n // content without the local edits — fail loudly instead. API calls\n // that carry the page's id (e.g. the member SDK) still go upstream.\n const html = injectHotReload(\n unresolvedTemplatePage(override, opts.themeId),\n opts.reloadMode,\n );\n res.writeHead(404, {\n \"content-type\": \"text/html; charset=utf-8\",\n \"content-length\": String(Buffer.byteLength(html)),\n });\n res.end(html);\n return;\n }\n }\n\n const pending = opts.pendingFiles?.() ?? [];\n const isGet = req.method === \"GET\" || req.method === \"HEAD\";\n let method = req.method ?? \"GET\";\n let body: string | Buffer | undefined;\n\n if (pending.length > 0 && isGet) {\n method = \"POST\";\n const params = new URLSearchParams();\n params.set(\"_method\", req.method ?? \"GET\");\n for (const f of pending) {\n params.set(`replace_templates[${f.relativePath}]`, f.read());\n }\n const token = getAuthToken();\n if (token) headers[\"authorization\"] = `Bearer ${token}`;\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = params.toString();\n headers[\"content-length\"] = String(Buffer.byteLength(body));\n } else if (!isGet) {\n body = await readBody(req);\n if (body.length > 0) {\n headers[\"content-length\"] = String(body.length);\n }\n }\n\n return new Promise((resolve, reject) => {\n const options: https.RequestOptions = {\n hostname: companyHost,\n port: 443,\n path: url.pathname + (url.search || \"\"),\n method,\n headers,\n };\n\n const proxyReq = https.request(options, (proxyRes) => {\n const contentType = proxyRes.headers[\"content-type\"] ?? \"\";\n const isHtml = contentType.includes(\"text/html\");\n\n const responseHeaders: Record<string, string | string[]> = {};\n for (const [k, v] of Object.entries(proxyRes.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && v !== undefined) {\n responseHeaders[k] = v as string | string[];\n }\n }\n\n if (isHtml) {\n const chunks: Buffer[] = [];\n proxyRes.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n proxyRes.on(\"end\", () => {\n let html = Buffer.concat(chunks).toString(\"utf-8\");\n html = injectHotReload(html, opts.reloadMode);\n responseHeaders[\"content-length\"] = String(Buffer.byteLength(html));\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n res.end(html);\n resolve();\n });\n } else {\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n proxyRes.pipe(res);\n proxyRes.on(\"end\", resolve);\n }\n });\n\n proxyReq.on(\"error\", (err) => {\n reject(err);\n });\n\n if (body) proxyReq.write(body);\n proxyReq.end();\n });\n}\n\nconst PAGE_FETCH_DESTS = new Set([\"document\", \"iframe\", \"frame\"]);\n\n/** Browser navigations to a storefront page, as opposed to API or asset calls. */\nfunction isPageRequest(req: IncomingMessage): boolean {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return false;\n const path = (req.url ?? \"/\").split(\"?\")[0] ?? \"/\";\n if (path.startsWith(\"/api/\")) return false;\n const dest = req.headers[\"sec-fetch-dest\"];\n // Mist and the web editor render the preview in an iframe.\n if (typeof dest === \"string\") return PAGE_FETCH_DESTS.has(dest);\n const accept = req.headers[\"accept\"];\n return typeof accept === \"string\" && accept.includes(\"text/html\");\n}\n\nfunction unresolvedTemplatePage(\n override: Extract<TemplateOverride, { kind: \"unresolved\" }>,\n devThemeId: number,\n): string {\n const template =\n `Template #${escapeHtml(override.requestedId)} ` +\n `(${escapeHtml(override.themeableType)} / ${escapeHtml(override.name)})`;\n const problem =\n override.reason === \"missing\"\n ? `dev theme #${devThemeId} has no template with that type and name. Sync or create it locally, or`\n : `dev theme #${devThemeId} has several templates with that type and name ` +\n `(#${override.candidateIds.join(\", #\")}), so the edited copy is ambiguous. Remove the duplicates, or`;\n return (\n `<!doctype html><title>Template not in dev theme</title>` +\n `<p>${template} belongs to another theme, and ${problem} ` +\n `remove <code>${TEMPLATE_OVERRIDE_PARAM}</code> to use the default.</p>`\n );\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n","import { isApiError } from \"@fluid-app/themes-api-client\";\n\n// Name of the bundled skill that walks the caller through migrating\n// per-template `styles.css` files to theme-level assets. See\n// `skills/template-stylesheet-to-asset-migration/SKILL.md` in this\n// package.\nexport const STYLESHEET_MIGRATION_SKILL =\n \"template-stylesheet-to-asset-migration\";\n\n// Detect the backend's 422 rejection for legacy stylesheet keys\n// (`ApplicationThemeResources::UpdateAction#stylesheet_rejected_response`\n// on the Rails side). Both the message and the errors payload identify\n// the rejection uniquely; match on either so a future wording tweak on\n// one surface does not silently drop the hint.\nfunction isStylesheetKeyRejection(error: {\n status: number;\n message: string;\n data: unknown;\n}): boolean {\n if (error.status !== 422) return false;\n if (/stylesheet.*no longer accepted/i.test(error.message)) return true;\n if (error.data && typeof error.data === \"object\") {\n const resourceErrors = (\n error.data as { application_theme_resource?: { key?: unknown } }\n ).application_theme_resource;\n if (\n resourceErrors &&\n typeof resourceErrors.key === \"string\" &&\n /stylesheet.*no longer accepted/i.test(resourceErrors.key)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Extract a human-readable message from a caught error. ApiError's default\n * `toString` prefixes with `ApiError:` and the class name; this returns the\n * API's own `error_message` verbatim so the CLI surfaces messages like\n * \"This stylesheet key is no longer accepted. Upload stylesheets as\n * theme-level assets.\" directly.\n *\n * When the error is the legacy stylesheet key rejection specifically,\n * a hint is appended pointing to the bundled migration skill — the\n * caller needs to move the per-template `styles.css` bytes to `assets/`\n * and stop pushing the deprecated column key.\n */\nexport function formatError(e: unknown): string {\n if (isApiError(e)) {\n const status = e.status ? ` [${e.status}]` : \"\";\n const hint = isStylesheetKeyRejection(e)\n ? `\\n ↳ Run \\`fluid theme skills install\\` — the bundled \\`${STYLESHEET_MIGRATION_SKILL}\\` skill can help you migrate this to a theme-level asset.`\n : \"\";\n return `${e.message}${status}${hint}`;\n }\n if (e instanceof Error) return e.message;\n return String(e);\n}\n","import { relative, sep } from \"node:path\";\nimport chokidar from \"chokidar\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ThemeFile } from \"../file.js\";\nimport { formatError } from \"../format-error.js\";\n\nexport type FileChangeHandler = (\n modified: ThemeFile[],\n added: ThemeFile[],\n removed: ThemeFile[],\n /** When the filesystem event arrived, not when the handler got to run.\n * Handlers are serialized behind awaited uploads, so a queued event can\n * start long after it happened — anything deciding where one edit ends and\n * the next begins has to use this, or it measures upload duration. */\n arrivedAt: number,\n) => Promise<void>;\n\nfunction relativeThemePath(root: ThemeRoot, filePath: string): string {\n return relative(root.root, filePath).split(sep).join(\"/\");\n}\n\nexport function watchTheme(\n root: ThemeRoot,\n handler: FileChangeHandler,\n): () => Promise<void> {\n const watcher = chokidar.watch(root.root, {\n ignoreInitial: true,\n ignored: (filePath: string) => {\n if (filePath.includes(\"node_modules\")) return true;\n try {\n const rel = relativeThemePath(root, filePath);\n const basename = rel.split(/[\\\\/]/).pop() ?? \"\";\n return basename.startsWith(\".\") || root.ignore.ignore(rel);\n } catch {\n return false;\n }\n },\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },\n });\n\n let pending = Promise.resolve();\n const enqueue = (fn: () => Promise<void>) => {\n // The change handler has its own internal try/catch around\n // per-file uploads/deletes, but anything that throws outside\n // that (e.g. `root.file()`, `validateSchema()`) must still be\n // surfaced — a bare `.catch(() => {})` here previously swallowed\n // it and left watch mode silently stuck.\n pending = pending.then(fn).catch((e) => {\n console.error(` [Watcher] change handling failed: ${formatError(e)}`);\n });\n };\n\n watcher.on(\"change\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([root.file(filePath)], [], [], arrivedAt));\n });\n\n watcher.on(\"add\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [root.file(filePath)], [], arrivedAt));\n });\n\n watcher.on(\"unlink\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [], [root.file(filePath)], arrivedAt));\n });\n\n return () => watcher.close();\n}\n","import { createHash } from \"node:crypto\";\nimport { lstatSync, readFileSync, readdirSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nconst METADATA = [\".fluid-assets.json\", \".fluid-theme.json\", \".fluidignore\"];\n\nexport class BackgroundPullChangedError extends Error {\n constructor() {\n super(\n \"Local files changed during background pull; remaining updates skipped.\",\n );\n }\n}\n\n/** Keep the preflight snapshot across asynchronous download/merge work. Each\n * mutation checks its original bytes synchronously and records only our writes.\n * This prevents an edit made after preflight from becoming a merge input or\n * being overwritten by a deferred write, asset cleanup, or metadata update. */\nexport class BackgroundPullGuard {\n private readonly root: string;\n private readonly expected: Map<string, string | null>;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.expected = this.snapshot();\n }\n\n assertUnchanged(): void {\n const current = this.snapshot();\n for (const key of new Set([...current.keys(), ...this.expected.keys()])) {\n if ((current.get(key) ?? null) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n }\n\n mutate(keys: readonly string[], action: () => void): void {\n for (const key of keys) {\n if (this.fingerprint(key) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n action();\n for (const key of keys) this.expected.set(key, this.fingerprint(key));\n }\n\n private fingerprint(key: string): string | null {\n const path = resolve(this.root, key);\n if (!path.startsWith(this.root + sep))\n throw new BackgroundPullChangedError();\n // Fail closed for a parent replaced by a symlink while awaiting Git/network.\n for (let cursor = path; ; cursor = dirname(cursor)) {\n try {\n if (lstatSync(cursor).isSymbolicLink())\n throw new BackgroundPullChangedError();\n } catch (error) {\n if (\n !(\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n )\n throw error;\n }\n if (cursor === this.root) break;\n }\n try {\n return createHash(\"sha256\").update(readFileSync(path)).digest(\"hex\");\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n return null;\n throw error;\n }\n }\n\n private snapshot(): Map<string, string | null> {\n const result = new Map<string, string | null>();\n const visit = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n continue;\n if (entry.isSymbolicLink()) throw new BackgroundPullChangedError();\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path);\n else {\n const key = relative(this.root, path).split(sep).join(\"/\");\n result.set(key, this.fingerprint(key));\n }\n }\n };\n visit(this.root);\n for (const key of METADATA) result.set(key, this.fingerprint(key));\n return result;\n }\n}\n","import { normalizeThemeResourceKey } from \"./resource-key.js\";\n\nexport class CaseCollisionError extends Error {\n constructor(readonly collisions: readonly (readonly string[])[]) {\n super(\n `Theme contains paths that differ only by letter case and cannot be synchronized safely:\\n${collisions\n .map((paths) => ` ${paths.join(\", \")}`)\n .join(\"\\n\")}`,\n );\n this.name = \"CaseCollisionError\";\n }\n}\n\n/** Refuse paths a case-insensitive checkout cannot represent independently. */\nexport function assertNoCaseCollisions(paths: readonly string[]): void {\n const pathsByFoldedKey = new Map<string, Set<string>>();\n for (const rawPath of paths) {\n const path = normalizeThemeResourceKey(rawPath);\n const foldedKey = path.toLowerCase();\n const matchingPaths = pathsByFoldedKey.get(foldedKey) ?? new Set<string>();\n matchingPaths.add(path);\n pathsByFoldedKey.set(foldedKey, matchingPaths);\n }\n\n const collisions = [...pathsByFoldedKey.values()]\n .filter((matchingPaths) => matchingPaths.size > 1)\n .map((matchingPaths) => [...matchingPaths].sort())\n .sort(([left = \"\"], [right = \"\"]) => left.localeCompare(right));\n\n if (collisions.length > 0) throw new CaseCollisionError(collisions);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nconst MANIFEST_FILE = \".fluid-assets.json\";\nconst MANIFEST_VERSION = 1;\n\n/**\n * A tiny shadow-repo placeholder for a manifest-backed asset. It preserves\n * the path as a deletion baseline without retaining the asset bytes locally.\n */\nexport const MANAGED_ASSET_SHADOW_SENTINEL = \"fluid-managed-asset\\n\";\n\nexport interface ThemeAssetLink {\n /** Theme that last supplied this asset reference. */\n sourceThemeId: number;\n /** SHA-256 checksum of the source resource, when the API provides one. */\n checksum?: string;\n /** ImageKit URL saved into themes that consume this asset. */\n url?: string;\n /** MIME type required to create a URL-backed FileResource. */\n contentType?: string;\n /** File size required to create a URL-backed FileResource. */\n contentSize?: number;\n /** Optional display metadata copied with the ImageKit URL. */\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n /** A dev-only source that must survive pull until a push makes it durable. */\n pending?: boolean;\n /** DAM asset identity retained as provenance for CLI-uploaded assets. */\n damAssetCode?: string;\n}\n\ninterface ThemeAssetManifestDocument {\n version: number;\n assets: Record<string, ThemeAssetLink>;\n}\n\n/**\n * Tracks binary theme assets that deliberately live only on the server.\n *\n * The manifest is a dotfile so it is excluded from theme uploads and file\n * watching. It is written before the corresponding local file is removed,\n * which prevents `delete: true` from mistaking the removed byte source for a\n * request to delete its remote FileResource.\n */\nexport class ThemeAssetManifest {\n private assets: Record<string, ThemeAssetLink>;\n private readonly path: string;\n\n constructor(themeRoot: string) {\n this.path = join(themeRoot, MANIFEST_FILE);\n this.assets = readDocument(this.path).assets;\n }\n\n reload(): void {\n this.assets = readDocument(this.path).assets;\n }\n\n keys(): string[] {\n return Object.keys(this.assets);\n }\n\n entries(): Array<[string, ThemeAssetLink]> {\n return Object.entries(this.assets).map(([key, link]) => [\n key,\n copyLink(link),\n ]);\n }\n\n /**\n * Stable digest of the URL references represented by this manifest.\n * Pull baselines exclude pending entries because those belong only to an\n * existing dev target and are deliberately absent from the pulled source.\n */\n fingerprint(opts: { excludePending?: boolean } = {}): string {\n const entries = this.entries()\n .filter(([, link]) => !opts.excludePending || !link.pending)\n .toSorted(([left], [right]) =>\n left < right ? -1 : left > right ? 1 : 0,\n );\n return createHash(\"sha256\").update(JSON.stringify(entries)).digest(\"hex\");\n }\n\n has(key: string): boolean {\n return this.assets[key] !== undefined;\n }\n\n get(key: string): ThemeAssetLink | undefined {\n const link = this.assets[key];\n return link ? copyLink(link) : undefined;\n }\n\n set(key: string, link: ThemeAssetLink): void {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(link)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n this.assets[key] = copyLink(link);\n }\n\n delete(key: string): void {\n delete this.assets[key];\n }\n\n write(): void {\n const document: ThemeAssetManifestDocument = {\n version: MANIFEST_VERSION,\n assets: copyAssets(this.assets),\n };\n const tempPath = `${this.path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\n try {\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(tempPath, JSON.stringify(document, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, this.path);\n } catch (error) {\n try {\n unlinkSync(tempPath);\n } catch {\n // The temporary file may not have been written yet.\n }\n throw error;\n }\n }\n}\n\nfunction readDocument(path: string): ThemeAssetManifestDocument {\n if (!existsSync(path)) return emptyDocument();\n\n try {\n return parseDocument(JSON.parse(readFileSync(path, \"utf-8\")));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Could not read ${MANIFEST_FILE}: ${message}`);\n }\n}\n\nfunction parseDocument(value: unknown): ThemeAssetManifestDocument {\n if (!isRecord(value) || value[\"version\"] !== MANIFEST_VERSION) {\n throw new Error(`expected version ${MANIFEST_VERSION}`);\n }\n\n const rawAssets = value[\"assets\"];\n if (!isRecord(rawAssets)) throw new Error(\"expected an assets object\");\n\n const assets: Record<string, ThemeAssetLink> = {};\n for (const [key, rawLink] of Object.entries(rawAssets)) {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(rawLink)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n assets[key] = copyLink(rawLink);\n }\n\n return { version: MANIFEST_VERSION, assets };\n}\n\nfunction emptyDocument(): ThemeAssetManifestDocument {\n return { version: MANIFEST_VERSION, assets: {} };\n}\n\nfunction copyAssets(\n assets: Record<string, ThemeAssetLink>,\n): Record<string, ThemeAssetLink> {\n return Object.fromEntries(\n Object.entries(assets).map(([key, link]) => [key, copyLink(link)]),\n );\n}\n\nfunction copyLink(link: ThemeAssetLink): ThemeAssetLink {\n return {\n sourceThemeId: link.sourceThemeId,\n ...(typeof link.checksum === \"string\" ? { checksum: link.checksum } : {}),\n ...(typeof link.url === \"string\" ? { url: link.url } : {}),\n ...(typeof link.contentType === \"string\"\n ? { contentType: link.contentType }\n : {}),\n ...(typeof link.contentSize === \"number\"\n ? { contentSize: link.contentSize }\n : {}),\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n ...(link.pending === true ? { pending: true } : {}),\n ...(typeof link.damAssetCode === \"string\"\n ? { damAssetCode: link.damAssetCode }\n : {}),\n };\n}\n\nfunction isThemeAssetLink(value: unknown): value is ThemeAssetLink {\n return (\n isRecord(value) &&\n typeof value[\"sourceThemeId\"] === \"number\" &&\n Number.isInteger(value[\"sourceThemeId\"]) &&\n value[\"sourceThemeId\"] > 0 &&\n (value[\"checksum\"] === undefined ||\n (typeof value[\"checksum\"] === \"string\" &&\n value[\"checksum\"].length > 0)) &&\n (value[\"url\"] === undefined ||\n (typeof value[\"url\"] === \"string\" && value[\"url\"].length > 0)) &&\n (value[\"contentType\"] === undefined ||\n (typeof value[\"contentType\"] === \"string\" &&\n value[\"contentType\"].length > 0)) &&\n (value[\"contentSize\"] === undefined ||\n (typeof value[\"contentSize\"] === \"number\" &&\n Number.isInteger(value[\"contentSize\"]) &&\n value[\"contentSize\"] > 0)) &&\n (value[\"previewImageUrl\"] === undefined ||\n (typeof value[\"previewImageUrl\"] === \"string\" &&\n value[\"previewImageUrl\"].length > 0)) &&\n (value[\"altText\"] === undefined || typeof value[\"altText\"] === \"string\") &&\n (value[\"handle\"] === undefined ||\n (typeof value[\"handle\"] === \"string\" && value[\"handle\"].length > 0)) &&\n (value[\"pending\"] === undefined || typeof value[\"pending\"] === \"boolean\") &&\n (value[\"damAssetCode\"] === undefined ||\n (typeof value[\"damAssetCode\"] === \"string\" &&\n value[\"damAssetCode\"].length > 0))\n );\n}\n\nexport function isThemeAssetKey(key: string): boolean {\n if (key.includes(\"\\\\\") || key.includes(\"\\0\")) return false;\n\n const segments = key.split(\"/\");\n return (\n segments[0] === \"assets\" &&\n segments.length === 2 &&\n segments[1] !== undefined &&\n segments[1].length > 0 &&\n segments[1] !== \".\" &&\n segments[1] !== \"..\"\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","// Resource keys that the backend can hide from the resources index\n// when `STYLESHEET_STRICT_INPUT` is enabled on the owning company —\n// theme-level `styles.css` / `global_styles.css` and per-template\n// composite `{type}/{name}/styles.css`. See `Themes::Resource.find_by`\n// + `Themes::Theme#theme_stylesheet_columns_hidden?` /\n// `Themes::Template#stylesheet_column_hidden?` on the Rails side.\n//\n// A pull that used the \"delete files missing on remote\" behavior would\n// otherwise wipe a merchant's local stylesheets the first time the\n// flag flips on — the resource just stops appearing in the index, not\n// the file itself. Skip these keys from the delete pass so local\n// content stays intact regardless of the flag state.\n\nconst STYLESHEET_KEY_PATTERN =\n /^(styles\\.css|global_styles\\.css|[^/]+\\/[^/]+\\/styles\\.css)$/;\n\nexport function isStylesheetKey(key: string): boolean {\n return STYLESHEET_KEY_PATTERN.test(key);\n}\n","import { isRetiredTemplateKey } from \"./resource-key.js\";\n\n/** The same file graph is used for uploads and incoming omission protection.\n * Rails remains responsible for resolving company-specific roles and handles.\n * Retired template types are never synced, so they are never dependencies. */\nexport function memberDependencyPaths(\n document: Record<string, unknown>,\n availablePaths: ReadonlySet<string>,\n deletedPaths: readonly string[] = [],\n changedPaths: ReadonlySet<string> = availablePaths,\n): Set<string> {\n const dependencies = new Set<string>();\n for (const entry of [\n ...records(document.templates),\n ...records(document.menu_references),\n ]) {\n if (typeof entry.path === \"string\") dependencies.add(entry.path);\n }\n for (const site of records(document.sites)) {\n for (const page of records(site.pages)) {\n if (typeof page.template_path === \"string\")\n dependencies.add(page.template_path);\n }\n const layout = site.layout_name;\n if (layout === \"none\") continue;\n if (\n typeof layout === \"string\" &&\n layout &&\n ![\"member\", \"theme\"].includes(layout)\n ) {\n dependencies.add(`layouts/${layout}.liquid`);\n continue;\n }\n const fallback = [layout, \"member\", \"theme\"]\n .filter(\n (name): name is string => typeof name === \"string\" && name.length > 0,\n )\n .map((name) => `layouts/${name}.liquid`)\n .find((path) => availablePaths.has(path) || deletedPaths.includes(path));\n if (fallback) dependencies.add(fallback);\n }\n\n // Variables are a separate resource, but their menu selectors belong to the\n // same template and must not replace the previous reference before import.\n for (const path of dependencies) {\n if (!path.endsWith(\"/index.liquid\")) continue;\n for (const suffix of [\"variables.json\", \"styles.css\"]) {\n const sibling = path.replace(/index\\.liquid$/, suffix);\n const candidates =\n suffix === \"styles.css\" ? changedPaths : availablePaths;\n if (candidates.has(sibling) || deletedPaths.includes(sibling))\n dependencies.add(sibling);\n }\n }\n\n for (const path of dependencies) {\n if (isRetiredTemplateKey(path)) dependencies.delete(path);\n }\n return dependencies;\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n const candidates: unknown[] = Array.isArray(entries) ? entries : [];\n return candidates.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === \"object\" && entry !== null && !Array.isArray(entry),\n );\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isRetiredTemplateKey } from \"./resource-key.js\";\n\nexport const MEMBER_CONFIGURATION_KEY = \"members_sites.json\";\n\nexport interface ThemeUpload {\n file: ThemeFile;\n memberConfiguration?: {\n content: string;\n files: ThemeFile[];\n };\n}\n\n/** Apply member selectors and their document together, before losing old references. */\nexport function planThemeUploads(\n themeRoot: ThemeRoot,\n changedFiles: readonly ThemeFile[],\n deletedPaths: readonly string[] = [],\n): ThemeUpload[] {\n const localFiles = themeRoot.files();\n const manifest = localFiles.find(\n (file) => file.relativePath === MEMBER_CONFIGURATION_KEY,\n );\n if (!manifest || themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY)) {\n return individualUploads(changedFiles);\n }\n\n const manifestContent = manifest.read();\n const parsed: unknown = JSON.parse(manifestContent);\n // Legacy arrays do not carry the V2 dependency graph. The server's existing\n // importer still accepts them through the ordinary manifest resource.\n if (Array.isArray(parsed)) return individualUploads(changedFiles);\n if (!isRecord(parsed)) {\n throw new Error(\"members_sites.json must contain a JSON object.\");\n }\n const { document, removed } = withoutRetiredTemplates(parsed);\n const content =\n removed.length > 0\n ? `${JSON.stringify(document, null, 2)}\\n`\n : manifestContent;\n\n const dependencies = memberDependencyPaths(\n document,\n new Set(localFiles.map((file) => file.relativePath)),\n deletedPaths,\n new Set(changedFiles.map((file) => file.relativePath)),\n );\n\n for (const path of deletedPaths) {\n if (dependencies.has(path)) {\n throw new Error(\n `Member dependency is still declared and cannot be deleted: ${path}`,\n );\n }\n }\n\n const memberChanged = changedFiles.some(\n (file) =>\n file.relativePath === MEMBER_CONFIGURATION_KEY ||\n dependencies.has(file.relativePath),\n );\n // A retired-reference cleanup has to reach the remote in the same push as\n // the local rewrite, so it rides along with any push, but only when the\n // bundle can be built: it must never block an unrelated change.\n if (!memberChanged && removed.length === 0) {\n return changedFiles.map((file) => ({ file }));\n }\n\n const availableFiles = new Map(\n localFiles.map((file) => [file.relativePath, file]),\n );\n const unavailable = [...dependencies].find((path) => {\n const file = availableFiles.get(path);\n return !file || !file.isText || themeRoot.ignore.ignore(path);\n });\n if (unavailable) {\n if (!memberChanged) return changedFiles.map((file) => ({ file }));\n throw new Error(\n `Member dependency must be an available text file: ${unavailable}`,\n );\n }\n const memberFiles = [...dependencies].map(\n (path) => availableFiles.get(path)!,\n );\n if (removed.length > 0) {\n // Rewrite the local file with what is uploaded. A filtered upload beside\n // an unfiltered file would differ from the acknowledged remote forever,\n // so every later sync would upload it again.\n manifest.write(content);\n }\n const uploads: ThemeUpload[] = changedFiles\n .filter(\n (file) =>\n file.relativePath !== MEMBER_CONFIGURATION_KEY &&\n !dependencies.has(file.relativePath),\n )\n .map((file) => ({ file }));\n uploads.push({\n file: manifest,\n memberConfiguration: { content, files: memberFiles },\n });\n return uploads;\n}\n\n/**\n * Drop references to retired template types. The server rejects the whole\n * document for one, which would also hold back every member site and menu it\n * carries.\n *\n * Only `templates` and `menu_references` can name one: a site page's template\n * must be a `members_page`. A menu left without any reference is dropped with\n * its reference, because the server rejects unreferenced menus too.\n */\nexport function withoutRetiredTemplates(document: Record<string, unknown>): {\n document: Record<string, unknown>;\n removed: string[];\n} {\n const removed = new Set<string>();\n const orphanCandidates = new Set<unknown>();\n const keep = (entry: unknown) => {\n if (!isRecord(entry) || typeof entry.path !== \"string\") return true;\n if (!isRetiredTemplateKey(entry.path)) return true;\n removed.add(entry.path);\n if (\"menu_key\" in entry) orphanCandidates.add(entry.menu_key);\n return false;\n };\n\n const next: Record<string, unknown> = { ...document };\n for (const key of [\"templates\", \"menu_references\"]) {\n if (Array.isArray(next[key])) next[key] = next[key].filter(keep);\n }\n if (removed.size === 0) return { document, removed: [] };\n\n if (orphanCandidates.size > 0 && Array.isArray(next.menus)) {\n const used = new Set<unknown>([\n ...records(next.sites).map((site) => site.menu_key),\n ...records(next.menu_references).map((entry) => entry.menu_key),\n ]);\n next.menus = next.menus.filter(\n (menu) =>\n !isRecord(menu) ||\n !orphanCandidates.has(menu.key) ||\n used.has(menu.key),\n );\n }\n return { document: next, removed: [...removed] };\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n return Array.isArray(entries) ? entries.filter(isRecord) : [];\n}\n\nfunction individualUploads(files: readonly ThemeFile[]): ThemeUpload[] {\n return [...files]\n .sort(\n (left, right) =>\n Number(left.relativePath === MEMBER_CONFIGURATION_KEY) -\n Number(right.relativePath === MEMBER_CONFIGURATION_KEY),\n )\n .map((file) => ({ file }));\n}\n\nfunction isRecord(entry: unknown): entry is Record<string, unknown> {\n return typeof entry === \"object\" && entry !== null && !Array.isArray(entry);\n}\n\n/** Removing an older file is not an instruction to erase a member site. */\nexport function assertMemberConfigurationRetained(\n deletedPaths: readonly string[],\n): void {\n if (deletedPaths.includes(MEMBER_CONFIGURATION_KEY)) {\n throw new Error(\n \"Keep members_sites.json. To remove member configuration, upload an explicit empty versioned document instead of deleting or renaming the file.\",\n );\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport {\n isApiError,\n themes,\n type components,\n} from \"@fluid-app/themes-api-client\";\nimport type { ApiClient } from \"../api.js\";\nimport { formatError } from \"./format-error.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport {\n isThemeAssetKey,\n ThemeAssetManifest,\n type ThemeAssetLink,\n} from \"./asset-manifest.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n MEMBER_CONFIGURATION_KEY,\n} from \"./member-configuration.js\";\nimport type {\n DevRemoteState,\n RemoteResourceState,\n} from \"./dev-remote-baseline.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\ninterface RemoteAssetMetadata {\n url: string;\n contentType: string;\n contentSize: number;\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n}\n\ninterface ManagedAssetPlan {\n key: string;\n link: ThemeAssetLink;\n targetResource?: RemoteResource;\n metadata?: RemoteAssetMetadata;\n}\n\ntype UploadedBinaryResource = RemoteResource & {\n damAssetCode?: string;\n assetMetadata?: RemoteAssetMetadata;\n};\n\nconst ASSET_REFERENCE_CONCURRENCY = 6;\n\nexport interface SyncResult {\n uploaded: number;\n downloaded: number;\n linked: number;\n deleted: number;\n errors: string[];\n validationFailed: boolean;\n}\n\n/**\n * Server rejected the push because the CLI's `base_sha` no longer\n * matches the theme's current `content_version_sha` (someone else\n * wrote to the theme since our last pull). Callers surface a\n * \"pull first\" message; no partial state has been written when this\n * throws from the preflight, and the per-file variant preserves the\n * atomicity guarantee mid-loop by short-circuiting the remaining\n * files on the first conflicting response.\n */\nexport class PushConflictError extends Error {\n constructor(public readonly remoteSha: string | null) {\n super(\n remoteSha\n ? `Your local is behind the server. Server is at ${remoteSha}; local is stale.`\n : \"Your local is behind the server.\",\n );\n this.name = \"PushConflictError\";\n }\n}\n\nexport class Syncer {\n private checksumIndex = new Map<string, string>();\n private memberAcknowledgments = new Map<string, string>();\n private rawRemoteResources = new Map<string, RemoteResource>();\n private remoteResourceGroups = new Map<string, RemoteResource[]>();\n private remoteResourceIndex = new Map<string, RemoteResource>();\n private remoteIndexesDirty = false;\n private remoteResourcesLoaded = false;\n private lastKnownRemoteSha: string | null = null;\n private assetManifestInstance: ThemeAssetManifest | undefined;\n\n constructor(\n private api: ApiClient,\n private themeId: number,\n private themeRoot: ThemeRoot,\n assetManifest?: ThemeAssetManifest,\n ) {\n this.assetManifestInstance = assetManifest;\n }\n\n private get assetManifest(): ThemeAssetManifest {\n this.assetManifestInstance ??= new ThemeAssetManifest(this.themeRoot.root);\n return this.assetManifestInstance;\n }\n\n // ─── Checksum Management ──────────────────────────────────────────────────\n\n async fetchChecksums(): Promise<void> {\n // `content_version_sha` on the resources index is a Phase 003a\n // server addition — older servers don't emit it. Cast through\n // `unknown` because the typed API client hasn't been regenerated\n // against the new OpenAPI spec yet; regeneration is a follow-up.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n this.updateChecksums(body.application_theme_resources ?? []);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n }\n\n /**\n * Server's `content_version_sha` captured on the last `fetchChecksums()`\n * or `downloadAll()`. `null` when talking to a pre-003a server.\n */\n remoteSha(): string | null {\n return this.lastKnownRemoteSha;\n }\n\n private updateChecksums(resources: RemoteResource[]): void {\n assertNoCaseCollisions(\n resources.flatMap((resource) => (resource.key ? [resource.key] : [])),\n );\n this.rawRemoteResources.clear();\n this.remoteResourceGroups.clear();\n for (const resource of resources) {\n if (!resource.key) continue;\n\n this.rawRemoteResources.set(resource.key, resource);\n const group = this.remoteResourceGroups.get(resource.key) ?? [];\n group.push(resource);\n this.remoteResourceGroups.set(resource.key, group);\n }\n this.remoteIndexesDirty = true;\n }\n\n private setRemoteResource(resource: RemoteResource): void {\n if (!resource.key) return;\n this.rawRemoteResources.set(resource.key, resource);\n this.remoteResourceGroups.set(resource.key, [resource]);\n this.remoteIndexesDirty = true;\n }\n\n private removeRemoteResource(relativePath: string): void {\n this.rawRemoteResources.delete(relativePath);\n this.remoteResourceGroups.delete(relativePath);\n this.remoteIndexesDirty = true;\n }\n\n // Rebuilding on every setRemoteResource/removeRemoteResource made bulk\n // upload loops O(n²) over the remote resource map, so mutations only mark\n // the indexes dirty and the next read rebuilds once.\n private get checksums(): Map<string, string> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.checksumIndex;\n }\n\n private get remoteResources(): Map<string, RemoteResource> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.remoteResourceIndex;\n }\n\n private rebuildRemoteIndexes(): void {\n this.remoteIndexesDirty = false;\n this.checksumIndex.clear();\n this.remoteResourceIndex.clear();\n\n for (const [key, resource] of this.rawRemoteResources) {\n // The backend may return both a legacy key and its .liquid equivalent.\n // Preserve the explicit .liquid resource when both are present.\n if (this.rawRemoteResources.has(`${key}.liquid`)) continue;\n\n this.remoteResourceIndex.set(key, resource);\n if (resource.checksum) this.checksumIndex.set(key, resource.checksum);\n }\n }\n\n hasChanged(file: ThemeFile): boolean {\n return file.checksum() !== this.checksums.get(file.relativePath);\n }\n\n remoteKeys(): string[] {\n return [...this.remoteResources.keys()];\n }\n\n /** A null-content resource has no possible working-tree counterpart. */\n private canDeleteRemoteResource(key: string): boolean {\n const resource = this.remoteResources.get(key);\n return resource?.content != null || isManagedAssetResource(resource);\n }\n\n /** Snapshot of remote checksums (key → sha256). Available after fetchChecksums() or downloadAll(). */\n remoteChecksums(): Record<string, string> {\n return Object.fromEntries(this.checksums);\n }\n\n /** URL-backed assets keyed by their exact theme resource path. */\n remoteAssetUrls(): Record<string, string> {\n const urls: Record<string, string> = {};\n for (const [key, resource] of this.remoteResources) {\n if (!isManagedAssetResource(resource)) continue;\n const url = resource.url;\n if (typeof url === \"string\" && url.length > 0) urls[key] = url;\n }\n return urls;\n }\n\n /** Compact, complete resource state paired with its acknowledged dev SHA. */\n devRemoteState(assetManifestSha: string): DevRemoteState | null {\n if (!this.remoteResourcesLoaded || !this.lastKnownRemoteSha) return null;\n\n return {\n themeId: this.themeId,\n remoteSha: this.lastKnownRemoteSha,\n assetManifestSha,\n resources: [...this.remoteResourceGroups.values()]\n .flat()\n .map(remoteResourceState),\n };\n }\n\n useDevRemoteState(state: DevRemoteState): void {\n if (state.themeId !== this.themeId) {\n throw new Error(\n `Dev remote state belongs to theme #${state.themeId}, not #${this.themeId}`,\n );\n }\n this.updateChecksums(state.resources.map(remoteResourceFromState));\n this.lastKnownRemoteSha = state.remoteSha;\n this.remoteResourcesLoaded = true;\n }\n\n private async ensureRemoteResourcesLoaded(): Promise<void> {\n if (!this.remoteResourcesLoaded) await this.fetchChecksums();\n }\n\n /**\n * Adds URL-backed FileResources for manifest assets without transferring\n * their bytes. The target stores the source asset's ImageKit URL.\n */\n async linkManagedAssets(opts: { replace?: boolean } = {}): Promise<number> {\n this.assetManifest.reload();\n await this.ensureRemoteResourcesLoaded();\n\n const plans: ManagedAssetPlan[] = [];\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n // A local binary is the recovery source after a failed metadata pull or\n // an intentional replacement. Let the normal upload path handle it;\n // never recreate the older manifest URL over those bytes first.\n if (this.themeRoot.file(key).exists) continue;\n\n const targetResource =\n this.managedAssetResourceForLink(key, link) ??\n this.remoteResources.get(key);\n if (\n targetResource &&\n !this.managedAssetNeedsRefresh(targetResource, link)\n ) {\n continue;\n }\n if (targetResource && !opts.replace) continue;\n\n const metadata = assetMetadataFromLink(link);\n plans.push({\n key,\n link,\n ...(targetResource ? { targetResource } : {}),\n ...(metadata ? { metadata } : {}),\n });\n }\n\n if (plans.length > 0) {\n await this.resolveAssetMetadata(plans);\n await this.createAssetReferences(plans);\n await this.fetchChecksums();\n }\n if (opts.replace && (await this.pruneDuplicateManagedAssetReferences())) {\n await this.fetchChecksums();\n }\n this.ensureManagedAssetsAreResolved();\n\n return plans.length;\n }\n\n private async resolveAssetMetadata(plans: ManagedAssetPlan[]): Promise<void> {\n const bySourceTheme = new Map<number, ManagedAssetPlan[]>();\n for (const plan of plans) {\n if (plan.metadata) continue;\n const sourcePlans = bySourceTheme.get(plan.link.sourceThemeId) ?? [];\n sourcePlans.push(plan);\n bySourceTheme.set(plan.link.sourceThemeId, sourcePlans);\n }\n\n let manifestChanged = false;\n for (const [sourceThemeId, sourcePlans] of bySourceTheme) {\n let sourceAssets: Map<string, RemoteAssetMetadata>;\n try {\n sourceAssets = await this.fetchThemeAssetMetadata(sourceThemeId);\n } catch (error) {\n throw new Error(\n `Could not read asset metadata from theme #${sourceThemeId}: ${formatError(error)}`,\n );\n }\n\n for (const plan of sourcePlans) {\n const sourceMetadata = sourceAssets.get(assetFilename(plan.key));\n if (!sourceMetadata) {\n throw new Error(\n `Could not find usable metadata for ${plan.key} in theme #${sourceThemeId}`,\n );\n }\n\n // Preserve a pulled URL while enriching an older manifest. A remote\n // source update must not silently change this checkout's asset URL.\n const metadata: RemoteAssetMetadata = {\n ...sourceMetadata,\n ...(typeof plan.link.url === \"string\" ? { url: plan.link.url } : {}),\n };\n plan.metadata = metadata;\n this.assetManifest.set(plan.key, {\n ...plan.link,\n ...metadata,\n });\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) this.assetManifest.write();\n }\n\n private async fetchThemeAssetMetadata(\n sourceThemeId: number,\n ): Promise<Map<string, RemoteAssetMetadata>> {\n const body = await themes.getThemeAssets(this.api, sourceThemeId);\n if (!isRecord(body) || !Array.isArray(body[\"file_resources\"])) {\n throw new Error(\"Theme assets response did not include file_resources\");\n }\n\n const assets = new Map<string, RemoteAssetMetadata>();\n for (const value of body[\"file_resources\"]) {\n const asset = parseThemeAssetMetadata(value);\n if (asset) assets.set(asset.filename, asset.metadata);\n }\n return assets;\n }\n\n private async createAssetReferences(\n plans: ManagedAssetPlan[],\n ): Promise<void> {\n const errors: string[] = [];\n let nextPlan = 0;\n\n const worker = async () => {\n while (nextPlan < plans.length) {\n const plan = plans[nextPlan];\n nextPlan += 1;\n if (!plan || !plan.metadata) continue;\n\n try {\n await this.createAssetReference(plan);\n } catch (error) {\n errors.push(`${plan.key}: ${formatError(error)}`);\n }\n }\n };\n\n await Promise.all(\n Array.from(\n { length: Math.min(ASSET_REFERENCE_CONCURRENCY, plans.length) },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not save ${errors.length} ImageKit URL reference(s) (this requires File Resources update access): ${errors.join(\"; \")}`,\n );\n }\n }\n\n private async createAssetReference(plan: ManagedAssetPlan): Promise<void> {\n const metadata = plan.metadata;\n if (!metadata) throw new Error(\"asset metadata was not resolved\");\n\n const targetResourceId = plan.targetResource?.resource_id;\n if (plan.targetResource && typeof targetResourceId !== \"number\") {\n throw new Error(\"existing target asset has no resource ID\");\n }\n\n const body = await themes.createFileResource(this.api, {\n file_resource: {\n url: metadata.url,\n filename: assetFilename(plan.key),\n content_type: metadata.contentType,\n content_size: metadata.contentSize,\n ...(metadata.previewImageUrl\n ? { preview_image_url: metadata.previewImageUrl }\n : {}),\n ...(metadata.altText !== undefined\n ? { alt_text: metadata.altText }\n : {}),\n ...(metadata.handle ? { handle: metadata.handle } : {}),\n relateable_id: this.themeId,\n relateable_type: \"ApplicationTheme\",\n },\n });\n\n const createdResourceId = createdFileResourceId(body);\n if (!createdResourceId) {\n throw new Error(\"create response did not include a FileResource ID\");\n }\n\n if (typeof targetResourceId !== \"number\") return;\n\n try {\n await themes.destroyFileResource(this.api, targetResourceId);\n } catch (error) {\n if (isNotFoundError(error)) return;\n try {\n await themes.destroyFileResource(this.api, createdResourceId);\n } catch {\n // Keep the original error; a later sync can repair any duplicate.\n }\n throw error;\n }\n }\n\n /**\n * Returns every resource for an exact logical key. A legacy `foo` resource\n * is hidden when `foo.liquid` exists, matching the normal remote index.\n */\n private resourcesForLogicalKey(key: string): RemoteResource[] {\n if (this.rawRemoteResources.has(`${key}.liquid`)) return [];\n return this.remoteResourceGroups.get(key) ?? [];\n }\n\n private managedAssetResourceForLink(\n key: string,\n link: ThemeAssetLink,\n ): RemoteResource | undefined {\n return this.resourcesForLogicalKey(key).find(\n (resource) =>\n isManagedAssetResource(resource) &&\n !this.managedAssetNeedsRefresh(resource, link),\n );\n }\n\n /** Make interrupted reference replacement converge to one FileResource. */\n private async pruneDuplicateManagedAssetReferences(): Promise<boolean> {\n const resourcesToDelete: Array<{ key: string; resourceId: number }> = [];\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key) || !link.url) continue;\n\n const resources = this.resourcesForLogicalKey(key).filter(\n isManagedAssetResource,\n );\n if (resources.length < 2) continue;\n\n const matchingResources = resources.filter(\n (resource) => resource.url === link.url,\n );\n if (matchingResources.length === 0) continue;\n\n const keeper = this.lowestResourceId(matchingResources, key);\n const keeperId = this.resourceIdForAssetReference(keeper, key);\n for (const resource of resources) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId !== keeperId)\n resourcesToDelete.push({ key, resourceId });\n }\n }\n\n if (resourcesToDelete.length === 0) return false;\n\n const errors: string[] = [];\n let nextResource = 0;\n const worker = async () => {\n while (nextResource < resourcesToDelete.length) {\n const resource = resourcesToDelete[nextResource];\n nextResource += 1;\n if (!resource) continue;\n\n try {\n await themes.destroyFileResource(this.api, resource.resourceId);\n } catch (error) {\n if (!isNotFoundError(error)) {\n errors.push(`${resource.key}: ${formatError(error)}`);\n }\n }\n }\n };\n\n await Promise.all(\n Array.from(\n {\n length: Math.min(\n ASSET_REFERENCE_CONCURRENCY,\n resourcesToDelete.length,\n ),\n },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not remove ${errors.length} duplicate ImageKit URL reference(s): ${errors.join(\"; \")}`,\n );\n }\n\n return true;\n }\n\n private lowestResourceId(\n resources: RemoteResource[],\n key: string,\n ): RemoteResource {\n let lowest = resources[0];\n if (!lowest) throw new Error(`No asset resources found for ${key}`);\n\n let lowestId = this.resourceIdForAssetReference(lowest, key);\n for (const resource of resources.slice(1)) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId < lowestId) {\n lowest = resource;\n lowestId = resourceId;\n }\n }\n return lowest;\n }\n\n private resourceIdForAssetReference(\n resource: RemoteResource,\n key: string,\n ): number {\n const resourceId = positiveInteger(resource.resource_id);\n if (!resourceId) {\n throw new Error(`Existing target asset has no resource ID: ${key}`);\n }\n return resourceId;\n }\n\n /** Makes this target the provenance source for assets it now resolves. */\n repointManagedAssetsToCurrentTheme(): void {\n this.assetManifest.reload();\n let changed = false;\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n if (this.themeRoot.file(key).exists) continue;\n\n const resource = this.remoteResources.get(key);\n if (!isManagedAssetResource(resource)) {\n throw new Error(`Managed asset is missing from target theme: ${key}`);\n }\n\n const nextLink: ThemeAssetLink = { ...link, sourceThemeId: this.themeId };\n delete nextLink.pending;\n delete nextLink.checksum;\n Object.assign(nextLink, resourceLink(resource));\n this.assetManifest.set(key, nextLink);\n changed = true;\n }\n\n if (changed) this.assetManifest.write();\n }\n\n // ─── Upload ───────────────────────────────────────────────────────────────\n\n /**\n * Uploads one file. Resolves with the exact text content that was sent to\n * the server (null for binary files) so callers can run diagnostics against\n * the same bytes instead of re-reading a file that may have changed on disk\n * while the request was in flight.\n */\n async uploadFile(\n file: ThemeFile,\n baseSha?: string | null,\n opts: { pendingAsset?: boolean } = {},\n ): Promise<string | null> {\n if (file.isText) {\n const content = file.read();\n const resource = await this.putResource(\n { key: file.relativePath, content },\n baseSha,\n );\n this.setRemoteResource({\n ...resource,\n key: file.relativePath,\n content,\n checksum: resource.checksum ?? file.checksum(),\n });\n return content;\n }\n\n if (isNestedBinaryThemeAsset(file)) {\n throw new Error(\n `Binary assets must be directly inside assets/: ${file.relativePath}`,\n );\n }\n\n const resource = await this.uploadBinaryFile(file, baseSha);\n this.setRemoteResource(resource);\n if (isThemeAssetKey(file.relativePath)) {\n this.externalizeBinaryFile(file, resource, opts.pendingAsset);\n }\n return null;\n }\n\n /** Canonical member bytes accepted by the server during this transfer. */\n acknowledgedMemberContents(): ReadonlyMap<string, string> {\n return this.memberAcknowledgments;\n }\n\n /** Upload one member document and its changed dependencies as one transaction. */\n async uploadMemberConfiguration(\n upload: ThemeUpload,\n baseSha?: string | null,\n ): Promise<Array<{ file: ThemeFile; content: string }>> {\n if (!upload.memberConfiguration) {\n throw new Error(\"A member configuration upload is required.\");\n }\n const { content, files } = upload.memberConfiguration;\n const capturedFiles = files.map((file) => ({ file, content: file.read() }));\n const resource = await this.putResource(\n {\n key: MEMBER_CONFIGURATION_KEY,\n content,\n member_files: capturedFiles.map(\n ({ file, content: capturedContent }) => ({\n key: file.relativePath,\n content: capturedContent,\n }),\n ),\n },\n baseSha,\n capturedFiles.map(({ file }) => file.relativePath),\n );\n // The server may allocate new menu handles and rewrite selectors. Keep its\n // acknowledged contents/checksums, never claim that the local bytes landed.\n this.setRemoteResource(resource);\n if (resource.key && resource.content != null) {\n this.memberAcknowledgments.set(\n resource.key,\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content),\n );\n }\n return [{ file: upload.file, content }, ...capturedFiles];\n }\n\n /**\n * Wraps the generated `updateThemeResource` client with the two\n * merge-aware Phase 003 additions: sending `base_sha` on the request\n * and reading the server's fresh `content_version_sha` off the\n * response so the caller can thread it forward on the next PUT.\n *\n * Accepts any `application_theme_resource` shape — text uploads pass\n * `{ key, content }`; binary uploads (after DAM + ImageKit\n * orchestration) pass `{ key, dam_asset: { ... } }`. Both must route\n * through here so `lastKnownRemoteSha` stays in lockstep with every\n * write the server has ack'd, mixed text/binary pushes included.\n *\n * The typed client hasn't been regenerated against the new OpenAPI\n * spec yet, so `base_sha` is threaded through as an extra property\n * (server accepts unknown fields on this endpoint) and the response\n * is cast to read the extra `content_version_sha`. Regeneration is\n * a follow-up; that PR will drop these casts.\n *\n * `PushConflictError` is thrown on a 409 so callers can distinguish\n * \"server rejected because of stale base\" from generic upload\n * failures.\n */\n private async putResource(\n resource: Record<string, unknown>,\n baseSha: string | null | undefined,\n expectedMemberKeys?: readonly string[],\n ): Promise<RemoteResource> {\n const body: Record<string, unknown> = {\n application_theme_resource: resource,\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.updateThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as {\n application_theme_resource?: RemoteResource;\n member_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n if (expectedMemberKeys) {\n const acknowledged = new Map(\n (response.member_resources ?? []).map((entry) => [entry.key, entry]),\n );\n const manifest = response.application_theme_resource;\n if (\n manifest?.key !== MEMBER_CONFIGURATION_KEY ||\n manifest.content == null ||\n !manifest.checksum ||\n expectedMemberKeys.some(\n (key) =>\n acknowledged.get(key)?.content == null ||\n !acknowledged.get(key)?.checksum,\n )\n ) {\n throw new Error(\n \"Fluid did not acknowledge every bundled member file. Update the server before retrying this push.\",\n );\n }\n }\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n for (const memberResource of response.member_resources ?? []) {\n this.setRemoteResource(memberResource);\n if (memberResource.key && memberResource.content != null) {\n this.memberAcknowledgments.set(\n memberResource.key,\n typeof memberResource.content === \"string\"\n ? memberResource.content\n : JSON.stringify(memberResource.content),\n );\n }\n }\n return (\n response.application_theme_resource ?? {\n key: typeof resource[\"key\"] === \"string\" ? resource[\"key\"] : \"\",\n checksum: null,\n }\n );\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n /**\n * Server-side push preflight (Phase 003a). Runs once at the start of\n * a push loop. On 200 the server's fresh `remote_sha` is stashed on\n * the syncer so subsequent per-file PUTs can carry it. On 409 a\n * `PushConflictError` is thrown carrying the server's `remote_sha`\n * from the `meta` payload — the caller renders \"pull first\".\n *\n * Skipped when `baseSha` is null/undefined so old-behavior pushes\n * (no stored `baseSha` in `.fluid-theme.json`) and `--force` pushes\n * short-circuit past the check.\n */\n /**\n * Tell Fluid the push is finished, so it commits the theme's current state\n * as one version.\n *\n * The other half of `preflightPush`. That one runs once before the file\n * loop to reject a stale base; this runs once after it, and is the only\n * thing that turns a push into a commit — the per-file writes just mark the\n * theme changed.\n *\n * It exists because the server cannot see where an operation ends. A push\n * arrives as a hundred-odd independent requests, and every way of inferring\n * \"these belong together\" either merges two publishes that happened to land\n * close together or splits one push across several commits. The client\n * knows; this says so.\n *\n * Returns false rather than throwing when the request fails. Callers must\n * retain their previous baseline until finalization succeeds; the server\n * can reject an incomplete member graph even after individual files land.\n *\n * Returns whether Fluid took the request, because a caller tracking edit\n * boundaries needs to know. A failed ask leaves the previous edit\n * uncommitted, and uploading the next one over it merges the two into\n * whichever commit eventually lands.\n */\n async requestSync(): Promise<boolean> {\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/sync`,\n {},\n )) as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n return true;\n } catch (e) {\n console.warn(\n ` ⚠ couldn't ask Fluid to commit this push (${formatError(e)}). Retry the push to finalize it.`,\n );\n return false;\n }\n }\n\n async preflightPush(baseSha: string | null | undefined): Promise<void> {\n if (!baseSha) return;\n\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/check_push`,\n { base_sha: baseSha },\n )) as { remote_sha?: string };\n if (response.remote_sha) this.lastKnownRemoteSha = response.remote_sha;\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n private rethrowIfConflict(e: unknown): unknown {\n const status =\n (e as { status?: number; response?: { status?: number } })?.status ??\n (e as { response?: { status?: number } })?.response?.status;\n if (status !== 409) return e;\n\n // `ApiError` from api-client-core stores the parsed response body\n // under `.data` (not `.body`). Reading the wrong key silently\n // dropped `remote_sha` in production so the \"server is at X\"\n // hint in the CLI output went missing.\n const meta = (e as { data?: { meta?: { remote_sha?: string } } })?.data\n ?.meta;\n return new PushConflictError(meta?.remote_sha ?? null);\n }\n\n private async uploadBinaryFile(\n file: ThemeFile,\n baseSha: string | null | undefined,\n ): Promise<UploadedBinaryResource> {\n // Step 1: Create DAM placeholder\n const placeholderBody = await this.api.post<{\n asset: { id: number; canonical_path: string };\n }>(\"/api/dam/assets\", {\n placeholder_asset: {\n description: `Uploaded via Fluid CLI: ${file.name}`,\n mime_type: file.mime.name,\n name: file.name,\n },\n });\n const asset = placeholderBody.asset;\n\n // Step 2: Get ImageKit auth token\n const authBody = await this.api.post<{\n token: string;\n signature: string;\n expire: number;\n }>(\"/api/dam/assets/imagekit_auth\", {});\n\n // Step 3: Upload to ImageKit via multipart\n const folder = this.canonicalPathToImageKitFolder(asset.canonical_path);\n const formData = new FormData();\n const blob = new Blob([file.readBinary() as unknown as ArrayBuffer], {\n type: file.mime.name,\n });\n formData.append(\"file\", blob, file.name);\n formData.append(\"token\", authBody.token);\n formData.append(\"signature\", authBody.signature);\n formData.append(\"expire\", String(authBody.expire));\n formData.append(\"folder\", folder);\n formData.append(\"fileName\", file.name);\n formData.append(\"publicKey\", \"public_j7s4Ih9ETh/OCp41mVQH7tlXBdU=\");\n\n const ikResp = await fetch(\n \"https://upload.imagekit.io/api/v1/files/upload\",\n {\n method: \"POST\",\n body: formData,\n },\n );\n if (!ikResp.ok) throw new Error(`ImageKit upload failed: ${ikResp.status}`);\n const ikBody = (await ikResp.json()) as {\n fileId: string;\n url: string;\n thumbnailUrl: string;\n size: number;\n height?: number;\n width?: number;\n };\n\n // Step 4: Backfill DAM asset\n const backfillPayload: Record<string, unknown> = {\n asset: {\n id: asset.id,\n imagekit_file_id: ikBody.fileId,\n imagekit_url: ikBody.url,\n mime_type: file.mime.name,\n name: file.name,\n file_size: ikBody.size,\n expected_path: asset.canonical_path,\n },\n };\n if (ikBody.height)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"height\"] =\n ikBody.height;\n if (ikBody.width)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"width\"] =\n ikBody.width;\n\n const backfillBody = await this.api.post<{\n asset: { code: string; default_variant_url: string };\n }>(\"/api/dam/assets/backfill_imagekit\", backfillPayload);\n\n // Step 5: Associate with theme resource. Route through `putResource`\n // so `base_sha` gets sent and `lastKnownRemoteSha` advances just\n // like text-file writes — a mixed text+binary push must keep them\n // in a single monotonic chain.\n const update = await this.putResource(\n {\n key: file.relativePath,\n dam_asset: {\n dam_asset_code: backfillBody.asset.code,\n content_type: file.mime.name,\n content_size: ikBody.size,\n filename: file.name,\n handle: backfillBody.asset.code,\n url: backfillBody.asset.default_variant_url,\n preview_image_url: ikBody.thumbnailUrl,\n },\n },\n baseSha,\n );\n\n // Older API responses can omit URL fields. Preserve the known DAM URL so\n // the manifest stays usable after a successful remote write.\n return {\n ...update,\n key: update.key || file.relativePath,\n url: update.url ?? backfillBody.asset.default_variant_url,\n damAssetCode: backfillBody.asset.code,\n assetMetadata: {\n url: backfillBody.asset.default_variant_url,\n contentType: file.mime.name,\n contentSize: ikBody.size,\n previewImageUrl: ikBody.thumbnailUrl,\n altText: file.name,\n handle: backfillBody.asset.code,\n },\n };\n }\n\n private externalizeBinaryFile(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n pending: boolean | undefined,\n ): void {\n // Persist before unlinking. If persistence fails, retain the local bytes so\n // the developer can retry instead of losing the only provenance record.\n this.assetManifest.reload();\n this.assetManifest.set(file.relativePath, {\n sourceThemeId: this.themeId,\n ...resourceLink(resource),\n ...uploadedAssetMetadata(file, resource),\n ...(pending ? { pending: true } : {}),\n ...(typeof resource.damAssetCode === \"string\"\n ? { damAssetCode: resource.damAssetCode }\n : {}),\n });\n this.assetManifest.write();\n unlinkSync(file.absolutePath);\n }\n\n private canonicalPathToImageKitFolder(canonicalPath: string): string {\n const parts = canonicalPath.split(\".\");\n const companyId = parts[0] ?? \"unknown\";\n const category = parts[1] ?? \"files\";\n const assetCode = parts[2] ?? \"unknown\";\n const folderMap: Record<string, string> = {\n images: \"images\",\n videos: \"videos\",\n audio: \"audio\",\n documents: \"documents\",\n files: \"files\",\n };\n return `${companyId}/${folderMap[category] ?? \"files\"}/${assetCode}`;\n }\n\n // ─── Delete ───────────────────────────────────────────────────────────────\n\n async deleteRemoteFile(\n relativePath: string,\n baseSha?: string | null,\n ): Promise<void> {\n assertMemberConfigurationRetained([relativePath]);\n // Chokidar observes the intentional unlink after a binary has been\n // externalized. That unlink must never delete the server-side DAM link.\n this.assetManifest.reload();\n if (this.assetManifest.has(relativePath)) return;\n\n const body: Record<string, unknown> = {\n application_theme_resource: { key: relativePath },\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.deleteThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n } catch (e) {\n if (!isNotFoundError(e)) throw this.rethrowIfConflict(e);\n }\n this.removeRemoteResource(relativePath);\n }\n\n // ─── Download ─────────────────────────────────────────────────────────────\n\n async downloadAll(): Promise<RemoteResource[]> {\n // Same cast rationale as `fetchChecksums`. The additional\n // `content_version_sha` field is stashed on the syncer so callers\n // (pull command) can persist it as the new `baseSha`.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n const resources = body.application_theme_resources ?? [];\n this.updateChecksums(resources);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n return resources;\n }\n\n async downloadBinaryAsset(url: string): Promise<Buffer> {\n const resp = await fetch(url);\n if (!resp.ok) throw new Error(`Failed to download asset: ${resp.status}`);\n return Buffer.from(await resp.arrayBuffer());\n }\n\n /**\n * Move directly-addressable binary `assets/*` resources into the local\n * manifest before merge-pull sees them. This prevents a byte download and\n * keeps those paths out of the shadow repository; their canonical state is\n * the ImageKit URL, not a local file.\n */\n async externalizePulledAssets(\n resources: RemoteResource[],\n opts: { delete: boolean; backgroundGuard?: BackgroundPullGuard },\n ): Promise<{ managedKeys: Set<string>; linked: number; errors: string[] }> {\n this.assetManifest.reload();\n\n const managedKeys = new Set<string>();\n const changedManifestKeys = new Set<string>();\n const remoteManagedKeys = new Set<string>();\n const preservedManifestKeys = new Set<string>();\n const filesToRemove = new Map<string, ThemeFile>();\n const errors: string[] = [];\n let manifestChanged = false;\n\n // ApplicationThemeResource deliberately exposes only the URL/checksum.\n // Capture FileResource metadata during the pull too, so a later push can\n // recreate the reference even if this source/development theme has since\n // been deleted. This is one small JSON request, never a binary download.\n const resourcesNeedingMetadata = resources.filter((resource) => {\n const key = resource.key;\n if (!key) return false;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) return false;\n if (!isLinkableBinaryResource(resource, key, file)) return false;\n\n const existing = this.assetManifest.get(key);\n if (this.themeRoot.ignore.ignore(key) || existing?.pending) return false;\n return (\n !existing ||\n existing.url !== resource.url ||\n !assetMetadataFromLink(existing)\n );\n });\n let assetMetadata = new Map<string, RemoteAssetMetadata>();\n const unresolvedMetadataKeys = new Set<string>();\n if (resourcesNeedingMetadata.length > 0) {\n try {\n assetMetadata = await this.fetchThemeAssetMetadata(this.themeId);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Read remote asset metadata: ${formatError(error)}`);\n for (const resource of resourcesNeedingMetadata) {\n if (resource.key) unresolvedMetadataKeys.add(resource.key);\n }\n }\n\n if (unresolvedMetadataKeys.size === 0) {\n for (const resource of resourcesNeedingMetadata) {\n const key = resource.key;\n if (!key || assetMetadata.has(assetFilename(key))) continue;\n unresolvedMetadataKeys.add(key);\n errors.push(\n `Could not find usable metadata for ${key} in theme #${this.themeId}`,\n );\n }\n }\n }\n\n opts.backgroundGuard?.assertUnchanged();\n for (const resource of resources) {\n const key = resource.key;\n if (!key) continue;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) continue;\n if (!isLinkableBinaryResource(resource, key, file)) continue;\n\n remoteManagedKeys.add(key);\n if (this.themeRoot.ignore.ignore(key)) {\n if (this.assetManifest.has(key)) preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // A newly added dev asset has not necessarily been promoted to the\n // pulled source yet. It remains authoritative until a push makes it\n // durable on the selected target.\n if (this.assetManifest.get(key)?.pending) {\n preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // Do not create an incomplete URL-only reference: a later push may need\n // content type and size after this source theme no longer exists. Let\n // merge-pull retain/download just this binary instead.\n if (unresolvedMetadataKeys.has(key)) {\n // The fallback bytes are now the only trustworthy copy. A manifest\n // entry pointing at a URL the remote no longer serves is stale —\n // keeping it would let linkManagedAssets restore the older asset if\n // the local file later disappears.\n const stale = this.assetManifest.get(key);\n if (stale && stale.url !== resource.url) {\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n continue;\n }\n\n try {\n const existing = this.assetManifest.get(key);\n const metadata = assetMetadata.get(assetFilename(key));\n this.assetManifest.set(key, {\n ...existing,\n sourceThemeId: this.themeId,\n ...metadata,\n ...resourceLink(resource),\n });\n manifestChanged = true;\n changedManifestKeys.add(key);\n managedKeys.add(key);\n filesToRemove.set(key, file);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n }\n }\n\n if (opts.delete) {\n for (const [key, link] of this.assetManifest.entries()) {\n if (\n remoteManagedKeys.has(key) ||\n preservedManifestKeys.has(key) ||\n this.themeRoot.ignore.ignore(key) ||\n link.pending\n ) {\n continue;\n }\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) {\n try {\n // Persist all links first. If this fails, preserve the local bytes and\n // let merge-pull materialize the affected resources normally.\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([\".fluid-assets.json\"], () =>\n this.assetManifest.write(),\n );\n else this.assetManifest.write();\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Persist remote asset manifest: ${formatError(error)}`);\n for (const key of changedManifestKeys) managedKeys.delete(key);\n return { managedKeys, linked: 0, errors };\n }\n }\n\n let linked = 0;\n for (const [key, file] of filesToRemove) {\n try {\n if (file.exists) {\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([key], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n }\n linked++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n // The manifest link is safely on disk; retain the resource outside the\n // shadow even if an operating-system lock delays local cleanup.\n }\n }\n\n return { managedKeys, linked, errors };\n }\n\n private isSafeThemeFile(key: string, file: ThemeFile): boolean {\n return (\n !key.includes(\"\\0\") &&\n !key.split(/[\\\\/]/).includes(\"..\") &&\n (file.absolutePath === this.themeRoot.root ||\n file.absolutePath.startsWith(this.themeRoot.root + sep))\n );\n }\n\n private managedAssetNeedsRefresh(\n resource: RemoteResource,\n link: ThemeAssetLink,\n ): boolean {\n if (!isManagedAssetResource(resource)) return true;\n if (link.url !== undefined) return resource.url !== link.url;\n if (link.checksum !== undefined) return resource.checksum !== link.checksum;\n return true;\n }\n\n private ensureManagedAssetsAreResolved(): void {\n const unresolved = this.assetManifest\n .entries()\n .filter(\n ([key]) =>\n !this.themeRoot.ignore.ignore(key) &&\n !this.themeRoot.file(key).exists,\n )\n .map(([key]) => key)\n .filter((key) => !isManagedAssetResource(this.remoteResources.get(key)));\n if (unresolved.length > 0) {\n throw new Error(\n `Managed asset(s) could not be linked: ${unresolved.join(\", \")}`,\n );\n }\n }\n\n // ─── Full Upload ──────────────────────────────────────────────────────────\n\n async uploadTheme(\n opts: {\n delete?: boolean;\n validate?: boolean;\n linkManagedAssets?: { replace?: boolean };\n pendingBinaryAssets?: boolean;\n onProgress?: (done: number, total: number) => void;\n // The SHA `.fluid-theme.json` stored on the last pull. Sent to the\n // server on every PUT/DELETE so a stale local aborts the push\n // instead of clobbering. Null / undefined skips the check entirely\n // (force-push and pre-Phase-003 servers).\n baseSha?: string | null;\n /** The caller already ran the stale-base preflight. */\n skipPreflight?: boolean;\n /** Last acknowledged dev resource index, validated by `baseSha`. */\n remoteState?: DevRemoteState;\n /** Without a stored baseSha, guard writes with the fetched index's SHA\n * so a concurrent change 409s instead of being overwritten. */\n guardFetchedRemote?: boolean;\n } = {},\n ): Promise<SyncResult> {\n const localFiles = this.themeRoot.files();\n assertNoCaseCollisions(localFiles.map((file) => file.relativePath));\n const result: SyncResult = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: 0,\n errors: [],\n validationFailed: false,\n };\n\n // Schema validation pass\n if (opts.validate) {\n for (const file of localFiles) {\n if (!file.isLiquid) continue;\n const diagnostics = file.validateSchema();\n const errors = diagnostics.filter((d) => d.severity === \"error\");\n for (const d of errors) {\n result.errors.push(`${file.relativePath}: ${d.message}`);\n }\n }\n if (result.errors.length > 0) {\n result.validationFailed = true;\n return result;\n }\n }\n\n if (opts.remoteState) {\n this.useDevRemoteState(opts.remoteState);\n } else {\n await this.fetchChecksums();\n }\n\n // Preflight before any state change so a stale local aborts with\n // zero writes. On 409, `PushConflictError` propagates to the CLI\n // and the per-file loop below never runs.\n if (!opts.skipPreflight) await this.preflightPush(opts.baseSha);\n\n // Roll the base_sha forward as the server bumps its version on\n // each successful write. Start from the caller's stored SHA (from\n // pull) and update from every response — the next PUT carries the\n // freshest SHA the server has given us so a concurrent third-party\n // write mid-loop 409s cleanly instead of the CLI overwriting.\n let baseSha =\n opts.baseSha ??\n (opts.guardFetchedRemote && !opts.remoteState ? this.remoteSha() : null);\n\n const localPaths = new Set(localFiles.map((file) => file.relativePath));\n if (opts.delete) {\n for (const key of this.assetManifest.keys()) localPaths.add(key);\n }\n const toDelete = opts.delete\n ? this.remoteKeys().filter(\n (key) =>\n key !== MEMBER_CONFIGURATION_KEY &&\n this.canDeleteRemoteResource(key) &&\n !localPaths.has(key) &&\n !this.themeRoot.ignore.ignore(key),\n )\n : [];\n\n const plannedUploads = planThemeUploads(\n this.themeRoot,\n localFiles.filter((f) => f.exists && this.hasChanged(f)),\n toDelete,\n );\n\n if (opts.linkManagedAssets) {\n result.linked = await this.linkManagedAssets(opts.linkManagedAssets);\n // FileResource writes can change the server's content version. The list\n // after reference creation gives the next normal resource write a fresh\n // base when the server supports Phase 003a.\n baseSha = this.lastKnownRemoteSha ?? baseSha;\n } else if (this.assetManifestInstance) {\n this.assetManifest.reload();\n this.ensureManagedAssetsAreResolved();\n }\n\n // Linking managed assets can acknowledge or externalize local binaries.\n // Recheck ordinary files after that step before attempting an upload.\n const toUpload = plannedUploads.filter(\n ({ file, memberConfiguration }) =>\n memberConfiguration || (file.exists && this.hasChanged(file)),\n );\n const uploadCount = toUpload.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n );\n let done = 0;\n for (const upload of toUpload) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n result.errors.length > 0\n ) {\n result.errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await this.uploadMemberConfiguration(upload, baseSha);\n } else {\n await this.uploadFile(file, baseSha, {\n pendingAsset: opts.pendingBinaryAssets,\n });\n }\n // `putResource` updates `lastKnownRemoteSha` from the server's\n // response. Thread it forward so the next iteration carries\n // the current SHA — this is how mid-push races surface.\n baseSha = this.lastKnownRemoteSha;\n result.uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n done += 1 + (upload.memberConfiguration?.files.length ?? 0);\n opts.onProgress?.(done, uploadCount);\n }\n\n if (opts.delete && result.errors.length === 0) {\n for (const key of toDelete) {\n try {\n await this.deleteRemoteFile(key, baseSha);\n baseSha = this.lastKnownRemoteSha;\n result.deleted++;\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n }\n }\n\n return result;\n }\n\n // ─── Full Download ────────────────────────────────────────────────────────\n\n async downloadTheme(\n opts: {\n delete?: boolean;\n skip?: Set<string>;\n onProgress?: (done: number, total: number) => void;\n } = {},\n ): Promise<SyncResult & { skipped: number }> {\n const resources = await this.downloadAll();\n const externalizedAssets = await this.externalizePulledAssets(resources, {\n delete: opts.delete ?? false,\n });\n const result: SyncResult & { skipped: number } = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: externalizedAssets.linked,\n skipped: 0,\n errors: [...externalizedAssets.errors],\n validationFailed: false,\n };\n\n let done = 0;\n for (const resource of resources) {\n if (externalizedAssets.managedKeys.has(resource.key)) {\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n if (opts.skip?.has(resource.key)) {\n result.skipped++;\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n const file = this.themeRoot.file(resource.key);\n\n // Guard against path traversal from malicious API responses\n if (!this.isSafeThemeFile(resource.key, file)) {\n result.errors.push(`Download ${resource.key}: path traversal detected`);\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n try {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n const buf = await this.downloadBinaryAsset(resource.url);\n file.write(buf);\n } else if (\n resource.content !== undefined &&\n resource.content !== null\n ) {\n const content =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n file.write(content);\n }\n result.downloaded++;\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${formatError(e)}`);\n }\n opts.onProgress?.(++done, resources.length);\n }\n\n if (opts.delete) {\n const remoteKeys = new Set(resources.map((r) => r.key));\n for (const file of this.themeRoot.files()) {\n if (remoteKeys.has(file.relativePath)) continue;\n // Preserve local stylesheets that the backend hides under\n // STYLESHEET_STRICT_INPUT — their absence from the resources\n // index is intentional (theme-level styles.css /\n // global_styles.css and per-template composite\n // {type}/{name}/styles.css), not a signal that the merchant\n // deleted them.\n if (isStylesheetKey(file.relativePath)) continue;\n\n try {\n unlinkSync(file.absolutePath);\n result.deleted++;\n } catch {\n // ignore\n }\n }\n }\n\n return result;\n }\n}\n\nfunction isLinkableBinaryResource(\n resource: RemoteResource,\n key: string,\n file: ThemeFile,\n): boolean {\n return (\n isThemeAssetKey(key) && isManagedAssetResource(resource) && !file.isText\n );\n}\n\nfunction isNestedBinaryThemeAsset(file: ThemeFile): boolean {\n return (\n !file.isText &&\n file.relativePath.startsWith(\"assets/\") &&\n !isThemeAssetKey(file.relativePath)\n );\n}\n\nfunction isManagedAssetResource(\n resource: RemoteResource | undefined,\n): resource is RemoteResource {\n return (\n resource?.resource_type === \"FileResource\" &&\n typeof resource.url === \"string\" &&\n resource.url.length > 0\n );\n}\n\nfunction remoteResourceFromState(state: RemoteResourceState): RemoteResource {\n return {\n key: state.key,\n checksum: state.checksum,\n content: state.contentPresent ? \"\" : null,\n resource_type: state.resourceType,\n resource_id: state.resourceId,\n url: state.url,\n };\n}\n\nfunction remoteResourceState(resource: RemoteResource): RemoteResourceState {\n return {\n key: resource.key,\n checksum: resource.checksum,\n contentPresent: resource.content != null,\n resourceType: resource.resource_type,\n resourceId: resource.resource_id,\n url: resource.url,\n };\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return isApiError(error) && error.status === 404;\n}\n\nfunction resourceLink(\n resource: RemoteResource,\n): Pick<ThemeAssetLink, \"checksum\" | \"url\"> {\n return {\n ...(typeof resource.checksum === \"string\" && resource.checksum.length > 0\n ? { checksum: resource.checksum }\n : {}),\n ...(typeof resource.url === \"string\" && resource.url.length > 0\n ? { url: resource.url }\n : {}),\n };\n}\n\nfunction assetFilename(key: string): string {\n return key.slice(\"assets/\".length);\n}\n\nfunction assetMetadataFromLink(\n link: ThemeAssetLink,\n): RemoteAssetMetadata | undefined {\n if (\n typeof link.url !== \"string\" ||\n link.url.length === 0 ||\n typeof link.contentType !== \"string\" ||\n link.contentType.length === 0 ||\n typeof link.contentSize !== \"number\" ||\n !Number.isInteger(link.contentSize) ||\n link.contentSize <= 0\n ) {\n return undefined;\n }\n\n return {\n url: link.url,\n contentType: link.contentType,\n contentSize: link.contentSize,\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n };\n}\n\nfunction uploadedAssetMetadata(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n): RemoteAssetMetadata {\n if (resource.assetMetadata) return resource.assetMetadata;\n if (!resource.url) {\n throw new Error(`Uploaded asset has no URL: ${file.relativePath}`);\n }\n\n return {\n url: resource.url,\n contentType: file.mime.name,\n contentSize: file.size(),\n altText: file.name,\n ...(typeof resource.damAssetCode === \"string\"\n ? { handle: resource.damAssetCode }\n : {}),\n };\n}\n\nfunction parseThemeAssetMetadata(\n value: unknown,\n): { filename: string; metadata: RemoteAssetMetadata } | undefined {\n if (!isRecord(value)) return undefined;\n\n const filename = nonEmptyString(value[\"filename\"]);\n const url = nonEmptyString(value[\"url\"]);\n const contentType = nonEmptyString(value[\"content_type\"]);\n const contentSize = positiveInteger(value[\"content_size\"]);\n if (!filename || !url || !contentType || !contentSize) return undefined;\n\n const previewImageUrl = nonEmptyString(value[\"preview_image_url\"]);\n const altText = optionalString(value[\"alt_text\"]);\n const handle = nonEmptyString(value[\"handle\"]);\n return {\n filename,\n metadata: {\n url,\n contentType,\n contentSize,\n ...(previewImageUrl ? { previewImageUrl } : {}),\n ...(altText !== undefined ? { altText } : {}),\n ...(handle ? { handle } : {}),\n },\n };\n}\n\nfunction createdFileResourceId(value: unknown): number | undefined {\n if (!isRecord(value) || !isRecord(value[\"file_resource\"])) {\n return undefined;\n }\n return positiveInteger(value[\"file_resource\"][\"id\"]);\n}\n\nfunction positiveInteger(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isInteger(value) && value > 0) {\n return value;\n }\n if (typeof value !== \"string\" || !/^\\d+$/.test(value)) return undefined;\n\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * Heuristic-only check for obviously unbalanced liquid delimiters\n * (`{% %}` and `{{ }}`). This is NOT a liquid parser — it only tracks\n * opening delimiters until they are closed. It exists to give watch-mode users a signal\n * when a save is liquid-syntax-broken: the server accepts\n * syntax-broken liquid silently on upload, and the storefront\n * renderer then serves stale content for that section with no error\n * anywhere else in the pipeline.\n *\n * Closing-looking tokens without a preceding Liquid opener are intentionally\n * ignored. Liquid files commonly contain CSS such as `width:100%}` or adjacent\n * block braces (`}}`), so treating every close token as Liquid creates noisy\n * false warnings on valid theme files.\n *\n * Known false positive: delimiters written literally inside a\n * `{% raw %}...{% endraw %}` block are still counted and can trip\n * this check even though the liquid is valid. Acceptable for a\n * warn-only heuristic — a real fix requires parsing liquid, which is\n * out of scope here (see the server-side validation note in the PR).\n */\nexport function hasUnbalancedLiquidDelimiters(content: string): boolean {\n let unclosedTags = 0;\n let unclosedOutputs = 0;\n\n for (const token of content.matchAll(/\\{%|%\\}|\\{\\{|\\}\\}/g)) {\n switch (token[0]) {\n case \"{%\":\n unclosedTags += 1;\n break;\n case \"%}\":\n if (unclosedTags > 0) unclosedTags -= 1;\n break;\n case \"{{\":\n unclosedOutputs += 1;\n break;\n case \"}}\":\n if (unclosedOutputs > 0) unclosedOutputs -= 1;\n break;\n }\n }\n\n return unclosedTags > 0 || unclosedOutputs > 0;\n}\n\nexport interface LiquidBlockTagDiagnostic {\n severity: \"error\";\n message: string;\n}\n\nconst BLOCK_TAGS = new Map([\n [\"capture\", \"endcapture\"],\n [\"case\", \"endcase\"],\n [\"comment\", \"endcomment\"],\n [\"for\", \"endfor\"],\n [\"form\", \"endform\"],\n [\"if\", \"endif\"],\n [\"ifchanged\", \"endifchanged\"],\n [\"javascript\", \"endjavascript\"],\n [\"paginate\", \"endpaginate\"],\n [\"raw\", \"endraw\"],\n [\"schema\", \"endschema\"],\n [\"style\", \"endstyle\"],\n [\"stylesheet\", \"endstylesheet\"],\n [\"tablerow\", \"endtablerow\"],\n [\"unless\", \"endunless\"],\n]);\n\nconst CLOSING_TAGS = new Set(BLOCK_TAGS.values());\nconst OPAQUE_BLOCK_TAGS = new Set([\n \"comment\",\n \"javascript\",\n \"raw\",\n \"schema\",\n \"style\",\n \"stylesheet\",\n]);\n\ninterface OpenBlock {\n name: string;\n expectedClose: string;\n line: number;\n}\n\n/**\n * Find structurally unbalanced Liquid block tags such as an `{% if %}` with\n * no `{% endif %}`. This intentionally recognizes only established paired\n * tags; custom and inline tags are ignored rather than guessed at.\n *\n * Content inside raw/comment/schema/style/javascript blocks is opaque to\n * Liquid and therefore skipped until that block's matching close tag. This\n * prevents CSS, JSON, and examples containing Liquid-looking text from\n * producing false errors.\n */\nexport function findLiquidBlockTagDiagnostics(\n content: string,\n): LiquidBlockTagDiagnostic[] {\n const stack: OpenBlock[] = [];\n const diagnostics: LiquidBlockTagDiagnostic[] = [];\n let line = 1;\n let previousTagIndex = 0;\n\n const processTag = (name: string, tagLine: number): void => {\n const open = stack.at(-1);\n\n if (open && OPAQUE_BLOCK_TAGS.has(open.name)) {\n if (name === open.expectedClose) stack.pop();\n return;\n }\n\n const expectedClose = BLOCK_TAGS.get(name);\n if (expectedClose) {\n stack.push({ name, expectedClose, line: tagLine });\n return;\n }\n\n if (!CLOSING_TAGS.has(name)) return;\n\n if (!open) {\n diagnostics.push({\n severity: \"error\",\n message: `Unexpected Liquid tag '{% ${name} %}' on line ${tagLine}; there is no open block to close.`,\n });\n return;\n }\n\n if (name !== open.expectedClose) {\n diagnostics.push({\n severity: \"error\",\n message: `Mismatched Liquid tag '{% ${name} %}' on line ${tagLine}; '{% ${open.name} %}' from line ${open.line} must close with '{% ${open.expectedClose} %}'.`,\n });\n return;\n }\n\n stack.pop();\n };\n\n for (const match of content.matchAll(\n /\\{%-?\\s*([a-zA-Z_][\\w-]*)\\b(?:(?!\\{%)[\\s\\S])*?-?%\\}/g,\n )) {\n const name = match[1]?.toLowerCase();\n if (!name) continue;\n\n const index = match.index ?? 0;\n // Count each character at most once across the scan. Re-slicing from the\n // start for every tag turns large generated templates into O(n²) work.\n for (let cursor = previousTagIndex; cursor < index; cursor++) {\n if (content.charCodeAt(cursor) === 10) line += 1;\n }\n previousTagIndex = index;\n\n const open = stack.at(-1);\n if (name === \"liquid\" && !(open && OPAQUE_BLOCK_TAGS.has(open.name))) {\n // `{% liquid %}` places one delimiter-free statement on each line. Feed\n // those statements through the same stack so this supported syntax\n // cannot bypass lint, push, or watch-mode validation.\n const statements = match[0]\n .replace(/^\\{%-?\\s*liquid\\b/i, \"\")\n .replace(/-?%\\}$/, \"\")\n .split(\"\\n\");\n for (const [offset, statement] of statements.entries()) {\n const statementName = /^\\s*([a-zA-Z_][\\w-]*)\\b/.exec(statement)?.[1];\n if (!statementName) continue;\n processTag(statementName.toLowerCase(), line + offset);\n }\n } else {\n processTag(name, line);\n }\n }\n\n for (const open of stack.reverse()) {\n diagnostics.push({\n severity: \"error\",\n message: `Unclosed Liquid tag '{% ${open.name} %}' on line ${open.line}; expected '{% ${open.expectedClose} %}'.`,\n });\n }\n\n return diagnostics;\n}\n","import net from \"node:net\";\n\n/**\n * The dev command does real work before it ever binds a port: it resolves\n * (or creates) a server-side dev theme and runs a full initial sync, which\n * can take minutes on a large theme. If the requested port is already taken\n * — most commonly by another `fluid theme dev` or the Mist Desktop preview,\n * which both default to 9292 — all of that work is wasted and the process\n * used to die with a raw `EADDRINUSE` stack trace. Call this before any of\n * that work starts so we fail fast with a clear message instead.\n */\nexport class PortInUseError extends Error {\n constructor(\n public readonly host: string,\n public readonly port: number,\n ) {\n super(formatPortConflictMessage(host, port));\n this.name = \"PortInUseError\";\n }\n}\n\nexport function formatPortConflictMessage(host: string, port: number): string {\n return (\n `Port ${port} on ${host} is already in use — likely another ` +\n \"`fluid theme dev` or the Mist Desktop preview. Stop the other \" +\n \"server or pass --port <number>.\"\n );\n}\n\n/**\n * Attempt to bind `host:port`, then immediately release it. Resolves if the\n * port is free; rejects with `PortInUseError` on `EADDRINUSE`/`EACCES`, or\n * the raw error for anything else unexpected.\n */\nexport function checkPortAvailable(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n reject(new PortInUseError(host, port));\n } else {\n reject(err);\n }\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve());\n });\n\n server.listen(port, host);\n });\n}\n","import http from \"node:http\";\nimport { MemberPreview, isLocalPreviewRequest } from \"./member-preview.js\";\nimport { SSEStream } from \"./sse.js\";\nimport { proxyRequest } from \"./proxy.js\";\nimport { createTemplateOverrideResolver } from \"./template-overrides.js\";\nimport { watchTheme } from \"./watcher.js\";\nimport { PushConflictError, Syncer } from \"../syncer.js\";\nimport {\n findLiquidBlockTagDiagnostics,\n hasUnbalancedLiquidDelimiters,\n} from \"../liquid-delimiters.js\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ApiClient } from \"../../api.js\";\nimport { formatPortConflictMessage } from \"./port-preflight.js\";\nimport { assertNoCaseCollisions } from \"../case-collisions.js\";\nimport { isStylesheetKey } from \"../stylesheet-keys.js\";\nimport { ThemeAssetManifest } from \"../asset-manifest.js\";\nimport type { DevRemoteState } from \"../dev-remote-baseline.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n} from \"../member-configuration.js\";\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString(\"en-US\", { hour12: false });\n}\n\nexport interface DevServerOptions {\n host: string;\n port: number;\n reloadMode: \"full-page\" | \"off\";\n /** Cached dev resource index paired with its acknowledged remote version. */\n initialSync?: DevRemoteState;\n /** Persist the complete dev resource baseline and its version atomically. */\n onRemoteState?: (state: DevRemoteState) => void;\n /** Discard persisted trust after the server rejects a watched write. */\n onRemoteStateInvalidated?: () => void;\n}\n\nexport interface DevServerTheme {\n id: number;\n name: string;\n company: string;\n editorUrl?: string;\n}\n\nexport async function startDevServer(\n api: ApiClient,\n theme: DevServerTheme,\n themeRoot: ThemeRoot,\n opts: DevServerOptions & { validate?: boolean },\n onReady?: (address: string) => void,\n): Promise<() => void> {\n const sse = new SSEStream();\n const memberPreview = new MemberPreview(api, theme, opts.reloadMode);\n const syncer = new Syncer(api, theme.id, themeRoot);\n const templateOverrides = createTemplateOverrideResolver(api, theme.id);\n let remoteStateSafe = true;\n const invalidateRemoteState = () => {\n if (!remoteStateSafe) return;\n remoteStateSafe = false;\n try {\n opts.onRemoteStateInvalidated?.();\n } catch {\n // The stale version will fail preflight on the next startup.\n }\n };\n const recordRemoteState = () => {\n if (!remoteStateSafe) return;\n if (opts.onRemoteState) {\n const state = syncer.devRemoteState(\n new ThemeAssetManifest(themeRoot.root).fingerprint(),\n );\n if (state) {\n try {\n opts.onRemoteState(state);\n } catch {\n invalidateRemoteState();\n }\n }\n }\n };\n\n const pendingUpdates = new Set<string>();\n\n // ── Initial sync ─────────────────────────────────────────────────────────\n console.log(`\\nSyncing theme ${theme.name} (#${theme.id})…`);\n const progress = (done: number, total: number) => {\n process.stdout.write(`\\r Uploading ${done}/${total} files…`);\n };\n const uploadFromRemoteIndex = () =>\n syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n onProgress: progress,\n });\n let syncResult;\n if (opts.initialSync) {\n let remoteStateIsTrusted = false;\n try {\n syncer.useDevRemoteState(opts.initialSync);\n await syncer.preflightPush(opts.initialSync.remoteSha);\n remoteStateIsTrusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n\n syncResult = remoteStateIsTrusted\n ? await syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n baseSha: syncer.remoteSha(),\n remoteState: opts.initialSync,\n skipPreflight: true,\n onProgress: progress,\n })\n : await uploadFromRemoteIndex();\n } else {\n syncResult = await uploadFromRemoteIndex();\n }\n process.stdout.write(\"\\n\");\n if (syncResult.linked > 0) {\n console.log(` Saved ${syncResult.linked} remote asset reference(s).`);\n }\n if (syncResult.validationFailed) {\n console.error(\n `\\nSchema validation failed (${syncResult.errors.length} error(s)). Use --force to skip.\\n`,\n );\n for (const e of syncResult.errors) console.error(` ${e}`);\n process.exit(1);\n } else if (syncResult.errors.length > 0) {\n invalidateRemoteState();\n for (const e of syncResult.errors) console.error(` ${e}`);\n if (syncResult.uploaded + syncResult.deleted === 0) process.exit(1);\n }\n if (syncResult.errors.length === 0) {\n recordRemoteState();\n }\n\n // ── File watcher ─────────────────────────────────────────────────────────\n //\n // Uploading a file no longer commits anything — Fluid only records that the\n // theme changed, and something has to ask for the commit. `theme push` asks\n // once when its loop ends; the watcher has no such moment, since chokidar\n // reports one event per file and nothing in it says whether this is a lone\n // save or the 200th write of a restore.\n //\n // So ask once the writes stop — but decide where \"the writes\" end by when\n // events ARRIVED, not by when their handlers ran. Handlers are serialized\n // behind awaited uploads, so a genuinely separate edit can queue behind a\n // slow one and start immediately after it; timing the handlers would fold\n // the two into a single commit and erase the boundary between them.\n //\n // An event that arrived within the window continues the operation already\n // in flight, so its pending ask is cancelled and rescheduled. One that\n // arrived after a real pause ends that operation, so the ask goes out NOW\n // rather than being cancelled — otherwise the earlier edit would be\n // swallowed into this one's commit.\n //\n // Asks are also chained against the uploads. A fire-and-forget request can\n // overlap the next batch, committing a half-written operation or leaving\n // the rest of it pending; awaiting the outstanding one before uploading\n // keeps each commit to a settled state.\n //\n // If the server stops with an ask still pending, Fluid's sweeper picks the\n // change up; the cost is a delay, never a lost commit.\n const SYNC_IDLE_MS = 2_000;\n let lastArrivedAt = 0;\n let pendingSync: ReturnType<typeof setTimeout> | null = null;\n let syncInFlight: Promise<void> = Promise.resolve();\n\n // Tracks an ask Fluid did not take. Until it lands the previous edit is\n // uncommitted, and uploading the next one over it merges the two into\n // whichever commit eventually arrives — so the boundary is retried before\n // any further writes go out. A retry that also fails has done what a client\n // can: the content is safe on the server and the sweeper commits it, but\n // the two edits will share a version.\n let askOwed = false;\n let remoteWritesBlocked = false;\n const blockRemoteWrites = (error: PushConflictError): void => {\n if (remoteWritesBlocked) return;\n remoteWritesBlocked = true;\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Remote theme changed outside this dev session (${error.message}). Restart theme dev to compare the current remote state before writing again.`,\n );\n };\n const sendSync = (): void => {\n syncInFlight = syncInFlight.then(async () => {\n const accepted = await syncer.requestSync();\n askOwed = !accepted;\n if (accepted) recordRemoteState();\n });\n };\n const flushSyncNow = (): void => {\n if (!pendingSync) return;\n clearTimeout(pendingSync);\n pendingSync = null;\n sendSync();\n };\n const scheduleSync = (): void => {\n if (pendingSync) clearTimeout(pendingSync);\n pendingSync = setTimeout(() => {\n pendingSync = null;\n sendSync();\n }, SYNC_IDLE_MS);\n };\n\n const stopWatcher = watchTheme(\n themeRoot,\n async (modified, added, removed, arrivedAt) => {\n if (arrivedAt - lastArrivedAt > SYNC_IDLE_MS) {\n // A real pause: whatever was pending belongs to the previous edit.\n flushSyncNow();\n } else if (pendingSync) {\n // Same operation still arriving — hold the ask.\n clearTimeout(pendingSync);\n pendingSync = null;\n }\n lastArrivedAt = arrivedAt;\n // Never upload across an outstanding ask, or it commits a half-written\n // operation.\n await syncInFlight;\n // An ask Fluid refused still owes the edit before this one its own\n // commit. Retry before writing over it.\n if (askOwed) {\n sendSync();\n await syncInFlight;\n }\n if (remoteWritesBlocked) return;\n\n let uploads: ThemeUpload[];\n try {\n assertNoCaseCollisions(\n themeRoot.files().map((file) => file.relativePath),\n );\n assertMemberConfigurationRetained(\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n uploads = planThemeUploads(\n themeRoot,\n [...modified, ...added],\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n } catch (error) {\n console.error(`\\n[Watcher] Sync blocked: ${String(error)}`);\n return;\n }\n\n let wroteRemote = false;\n const memberTransfer = uploads.some(\n (upload) =>\n upload.memberConfiguration ||\n upload.file.relativePath === \"members_sites.json\",\n );\n\n for (const upload of uploads) {\n const { file } = upload;\n const uploadFiles = [\n file,\n ...(upload.memberConfiguration?.files ?? []),\n ];\n // Validate schema on liquid files during dev (warn, don't block)\n if (opts.validate) {\n for (const candidate of uploadFiles) {\n if (!candidate.isLiquid) continue;\n for (const diagnostic of candidate.validateSchema()) {\n const prefix =\n diagnostic.severity === \"error\"\n ? \"Schema error\"\n : \"Schema warning\";\n console.warn(\n `\\n[${prefix}] ${candidate.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n\n for (const pendingFile of uploadFiles)\n pendingUpdates.add(pendingFile.relativePath);\n try {\n // uploadFile() resolves with the exact bytes it sent, so the\n // diagnostic below always describes the uploaded content —\n // never a re-read of a file an editor may have changed,\n // replaced, or deleted while the request was in flight.\n const uploadedFiles = upload.memberConfiguration\n ? await syncer.uploadMemberConfiguration(upload, syncer.remoteSha())\n : [\n {\n file,\n content: await syncer.uploadFile(file, syncer.remoteSha(), {\n pendingAsset: true,\n }),\n },\n ];\n wroteRemote = true;\n for (const {\n file: uploadedFile,\n content: uploadedContent,\n } of uploadedFiles) {\n console.log(\n ` ✓ synced ${uploadedFile.relativePath} (${timestamp()})`,\n );\n // \"synced\" is true of the upload and false of the page. The\n // storefront inlines a stylesheet from a Themes::FileResource that a\n // background job rewrites after the publish commits, so\n // `inline_asset_content` keeps emitting the previous bytes for a\n // moment after the server accepts this write. Say so, or the ✓ reads\n // as \"the rendered CSS is up to date\" (CURRENT-3899).\n if (isStylesheetKey(uploadedFile.relativePath)) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: the storefront inlines this CSS from a published asset, so it serves the previous bytes until the stylesheet asset sync finishes`,\n );\n }\n // Cheap warn-only heuristic: the server accepts syntax-broken\n // liquid silently, and the storefront then serves stale\n // content for the section with no error anywhere. This does\n // not parse liquid — it can false-positive on delimiters\n // written literally inside {% raw %} — but it's the only\n // client-side signal a save is likely broken.\n if (\n uploadedFile.isLiquid &&\n uploadedContent !== null &&\n hasUnbalancedLiquidDelimiters(uploadedContent)\n ) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: unbalanced liquid delimiters — the storefront may silently serve stale content for this section`,\n );\n }\n if (uploadedFile.isLiquid && uploadedContent !== null) {\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n uploadedContent,\n )) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n } catch (e) {\n if (e instanceof PushConflictError) {\n blockRemoteWrites(e);\n break;\n }\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Upload failed: ${file.relativePath}: ${e}`,\n );\n // Keep safe first-write failures retryable, but never finalize a partial batch.\n remoteWritesBlocked = wroteRemote || memberTransfer;\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n } finally {\n for (const pendingFile of uploadFiles)\n pendingUpdates.delete(pendingFile.relativePath);\n }\n }\n\n if (remoteWritesBlocked) return;\n\n for (const file of removed) {\n if (themeRoot.ignore.ignore(file.relativePath)) continue;\n try {\n await syncer.deleteRemoteFile(file.relativePath, syncer.remoteSha());\n wroteRemote = true;\n console.log(` ✓ removed ${file.relativePath}`);\n } catch (error) {\n if (error instanceof PushConflictError) {\n blockRemoteWrites(error);\n break;\n }\n invalidateRemoteState();\n remoteWritesBlocked = wroteRemote || memberTransfer;\n console.error(\n `[Watcher] Delete failed: ${file.relativePath}: ${error}`,\n );\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n }\n }\n\n if (remoteWritesBlocked) return;\n\n if (wroteRemote) {\n recordRemoteState();\n try {\n await memberPreview.refreshAfterSync();\n } catch {\n console.error(\n \"[Member preview] Could not refresh routes after sync. The next member request will retry.\",\n );\n }\n }\n // Added or removed files can create or delete dev-theme templates.\n if (added.length > 0 || removed.length > 0) templateOverrides.clear();\n\n if (removed.length > 0) {\n sse.broadcast(JSON.stringify({ reload_page: true }));\n } else if (uploads.length > 0) {\n sse.broadcast(\n JSON.stringify({\n modified: uploads.flatMap(({ file, memberConfiguration }) =>\n [file, ...(memberConfiguration?.files ?? [])].map(\n (updatedFile) => updatedFile.relativePath,\n ),\n ),\n }),\n );\n }\n\n scheduleSync();\n },\n );\n\n // ── HTTP server ───────────────────────────────────────────────────────────\n const server = http.createServer(async (req, res) => {\n if (!isLocalPreviewRequest(req, opts.host)) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Local preview requests only.\");\n return;\n }\n if (await memberPreview.handle(req, res)) return;\n if (req.url === \"/hot-reload\") {\n sse.add(res);\n return;\n }\n\n try {\n await proxyRequest(req, res, {\n company: theme.company,\n themeId: theme.id,\n reloadMode: opts.reloadMode,\n templateOverrides,\n pendingFiles: () =>\n [...pendingUpdates]\n .map((p) => themeRoot.file(p))\n .filter((f) => f.isText)\n .map((f) => ({\n relativePath: f.relativePath,\n read: () => f.read(),\n })),\n });\n } catch (e) {\n console.error(`[Proxy] ${req.method} ${req.url} → ${e}`);\n if (!res.headersSent) {\n // Surface the real upstream failure instead of a bare 502.\n // Every local render round-trips through <company>.fluid.app,\n // so this error is usually environmental — a TLS-inspecting\n // security agent whose root CA the browser trusts via the OS\n // keychain but Node does not, DNS, or a dropped connection —\n // and a naked \"Bad Gateway\" sends humans and QA agents off\n // chasing the theme instead of the network. Diagnosed live:\n // four workflow page steps went needs-review over one masked\n // cert error.\n const message = e instanceof Error ? e.message : String(e);\n res.writeHead(502, { \"content-type\": \"text/plain; charset=utf-8\" });\n res.end(\n `Bad Gateway — the local preview could not reach ${theme.company}.fluid.app: ${message}\\n` +\n \"This is the dev machine's network path to Fluid, not the theme. \" +\n \"Common causes: TLS-inspecting security software (its root CA is in the OS keychain, which Node does not read — set NODE_EXTRA_CA_CERTS to its certificate), DNS, or a proxy. \" +\n \"The same error is logged by the theme dev server process.\",\n );\n }\n }\n });\n\n // The preflight check in `dev.ts` runs before the (potentially minutes-long)\n // initial sync above, but the port could still be grabbed by another\n // process in the window between that check and this `listen()` call.\n // Handle that race the same way: a friendly message and a clean exit\n // instead of an unhandled `EADDRINUSE`/`EACCES` stack trace.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n console.error(formatPortConflictMessage(opts.host, opts.port));\n process.exit(1);\n }\n reject(err);\n });\n server.listen(opts.port, opts.host, () => resolve());\n });\n\n const address = `http://${opts.host}:${opts.port}`;\n onReady?.(address);\n\n // ── Teardown ──────────────────────────────────────────────────────────────\n return function stop() {\n sse.close();\n stopWatcher();\n server.close();\n };\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\n\nconst BASELINE_VERSION = 1;\nconst BASELINE_FILE = join(\".fluid-theme\", \"dev-baseline.json\");\n\nexport interface RemoteResourceState {\n key: string;\n checksum: string | null;\n contentPresent: boolean;\n resourceType?: string | null;\n resourceId?: number | null;\n url?: string | null;\n}\n\nexport interface DevRemoteState {\n themeId: number;\n remoteSha: string;\n assetManifestSha: string;\n resources: RemoteResourceState[];\n}\n\nexport function readDevRemoteBaseline(\n themeRoot: string,\n themeId: number,\n assetManifestSha: string,\n): DevRemoteState | null {\n try {\n const parsed = JSON.parse(\n readFileSync(join(themeRoot, BASELINE_FILE), \"utf-8\"),\n );\n const state = parseDocument(parsed);\n if (state.themeId !== themeId) return null;\n if (state.assetManifestSha !== assetManifestSha) return null;\n return state;\n } catch {\n return null;\n }\n}\n\nexport function writeDevRemoteBaseline(\n themeRoot: string,\n state: DevRemoteState,\n): void {\n const document = { version: BASELINE_VERSION, ...state };\n parseDocument(document);\n\n const path = join(themeRoot, BASELINE_FILE);\n const tempPath = `${path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(tempPath, `${JSON.stringify(document)}\\n`, {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, path);\n } catch (error) {\n rmSync(tempPath, { force: true });\n throw error;\n }\n}\n\nexport function removeDevRemoteBaseline(themeRoot: string): void {\n rmSync(join(themeRoot, BASELINE_FILE), { force: true });\n}\n\nexport async function devRemoteStateFromSourceShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n themeId: number,\n remoteSha: string,\n): Promise<DevRemoteState | null> {\n try {\n if (!(await shadow.hasHead())) return null;\n\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const resources: RemoteResourceState[] = [];\n for (const key of await shadow.headPaths()) {\n const content = await shadow.blobAtHead(key);\n if (!content) return null;\n const asset = assetManifest.get(key);\n if (asset) {\n if (\n asset.pending ||\n !asset.url ||\n !content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL))\n ) {\n return null;\n }\n resources.push({\n key,\n checksum: asset.checksum ?? null,\n contentPresent: false,\n resourceType: \"FileResource\",\n url: asset.url,\n });\n continue;\n }\n\n if (content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL)))\n return null;\n resources.push({\n key,\n checksum: createHash(\"sha256\").update(content).digest(\"hex\"),\n contentPresent: true,\n });\n }\n\n return {\n themeId,\n remoteSha,\n assetManifestSha: assetManifest.fingerprint(),\n resources,\n };\n } catch {\n return null;\n }\n}\n\nfunction parseDocument(document: unknown): DevRemoteState {\n if (!isRecord(document) || document[\"version\"] !== BASELINE_VERSION) {\n throw new Error(`expected version ${BASELINE_VERSION}`);\n }\n\n const themeId = document[\"themeId\"];\n const remoteSha = document[\"remoteSha\"];\n const assetManifestSha = document[\"assetManifestSha\"];\n const rawResources = document[\"resources\"];\n if (!isPositiveInteger(themeId)) throw new Error(\"invalid theme id\");\n if (!isNonEmptyString(remoteSha)) throw new Error(\"invalid remote sha\");\n if (!isNonEmptyString(assetManifestSha)) {\n throw new Error(\"invalid asset manifest sha\");\n }\n if (!Array.isArray(rawResources)) throw new Error(\"invalid resources\");\n\n const resources = rawResources.map(parseResource);\n assertNoCaseCollisions(resources.map((resource) => resource.key));\n return { themeId, remoteSha, assetManifestSha, resources };\n}\n\nfunction parseResource(resource: unknown): RemoteResourceState {\n if (!isRecord(resource)) throw new Error(\"invalid resource\");\n\n const key = resource[\"key\"];\n const checksum = resource[\"checksum\"];\n const contentPresent = resource[\"contentPresent\"];\n const resourceType = resource[\"resourceType\"];\n const resourceId = resource[\"resourceId\"];\n const url = resource[\"url\"];\n if (!isNonEmptyString(key) || key.includes(\"\\0\")) {\n throw new Error(\"invalid resource key\");\n }\n if (checksum !== null && !isNonEmptyString(checksum)) {\n throw new Error(\"invalid resource checksum\");\n }\n if (typeof contentPresent !== \"boolean\") {\n throw new Error(\"invalid resource content marker\");\n }\n if (\n resourceType !== undefined &&\n resourceType !== null &&\n !isNonEmptyString(resourceType)\n ) {\n throw new Error(\"invalid resource type\");\n }\n if (\n resourceId !== undefined &&\n resourceId !== null &&\n !isPositiveInteger(resourceId)\n ) {\n throw new Error(\"invalid resource id\");\n }\n if (url !== undefined && url !== null && !isNonEmptyString(url)) {\n throw new Error(\"invalid resource url\");\n }\n\n return {\n key,\n checksum,\n contentPresent,\n resourceType,\n resourceId,\n url,\n };\n}\n\nfunction isRecord(candidate: unknown): candidate is Record<string, unknown> {\n return typeof candidate === \"object\" && candidate !== null;\n}\n\nfunction isNonEmptyString(candidate: unknown): candidate is string {\n return typeof candidate === \"string\" && candidate.length > 0;\n}\n\nfunction isPositiveInteger(candidate: unknown): candidate is number {\n return (\n typeof candidate === \"number\" &&\n Number.isInteger(candidate) &&\n candidate > 0\n );\n}\n","import { spawn } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * A bare git repo hidden under `.fluid-theme/repo` that stands in for\n * git's index+HEAD when talking to the Fluid server. Every pull commits\n * the incoming remote state onto a single branch (`refs/heads/main`);\n * every successful push commits the outgoing local state onto the same\n * branch. HEAD therefore represents \"the last state the CLI and server\n * agreed on\", and its tree is the natural three-way-merge base for the\n * next pull.\n *\n * We stay in git's plumbing layer — no working tree, no index file\n * next to the theme content — so the shadow repo cannot interfere\n * with the user's own git repo (if they have one) around the theme\n * dir. All state lives inside `.fluid-theme/`.\n *\n * The class only wraps the small handful of plumbing commands we\n * actually need: hash-object, cat-file, write-tree (via a temp index),\n * commit-tree, update-ref, and merge-file. Everything else stays out.\n */\nexport class ShadowRepo {\n // Cached \"does HEAD exist\" result. `pull`/`push` call `blobAtHead`\n // per file (potentially hundreds of times per invocation), and\n // spawning a `git rev-parse` subprocess each time is measurable\n // overhead. HEAD presence only flips one way per instance —\n // `commitState` sets it true after a successful `update-ref` — so\n // the cache stays correct without invalidation.\n private headExists: boolean | undefined = undefined;\n\n private constructor(\n private readonly themeRoot: string,\n private readonly gitDir: string,\n ) {}\n\n /**\n * Return a ShadowRepo bound to `themeId` for the given theme root.\n * The shadow is initialized on first use and re-initialized when\n * the caller passes a themeId different from the one previously\n * recorded — the merge base is only meaningful for the theme it\n * was captured against, so a cross-theme operation (pull A → push\n * B, or two pulls of different themes into one dir) starts from a\n * clean slate rather than pretending B's state matches A's HEAD.\n */\n static async open(themeRoot: string, themeId: number): Promise<ShadowRepo> {\n const shadowDir = join(themeRoot, \".fluid-theme\");\n const gitDir = join(shadowDir, \"repo\");\n const themeIdFile = join(shadowDir, \"theme-id\");\n\n // The stored themeId is written on every commit, so a shadow with\n // a HEAD but no theme-id file is one written by an older CLI —\n // treat it as \"unknown\" and wipe rather than trust it against\n // whatever theme the caller is now operating on.\n if (existsSync(gitDir)) {\n const stored = readStoredThemeId(themeIdFile);\n if (stored !== themeId) {\n rmSync(gitDir, { recursive: true, force: true });\n }\n }\n\n const repo = new ShadowRepo(themeRoot, gitDir);\n\n if (!existsSync(gitDir)) {\n mkdirSync(shadowDir, { recursive: true });\n await repo.git([\"init\", \"--bare\", \"-b\", \"main\", gitDir], {\n cwd: themeRoot,\n });\n }\n\n // Rewrite even when the dir already existed — a fresh init above\n // won't have this file yet, and a preserved dir may have missed a\n // previous write if the process crashed mid-way. Idempotent.\n writeFileSync(themeIdFile, `${themeId}\\n`, \"utf-8\");\n\n // Idempotent: keep `.fluid-theme/` invisible to the user's own git\n // repo. The nested `.gitignore` handles `git add .` inside the\n // shadow dir; the root-level entry handles `git status` on the\n // theme root, which otherwise still lists the untracked shadow\n // directory itself (only its *contents* are ignored by the nested\n // file).\n const shadowIgnore = join(shadowDir, \".gitignore\");\n if (!existsSync(shadowIgnore)) {\n writeFileSync(\n shadowIgnore,\n \"# Fluid CLI shadow repo — internal state, not for version control.\\n*\\n\",\n );\n }\n await ensureRootGitignoreHidesShadow(themeRoot);\n\n return repo;\n }\n\n /** True when the repo has at least one commit on `refs/heads/main`. */\n async hasHead(): Promise<boolean> {\n if (this.headExists !== undefined) return this.headExists;\n try {\n await this.git([\"rev-parse\", \"--verify\", \"HEAD\"]);\n this.headExists = true;\n } catch {\n this.headExists = false;\n }\n return this.headExists;\n }\n\n /** Desktop snapshots deliberately use a different author. They are local\n * recovery points, not proof that those bytes were synced to the server. */\n async hasSyncedHead(): Promise<boolean> {\n if (!(await this.hasHead())) return false;\n const { stdout } = await this.git([\"log\", \"-1\", \"--format=%ae\"]);\n return stdout.toString(\"utf8\").trim() === \"cli@fluid.app\";\n }\n\n /**\n * Every path recorded under HEAD's tree, recursively. Callers use\n * this to detect local deletions (paths in HEAD, absent from the\n * working tree). Returns [] when HEAD has never been committed.\n */\n async headPaths(): Promise<string[]> {\n if (!(await this.hasHead())) return [];\n // -z is required, not cosmetic. Without it git honours core.quotePath\n // (default true) and renders any path containing a non-ASCII byte in\n // its C-quoted display form — `\"assets/caf\\303\\251.css\"`, quotes\n // included. That string matches nothing in the working tree, so\n // diffAgainstShadow reports the file as a local deletion and push\n // sends a DELETE for a key the server has never had. The failed\n // delete then blocks commitPushedState, so shadow HEAD never\n // advances and the phantom delete repeats on every later push.\n // -z also covers the rarer newline-in-path case; splitting on \"\\n\"\n // could not.\n const { stdout } = await this.git([\n \"ls-tree\",\n \"-r\",\n \"-z\",\n \"HEAD\",\n \"--name-only\",\n ]);\n return stdout\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter((line) => line.length > 0);\n }\n\n /**\n * The content of `path` in HEAD's tree, or null when the path does\n * not exist there. Callers use this as the merge base for pull\n * conflict resolution.\n */\n async blobAtHead(path: string): Promise<Buffer | null> {\n if (!(await this.hasHead())) return null;\n try {\n const { stdout } = await this.git([\"cat-file\", \"-p\", `HEAD:${path}`]);\n return stdout;\n } catch {\n return null;\n }\n }\n\n /**\n * Write `content` as a blob in the shadow repo and return its sha.\n * Used by `commitState` to stage each file's content before the\n * `write-tree` call.\n */\n async writeBlob(content: string | Buffer): Promise<string> {\n const buf = typeof content === \"string\" ? Buffer.from(content) : content;\n const { stdout } = await this.git([\"hash-object\", \"-w\", \"--stdin\"], {\n input: buf,\n });\n return stdout.toString(\"utf8\").trim();\n }\n\n /**\n * Commit `files` as HEAD's new tree, threaded onto the current HEAD\n * as the parent. Uses a per-call temp index so a partial run can't\n * corrupt anything reachable from HEAD; the previous commit stays\n * intact until `update-ref` at the end.\n *\n * Returns the new commit sha.\n */\n async commitState(\n files: Array<{ path: string; sha: string }>,\n message: string,\n ): Promise<string> {\n const indexPath = mkdtempSync(join(tmpdir(), \"fluid-shadow-\")) + \"/index\";\n\n try {\n if (files.length > 0) {\n // Keep file lists off argv: large themes exceed Windows' process\n // command-line limit. NUL delimiters preserve paths verbatim.\n await this.git([\"update-index\", \"-z\", \"--index-info\"], {\n input: Buffer.from(\n files.map(({ path, sha }) => `100644 ${sha}\\t${path}\\0`).join(\"\"),\n ),\n env: { GIT_INDEX_FILE: indexPath },\n });\n }\n\n const treeSha = (\n await this.git([\"write-tree\"], { env: { GIT_INDEX_FILE: indexPath } })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n const parent = (await this.hasHead())\n ? (await this.git([\"rev-parse\", \"HEAD\"])).stdout.toString(\"utf8\").trim()\n : null;\n\n const commitArgs = [\"commit-tree\", treeSha, \"-m\", message];\n if (parent) commitArgs.push(\"-p\", parent);\n\n const commitSha = (\n await this.git(commitArgs, {\n env: {\n GIT_AUTHOR_NAME: \"Fluid CLI\",\n GIT_AUTHOR_EMAIL: \"cli@fluid.app\",\n GIT_COMMITTER_NAME: \"Fluid CLI\",\n GIT_COMMITTER_EMAIL: \"cli@fluid.app\",\n },\n })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n await this.git([\"update-ref\", \"refs/heads/main\", commitSha]);\n this.headExists = true;\n return commitSha;\n } finally {\n // Best-effort — a stale index in tmp is harmless.\n try {\n rmSync(indexPath, { force: true });\n rmSync(indexPath.substring(0, indexPath.length - \"/index\".length), {\n recursive: true,\n force: true,\n });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Three-way merge of `local` against `remote` with `base` as the\n * common ancestor. Returns the merged bytes and a flag when\n * `git merge-file` reported unresolved conflicts (i.e. the output\n * contains `<<<<<<<` markers for the reader to resolve).\n *\n * `base` is null when HEAD has never seen this path; we merge\n * against an empty base, which is what git itself does for a new\n * file added on both sides.\n *\n * `favor` maps to `git merge-file`'s `--ours` / `--theirs`: instead\n * of emitting `<<<<<<<` markers, conflicting hunks are resolved to\n * the local (`\"local\"` → `--ours`, local is file1) or remote\n * (`\"remote\"` → `--theirs`) side. The output then never contains\n * markers, so the result is reported conflict-free even when\n * merge-file's exit code still counts the auto-resolved hunks.\n */\n async merge3(\n base: Buffer | null,\n local: Buffer,\n remote: Buffer,\n favor?: \"local\" | \"remote\",\n ): Promise<{ merged: Buffer; hasConflicts: boolean }> {\n const dir = mkdtempSync(join(tmpdir(), \"fluid-merge-\"));\n const localPath = join(dir, \"local\");\n const basePath = join(dir, \"base\");\n const remotePath = join(dir, \"remote\");\n\n try {\n writeFileSync(localPath, local);\n writeFileSync(basePath, base ?? Buffer.alloc(0));\n writeFileSync(remotePath, remote);\n\n // `git merge-file -p` writes the merged result to stdout instead\n // of mutating `localPath` in place, and exits non-zero (= number\n // of unresolved conflicts) when it couldn't reconcile everything.\n // We treat any non-zero exit as \"conflicts present\" as long as\n // stdout is populated — the caller writes it to disk with markers.\n try {\n const { stdout } = await this.git([\n \"merge-file\",\n \"-p\",\n ...(favor === \"local\"\n ? [\"--ours\"]\n : favor === \"remote\"\n ? [\"--theirs\"]\n : []),\n \"-L\",\n \"local\",\n \"-L\",\n \"base\",\n \"-L\",\n \"remote\",\n localPath,\n basePath,\n remotePath,\n ]);\n return { merged: stdout, hasConflicts: false };\n } catch (err) {\n const e = err as {\n code?: number;\n stdout?: Buffer | string;\n stderr?: Buffer | string;\n };\n // `git merge-file` exit codes:\n // 0 clean merge (handled in the try branch above)\n // 1..127 count of unresolved conflict hunks; stdout is the\n // merged content with markers — treat as conflict.\n // >=128 git itself crashed (SIGSEGV, out-of-memory) —\n // stdout is empty, don't clobber the user's file.\n // negative usage error (bad args) — same, don't clobber.\n // Requiring both the conflict-range code AND a non-empty\n // stdout matches the comment above and stops a crash from\n // silently writing 0 bytes to disk.\n const merged =\n e.stdout instanceof Buffer\n ? e.stdout\n : e.stdout != null\n ? Buffer.from(e.stdout)\n : Buffer.alloc(0);\n const inConflictRange =\n typeof e.code === \"number\" && e.code >= 1 && e.code <= 127;\n // With a favor side, merge-file resolved every hunk itself —\n // the exit code still counts them, but the output carries no\n // markers, so it's a finished merge from the caller's view.\n // The stdout-length guard is dropped here: an auto-resolved\n // result can be legitimately empty (favoring a side that\n // deleted all content).\n if (inConflictRange && favor) {\n return { merged, hasConflicts: false };\n }\n if (inConflictRange && merged.length > 0) {\n return { merged, hasConflicts: true };\n }\n throw err;\n }\n } finally {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Snapshot the working-tree copy of `paths` into HEAD as a single\n * commit. Intended for the migration path: on the first pull with\n * the new CLI (no shadow repo yet, but a `.fluid-theme.json` with\n * checksums exists) we seed HEAD with whatever is on disk before\n * running the merge, so unmodified files fast-forward cleanly and\n * modified files show a diff.\n */\n async seedFromWorkingTree(\n files: Array<{ path: string; content: string | Buffer }>,\n message: string,\n ): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n for (const { path, content } of files) {\n entries.push({ path, sha: await this.writeBlob(content) });\n }\n await this.commitState(entries, message);\n }\n\n private async git(\n args: string[],\n opts: {\n cwd?: string;\n input?: Buffer;\n env?: Record<string, string>;\n } = {},\n ): Promise<{ stdout: Buffer; stderr: Buffer }> {\n const fullArgs =\n args[0] === \"init\" ? args : [\"--git-dir\", this.gitDir, ...args];\n const child = spawn(\"git\", fullArgs, {\n cwd: opts.cwd ?? this.themeRoot,\n env: { ...process.env, ...opts.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n return new Promise((resolve, reject) => {\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.on(\"error\", reject);\n child.stdin.on(\"error\", reject);\n child.on(\"close\", (code) => {\n const out = Buffer.concat(stdout);\n const err = Buffer.concat(stderr);\n if (code === 0) {\n resolve({ stdout: out, stderr: err });\n } else {\n const e = new Error(\n `git ${args.join(\" \")} exited with ${code}: ${err.toString(\"utf8\")}`,\n ) as Error & { code: number; stdout: Buffer; stderr: Buffer };\n e.code = code ?? -1;\n e.stdout = out;\n e.stderr = err;\n reject(e);\n }\n });\n child.stdin.end(opts.input);\n });\n }\n}\n\n/**\n * Content-type check the pull command uses to decide whether a file\n * is safe to run through `merge3` (line-based) or must fall back to\n * whole-file \"either/or\" resolution (binary).\n */\nexport function looksBinary(content: Buffer): boolean {\n // Same heuristic git uses for `core.autocrlf` detection: any NUL in\n // the first 8000 bytes counts as binary.\n const scan = content.subarray(0, Math.min(content.length, 8000));\n return scan.includes(0);\n}\n\n/** Best-effort readFile that returns null when the file does not exist. */\nexport function readIfExists(path: string): Buffer | null {\n try {\n return readFileSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the numeric theme id from `.fluid-theme/theme-id`. Returns\n * null when the file is missing or the contents don't parse cleanly;\n * `open` treats that as \"unknown theme\" and rebuilds the shadow.\n */\nfunction readStoredThemeId(themeIdFile: string): number | null {\n try {\n const stored = parseInt(readFileSync(themeIdFile, \"utf-8\").trim(), 10);\n return Number.isFinite(stored) ? stored : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Append `.fluid-theme/` to the theme root's `.gitignore` when the\n * theme dir sits inside a git working tree and the entry isn't\n * already there. Skipped when the user isn't in a git repo — no\n * point manufacturing a `.gitignore` for someone who doesn't use\n * git. Idempotent — a second call is a no-op.\n */\nasync function ensureRootGitignoreHidesShadow(\n themeRoot: string,\n): Promise<void> {\n const insideGit = await new Promise<boolean>((resolve) => {\n const child = spawn(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], {\n cwd: themeRoot,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n child.on(\"close\", (code) => resolve(code === 0));\n child.on(\"error\", () => resolve(false));\n });\n if (!insideGit) return;\n\n const gitignorePath = join(themeRoot, \".gitignore\");\n let existing = \"\";\n try {\n existing = readFileSync(gitignorePath, \"utf-8\");\n } catch {\n existing = \"\";\n }\n\n const lines = existing.split(\"\\n\").map((line) => line.trim());\n // Match both `.fluid-theme/` and `.fluid-theme` — either shape hides\n // the dir from `git status`.\n if (lines.includes(\".fluid-theme/\") || lines.includes(\".fluid-theme\")) {\n return;\n }\n\n const separator =\n existing.length === 0 || existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n writeFileSync(\n gitignorePath,\n `${existing}${separator}.fluid-theme/\\n`,\n \"utf-8\",\n );\n}\n","import chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport type { createApiClient } from \"./api.js\";\nimport { themes, type components } from \"@fluid-app/themes-api-client\";\n\nexport type ApplicationTheme = components[\"schemas\"][\"ApplicationTheme\"];\n\nconst PAGE_SIZE = 50;\nconst LOAD_MORE_VALUE = -1;\n\nfunction themeLabel(t: ApplicationTheme): string {\n const active = t.status === \"active\" ? ` ${chalk.green(\"[active]\")}` : \"\";\n return `${t.name} (#${t.id})${active}`;\n}\n\nfunction themeChoices(\n themeList: ApplicationTheme[],\n hasMore: boolean,\n): prompts.Choice[] {\n const choices: prompts.Choice[] = themeList.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n if (hasMore) {\n choices.push({\n title: chalk.dim(`── Load more themes ──`),\n value: LOAD_MORE_VALUE,\n });\n }\n return choices;\n}\n\nasync function fetchThemesPage(\n api: ReturnType<typeof createApiClient>,\n page: number,\n searchQuery?: string,\n): Promise<{\n themes: ApplicationTheme[];\n hasMore: boolean;\n}> {\n const body = await themes.listApplicationThemes(api, {\n per_page: PAGE_SIZE,\n page,\n ...(searchQuery ? { search_query: searchQuery } : {}),\n });\n const list = body.application_themes ?? [];\n const totalPages = body.meta?.total_pages ?? 1;\n return { themes: list, hasMore: page < totalPages };\n}\n\nexport async function selectTheme(\n api: ReturnType<typeof createApiClient>,\n message: string,\n): Promise<ApplicationTheme> {\n const allThemes: ApplicationTheme[] = [];\n let page = 1;\n let hasMore = true;\n let initialIndex = 0;\n\n // Search cache — persists across suggest calls\n let searchQuery = \"\";\n let searchResults: ApplicationTheme[] = [];\n\n while (true) {\n if (hasMore && allThemes.length < page * PAGE_SIZE) {\n const result = await fetchThemesPage(api, page);\n allThemes.push(...result.themes);\n hasMore = result.hasMore;\n }\n\n if (!allThemes.length) {\n console.error(\"No themes found.\");\n process.exit(1);\n }\n\n const choices = themeChoices(allThemes, hasMore);\n\n const { id } = await prompts(\n {\n type: \"autocomplete\",\n name: \"id\",\n message,\n initial: initialIndex,\n choices,\n suggest: async (input: string, choices: prompts.Choice[]) => {\n if (!input) {\n searchQuery = \"\";\n searchResults = [];\n return choices;\n }\n\n if (input !== searchQuery) {\n searchQuery = input;\n try {\n const result = await fetchThemesPage(api, 1, input);\n searchResults = result.themes;\n } catch {\n searchResults = [];\n }\n }\n\n return searchResults.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n },\n },\n { onCancel: () => process.exit(130) },\n );\n\n if (id === LOAD_MORE_VALUE) {\n initialIndex = allThemes.length;\n page++;\n continue;\n }\n\n if (!id) {\n console.error(\"No theme selected.\");\n process.exit(1);\n }\n\n // Check loaded themes first, then search results\n const found =\n allThemes.find((t) => t.id === id) ??\n searchResults.find((t) => t.id === id);\n if (found) return found;\n\n // Fetch directly by ID as fallback\n const body = await themes.getApplicationTheme(api, id);\n return body.application_theme;\n }\n}\n\nexport async function findTheme(\n api: ReturnType<typeof createApiClient>,\n identifier: string,\n): Promise<ApplicationTheme> {\n // Try ID lookup first\n const idNum = Number(identifier);\n if (Number.isInteger(idNum) && idNum > 0) {\n try {\n const body = await themes.getApplicationTheme(api, idNum);\n if (body.application_theme) return body.application_theme;\n } catch {\n // Not found by ID, fall through to search\n }\n }\n\n // Search by name via API with pagination\n let page = 1;\n let hasMore = true;\n while (hasMore) {\n const result = await fetchThemesPage(api, page, identifier);\n const found = result.themes.find(\n (t) => t.name.toLowerCase() === identifier.toLowerCase(),\n );\n if (found) return found;\n hasMore = result.hasMore;\n page++;\n }\n\n console.error(`No theme found with identifier: ${identifier}`);\n process.exit(1);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nexport interface FluidWorkspace {\n /** Absolute path to the workspace root (where .fluid-workspace.json lives) */\n root: string;\n /** Parsed workspace config */\n config: WorkspaceConfig;\n}\n\ninterface WorkspaceConfig {\n type: string;\n version: number;\n}\n\nconst WORKSPACE_FILE = \".fluid-workspace.json\";\n\n/**\n * Walk up from `startDir` looking for `.fluid-workspace.json`.\n * Returns the workspace info if found, or `null` if not in a workspace.\n */\nexport function findWorkspace(startDir?: string): FluidWorkspace | null {\n let dir = resolve(startDir ?? process.cwd());\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const candidate = join(dir, WORKSPACE_FILE);\n if (existsSync(candidate)) {\n try {\n const raw = readFileSync(candidate, \"utf-8\");\n const config = JSON.parse(raw) as WorkspaceConfig;\n return { root: dir, config };\n } catch {\n return null;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n return null;\n}\n\n/**\n * If cwd is already inside `{workspace}/local/{company}/...`, return that\n * theme root directory. Otherwise return null.\n *\n * Examples (workspace root = /code/fluid-theme-dev):\n * cwd = /code/fluid-theme-dev/local/acme-co → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev/local/acme-co/templates → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev → null\n * cwd = /code/fluid-theme-dev/local → null\n */\nexport function resolveThemeRootFromCwd(\n workspace: FluidWorkspace,\n): string | null {\n const cwd = resolve(process.cwd());\n const localDir = join(workspace.root, \"local\");\n const rel = relative(localDir, cwd);\n\n // Not under local/ at all, or exactly at local/\n if (rel.startsWith(\"..\") || rel === \".\") return null;\n\n // rel is like \"acme-co\" or \"acme-co/templates/subfolder\"\n // The theme root is the first segment: local/{company}\n const firstSegment = rel.split(sep)[0];\n if (!firstSegment) return null;\n\n return join(localDir, firstSegment);\n}\n","import { Command } from \"commander\";\nimport { navigateTheme } from \"./navigate.js\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport {\n devThemeKey,\n getDevTheme,\n setDevTheme,\n setLastDevThemeId,\n clearDevTheme,\n type DevThemeRef,\n} from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { startDevServer } from \"../theme/dev-server/index.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport {\n devRemoteStateFromSourceShadow,\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"../theme/dev-remote-baseline.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n checkPortAvailable,\n PortInUseError,\n} from \"../theme/dev-server/port-preflight.js\";\nimport { isApiError, themes } from \"@fluid-app/themes-api-client\";\nimport { findTheme, type ApplicationTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\n/** Whether this invocation may read and persist the dev remote baseline. */\nexport function devRemoteBaselineEnabled(explicitTheme: boolean): boolean {\n return (\n !explicitTheme && process.env[\"FLUID_THEME_DEV_DISABLE_SHADOW_SYNC\"] !== \"1\"\n );\n}\n\n/**\n * Create the isolated theme used by `theme dev`.\n *\n * A checkout from `theme pull` has a source theme id. New servers clone that\n * source by reference, preserving its DAM/ImageKit assets without moving\n * bytes. A 404/405 keeps older deployments compatible with the established\n * empty-theme flow; other failures must remain visible to the developer.\n */\nexport async function createDevelopmentTheme(\n api: ReturnType<typeof createApiClient>,\n sourceThemeId: number | undefined,\n name: string,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (sourceThemeId !== undefined) {\n try {\n const body = await themes.cloneApplicationThemeForDevelopment(\n api,\n sourceThemeId,\n { application_theme: { name } },\n );\n return { theme: body.application_theme, referenceCloned: true };\n } catch (error) {\n if (\n !isApiError(error) ||\n (error.status !== 404 && error.status !== 405)\n ) {\n throw error;\n }\n\n console.warn(\n \"Server-side theme cloning is unavailable; falling back to an empty development theme. The first sync may take longer.\",\n );\n }\n }\n\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"development\" },\n });\n return { theme: body.application_theme, referenceCloned: false };\n}\n\nasync function ensureDevTheme(\n api: ReturnType<typeof createApiClient>,\n projectKey: string,\n identifier?: string,\n sourceThemeId?: number,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (identifier) {\n const theme = await findTheme(api, identifier);\n // Keep `navigate` pointed at whatever the dev server is actually serving.\n setLastDevThemeId(theme.id);\n return { theme, referenceCloned: false };\n }\n\n // Reuse this project's stored dev theme if it still exists and is still a\n // development theme (a published/promoted theme must not be edited in place).\n const stored = getDevTheme(projectKey);\n // A checkout pulled from a different source needs an isolated sandbox of\n // its own. Older stored entries have no source id, so they are retained only\n // for worktrees that were not pulled from a remote theme.\n if (stored && stored.sourceThemeId === sourceThemeId) {\n try {\n const body = await themes.getApplicationTheme(api, stored.id);\n const existing = body.application_theme;\n if (existing && existing.status === \"development\") {\n console.log(`Using existing dev theme #${existing.id}`);\n // Refresh the stored name and mark it most-recent for `navigate`.\n setDevTheme(projectKey, {\n ...stored,\n id: existing.id,\n name: existing.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n });\n return { theme: existing, referenceCloned: false };\n }\n } catch {\n // Theme no longer exists — fall through to create a new one.\n }\n // Stored theme is gone or no longer a dev theme; forget it.\n clearDevTheme(projectKey);\n }\n\n // Create a new development theme\n const { hostname } = await import(\"node:os\");\n const host = hostname().split(\".\")[0] ?? \"dev\";\n const name =\n `Development (${host}-${Math.random().toString(36).slice(2, 8)})`.slice(\n 0,\n 50,\n );\n\n const creation = await createDevelopmentTheme(api, sourceThemeId, name);\n const { theme } = creation;\n const devTheme: DevThemeRef = {\n id: theme.id,\n name: theme.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n };\n setDevTheme(projectKey, devTheme);\n console.log(`Created dev theme: ${theme.name} (#${theme.id})`);\n return creation;\n}\n\nexport function createDevCommand(): Command {\n return new Command(\"dev\")\n .description(\"Start the theme dev server with hot reload\")\n .option(\"--host <host>\", \"Local server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Local server port\", \"9292\")\n .option(\n \"-t, --theme <name-or-id>\",\n \"Use an existing theme instead of dev theme\",\n )\n .option(\"-f, --force\", \"Skip schema validation on upload\")\n .option(\"--live-reload <mode>\", \"Reload mode: full-page | off\", \"full-page\")\n .option(\"--navigate\", \"Open browser navigator after server starts\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n host: string;\n port: string;\n theme?: string;\n force?: boolean;\n liveReload: string;\n navigate?: boolean;\n root: string;\n }) => {\n requireToken();\n\n // If no explicit --root and we're inside a workspace, resolve to the theme root\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const port = Number(opts.port);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n console.error(\n `Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`,\n );\n process.exit(1);\n }\n\n // Fail fast if the port is already taken — everything below this\n // point (resolving/creating the dev theme, the initial sync) is\n // expensive and would otherwise run to completion only to crash\n // with a raw EADDRINUSE stack trace when the server finally binds.\n try {\n await checkPortAvailable(opts.host, port);\n } catch (e) {\n if (e instanceof PortInUseError) {\n console.error(e.message);\n } else {\n console.error(`Failed to check port availability: ${e}`);\n }\n process.exit(1);\n }\n\n const reloadMode = opts.liveReload === \"off\" ? \"off\" : \"full-page\";\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n\n // Use company from .fluid-theme.json if available, otherwise fetch\n let company: string;\n if (config?.company) {\n company = config.company;\n } else {\n const companyRes = await api.get<CompanyMe>(\n \"/api/company/v1/companies/me\",\n );\n company = companyRes.data?.company?.subdomain ?? \"\";\n if (!company) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n }\n\n // Always iterate on an isolated dev theme: reuse the stored one or\n // create a fresh `development` theme. `--theme` is the explicit\n // escape hatch for targeting an existing theme. A pulled theme id is\n // never a sync target: it only seeds a new isolated reference clone.\n const projectKey = devThemeKey(company, themeRoot.root);\n const devTarget = opts.theme\n ? await ensureDevTheme(api, projectKey, opts.theme)\n : await ensureDevTheme(api, projectKey, undefined, config?.themeId);\n const { theme } = devTarget;\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const baselineEnabled = devRemoteBaselineEnabled(Boolean(opts.theme));\n let initialRemoteState = baselineEnabled\n ? readDevRemoteBaseline(\n themeRoot.root,\n theme.id,\n assetManifest.fingerprint(),\n )\n : null;\n if (\n !initialRemoteState &&\n baselineEnabled &&\n devTarget.referenceCloned &&\n config?.themeId &&\n config.baseSha\n ) {\n try {\n const sourceShadow = await ShadowRepo.open(\n themeRoot.root,\n config.themeId,\n );\n initialRemoteState = await devRemoteStateFromSourceShadow(\n themeRoot,\n sourceShadow,\n theme.id,\n config.baseSha,\n );\n if (initialRemoteState) {\n writeDevRemoteBaseline(themeRoot.root, initialRemoteState);\n }\n } catch {\n initialRemoteState = null;\n }\n }\n const editorUrl = `https://admin.fluid.app/themes/${theme.id}/editor`;\n\n let stop: (() => void) | undefined;\n\n const cleanup = () => {\n stop?.();\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n stop = await startDevServer(\n api,\n {\n id: theme.id,\n name: theme.name,\n company,\n editorUrl,\n },\n themeRoot,\n {\n host: opts.host,\n port,\n reloadMode,\n validate: !opts.force,\n ...(initialRemoteState ? { initialSync: initialRemoteState } : {}),\n ...(baselineEnabled\n ? {\n onRemoteState: (state) =>\n writeDevRemoteBaseline(themeRoot.root, state),\n onRemoteStateInvalidated: () =>\n removeDevRemoteBaseline(themeRoot.root),\n }\n : {}),\n },\n (address) => {\n console.log(`\\n Dev server: ${address}`);\n console.log(` Web editor: ${editorUrl}`);\n console.log(\"\\n Watching for file changes…\\n\");\n\n if (opts.navigate) {\n void navigateTheme(api, theme.id, address).catch(\n (error: unknown) => {\n console.error(\n error instanceof Error\n ? error.message\n : \"Could not open the preview navigator.\",\n );\n },\n );\n }\n },\n );\n\n // Keep process alive\n await new Promise(() => {});\n },\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport {\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"./dev-remote-baseline.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { PushConflictError, Syncer } from \"./syncer.js\";\n\nexport interface DevelopmentPushResult {\n uploaded: number;\n deleted: number;\n linked: number;\n errors: string[];\n validationFailed: boolean;\n /** The remote changed during the push; nothing further was written. */\n conflict: boolean;\n}\n\n/**\n * Push a project to a development theme without touching the project's own\n * sync state.\n *\n * A project directory keeps one shadow repo and one `.fluid-theme.json`, both\n * for the theme it is bound to. Opening the shadow repo for another theme\n * wipes it, so an ordinary push here would cost the bound theme its baseline:\n * the next Publish could no longer detect deletions or stale remotes. This\n * syncs the way `fluid theme dev` does instead: against the development\n * theme's own remote index and `.fluid-theme/dev-baseline.json`, which it\n * keeps current for the next dev session.\n */\nexport async function pushToDevelopmentTheme(\n api: ApiClient,\n themeId: number,\n themeRoot: ThemeRoot,\n opts: {\n validate: boolean;\n /** Remove remote files that no longer exist locally (`--nodelete` off). */\n delete: boolean;\n onProgress?: (done: number, total: number) => void;\n },\n): Promise<DevelopmentPushResult> {\n const syncer = new Syncer(api, themeId, themeRoot);\n const fingerprint = () =>\n new ThemeAssetManifest(themeRoot.root).fingerprint();\n const common = {\n delete: opts.delete,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n ...(opts.onProgress ? { onProgress: opts.onProgress } : {}),\n } as const;\n\n // Trust the saved dev baseline only while the remote still matches it;\n // otherwise compare against the remote index, as `theme dev` does.\n const saved = readDevRemoteBaseline(themeRoot.root, themeId, fingerprint());\n let trusted = false;\n if (saved) {\n try {\n syncer.useDevRemoteState(saved);\n await syncer.preflightPush(saved.remoteSha);\n trusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n }\n let result;\n try {\n result =\n trusted && saved\n ? await syncer.uploadTheme({\n ...common,\n baseSha: syncer.remoteSha(),\n remoteState: saved,\n skipPreflight: true,\n })\n : await syncer.uploadTheme({ ...common, guardFetchedRemote: true });\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n removeDevRemoteBaseline(themeRoot.root);\n return {\n uploaded: 0,\n deleted: 0,\n linked: 0,\n errors: [\n \"The development theme changed during this push. Run the push again to compare against its current state.\",\n ],\n validationFailed: false,\n conflict: true,\n };\n }\n\n const errors = [...result.errors];\n if (result.validationFailed || errors.length > 0) {\n removeDevRemoteBaseline(themeRoot.root);\n } else if (!(await syncer.requestSync())) {\n // Always ask, even with nothing uploaded: a retry after a rejected\n // finalization finds the files already remote, and some writes (asset\n // reference cleanup) never show in the counts.\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push to commit it.\",\n );\n } else {\n const state = syncer.devRemoteState(fingerprint());\n if (state) writeDevRemoteBaseline(themeRoot.root, state);\n }\n\n return {\n uploaded: result.uploaded,\n deleted: result.deleted,\n linked: result.linked,\n errors,\n validationFailed: result.validationFailed,\n conflict: false,\n };\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, readIfExists } from \"./shadow-repo.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\n\nexport interface DiffSet {\n /** Files whose local content differs from shadow HEAD — the ones to send. */\n changed: ThemeFile[];\n /** Paths present in shadow HEAD but no longer on disk — deletions to send. */\n deleted: string[];\n}\n\n/**\n * Compute what changed locally since the last time the shadow repo\n * committed a state. Replaces the sha256 `checksums` map: shadow HEAD\n * is the source of truth for \"what the CLI last saw the server have\".\n *\n * Files whose local bytes are byte-identical to their HEAD blob are\n * skipped; anything else — new, modified, or a locally-deleted path\n * that HEAD still has — is included.\n */\nexport async function diffAgainstShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<DiffSet> {\n const changed: ThemeFile[] = [];\n const deleted: string[] = [];\n\n const localFiles = themeRoot.files();\n const localByKey = new Map<string, ThemeFile>();\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let headPaths: string[];\n try {\n headPaths = await shadow.headPaths();\n } catch (error) {\n throw new Error(\"Could not read the local theme shadow\", { cause: error });\n }\n\n assertNoCaseCollisions([\n ...localFiles.map((file) => file.relativePath),\n ...headPaths,\n ]);\n const headPathSet = new Set(headPaths);\n\n for (const file of localFiles) {\n if (!file.exists) continue;\n localByKey.set(file.relativePath, file);\n\n const headBlob = await shadow.blobAtHead(file.relativePath);\n if (!headBlob && headPathSet.has(file.relativePath)) {\n throw new Error(\n `Could not read the local theme shadow: ${file.relativePath}`,\n );\n }\n const localBuf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n if (headBlob && headBlob.equals(localBuf)) continue;\n\n changed.push(file);\n }\n\n for (const key of headPaths) {\n if (localByKey.has(key)) continue;\n if (themeRoot.ignore.ignore(key)) continue;\n // A manifest-backed asset is deliberately absent from disk. Its URL is\n // still authoritative and must never be emitted as a remote deletion.\n if (assetManifest.has(key)) continue;\n if (isStylesheetKey(key)) continue; // hidden from the API surface\n deleted.push(key);\n }\n\n return { changed, deleted };\n}\n\n/**\n * Refuse a push when any working file still contains a conflict marker\n * from a previous pull. Mirrors git's \"you have unresolved conflicts;\n * fix them and re-run\" behavior — the whole point of writing markers\n * on pull was to hand resolution to the user, so we can't send them\n * upstream.\n */\nexport function findUnresolvedConflicts(files: ThemeFile[]): string[] {\n const flagged: string[] = [];\n for (const file of files) {\n if (!file.isText) continue;\n const buf = readIfExists(file.absolutePath);\n if (!buf) continue;\n if (containsConflictMarker(buf)) flagged.push(file.relativePath);\n }\n return flagged;\n}\n\n/**\n * Stable, machine-readable one-liner for non-interactive callers\n * (Mist Desktop's publish flow parses push output). Uploading marker-\n * bearing files to a live theme is never acceptable, so `--auto-\n * baseline` pushes still refuse — but they emit this line so the\n * desktop can surface WHICH files block the publish instead of a\n * dead-end wall of prose. Format:\n *\n * FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=a.liquid,b.json\n */\nexport function conflictMarkerBlockLine(files: string[]): string {\n return `FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=${files.join(\",\")}`;\n}\n\nconst CONFLICT_START = Buffer.from(\"<<<<<<<\");\nconst CONFLICT_MID = Buffer.from(\"=======\");\nconst CONFLICT_END = Buffer.from(\">>>>>>>\");\n\n/**\n * A file counts as unresolved when it contains all three marker\n * shapes: `<<<<<<<`, `=======`, and `>>>>>>>`. Requiring all three\n * avoids false positives — a line of equals signs alone (e.g. inside\n * an ASCII table in a template comment) doesn't trip the guard.\n */\nfunction containsConflictMarker(buf: Buffer): boolean {\n return (\n buf.includes(CONFLICT_START) &&\n buf.includes(CONFLICT_MID) &&\n buf.includes(CONFLICT_END)\n );\n}\n\n/**\n * Commit the pushed state to shadow HEAD. Server-acknowledged contents\n * override local bytes for normalized member files, including files edited\n * or deleted locally during upload. Local files remain untouched so those\n * later changes still differ from the next pull's merge base.\n */\nexport async function commitPushedState(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n message: string,\n acknowledgedContents?: ReadonlyMap<string, string>,\n): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const recordedKeys = new Set<string>();\n for (const [path, content] of new Map(acknowledgedContents)) {\n entries.push({ path, sha: await shadow.writeBlob(content) });\n recordedKeys.add(path);\n }\n for (const file of themeRoot.files()) {\n if (!file.exists || recordedKeys.has(file.relativePath)) continue;\n recordedKeys.add(file.relativePath);\n // A manifest entry normally has no file at all. If bytes are present (for\n // example after a metadata fallback), they remain authoritative until a\n // successful upload can externalize them again.\n const buf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n entries.push({ path: file.relativePath, sha: await shadow.writeBlob(buf) });\n }\n let managedAssetSentinelSha: string | undefined;\n for (const key of assetManifest.keys()) {\n if (recordedKeys.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n entries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // Commit an empty tree too when HEAD exists: a successful final deletion\n // must clear its old path/sentinel so later pushes do not repeat it.\n if (entries.length > 0 || (await shadow.hasHead())) {\n await shadow.commitState(entries, message);\n }\n}\n\n/**\n * A target can already have every URL-backed FileResource (for example from a\n * reference clone), while this checkout's manifest still names its old source\n * theme. Adopt the target and clear any legacy binary from shadow even though\n * no remote write was necessary.\n */\nexport async function finalizeManifestOnlyPush(\n syncer: { repointManagedAssetsToCurrentTheme(): void },\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<void> {\n syncer.repointManagedAssetsToCurrentTheme();\n await commitPushedState(\n themeRoot,\n shadow,\n `push @ ${new Date().toISOString()}`,\n );\n}\n","import { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { FetchBinary } from \"./merge-pull.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\nexport interface SeedBaselineResult {\n /** False when a baseline already existed (HEAD present) — e.g. the\n * legacy checksum-era migration already seeded it. The migration\n * always wins over server seeding: a migrated HEAD carries the\n * local \"unmodified since last sync\" blobs, which is a better\n * merge base than the server's current state. */\n seeded: boolean;\n /** Paths recorded into the baseline commit. */\n recorded: string[];\n /** Server paths with no local counterpart, deliberately left OUT of\n * the baseline (see invariant below). */\n serverOnly: string[];\n /** Non-fatal per-file problems (e.g. a binary download failed). */\n errors: string[];\n}\n\n/**\n * `fluid theme push --auto-baseline`: when a theme directory has no\n * shadow baseline (scaffold that never pulled, or a dir last synced\n * by the checksum-era CLI whose migration had nothing to seed),\n * record the server's CURRENT state as the baseline commit so the\n * normal push diff (local vs baseline) can run.\n *\n * Invariants:\n *\n * 1. **The working tree is never touched.** Baseline recording writes\n * blobs into the bare shadow repo only; not a single byte on disk\n * changes. Local files identical to the server simply won't diff;\n * files that differ (or exist only locally) will push.\n *\n * 2. **Server-only files are never deleted.** Paths that exist on the\n * server but not locally are deliberately excluded from the\n * baseline commit. `diffAgainstShadow` reports deletions as \"in\n * HEAD but not on disk\", so putting server-only paths into HEAD\n * would mark them for remote deletion — on THIS push (or worse, a\n * later one) — for files the user never had. Excluding them makes\n * the diff structurally unable to delete them; the next `pull`\n * materializes them locally and records them for real.\n *\n * Bandwidth note: a server file whose sha256 checksum matches the\n * local file's is recorded from the LOCAL bytes (identical by\n * definition), so binary assets that are already in sync are never\n * downloaded just to seed the baseline.\n *\n * No-op (`seeded: false`) when HEAD already exists.\n */\nexport async function seedBaselineFromServer(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n message: string;\n}): Promise<SeedBaselineResult> {\n const { shadow, themeRoot, remote, fetchBinary, message } = input;\n const result: SeedBaselineResult = {\n seeded: false,\n recorded: [],\n serverOnly: [],\n errors: [],\n };\n\n if (await shadow.hasHead()) return result;\n\n const entries: Array<{ path: string; sha: string }> = [];\n\n for (const resource of remote) {\n const file = themeRoot.file(resource.key);\n\n // Same traversal guard as mergePull — never trust remote keys.\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Baseline ${resource.key}: path traversal detected`);\n continue;\n }\n\n if (!file.exists) {\n result.serverOnly.push(resource.key);\n continue;\n }\n\n let content: Buffer | null;\n if (resource.checksum && file.checksum() === resource.checksum) {\n // Identical to the server — local bytes ARE the server bytes.\n content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n } else {\n try {\n content = await materializeRemote(resource, fetchBinary);\n } catch (e) {\n result.errors.push(\n `Baseline ${resource.key}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n }\n if (content == null) {\n // Nothing to record (empty resource) — the local file will be\n // treated as new and pushed, which is the honest outcome.\n continue;\n }\n\n entries.push({ path: resource.key, sha: await shadow.writeBlob(content) });\n result.recorded.push(resource.key);\n }\n\n if (entries.length > 0) {\n await shadow.commitState(entries, message);\n result.seeded = true;\n }\n\n return result;\n}\n\nasync function materializeRemote(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n","import { readLegacyThemeConfig } from \"../theme-config.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\n\n/**\n * On first pull after upgrading from a checksum-era CLI, the shadow\n * repo starts with no HEAD. `mergePull` would then run every diverged\n * file through a null-base merge — even files the user never touched\n * locally — producing spurious `<<<<<<<` markers for every file the\n * server updated since the last pull.\n *\n * Recover a real merge base by trusting the legacy sha256 checksums:\n * any local file whose content still matches its stored checksum is\n * \"unmodified since last pull\" and can be committed as HEAD. Files\n * whose local sha256 diverges from the stored checksum stay\n * unseeded — we don't have their pre-modification content, so a\n * null-base merge (marker-first UX) is the honest fallback for them.\n *\n * Idempotent: no-op when HEAD already exists, when there is no\n * legacy config, when the config is for a different theme, or when\n * the checksums map is empty. Runs before `mergePull` so its base\n * lookups see the seeded tree.\n */\nexport async function migrateLegacyChecksumsIntoShadow(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n absoluteRoot: string;\n themeId: number;\n}): Promise<void> {\n const { shadow, themeRoot, absoluteRoot, themeId } = input;\n\n if (await shadow.hasHead()) return;\n\n const legacy = readLegacyThemeConfig(absoluteRoot);\n if (!legacy) return;\n if (legacy.themeId !== themeId) return;\n if (!legacy.checksums || Object.keys(legacy.checksums).length === 0) return;\n\n const seed: Array<{ path: string; content: Buffer }> = [];\n for (const file of themeRoot.files()) {\n if (!file.exists) continue;\n const stored = legacy.checksums[file.relativePath];\n if (!stored) continue;\n if (file.checksum() !== stored) continue;\n\n const content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n seed.push({ path: file.relativePath, content });\n }\n\n if (seed.length === 0) return;\n\n await shadow.seedFromWorkingTree(\n seed,\n `migrate from checksum-era CLI @ ${new Date().toISOString()}`,\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport type { ChangeEntry, GitSyncActor } from \"@fluid-app/fluid-cli\";\nimport {\n gitSyncActorFromMe,\n gitSyncCommitSubject,\n resolveGitSyncActor,\n summarizeChanges,\n} from \"@fluid-app/fluid-cli\";\n\n/**\n * User-stamped shadow-repo commit subjects, in the canonical GitSync\n * format shared with the mist / portal / widget CLIs and the Ruby\n * adapters (see `@fluid-app/fluid-cli`'s git-sync/commit-subject.ts):\n *\n * Mike Tingey: Push theme Aurora · 2026-07-22 05:10:33 (0182d21e-…)\n *\n * When the actor is unknown (offline, /api/me down, token weirdness) the\n * commit is attributed to `Fluid` in the same shape, so consumers need\n * one grammar rather than two:\n *\n * Fluid: Push theme Aurora · 2026-07-22 05:10:33\n *\n * Shadow repos written by older CLIs still hold the pre-format machine\n * wording (`push @ <iso>`). We no longer emit it, but Mist Desktop's\n * History popover still parses it so those repos keep rendering.\n */\n\nexport type SyncVerb =\n | \"Push\"\n | \"Pull\"\n | \"Baseline from server\"\n | \"Snapshot before pull\";\n\n/** Human action per verb, used as the canonical subject's action. */\nconst BASE_MESSAGE: Record<SyncVerb, string> = {\n Push: \"Push from Fluid CLI\",\n Pull: \"Pull from Fluid CLI\",\n \"Baseline from server\": \"Baseline from server\",\n \"Snapshot before pull\": \"Snapshot before pull\",\n};\n\n/** Re-exported under the local name the theme CLI has always used. */\nexport type SyncActor = GitSyncActor;\n\n/**\n * Build a shadow-repo commit subject.\n *\n * `changes` (when supplied) replaces the per-verb constant with a\n * description of what the sync actually carried, so the shadow log reads\n * `Update 3 files in templates` rather than `Push from Fluid CLI` on\n * every entry. Falls back to the constant for an empty change set — a\n * baseline or a no-op sync still deserves a sensible subject.\n */\nexport function syncCommitSubject(\n verb: SyncVerb,\n actor: SyncActor | null,\n when: Date = new Date(),\n changes: readonly ChangeEntry[] = [],\n): string {\n const action = summarizeChanges(changes) ?? BASE_MESSAGE[verb];\n return gitSyncCommitSubject(action, actor, when);\n}\n\n/** Resolve a `/api/me` body to a commit actor. Exported for tests.\n * Delegates to the shared resolver so the name fallback chain\n * (full_name → first+last → email → `user-<id>`) can't drift between\n * CLIs. Null when the body carries no usable identity — the caller then\n * writes a `Fluid:` system commit. */\nexport function actorFromMe(\n raw: Parameters<typeof gitSyncActorFromMe>[0],\n): SyncActor | null {\n return gitSyncActorFromMe(raw);\n}\n\n/**\n * Best-effort fetch of the signed-in user for commit stamping. Races\n * `/api/me` against a short timeout and NEVER rejects — a sync must\n * not block or fail because the identity lookup did. Callers kick\n * this off early and await it only at commit time.\n */\nexport async function fetchSyncActor(\n api: ApiClient,\n timeoutMs = 5_000,\n): Promise<SyncActor | null> {\n // Served from the profile cache after the first sync; /api/me only\n // fires on a miss.\n return resolveGitSyncActor(() => requestSyncActor(api, timeoutMs));\n}\n\nasync function requestSyncActor(\n api: ApiClient,\n timeoutMs: number,\n): Promise<SyncActor | null> {\n try {\n const raw = await Promise.race([\n api.get<Record<string, unknown>>(\"/api/me\"),\n new Promise<never>((_, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n // Don't keep the process alive just for the stamp timeout.\n timer.unref?.();\n }),\n ]);\n return actorFromMe(raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(\n ` (skipping user-stamp on sync commit — couldn't fetch /api/me: ${reason})`,\n );\n return null;\n }\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport {\n mayBindTheme,\n readThemeConfig,\n writeThemeConfig,\n} from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { pushToDevelopmentTheme } from \"../theme/development-push.js\";\nimport { devThemeKey, getDevTheme } from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { PushConflictError, Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n commitPushedState,\n conflictMarkerBlockLine,\n diffAgainstShadow,\n finalizeManifestOnlyPush,\n findUnresolvedConflicts,\n} from \"../theme/merge-push.js\";\nimport { seedBaselineFromServer } from \"../theme/auto-baseline.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport {\n fetchSyncActor,\n syncCommitSubject,\n type SyncActor,\n} from \"../theme/sync-identity.js\";\nimport type { ChangeEntry } from \"@fluid-app/fluid-cli\";\nimport { themes } from \"@fluid-app/themes-api-client\";\nimport {\n selectTheme,\n findTheme,\n type ApplicationTheme,\n} from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { formatError } from \"../theme/format-error.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n MEMBER_CONFIGURATION_KEY,\n} from \"../theme/member-configuration.js\";\n\nexport function createPushCommand(): Command {\n return new Command(\"push\")\n .description(\"Push local theme files to a remote theme\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to push to\")\n .option(\"-n, --nodelete\", \"Do not delete remote files missing locally\")\n .option(\n \"-f, --force\",\n \"Skip local Liquid validation and the server-side merge check\",\n )\n .option(\"-p, --publish\", \"Publish the theme after pushing\")\n .option(\n \"-u, --unpublished\",\n \"Create a new unpublished theme and push to it\",\n )\n .option(\n \"--auto-baseline\",\n \"When no local baseline exists, record the server's current state \" +\n \"as the baseline and push only what differs locally (never \" +\n \"modifies local files, never deletes server-only files)\",\n )\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n force?: boolean;\n publish?: boolean;\n unpublished?: boolean;\n autoBaseline?: boolean;\n root: string;\n }) => {\n requireToken();\n\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n let theme: ApplicationTheme;\n\n if (opts.unpublished) {\n const { name } = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Name for the new theme\",\n },\n { onCancel: () => process.exit(130) },\n );\n if (!name) {\n console.error(\"Theme name is required.\");\n process.exit(1);\n }\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"draft\" },\n });\n theme = body.application_theme;\n console.log(\n `Created unpublished theme: ${theme.name} (#${theme.id})`,\n );\n } else if (opts.theme) {\n theme = await findTheme(api, opts.theme);\n } else if (config) {\n console.log(\n ` Using theme from .fluid-theme.json: ${chalk.bold(config.themeName)} (#${config.themeId})`,\n );\n const body = await themes.getApplicationTheme(api, config.themeId);\n theme = body.application_theme;\n if (theme.status === \"development\") {\n console.warn(\n chalk.yellow(\n ` ${theme.name} (#${theme.id}) is a development theme: a scratch copy made by \\`fluid theme dev\\`, not a theme your storefront serves. ` +\n `This push updates only that copy. To update the real theme, run \\`fluid theme pull -t <theme-id>\\` here to bind the project to it, or pass --theme.`,\n ),\n );\n }\n } else {\n theme = await selectTheme(api, \"Select a theme to push to\");\n }\n\n if (config && !mayBindTheme(config, theme)) {\n // Mirroring overwrites the target, so only the development theme\n // `fluid theme dev` made for this directory may be pushed, never a\n // teammate's or another project's scratch copy.\n const own = getDevTheme(devThemeKey(config.company, themeRoot.root));\n if (own?.id !== theme.id) {\n console.error(\n `${theme.name} (#${theme.id}) is a development theme, but not this project's` +\n (own ? ` (that is #${own.id})` : \"\") +\n `. Pushing would overwrite it with this directory. Run \\`fluid theme dev\\` here to sync this project's own development theme, or \\`fluid theme dev --theme ${theme.id}\\` to sync this one deliberately.`,\n );\n process.exit(1);\n }\n const unresolvedDev = findUnresolvedConflicts(themeRoot.files());\n if (unresolvedDev.length > 0) {\n console.error(\n `✗ ${unresolvedDev.length} file(s) still contain unresolved conflict markers: ${unresolvedDev.join(\", \")}. Resolve them before pushing.`,\n );\n process.exit(1);\n }\n if (opts.publish) {\n console.error(\n `${theme.name} (#${theme.id}) is a development theme; --publish would make that scratch copy the live theme. Push without --publish, or publish ${config.themeName} (#${config.themeId}).`,\n );\n process.exit(1);\n }\n const devSpinner = ora(\n `Pushing to development theme ${theme.name} (#${theme.id})…`,\n ).start();\n const result = await pushToDevelopmentTheme(\n api,\n theme.id,\n themeRoot,\n {\n validate: !opts.force,\n // --auto-baseline promises to leave server-only files alone.\n delete: !opts.nodelete && !opts.autoBaseline,\n onProgress: (done, total) => {\n devSpinner.text = `Pushing ${done}/${total} files to ${theme.name} (#${theme.id})…`;\n },\n },\n );\n if (result.validationFailed) {\n devSpinner.fail(\n `Schema validation failed (${result.errors.length} error(s)). Use --force to skip.`,\n );\n } else if (result.errors.length > 0) {\n devSpinner.warn(`Pushed with ${result.errors.length} error(s).`);\n } else {\n devSpinner.succeed(\n `Pushed ${result.uploaded} file(s), removed ${result.deleted}, to development theme #${theme.id}.`,\n );\n }\n for (const e of result.errors) console.error(` ${e}`);\n console.log(\n ` This project stays bound to ${config.themeName} (#${config.themeId}); Publish still pushes there.`,\n );\n if (result.errors.length > 0) process.exitCode = 1;\n return;\n }\n\n const shadow = await ShadowRepo.open(themeRoot.root, theme.id);\n\n // Legacy checksum-era dirs (pre-shadow CLI) migrate here — on\n // push as well as pull, so a dir that never pulled with the\n // new CLI still recovers its merge base instead of dead-ending\n // on the baseline check below. Idempotent; the migration wins\n // over `--auto-baseline` server seeding because a migrated\n // HEAD holds the local \"unmodified since last sync\" blobs.\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot: themeRoot.root,\n themeId: theme.id,\n });\n\n const localFiles = themeRoot.files().filter((f) => f.exists);\n\n // Lazy, memoized identity lookup for commit stamping — only\n // fired when we actually commit, never blocks or fails a push.\n let actorPromise: Promise<SyncActor | null> | null = null;\n const getActor = (): Promise<SyncActor | null> =>\n (actorPromise ??= fetchSyncActor(api));\n\n // Refuse to push files that still contain `<<<<<<< / >>>>>>>`\n // markers from a previous merge — the whole point of writing\n // them was to hand resolution to the user; sending them\n // upstream would ship broken content. This guard is absolute:\n // `--auto-baseline` (non-interactive) refuses too, but emits a\n // machine-readable line the desktop can surface well.\n const unresolved = findUnresolvedConflicts(localFiles);\n if (unresolved.length > 0) {\n console.log();\n console.log(\n chalk.red(\n `✗ ${unresolved.length} file(s) still contain unresolved conflict markers:`,\n ),\n );\n for (const key of unresolved) console.log(` ${key}`);\n console.log();\n console.log(\n ` Edit each file to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} sections,`,\n );\n console.log(` then re-run ${chalk.cyan(\"fluid theme push\")}.`);\n console.log();\n if (opts.autoBaseline) {\n console.error(conflictMarkerBlockLine(unresolved));\n }\n process.exit(1);\n }\n\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // baseSha — server-side merge check on every write. Skipped\n // when `--force`, when the config was written by an older CLI\n // that didn't yet capture the pull's `content_version_sha`,\n // or when we're pushing to a different theme than the one the\n // config was written for (a `--unpublished` create, or\n // `--theme` pointing somewhere else). `baseSha` is per-theme —\n // reusing one theme's sha as the base of another theme's push\n // always 409s.\n const configMatchesTheme = config?.themeId === theme.id;\n let baseSha =\n opts.force || !configMatchesTheme ? null : (config?.baseSha ?? null);\n\n // A baseline exists when either half is present: shadow HEAD\n // (the local diff base — written by pull, a legacy migration,\n // or a previous auto-baseline) or a stored `baseSha` (the\n // server-side race check). HEAD alone is enough to diff\n // honestly; a missing baseSha only skips the server preflight.\n const hasBaseline = baseSha != null || (await shadow.hasHead());\n\n // No baseline at all: without `--auto-baseline`, refuse —\n // otherwise the CLI silently clobbers whatever the server\n // currently has, with no way for the user to see the\n // divergence first. `--force` opts out (explicit clobber);\n // `--unpublished` skips because the theme was created empty\n // in this same command and has nothing to protect.\n if (!opts.force && !opts.unpublished && !hasBaseline) {\n if (!opts.autoBaseline) {\n console.error();\n console.error(\n chalk.red(\n `No local baseline for theme \"${theme.name}\" (#${theme.id}).`,\n ),\n );\n console.error();\n console.error(\n ` Run ${chalk.cyan(`fluid theme pull -t ${theme.id}`)} first to sync down the current server state,`,\n );\n console.error(\n ` then push — this way you see what would change before it goes live.`,\n );\n console.error();\n console.error(\n ` Or re-run with ${chalk.cyan(\"--auto-baseline\")} to record the server's current state`,\n );\n console.error(\n ` as the baseline and push only what differs locally (local files are never modified).`,\n );\n console.error();\n console.error(\n ` Or, if you know what you're doing and want to overwrite the server's current`,\n );\n console.error(\n ` contents wholesale, re-run with ${chalk.cyan(\"--force\")}.`,\n );\n console.error();\n process.exit(1);\n }\n\n // `--auto-baseline`: record the server's current state as\n // the baseline WITHOUT touching a single local file, then\n // fall through to the normal diff. Server-only files are\n // deliberately kept out of the baseline so they can never\n // be reported as local deletions (see auto-baseline.ts).\n const baselineSpinner = ora(\n `No local baseline — recording current server state for ${theme.name} (#${theme.id})…`,\n ).start();\n try {\n const resources = await syncer.downloadAll();\n const seedResult = await seedBaselineFromServer({\n shadow,\n themeRoot,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n message: syncCommitSubject(\n \"Baseline from server\",\n await getActor(),\n ),\n });\n baseSha = syncer.remoteSha() ?? null;\n const parts = [`recorded ${seedResult.recorded.length} file(s)`];\n if (seedResult.serverOnly.length > 0) {\n parts.push(\n `left ${seedResult.serverOnly.length} server-only file(s) untouched`,\n );\n }\n baselineSpinner.succeed(`Baseline recorded — ${parts.join(\", \")}.`);\n for (const err of seedResult.errors) {\n console.warn(` ${chalk.yellow(\"warn\")} ${err}`);\n }\n } catch (e) {\n baselineSpinner.fail(\n `Could not record a baseline from the server: ${formatError(e)}`,\n );\n process.exit(1);\n }\n }\n\n // Diff against shadow HEAD is the source of truth for \"what\n // changed since last sync\" — replaces the sha256 checksum map.\n const { changed, deleted } = await diffAgainstShadow(themeRoot, shadow);\n if (!opts.nodelete) assertMemberConfigurationRetained(deleted);\n const managedAssetCount = new ThemeAssetManifest(themeRoot.root).keys()\n .length;\n\n if (\n changed.length === 0 &&\n deleted.length === 0 &&\n managedAssetCount === 0\n ) {\n console.log(\"Nothing to push — local matches the last synced state.\");\n await persistConfig();\n return;\n }\n\n const spinner = ora(`Pushing to ${theme.name} (#${theme.id})…`).start();\n\n // Validate before linking URL references so a schema failure cannot\n // mutate the target theme at all.\n if (!opts.force) {\n const validationErrors: string[] = [];\n for (const file of changed) {\n if (!file.isLiquid) continue;\n for (const diagnostic of file.validateSchema()) {\n if (diagnostic.severity === \"error\") {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n file.read(),\n )) {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n if (validationErrors.length > 0) {\n spinner.fail(\n `Liquid validation failed (${validationErrors.length} error(s)). Use --force to skip.`,\n );\n for (const error of validationErrors) console.error(` ${error}`);\n process.exit(1);\n }\n }\n try {\n await syncer.preflightPush(baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (e) {\n if (e instanceof PushConflictError) {\n renderPullFirst(spinner);\n process.exit(1);\n }\n throw e;\n }\n\n const uploads = planThemeUploads(\n themeRoot,\n changed,\n opts.nodelete ? [] : deleted,\n );\n let linked = 0;\n try {\n linked = await syncer.linkManagedAssets({ replace: true });\n // Reference writes are outside the ordinary resource PUT path. The\n // syncer refreshes its remote SHA afterwards so following PUTs keep\n // the existing Phase-003a conflict chain intact.\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (error) {\n spinner.fail(\n `Could not save remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n\n if (changed.length === 0 && deleted.length === 0 && linked === 0) {\n try {\n await finalizeManifestOnlyPush(syncer, themeRoot, shadow);\n } catch (error) {\n spinner.fail(\n `Could not finalize remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n spinner.succeed(\"Nothing to push — local matches the remote theme.\");\n await persistConfig();\n return;\n }\n\n let uploaded = 0;\n let deletedCount = 0;\n const errors: string[] = [];\n let progress = 0;\n const total =\n uploads.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n ) + (opts.nodelete ? 0 : deleted.length);\n\n for (const upload of uploads) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n errors.length > 0\n ) {\n errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await syncer.uploadMemberConfiguration(upload, baseSha);\n } else {\n await syncer.uploadFile(file, baseSha);\n }\n baseSha = syncer.remoteSha() ?? baseSha;\n uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n progress += 1 + (upload.memberConfiguration?.files.length ?? 0);\n spinner.text = `Pushing ${progress}/${total} files…`;\n }\n\n if (!opts.nodelete && errors.length === 0) {\n for (const key of deleted) {\n try {\n await syncer.deleteRemoteFile(key, baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n deletedCount++;\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n spinner.text = `Pushing ${++progress}/${total} files…`;\n }\n }\n\n if (errors.length === 0) {\n try {\n // The selected target now owns every manifest reference, so a\n // later deleted dev sandbox cannot force a byte upload again.\n syncer.repointManagedAssetsToCurrentTheme();\n } catch (error) {\n errors.push(`Save asset provenance: ${formatError(error)}`);\n }\n }\n\n // Every write is on the server; ask Fluid to commit them as one\n // version. The writes themselves only mark the theme changed — this\n // one call is what makes a 120-file push a single commit instead of\n // 120, and it is the reason the server no longer has to guess where\n // an operation ended.\n //\n // A partial transfer may contain unresolved member dependencies.\n // Managed-asset-only pushes still finalize once every write succeeds.\n if (\n errors.length === 0 &&\n (uploaded > 0 || deletedCount > 0 || linked > 0)\n ) {\n if (!(await syncer.requestSync())) {\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push before publishing.\",\n );\n }\n }\n\n if (errors.length) {\n spinner.warn(`Pushed with ${errors.length} error(s).`);\n for (const err of errors) console.error(` ${err}`);\n // The live theme is missing every rejected file, so this is a\n // failure. Mist's lifecycle retry runs `fluid theme push\n // --auto-baseline` and keys off the exit code, and every other\n // failure in this command already exits 1 — exiting 0 here\n // reported a publish that did not happen.\n //\n // exitCode rather than process.exit(): the command still has\n // output to flush, and an immediate exit can truncate it.\n process.exitCode = 1;\n } else {\n spinner.succeed(\n `Pushed ${uploaded} file(s), saved ${linked} remote asset reference(s)` +\n (deletedCount > 0\n ? `, deleted ${deletedCount} remote file(s).`\n : \".\"),\n );\n }\n\n // Roll shadow HEAD forward only when every write succeeded —\n // committing the working tree on a partial push would make\n // `diffAgainstShadow` treat the failed files as \"already\n // synced\" on the next push, silently swallowing the retry.\n // Leave HEAD stale on failure and let the next push re-diff\n // and re-attempt.\n if (errors.length === 0) {\n // Describe what the push carried rather than stamping every\n // entry \"Push from Fluid CLI\". `changed` is the uploaded set\n // and `deleted` the removed keys — exactly what landed, since\n // we only get here when no write failed.\n const pushedChanges: ChangeEntry[] = [\n ...changed.map((file) => ({\n status: \"modified\" as const,\n path: file.relativePath,\n })),\n ...(opts.nodelete\n ? []\n : deleted.map((key) => ({\n status: \"deleted\" as const,\n path: key,\n }))),\n ];\n try {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\n \"Push\",\n await getActor(),\n new Date(),\n pushedChanges,\n ),\n syncer.acknowledgedMemberContents(),\n );\n } catch (error) {\n console.error(\n `Theme content was uploaded to the server, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Do not repeat the push with --force. Review the remote theme and run a normal pull to recover the baseline.`,\n );\n if (opts.publish) console.error(\"Publishing was not attempted.\");\n process.exitCode = 1;\n return;\n }\n }\n\n await persistConfig();\n\n /**\n * Persist `.fluid-theme.json`. When a config already exists,\n * this is the pre-existing baseSha refresh. When it doesn't\n * (a scaffold's first `--auto-baseline` push), bind the dir\n * to the theme now — best-effort, since the company subdomain\n * needs one more API call — so the next push/pull needs no\n * interactive picker.\n */\n async function persistConfig(): Promise<void> {\n if (config) {\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: config.company,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n return;\n }\n if (!opts.autoBaseline) return;\n try {\n const res = await api.get<{\n data?: { company?: { subdomain?: string } };\n }>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) return;\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n } catch {\n // Best-effort — the shadow baseline alone is enough for\n // the next push to diff correctly.\n }\n }\n\n if (opts.publish && errors.length === 0) {\n const pubSpinner = ora(\"Publishing theme…\").start();\n try {\n await themes.publishApplicationTheme(api, theme.id);\n pubSpinner.succeed(\"Theme published.\");\n } catch (e) {\n pubSpinner.fail(`Publish failed: ${e}`);\n // Content uploaded but the theme is still a draft. Without\n // this the caller is told the publish succeeded.\n process.exitCode = 1;\n }\n }\n },\n );\n}\n\nfunction renderPullFirst(spinner: ReturnType<typeof ora>): void {\n spinner.fail(\"Server has changed since your last pull. Push aborted.\");\n console.log();\n console.log(\n ` ${chalk.cyan(\"Run `fluid theme pull` first\")} to merge the remote changes,`,\n );\n console.log(\n ` then re-run push. Use ${chalk.cyan(\"fluid theme push --force\")} to overwrite anyway.`,\n );\n console.log();\n}\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport { diffAgainstShadow } from \"./merge-push.js\";\nimport { ThemeRoot } from \"./root.js\";\nimport { ShadowRepo } from \"./shadow-repo.js\";\n\nfunction containsSymlink(directory: string): boolean {\n return readdirSync(directory, { withFileTypes: true }).some((entry) => {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n return false;\n return (\n entry.isSymbolicLink() ||\n (entry.isDirectory() && containsSymlink(join(directory, entry.name)))\n );\n });\n}\n\n/** Fail closed for unattended updates. Desktop snapshots can advance shadow\n * HEAD with local work, so only a CLI sync baseline is eligible. */\nexport async function canPullThemeInBackground(\n root: string,\n themeId: number,\n): Promise<boolean> {\n try {\n if (containsSymlink(root)) return false;\n const config = readThemeConfig(root);\n if (\n !config ||\n config.themeId !== themeId ||\n !existsSync(join(root, \".fluid-theme\", \"repo\", \"HEAD\"))\n )\n return false;\n const storedId = readFileSync(\n join(root, \".fluid-theme\", \"theme-id\"),\n \"utf8\",\n ).trim();\n if (storedId !== String(themeId)) return false;\n const manifestPath = join(root, \".fluid-assets.json\");\n if (existsSync(manifestPath)) {\n const raw: unknown = JSON.parse(readFileSync(manifestPath, \"utf8\"));\n if (\n !raw ||\n typeof raw !== \"object\" ||\n !(\"version\" in raw) ||\n raw.version !== 1 ||\n !(\"assets\" in raw) ||\n !raw.assets ||\n typeof raw.assets !== \"object\" ||\n Array.isArray(raw.assets)\n )\n return false;\n const manifest = new ThemeAssetManifest(root);\n if (manifest.entries().some(([, link]) => link.pending)) return false;\n if (\n !config.assetManifestSha ||\n config.assetManifestSha !== manifest.fingerprint()\n )\n return false;\n } else if (\n config.assetManifestSha &&\n config.assetManifestSha !== new ThemeAssetManifest(root).fingerprint()\n )\n return false;\n const shadow = await ShadowRepo.open(root, themeId);\n if (!(await shadow.hasSyncedHead())) return false;\n const diff = await diffAgainstShadow(new ThemeRoot(root), shadow);\n return diff.changed.length === 0 && diff.deleted.length === 0;\n } catch {\n return false;\n }\n}\n","import { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nexport interface MemberOmissionInput {\n /** Both accepted and edited documents: omission must not erase either graph. */\n previousDocuments: readonly (Buffer | null)[];\n availablePaths: ReadonlySet<string>;\n incomingPaths: ReadonlySet<string>;\n ignored: (path: string) => boolean;\n}\n\n/** Missing/ignored is not empty. Return paths to retain without acknowledging\n * their local bytes as remotely accepted. Used before pull writes or pruning. */\nexport function planOmittedMemberFiles(\n input: MemberOmissionInput,\n): Set<string> {\n if (\n input.incomingPaths.has(MEMBER_CONFIGURATION_KEY) &&\n !input.ignored(MEMBER_CONFIGURATION_KEY)\n )\n return new Set();\n\n const protectedPaths = new Set<string>();\n for (const content of input.previousDocuments) {\n if (content === null) continue;\n protectedPaths.add(MEMBER_CONFIGURATION_KEY);\n if (content.byteLength > 2 * 1024 * 1024) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json exceeds 2 MB.\",\n );\n }\n const document: unknown = JSON.parse(content.toString(\"utf8\"));\n if (!isRecord(document) || document.version !== 2) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json must be a version 2 document.\",\n );\n }\n for (const field of [\"sites\", \"menus\", \"menu_references\"]) {\n if (!Array.isArray(document[field])) {\n throw new Error(\n `Cannot preserve member files: members_sites.json ${field} must be an array.`,\n );\n }\n }\n assertDependencyShape(document);\n // An omitted manifest cannot authorize pruning shared composition files.\n // Their dependency relationships live in the template contents, not the manifest.\n if ((document.sites as unknown[]).length > 0) {\n for (const path of input.availablePaths) {\n if (/^(sections|blocks|components|snippets|navbar|footer)\\//.test(path))\n protectedPaths.add(path);\n }\n }\n for (const path of memberDependencyPaths(document, input.availablePaths)) {\n if (\n !isThemeResourceKey(path) ||\n path.includes(\"\\\\\") ||\n [...path].some((character) => character.charCodeAt(0) < 32) ||\n path === \"config/members_sites.json\"\n ) {\n throw new Error(\n `Cannot preserve member files: invalid dependency path ${path}.`,\n );\n }\n protectedPaths.add(path);\n }\n }\n return new Set(\n [...protectedPaths].filter(\n (path) => !input.incomingPaths.has(path) || input.ignored(path),\n ),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Reject incomplete dependency declarations rather than silently pruning files. */\nfunction assertDependencyShape(document: Record<string, unknown>): void {\n const entries = (value: unknown): Record<string, unknown>[] => {\n if (!Array.isArray(value) || !value.every(isRecord)) {\n throw new Error(\n \"Cannot preserve member files: malformed dependency declaration.\",\n );\n }\n return value;\n };\n for (const entry of [\n ...entries(document.templates ?? []),\n ...entries(document.menu_references),\n ]) {\n if (typeof entry.path !== \"string\" || !entry.path) {\n throw new Error(\n \"Cannot preserve member files: dependency path is missing.\",\n );\n }\n }\n for (const site of entries(document.sites)) {\n if (site.layout_name !== null && typeof site.layout_name !== \"string\") {\n throw new Error(\"Cannot preserve member files: invalid layout name.\");\n }\n for (const page of entries(site.pages)) {\n if (\n page.template_path !== null &&\n typeof page.template_path !== \"string\"\n ) {\n throw new Error(\n \"Cannot preserve member files: invalid page template path.\",\n );\n }\n }\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, looksBinary, readIfExists } from \"./shadow-repo.js\";\nimport { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { planOmittedMemberFiles } from \"./member-transfer.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { commitPushedState } from \"./merge-push.js\";\nimport { syncCommitSubject, type SyncActor } from \"./sync-identity.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\n/** `write(..., RESOLVES_CONFLICT)`: this write is the chosen side of a conflict. */\nconst RESOLVES_CONFLICT = true;\n\n/**\n * Appended to every per-file write failure. Resource keys are verbatim\n * relative paths, so a template named `Sale 11/14/2025, 12:30 PM` asks\n * the filesystem for directories that Windows (and any path already\n * occupied by a file) refuses. Renaming the template in the admin is\n * the only fix the user owns.\n */\nconst RENAME_REMEDY =\n 'Rename the template in the admin visual builder to remove / \\\\ : * ? \" < > | from its name, then pull again.';\n\nexport interface MergePullResult {\n /** Files written from the remote without any local content to merge. */\n written: number;\n /** Files that went through a clean three-way merge with no markers. */\n merged: number;\n /** Files where markers remain — the caller must surface these. */\n conflicts: string[];\n /** Conflicting files auto-resolved to one side via `resolve`. */\n autoResolved: string[];\n /** Files removed locally because remote no longer emitted them. */\n deleted: number;\n /** Files that needed no change (already in sync). */\n skipped: number;\n /** Non-fatal per-file errors. */\n errors: string[];\n /** The remote revision must not be accepted as the new sync baseline. */\n baselinePreserved?: boolean;\n}\n\nexport type FetchBinary = (url: string) => Promise<Buffer>;\n\nexport interface MergePullInput {\n themeRoot: ThemeRoot;\n backgroundGuard?: BackgroundPullGuard;\n shadow: ShadowRepo;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n delete: boolean;\n /**\n * When true, skip the merge and take remote wholesale for every\n * file. Used by `fluid theme pull --force` when the user wants a\n * clean slate. HEAD still advances to reflect the state we wrote.\n */\n force?: boolean;\n /**\n * Remote resources already represented by `.fluid-assets.json`. They are\n * intentionally not materialized; ShadowRepo retains only a tiny path\n * sentinel, never their binary bytes.\n */\n skipRemoteKeys?: ReadonlySet<string>;\n /**\n * Non-interactive conflict resolution (`fluid theme pull --resolve\n * <local|remote>`): conflicting hunks are resolved to the chosen\n * side via `git merge-file --ours/--theirs` instead of writing\n * `<<<<<<<` markers; binary conflicts keep the whole chosen side.\n * Because \"remote\" resolution discards local hunks that live\n * NOWHERE else (shadow HEAD only holds the last-synced base, not\n * the user's edits), the pre-merge working tree is committed to the\n * shadow repo first — so the discarded state stays restorable.\n * Undefined = current behavior (markers).\n */\n resolve?: \"local\" | \"remote\";\n /**\n * Signed-in user for commit stamping (best-effort — null falls back\n * to the legacy machine-flavored subjects).\n */\n actor?: SyncActor | null;\n onProgress?: (done: number, total: number) => void;\n}\n\n/**\n * Reconcile the just-downloaded remote tree against the working tree,\n * using the shadow repo's HEAD as the merge base. Text files that\n * diverge on both sides get run through `git merge-file`; the result\n * — clean or with `<<<<<<<` markers — is written to disk. Binary\n * files fall back to \"take remote\" because there is no principled\n * three-way merge for them.\n *\n * HEAD advances to reflect the remote state we just materialized,\n * even when unresolved conflict markers remain in the working tree.\n * The user resolves markers in their editor and re-runs push; push\n * refuses to send files whose content still starts with a marker,\n * so a half-resolved push cannot silently ship broken content.\n */\nexport async function mergePull(\n input: MergePullInput,\n): Promise<MergePullResult> {\n const { themeRoot, shadow, remote, fetchBinary, onProgress } = input;\n const doDelete = input.delete;\n const result: MergePullResult = {\n written: 0,\n merged: 0,\n conflicts: [],\n autoResolved: [],\n deleted: 0,\n skipped: 0,\n errors: [],\n };\n\n // In `resolve` mode, working-tree writes are DEFERRED until after\n // the merge loop: if any conflict got auto-resolved, the pre-merge\n // working tree (still untouched at that point) is committed to the\n // shadow repo first, so the side we discard remains restorable.\n const pendingWrites: Array<{\n key: string;\n file: ThemeFile;\n content: Buffer;\n record: () => void;\n resolvesConflict: boolean;\n }> = [];\n // Keys whose bytes never reached disk — refused by the traversal\n // guard, or failed on the write. They must be kept out of the shadow\n // HEAD commit below, exactly like a failed download: HEAD is what\n // push diffs against, so recording content we did not write would\n // turn a tolerated failure into a remote DELETE. Refused keys have a\n // second reason — git rejects paths like `../x` outright, and the\n // commit takes the whole pull down with it.\n const unwrittenKeys = new Set<string>();\n const tryWrite = (key: string, file: ThemeFile, content: Buffer): boolean => {\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([key], () => file.write(content));\n else file.write(content);\n return true;\n } catch (e) {\n if (e instanceof BackgroundPullChangedError) throw e;\n unwrittenKeys.add(key);\n result.errors.push(`Reconcile ${key}: ${errMsg(e)}. ${RENAME_REMEDY}`);\n return false;\n }\n };\n /**\n * Write `content`, then apply `record` — the bookkeeping that says\n * which bucket of the summary this file counted as. The two travel\n * together, including into the deferred flush: bookkeeping applied\n * when the write was merely QUEUED would report a file the flush\n * never managed to write as both written and errored.\n */\n const write = (\n key: string,\n file: ThemeFile,\n content: Buffer,\n record: () => void,\n resolvesConflict = false,\n ): void => {\n if (input.resolve) {\n pendingWrites.push({ key, file, content, record, resolvesConflict });\n return;\n }\n if (tryWrite(key, file, content)) record();\n };\n\n const remoteContent = new Map<string, Buffer>();\n const remoteKeys = new Set<string>();\n let done = 0;\n for (const resource of remote) {\n remoteKeys.add(resource.key);\n if (input.skipRemoteKeys?.has(resource.key)) {\n onProgress?.(++done, remote.length);\n continue;\n }\n try {\n const buf = await materialize(resource, fetchBinary);\n if (buf) remoteContent.set(resource.key, buf);\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${errMsg(e)}`);\n }\n onProgress?.(++done, remote.length);\n }\n\n input.backgroundGuard?.assertUnchanged();\n let retainedMemberPaths: Set<string>;\n try {\n if (\n remoteKeys.has(MEMBER_CONFIGURATION_KEY) &&\n !themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY) &&\n !remoteContent.has(MEMBER_CONFIGURATION_KEY)\n ) {\n throw new Error(\n \"members_sites.json was listed but could not be downloaded; previous member state retained.\",\n );\n }\n retainedMemberPaths = planOmittedMemberFiles({\n previousDocuments: [\n readIfExists(themeRoot.file(MEMBER_CONFIGURATION_KEY).absolutePath),\n await shadow.blobAtHead(MEMBER_CONFIGURATION_KEY),\n ],\n availablePaths: new Set([\n ...themeRoot.files().map((file) => file.relativePath),\n ...(await shadow.headPaths()),\n ]),\n incomingPaths: remoteKeys,\n ignored: (path) => themeRoot.ignore.ignore(path),\n });\n } catch (error) {\n result.errors.push(errMsg(error));\n result.baselinePreserved = true;\n return result;\n }\n for (const path of retainedMemberPaths) remoteContent.delete(path);\n\n for (const [key, remoteBuf] of remoteContent) {\n const file = themeRoot.file(key);\n\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Reconcile ${key}: path traversal detected`);\n unwrittenKeys.add(key);\n continue;\n }\n\n if (input.force) {\n // `--force`: clobber everything with the remote content. HEAD\n // still advances below so the next pull's merge base is right.\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n const localBuf = readIfExists(file.absolutePath);\n const baseBuf = await shadow.blobAtHead(key);\n\n if (localBuf == null) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (localBuf.equals(remoteBuf)) {\n result.skipped++;\n continue;\n }\n\n if (baseBuf && localBuf.equals(baseBuf)) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (baseBuf && remoteBuf.equals(baseBuf)) {\n // A prior unresolved pull advances shadow HEAD to the clean remote\n // state while leaving markers in the working tree. A later\n // `--resolve remote` must be able to finish that interrupted clone;\n // treating the marker-filled file as an ordinary local edit traps\n // every retry in the same broken state.\n if (\n input.resolve === \"remote\" &&\n !looksBinary(localBuf) &&\n hasGeneratedConflictMarkers(localBuf)\n ) {\n write(\n key,\n file,\n remoteBuf,\n () => result.autoResolved.push(`${key} (cleared conflict markers)`),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n // Server didn't move; keep the user's local edits.\n result.skipped++;\n continue;\n }\n\n const isBinary =\n looksBinary(localBuf) ||\n looksBinary(remoteBuf) ||\n (baseBuf ? looksBinary(baseBuf) : false);\n\n if (isBinary) {\n // No textual merge for binary files: whole-file either/or.\n if (input.resolve === \"local\") {\n // Keep the local side untouched — nothing to write.\n result.autoResolved.push(`${key} (binary — kept local)`);\n continue;\n }\n // Take remote. In `--resolve remote` that's the chosen side;\n // otherwise flag it so the user can compare against the shadow\n // repo (`git show HEAD:...`) and reapply local intent by hand.\n write(\n key,\n file,\n remoteBuf,\n () => {\n if (input.resolve === \"remote\") {\n result.autoResolved.push(`${key} (binary — kept remote)`);\n } else {\n result.conflicts.push(`${key} (binary — kept remote)`);\n }\n },\n input.resolve === \"remote\",\n );\n continue;\n }\n\n // First merge WITHOUT a favor side so we know whether this file\n // actually conflicts — that drives honest bookkeeping (merged vs\n // autoResolved) and the pre-merge snapshot decision.\n const { merged, hasConflicts } = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n );\n if (hasConflicts && input.resolve) {\n const favored = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n input.resolve,\n );\n write(\n key,\n file,\n favored.merged,\n () => result.autoResolved.push(key),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n write(key, file, merged, () => {\n if (hasConflicts) result.conflicts.push(key);\n else result.merged++;\n });\n }\n\n // Flush deferred writes (resolve mode only). When any conflict was\n // auto-resolved, snapshot the pre-merge working tree first — it is\n // the ONLY place the losing side of each resolution still exists.\n if (input.resolve) {\n if (pendingWrites.some((w) => w.resolvesConflict)) {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\"Snapshot before pull\", input.actor ?? null),\n );\n }\n input.backgroundGuard?.assertUnchanged();\n for (const { key, file, content, record } of pendingWrites) {\n if (tryWrite(key, file, content)) record();\n }\n }\n\n if (doDelete && (await shadow.hasHead())) {\n for (const file of themeRoot.files()) {\n if (\n remoteKeys.has(file.relativePath) ||\n retainedMemberPaths.has(file.relativePath)\n )\n continue;\n // Stylesheet-hidden files are intentionally absent from the\n // resources index — deleting them locally would fight the\n // STYLESHEET_STRICT_INPUT feature, not honor a real deletion.\n if (isStylesheetKey(file.relativePath)) continue;\n\n const baseBuf = await shadow.blobAtHead(file.relativePath);\n if (!baseBuf) continue; // never seen by shadow — leave alone\n const localBuf = readIfExists(file.absolutePath);\n if (!localBuf) continue;\n if (!localBuf.equals(baseBuf)) continue; // user has local edits\n\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([file.relativePath], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n result.deleted++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n // ignore best-effort deletion\n }\n }\n }\n\n // Advance HEAD to the remote state we just downloaded. Even when\n // some files carry unresolved conflict markers, HEAD reflects the\n // server's clean bytes — the next push's diff-against-HEAD is what\n // surfaces the markers as \"unresolved\".\n //\n // Preserve failed-download paths from the previous HEAD so a\n // transient network error doesn't quietly drop them from the merge\n // base. If a file was in HEAD before the pull and its download\n // just failed, the next pull needs the previous blob as the base\n // for its merge — otherwise `blobAtHead` returns null next time and\n // a server-side change fires a spurious null-base conflict on a\n // file the user never touched.\n //\n // Keys whose write failed take the same route: their bytes are on\n // the server but not on disk, so recording the remote content here\n // would claim a state this checkout never had.\n try {\n const commitEntries: Array<{ path: string; sha: string }> = [];\n for (const [key, buf] of remoteContent) {\n if (unwrittenKeys.has(key)) continue;\n commitEntries.push({ path: key, sha: await shadow.writeBlob(buf) });\n }\n for (const key of new Set([...remoteKeys, ...retainedMemberPaths])) {\n if (input.skipRemoteKeys?.has(key) && !retainedMemberPaths.has(key))\n continue;\n // Downloaded AND written — already added above.\n if (remoteContent.has(key) && !unwrittenKeys.has(key)) continue;\n const prevBlob = await shadow.blobAtHead(key);\n if (prevBlob == null) continue; // wasn't in HEAD to begin with\n commitEntries.push({ path: key, sha: await shadow.writeBlob(prevBlob) });\n }\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let managedAssetSentinelSha: string | undefined;\n for (const key of input.skipRemoteKeys ?? []) {\n // Ignored resources without a manifest link remain outside the shadow\n // entirely. Otherwise removing an ignore rule could make a skipped file\n // look like a local deletion on the next push.\n if (!remoteKeys.has(key) || !assetManifest.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n commitEntries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // A pull containing only manifest-backed assets still needs a shadow commit:\n // sentinel entries replace any legacy binary blobs while keeping a baseline\n // for an intentional manifest-entry removal on a later push.\n // When the server becomes empty, record an empty tree rather than retaining\n // stale paths from HEAD as a future merge/delete baseline — but only when\n // the server really is empty. Empty entries with a non-empty remote mean\n // every write failed, and HEAD must survive as the next pull's merge base.\n if (\n commitEntries.length > 0 ||\n (remoteKeys.size === 0 && (await shadow.hasHead()))\n ) {\n input.backgroundGuard?.assertUnchanged();\n await shadow.commitState(\n commitEntries,\n syncCommitSubject(\"Pull\", input.actor ?? null),\n );\n }\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n result.baselinePreserved = true;\n result.errors.push(\n `Downloaded changes have already been applied locally, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Review the files and retry a normal pull before pushing; do not use --force to bypass the missing baseline.`,\n );\n }\n\n return result;\n}\n\nfunction hasGeneratedConflictMarkers(content: Buffer): boolean {\n const text = content.toString(\"utf8\");\n return (\n /^<<<<<<< local\\r?$/m.test(text) &&\n /^=======\\r?$/m.test(text) &&\n /^>>>>>>> remote\\r?$/m.test(text)\n );\n}\n\nasync function materialize(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","import {\n BackgroundPullChangedError,\n BackgroundPullGuard,\n} from \"../theme/background-pull-guard.js\";\nimport { canPullThemeInBackground } from \"../theme/background-pull.js\";\nimport { join, resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport {\n developmentThemeRefusal,\n mayBindTheme,\n readThemeConfig,\n writeThemeConfig,\n} from \"../theme-config.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport { mergePull } from \"../theme/merge-pull.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport { fetchSyncActor } from \"../theme/sync-identity.js\";\nimport { selectTheme, findTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { CaseCollisionError } from \"../theme/case-collisions.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\nasync function fetchCompanySubdomain(\n api: ReturnType<typeof createApiClient>,\n): Promise<string> {\n const res = await api.get<CompanyMe>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n return subdomain;\n}\n\nexport function createPullCommand(): Command {\n return new Command(\"pull\")\n .description(\"Pull a remote theme to your local directory\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to pull\")\n .option(\"-n, --nodelete\", \"Do not delete local files missing on remote\")\n .option(\"--root <path>\", \"Theme root directory\")\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .option(\n \"--only-if-clean\",\n \"Skip with exit code 3 when local work cannot be proven synced\",\n )\n .option(\n \"-f, --force\",\n \"Overwrite local without merging (skip conflict markers)\",\n )\n .option(\n \"--resolve <side>\",\n \"Auto-resolve merge conflicts to one side instead of writing \" +\n \"conflict markers: 'local' keeps your files' hunks, 'remote' \" +\n \"takes the server's (for non-interactive use)\",\n )\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n root?: string;\n yes?: boolean;\n force?: boolean;\n resolve?: string;\n onlyIfClean?: boolean;\n }) => {\n let spinner: ReturnType<typeof ora> | undefined;\n try {\n requireToken();\n\n if (\n opts.resolve !== undefined &&\n opts.resolve !== \"local\" &&\n opts.resolve !== \"remote\"\n ) {\n console.error(\n `Invalid --resolve value \"${opts.resolve}\" — use \"local\" or \"remote\".`,\n );\n process.exit(1);\n }\n const resolveSide = opts.resolve as \"local\" | \"remote\" | undefined;\n\n const api = createApiClient();\n const workspace = findWorkspace();\n\n const theme = opts.theme\n ? await findTheme(api, opts.theme)\n : await selectTheme(api, \"Select a theme to pull\");\n\n const subdomain = await fetchCompanySubdomain(api);\n let root: string;\n if (opts.root) {\n root = opts.root;\n } else if (workspace) {\n root =\n resolveThemeRootFromCwd(workspace) ??\n join(workspace.root, \"local\", subdomain);\n } else {\n root = `.`;\n }\n\n const absoluteRoot = resolve(root);\n const existingConfig = readThemeConfig(absoluteRoot);\n if (existingConfig && !mayBindTheme(existingConfig, theme)) {\n // Background preparation treats 3 as a safe skip; 1 would pause\n // its queue and retry a directory that belongs to another theme.\n if (opts.onlyIfClean) {\n console.log(\n `Skipped: this directory is bound to ${existingConfig.themeName} (#${existingConfig.themeId}), not development theme #${theme.id}.`,\n );\n process.exit(3);\n }\n console.error(developmentThemeRefusal(existingConfig, theme));\n process.exit(1);\n }\n const backgroundGuard = opts.onlyIfClean\n ? new BackgroundPullGuard(absoluteRoot)\n : undefined;\n\n console.log();\n console.log(` Theme: ${chalk.bold(theme.name)} (#${theme.id})`);\n console.log(` Company: ${chalk.bold(subdomain)}`);\n console.log(` Target: ${chalk.bold(absoluteRoot)}`);\n console.log();\n\n if (!opts.yes) {\n const { confirmed } = await prompts(\n {\n type: \"confirm\",\n name: \"confirmed\",\n message: \"Pull theme to this directory?\",\n initial: true,\n },\n { onCancel: () => process.exit(130) },\n );\n if (!confirmed) {\n console.log(\"Aborted.\");\n process.exit(0);\n }\n }\n\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n console.log(\"Skipped: local changes or an unknown sync baseline.\");\n process.exit(3);\n }\n backgroundGuard?.assertUnchanged();\n const themeRoot = new ThemeRoot(root);\n const shadow = await ShadowRepo.open(absoluteRoot, theme.id);\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot,\n themeId: theme.id,\n });\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // Fire the identity lookup alongside the download — it never\n // rejects, and mergePull only awaits it at commit time.\n const actorPromise = fetchSyncActor(api);\n\n spinner = ora(`Pulling ${theme.name} (#${theme.id})…`).start();\n const resources = await syncer\n .downloadAll()\n .catch((error: unknown) => {\n if (!(error instanceof CaseCollisionError)) throw error;\n if (spinner) spinner.fail(error.message);\n process.exit(1);\n });\n // Recheck after network I/O and before any asset or content writes.\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n spinner.stop();\n console.log(\"Skipped: local files changed while downloading.\");\n process.exit(3);\n }\n const externalizedAssets = await syncer.externalizePulledAssets(\n resources,\n {\n delete: !opts.nodelete,\n backgroundGuard,\n },\n );\n\n const result = await mergePull({\n themeRoot,\n shadow,\n backgroundGuard,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n delete: !opts.nodelete,\n force: opts.force ?? false,\n skipRemoteKeys: externalizedAssets.managedKeys,\n resolve: resolveSide,\n actor: await actorPromise,\n onProgress: (done, total) => {\n if (spinner) spinner.text = `Downloading ${done}/${total} files…`;\n },\n });\n result.errors.push(...externalizedAssets.errors);\n\n const parts: string[] = [];\n if (result.written > 0) parts.push(`wrote ${result.written} file(s)`);\n if (result.merged > 0)\n parts.push(`merged ${result.merged} file(s) cleanly`);\n if (externalizedAssets.linked > 0) {\n parts.push(\n `kept ${externalizedAssets.linked} binary asset(s) remote`,\n );\n }\n if (result.autoResolved.length > 0)\n parts.push(\n `auto-resolved ${result.autoResolved.length} conflict(s) (kept ${resolveSide})`,\n );\n if (result.deleted > 0)\n parts.push(`deleted ${result.deleted} local file(s)`);\n if (result.skipped > 0)\n parts.push(`${result.skipped} already in sync`);\n\n if (result.errors.length) {\n spinner.warn(\n `Pulled with ${result.errors.length} error(s): ${parts.join(\", \")}.`,\n );\n for (const e of result.errors) console.error(` ${e}`);\n // A failed file or restore point leaves the checkout incomplete\n // or without a safe sync baseline. Conflicts already exit 1\n // below; this branch used to fall through at 0.\n //\n // Mist spawns `fluid theme pull -t <id> --root <path> --yes` to\n // clone a theme, and only records a lifecycle failure when the\n // exit code is non-zero — so a partial clone never reached the\n // agent's context and it went on to edit a checkout that was\n // missing files.\n //\n // exitCode rather than an immediate exit: the per-file error\n // list is printed right here and `.fluid-theme.json` is written\n // below, both of which still need to happen.\n process.exitCode = 1;\n }\n // Conflicts still need to be reported when snapshot saving failed;\n // Mist parses these paths to guide the user through resolution.\n if (result.conflicts.length > 0) {\n spinner.warn(\n `${result.conflicts.length} conflict(s) — resolve markers before pushing: ${parts.join(\", \")}.`,\n );\n console.log();\n for (const c of result.conflicts) {\n console.log(` ${chalk.yellow(\"CONFLICT\")} ${c}`);\n }\n console.log();\n console.log(\n ` Edit each file above to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} markers,`,\n );\n console.log(\n ` then run ${chalk.cyan(\"fluid theme push\")} once your resolution is in place.`,\n );\n console.log();\n } else if (result.errors.length === 0) {\n spinner.succeed(parts.join(\", \") || \"Already up to date.\");\n }\n\n // A rejected transfer or failed snapshot did not accept the revision.\n if (result.baselinePreserved) return;\n\n // Update .fluid-theme.json — baseSha is the server-side handle\n // the push preflight uses; the merge base itself lives in the\n // shadow repo.\n const remoteSha = syncer.remoteSha();\n backgroundGuard?.assertUnchanged();\n const updateConfig = () =>\n writeThemeConfig(absoluteRoot, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: remoteSha ?? existingConfig?.baseSha,\n assetManifestSha: new ThemeAssetManifest(\n absoluteRoot,\n ).fingerprint({\n excludePending: true,\n }),\n });\n\n if (backgroundGuard)\n backgroundGuard.mutate([\".fluid-theme.json\"], updateConfig);\n else updateConfig();\n if (result.conflicts.length > 0) process.exit(1);\n } catch (error) {\n if (!(error instanceof BackgroundPullChangedError)) throw error;\n spinner?.stop();\n console.log(error.message);\n process.exitCode = 3;\n }\n },\n );\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport {\n findMissingSectionReferences,\n validateSchemaText,\n VALID_SETTING_TYPES,\n type BlocksSchemaType,\n type Diagnostic,\n type TemplateInput,\n} from \"@fluid-app/theme-schema\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface FileDiagnostics {\n path: string;\n diagnostics: Diagnostic[];\n}\n\n// A theme section is defined by a liquid file under the top-level `sections/`\n// directory. Returns the section name a `{% section %}` tag would reference, or\n// null if the file is not a section definition. Handles both the flat layout\n// (`sections/hero.liquid`) and the nested one (`sections/hero/index.liquid`).\nfunction sectionNameOf(relativePath: string): string | null {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts[0] === \"sections\" && parts.length >= 2) {\n return parts[1]!.replace(/\\.liquid$/, \"\");\n }\n return null;\n}\n\nexport function createLintCommand(): Command {\n return new Command(\"lint\")\n .description(\"Validate theme files locally (read-only — no upload)\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .option(\"--json\", \"Output results as compact JSON\")\n .action(async (opts: { root: string; json?: boolean }) => {\n // Resolve the theme root the same way push/dev do: when left at the\n // default, prefer the workspace's theme root if we're inside one.\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n const message = `'${rootPath}' does not look like a theme directory.`;\n if (opts.json) {\n console.log(JSON.stringify({ ok: false, error: message }));\n } else {\n console.error(message);\n }\n process.exit(1);\n }\n\n const files = themeRoot.files();\n // Read each liquid file once and reuse the content for both passes\n // (validateSchemaText and the section scan) to avoid a double disk read.\n const liquidFiles = files\n .filter((f) => f.isLiquid)\n .map((f) => ({ file: f, content: f.read() }));\n\n const byFile = new Map<string, Diagnostic[]>();\n const record = (path: string, diagnostic: Diagnostic): void => {\n const existing = byFile.get(path);\n if (existing) existing.push(diagnostic);\n else byFile.set(path, [diagnostic]);\n };\n\n // ── Schema pass — the same {% schema %} validation `fluid theme push`\n // runs. blocksSchemaType mirrors ThemeFile.validateSchema: page/layout\n // templates use object blocks, sections use array blocks.\n for (const { file, content } of liquidFiles) {\n const blocksSchemaType: BlocksSchemaType = file.isTemplate\n ? \"object\"\n : \"array\";\n for (const diagnostic of validateSchemaText(content, {\n blocksSchemaType,\n })) {\n record(file.relativePath, diagnostic);\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(content)) {\n record(file.relativePath, diagnostic);\n }\n }\n\n // ── Section pass — flag `{% section 'x' %}` references to a section\n // that has no definition on disk. Section definitions and assets are\n // not themselves referrers, so they are excluded from the scan.\n const existingSectionNames = new Set<string>();\n for (const { file } of liquidFiles) {\n const name = sectionNameOf(file.relativePath);\n if (name) existingSectionNames.add(name);\n }\n const referrers: TemplateInput[] = liquidFiles\n .filter(({ file }) => sectionNameOf(file.relativePath) === null)\n .map(({ file, content }) => ({ path: file.relativePath, content }));\n for (const missing of findMissingSectionReferences(\n referrers,\n existingSectionNames,\n )) {\n record(missing.templatePath, missing.diagnostic);\n }\n\n const results: FileDiagnostics[] = [...byFile.entries()]\n .map(([path, diagnostics]) => ({ path, diagnostics }))\n .sort((a, b) => a.path.localeCompare(b.path));\n\n let errors = 0;\n let warnings = 0;\n for (const { diagnostics } of results) {\n for (const d of diagnostics) {\n if (d.severity === \"error\") errors++;\n else warnings++;\n }\n }\n\n // Surface the canonical setting types once (not in every diagnostic) so a\n // consumer fixing an \"Invalid settings type\" error has the valid set to\n // hand without it bloating each message.\n const hasInvalidSettingType = results.some(({ diagnostics }) =>\n diagnostics.some(\n (d) =>\n d.target?.kind === \"setting\" &&\n d.target.field === \"type\" &&\n d.target.settingType !== undefined,\n ),\n );\n\n if (opts.json) {\n console.log(\n JSON.stringify({\n ok: errors === 0,\n errors,\n warnings,\n filesChecked: liquidFiles.length,\n ...(hasInvalidSettingType\n ? { validSettingTypes: VALID_SETTING_TYPES }\n : {}),\n files: results,\n }),\n );\n } else {\n printText(results, errors, warnings, liquidFiles.length);\n }\n\n process.exit(errors > 0 ? 1 : 0);\n });\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction printText(\n results: FileDiagnostics[],\n errors: number,\n warnings: number,\n filesChecked: number,\n): void {\n for (const { path, diagnostics } of results) {\n console.log(chalk.bold(path));\n for (const d of diagnostics) {\n const label =\n d.severity === \"error\"\n ? chalk.red(\"error\".padEnd(7))\n : chalk.yellow(\"warning\".padEnd(7));\n // Only the first line — a few messages (e.g. the `Invalid JSON:` parse\n // error) carry a multi-line body that `--json` preserves in full.\n const message = d.message.split(\"\\n\")[0];\n console.log(` ${label} ${message}`);\n }\n }\n\n const suffix = `(${plural(filesChecked, \"file\")} checked)`;\n if (errors > 0) {\n console.log(\n `\\n${chalk.red(`✖ ${plural(errors, \"error\")}, ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else if (warnings > 0) {\n console.log(\n `\\n${chalk.yellow(`⚠ ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else {\n console.log(`${chalk.green(\"✓ No problems found\")} ${suffix}`);\n }\n}\n","import { Command } from \"commander\";\nimport { execFileSync } from \"node:child_process\";\nimport { rmSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport prompts from \"prompts\";\n\nconst DEFAULT_CLONE_URL = \"git@github.com:fluid-commerce/base-theme.git\";\n\nconst SAFE_NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/;\n\nexport function createInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Initialize a new theme by cloning the base theme\")\n .argument(\"[name]\", \"Directory name for the new theme\")\n .option(\"-u, --clone-url <url>\", \"Git URL to clone from\", DEFAULT_CLONE_URL)\n .action(async (name: string | undefined, opts: { cloneUrl: string }) => {\n if (!name) {\n const res = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Theme name\",\n },\n { onCancel: () => process.exit(130) },\n );\n name = res.name as string;\n if (!name) {\n console.error(\"No name provided.\");\n process.exit(1);\n }\n }\n\n if (!SAFE_NAME_RE.test(name)) {\n console.error(\n `Invalid theme name: '${name}'. Use only letters, numbers, hyphens, underscores, and dots.`,\n );\n process.exit(1);\n }\n\n console.log(`Cloning theme from ${opts.cloneUrl} into ${name}…`);\n execFileSync(\"git\", [\"clone\", opts.cloneUrl, name], { stdio: \"inherit\" });\n\n for (const dir of [\".git\", \".github\"]) {\n const path = join(name, dir);\n if (existsSync(path)) rmSync(path, { recursive: true, force: true });\n }\n\n console.log(`\\nTheme initialized in ./${name}`);\n console.log(`Next steps:\\n cd ${name}\\n fluid theme push`);\n });\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\n// A skill is a directory containing a SKILL.md. The bundled skills directory\n// holds one such directory per skill (e.g. `themes-review/`).\nexport function listSkillNames(skillsDir: string): string[] {\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(skillsDir, name, \"SKILL.md\")))\n .sort();\n}\n\nexport interface InstallSkillsOptions {\n /** Directory holding the bundled skills (one sub-directory per skill). */\n sourceDir: string;\n /** Directory the skills are copied into (one sub-directory per skill). */\n targetRoot: string;\n /** Overwrite existing skills without asking. */\n force: boolean;\n /**\n * Asked once per skill that already exists when not forcing. Return true to\n * overwrite, false to leave the existing copy untouched.\n */\n confirmOverwrite: (name: string) => Promise<boolean>;\n /**\n * Called immediately for each install that left a temporary backup directory\n * behind (it could not be removed). Reported as it happens — not via the\n * return value — so the warning isn't lost if a later skill throws mid-loop.\n */\n onLeftover: (path: string) => void;\n}\n\nexport interface InstallSkillsResult {\n readonly installed: readonly string[];\n readonly skipped: readonly string[];\n}\n\n// Copy each bundled skill into `targetRoot/<name>`. Existing skills are only\n// replaced with `force` or an affirmative `confirmOverwrite`; everything else is\n// reported as skipped so the caller can summarize what happened.\nexport async function installSkills(\n options: InstallSkillsOptions,\n): Promise<InstallSkillsResult> {\n const { sourceDir, targetRoot, force, confirmOverwrite, onLeftover } =\n options;\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n mkdirSync(targetRoot, { recursive: true });\n\n for (const name of listSkillNames(sourceDir)) {\n const from = join(sourceDir, name);\n const to = join(targetRoot, name);\n const exists = existsSync(to);\n\n if (exists && !force && !(await confirmOverwrite(name))) {\n skipped.push(name);\n continue;\n }\n\n // Atomic copy + swap: a failed copy never destroys an existing install, and\n // the whole-directory replace prunes files removed from the bundled skill.\n // Report any leftover backup immediately so the warning isn't lost if a\n // later skill throws mid-loop.\n const leftover = replaceDirectory(from, to);\n if (leftover !== null) onLeftover(leftover);\n installed.push(name);\n }\n\n return { installed, skipped };\n}\n\n/**\n * Replace `target` with a fresh copy of `source` without ever leaving `target`\n * missing or partially written.\n *\n * Filesystem copies are not atomic, so a naive \"delete then copy\" loses the\n * original if the copy fails (permissions, no disk space, an interrupted\n * process). This stages the copy in a sibling directory and only swaps it into\n * place once it has fully succeeded; an existing `target` is moved aside to a\n * sibling backup first and restored if the swap fails. Because the whole\n * directory is replaced, files removed or renamed in `source` do not linger.\n *\n * Staging and backup directories live beside `target`, so its parent must\n * already exist and be on the same filesystem — that keeps the swap a cheap,\n * atomic rename rather than a cross-device copy.\n *\n * Not safe against a second process racing on the same `target`; intended for\n * single-process CLI use.\n *\n * @returns the path of a leftover backup directory that could not be removed\n * after an otherwise-successful replace (the previous contents are retained\n * there for manual cleanup), or `null` when nothing was left behind. The caller\n * should surface a non-null result so the leftover isn't silently hidden.\n */\nexport function replaceDirectory(\n source: string,\n target: string,\n): string | null {\n const staging = reserveSiblingPath(target, \"staging\");\n try {\n cpSync(source, staging, { recursive: true });\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n\n // No existing target: a single rename moves the staged copy into place.\n if (!existsSync(target)) {\n return swapIntoPlace(staging, target, null);\n }\n\n // Existing target: move it aside first so the swap stays reversible.\n const backup = reserveSiblingPath(target, \"backup\");\n try {\n renameSync(target, backup);\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n return swapIntoPlace(staging, target, backup);\n}\n\n// Move the staged copy into `target`. If the move fails, restore the original\n// from `backup` (when there was one) *before* any cleanup, so a failed staging\n// removal can never leave the caller without a directory at `target`; then\n// best-effort discard the staged copy and rethrow. On success, returns the\n// backup path if it could not be removed (a leftover the caller should report),\n// or `null`.\nfunction swapIntoPlace(\n staging: string,\n target: string,\n backup: string | null,\n): string | null {\n try {\n renameSync(staging, target);\n } catch (error) {\n if (backup !== null) restoreBackup(backup, target, error);\n removeQuietly(staging);\n throw error;\n }\n return removeQuietly(backup);\n}\n\n// Best-effort restore of the original directory. If even this fails, surface an\n// error pointing at the backup so the user can recover their data by hand.\nfunction restoreBackup(backup: string, target: string, cause: unknown): void {\n try {\n renameSync(backup, target);\n } catch {\n throw new Error(\n `Failed to replace ${target}; its previous contents are preserved at ${backup}.`,\n { cause },\n );\n }\n}\n\n// Best-effort removal of a temporary staging/backup directory. Cleanup must\n// never throw: a failure here should not mask the real outcome or strand the\n// caller. Returns the path when removal failed (so the caller can surface the\n// leftover), or `null` when the directory was removed or there was none.\nfunction removeQuietly(path: string | null): string | null {\n if (path === null) return null;\n try {\n rmSync(path, { recursive: true, force: true });\n return null;\n } catch {\n return path;\n }\n}\n\n// Pick a sibling path of `basePath` that does not exist yet, so renaming onto it\n// is a clean create on every platform (Windows rejects a rename onto an existing\n// directory). Deterministic — no randomness.\nfunction reserveSiblingPath(basePath: string, label: string): string {\n let candidate = `${basePath}.${label}`;\n for (let n = 1; existsSync(candidate); n += 1) {\n candidate = `${basePath}.${label}.${n}`;\n }\n return candidate;\n}\n","import { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { installSkills, listSkillNames } from \"../skills/install.js\";\n\n// Where skills land by default — `.agents/skills/` is the tool-neutral\n// convention for agent skills.\nconst DEFAULT_TARGET_DIR = \".agents/skills\";\n\n// The bundled skills ship beside the compiled CLI at `dist/skills/` and live at\n// the package root `skills/` in source. Resolve relative to this module so the\n// lookup works whether the code runs from the published bundle or from source.\nfunction resolveBundledSkillsDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, \"skills\"), // dist/skills (bundled build output)\n join(here, \"..\", \"skills\"), // package root from dist/index.mjs\n join(here, \"..\", \"..\", \"skills\"), // package root from src/commands/\n ];\n for (const dir of candidates) {\n if (listSkillNames(dir).length > 0) return dir;\n }\n // Last resort: walk up looking for a skills/ dir that holds a skill.\n let dir = here;\n for (let depth = 0; depth < 6; depth++) {\n const candidate = join(dir, \"skills\");\n if (listSkillNames(candidate).length > 0) return candidate;\n dir = dirname(dir);\n }\n throw new Error(\n \"Could not locate the bundled theme skills — this is a packaging bug.\",\n );\n}\n\n// Skills stay out of `fluid theme push/pull/dev/lint` only when some path\n// segment is a dot-directory — that is the sole thing ThemeRoot.glob() skips.\n// Returns true when installing into a *visible* dir inside a theme, where the\n// files would be uploaded as theme content instead of kept local.\nfunction skillsWouldShipWithTheme(cwd: string, targetRoot: string): boolean {\n if (!new ThemeRoot(cwd).isValid()) return false; // not a theme dir — irrelevant\n const rel = relative(cwd, targetRoot);\n if (rel.startsWith(\"..\")) return false; // outside the theme root — never scanned\n return !rel.split(sep).some((segment) => segment.startsWith(\".\"));\n}\n\nexport function createSkillsCommand(): Command {\n const skills = new Command(\"skills\").description(\n \"Manage the bundled Fluid theme AI skills\",\n );\n\n skills\n .command(\"install\")\n .description(\n \"Copy the bundled theme skills into the current directory (default: .agents/skills/)\",\n )\n .option(\"-d, --dir <path>\", \"Directory to install into\", DEFAULT_TARGET_DIR)\n .option(\"-f, --force\", \"Overwrite existing skills without prompting\")\n .action(async (opts: { dir: string; force?: boolean }) => {\n const sourceDir = resolveBundledSkillsDir();\n if (listSkillNames(sourceDir).length === 0) {\n console.error(\"No bundled skills found to install.\");\n process.exit(1);\n }\n\n const targetRoot = resolve(process.cwd(), opts.dir);\n if (skillsWouldShipWithTheme(process.cwd(), targetRoot)) {\n console.log(\n `${chalk.yellow(\"⚠\")} ${chalk.bold(opts.dir)} is not a hidden directory — ` +\n `'fluid theme push' will upload these skills as theme files. ` +\n `Install into a dot-directory like ${chalk.cyan(DEFAULT_TARGET_DIR)} to keep them local.`,\n );\n }\n\n const { installed, skipped } = await installSkills({\n sourceDir,\n targetRoot,\n force: Boolean(opts.force),\n confirmOverwrite: async (name) => {\n const res = await prompts(\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `${chalk.yellow(name)} already exists in ${opts.dir}. Overwrite?`,\n initial: false,\n },\n { onCancel: () => process.exit(130) },\n );\n return Boolean(res.overwrite);\n },\n // Warn as soon as a leftover is found, so it's reported even if a later\n // skill fails before the install finishes.\n onLeftover: (path) => {\n console.log(\n `${chalk.yellow(\"⚠\")} kept the previous copy at ${path} (couldn't remove it — delete it manually)`,\n );\n },\n });\n\n for (const name of installed) {\n console.log(`${chalk.green(\"✓\")} ${name} → ${join(opts.dir, name)}`);\n }\n for (const name of skipped) {\n console.log(`${chalk.dim(`· skipped ${name} (kept existing)`)}`);\n }\n\n const parts = [\n installed.length > 0 ? `${installed.length} installed` : null,\n skipped.length > 0 ? `${skipped.length} skipped` : null,\n ].filter(Boolean);\n console.log(\n `\\n${chalk.bold(parts.join(\", \") || \"Nothing to do\")} in ${targetRoot}`,\n );\n if (installed.length > 0) {\n console.log(\n chalk.dim(\"Restart your agent session to pick up the new skills.\"),\n );\n }\n });\n\n return skills;\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createDevCommand } from \"./dev.js\";\nimport { createPushCommand } from \"./push.js\";\nimport { createPullCommand } from \"./pull.js\";\nimport { createLintCommand } from \"./lint.js\";\nimport { createInitCommand } from \"./init.js\";\nimport { createNavigateCommand } from \"./navigate.js\";\nimport { createSkillsCommand } from \"./skills.js\";\n\nexport function registerThemeCommand(ctx: PluginContext): void {\n const cmd = new Command(\"theme\").description(\n \"Theme developer workflow — dev server, push, pull, lint, init, skills\",\n );\n\n cmd.addCommand(createDevCommand());\n cmd.addCommand(createPushCommand());\n cmd.addCommand(createPullCommand());\n cmd.addCommand(createLintCommand());\n cmd.addCommand(createInitCommand());\n cmd.addCommand(createNavigateCommand());\n cmd.addCommand(createSkillsCommand());\n\n ctx.program.addCommand(cmd);\n}\n","import type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerThemeCommand } from \"./commands/theme.js\";\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-theme-dev\",\n version: \"0.1.0\",\n register(ctx: PluginContext) {\n registerThemeCommand(ctx);\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}