@riverbankcms/sdk 0.7.5 → 0.8.1

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 (173) hide show
  1. package/README.md +88 -1
  2. package/dist/cli/index.js +4299 -209
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/client/client.d.mts +2 -2
  5. package/dist/client/client.d.ts +2 -2
  6. package/dist/client/client.js +230 -31
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/client.mjs +230 -31
  9. package/dist/client/client.mjs.map +1 -1
  10. package/dist/client/hooks.d.mts +2 -2
  11. package/dist/client/hooks.d.ts +2 -2
  12. package/dist/client/hooks.js +34 -20
  13. package/dist/client/hooks.js.map +1 -1
  14. package/dist/client/hooks.mjs +34 -20
  15. package/dist/client/hooks.mjs.map +1 -1
  16. package/dist/client/rendering/client.js +182 -123
  17. package/dist/client/rendering/client.js.map +1 -1
  18. package/dist/client/rendering/client.mjs +174 -109
  19. package/dist/client/rendering/client.mjs.map +1 -1
  20. package/dist/client/usePage-CdnO2CP5.d.mts +6875 -0
  21. package/dist/client/usePage-Dsi39Exp.d.ts +6915 -0
  22. package/dist/client/usePage-Im82JRRe.d.mts +6915 -0
  23. package/dist/client/usePage-_ksKXlUF.d.ts +6875 -0
  24. package/dist/server/{Layout-Yluyb6sK.d.ts → Layout-CZ-kxKfl.d.ts} +1 -1
  25. package/dist/server/{Layout-qWLdVm5-.d.mts → Layout-ESG8zvrk.d.mts} +1 -1
  26. package/dist/server/{chunk-4YQJUL5W.mjs → chunk-5GCSRTIU.mjs} +8 -4
  27. package/dist/server/chunk-5GCSRTIU.mjs.map +1 -0
  28. package/dist/server/{chunk-YYO3RIFO.js → chunk-6ERSDFTY.js} +35 -21
  29. package/dist/server/chunk-6ERSDFTY.js.map +1 -0
  30. package/dist/server/{chunk-YXA4GAAQ.mjs → chunk-6VTKALLN.mjs} +2 -6
  31. package/dist/server/{chunk-YXA4GAAQ.mjs.map → chunk-6VTKALLN.mjs.map} +1 -1
  32. package/dist/server/{chunk-BYBJA6SP.mjs → chunk-A3UZ2LDH.mjs} +35 -21
  33. package/dist/server/chunk-A3UZ2LDH.mjs.map +1 -0
  34. package/dist/server/{chunk-OSF34JTQ.mjs → chunk-ADD3O2QO.mjs} +4 -4
  35. package/dist/server/{chunk-C6FIJC7T.mjs → chunk-BNHK7YOC.mjs} +2 -2
  36. package/dist/server/{chunk-TT5JWA4X.js → chunk-DAXWU3S3.js} +9 -9
  37. package/dist/server/{chunk-TT5JWA4X.js.map → chunk-DAXWU3S3.js.map} +1 -1
  38. package/dist/server/{chunk-7UPVCT3K.js → chunk-F2NDLDDA.js} +239 -154
  39. package/dist/server/chunk-F2NDLDDA.js.map +1 -0
  40. package/dist/server/{chunk-LNOUXALA.mjs → chunk-FUFPKTSI.mjs} +96 -11
  41. package/dist/server/chunk-FUFPKTSI.mjs.map +1 -0
  42. package/dist/server/{chunk-2NBNOY3C.mjs → chunk-GRFFJUCO.mjs} +107 -4
  43. package/dist/server/chunk-GRFFJUCO.mjs.map +1 -0
  44. package/dist/server/{chunk-AEFWG657.mjs → chunk-HDHY4236.mjs} +2 -2
  45. package/dist/server/{chunk-2KCF2DNK.js → chunk-HE3RTUDX.js} +8 -8
  46. package/dist/server/{chunk-2KCF2DNK.js.map → chunk-HE3RTUDX.js.map} +1 -1
  47. package/dist/server/{chunk-RVDS7VSP.js → chunk-IJTJH4J3.js} +4 -4
  48. package/dist/server/{chunk-RVDS7VSP.js.map → chunk-IJTJH4J3.js.map} +1 -1
  49. package/dist/server/{chunk-P3NNN73G.js → chunk-K44OPKLA.js} +3 -3
  50. package/dist/server/{chunk-P3NNN73G.js.map → chunk-K44OPKLA.js.map} +1 -1
  51. package/dist/server/{chunk-EIJ27EZQ.js → chunk-KDCVCDW6.js} +10 -6
  52. package/dist/server/chunk-KDCVCDW6.js.map +1 -0
  53. package/dist/server/{chunk-7BVRA5MY.js → chunk-KGORQCHF.js} +9 -9
  54. package/dist/server/{chunk-7BVRA5MY.js.map → chunk-KGORQCHF.js.map} +1 -1
  55. package/dist/server/{chunk-KH3EXBJM.js → chunk-MFNWLB5G.js} +111 -8
  56. package/dist/server/chunk-MFNWLB5G.js.map +1 -0
  57. package/dist/server/{chunk-EIVISR62.js → chunk-P4O3WSAR.js} +2 -6
  58. package/dist/server/chunk-P4O3WSAR.js.map +1 -0
  59. package/dist/server/{chunk-RBJFXNDM.mjs → chunk-PGZJUNCY.mjs} +4 -4
  60. package/dist/server/{chunk-ARNCLSQT.mjs → chunk-T5PAA22U.mjs} +2 -2
  61. package/dist/server/{chunk-T26N3P26.js → chunk-TLZHVGTL.js} +4 -4
  62. package/dist/server/{chunk-T26N3P26.js.map → chunk-TLZHVGTL.js.map} +1 -1
  63. package/dist/server/{chunk-P4K63SBZ.mjs → chunk-TR7MSLWL.mjs} +3 -3
  64. package/dist/server/{chunk-NFEGQTCC.mjs → chunk-WMJKH4XE.mjs} +8 -1
  65. package/dist/server/{chunk-4CV4JOE5.js → chunk-Z6ZWNWWR.js} +9 -2
  66. package/dist/server/chunk-Z6ZWNWWR.js.map +1 -0
  67. package/dist/server/{components-Di5ME6He.d.ts → components-CE48wJM1.d.ts} +4 -4
  68. package/dist/server/{components-DNHfSCML.d.mts → components-iEDvl2Yw.d.mts} +4 -4
  69. package/dist/server/components.d.mts +6 -6
  70. package/dist/server/components.d.ts +6 -6
  71. package/dist/server/components.js +7 -7
  72. package/dist/server/components.mjs +6 -6
  73. package/dist/server/config-validation.d.mts +3 -3
  74. package/dist/server/config-validation.d.ts +3 -3
  75. package/dist/server/config-validation.js +6 -6
  76. package/dist/server/config-validation.mjs +5 -5
  77. package/dist/server/config.d.mts +5 -5
  78. package/dist/server/config.d.ts +5 -5
  79. package/dist/server/config.js +6 -6
  80. package/dist/server/config.mjs +5 -5
  81. package/dist/server/data.d.mts +3 -3
  82. package/dist/server/data.d.ts +3 -3
  83. package/dist/server/data.js +4 -4
  84. package/dist/server/data.mjs +3 -3
  85. package/dist/server/env.js +1 -1
  86. package/dist/server/env.mjs +1 -1
  87. package/dist/server/{index-DLvNddi-.d.ts → index-BHLK2mgQ.d.ts} +2 -2
  88. package/dist/server/{index--Oyunk_B.d.mts → index-BrH_NIRO.d.mts} +2 -2
  89. package/dist/server/{index-Clm3skz_.d.mts → index-Cgvb5fVQ.d.mts} +2 -2
  90. package/dist/server/{index-C9Ra8dza.d.ts → index-DTBg8eXj.d.ts} +2 -2
  91. package/dist/server/index.d.mts +14 -6
  92. package/dist/server/index.d.ts +14 -6
  93. package/dist/server/index.js +11 -11
  94. package/dist/server/index.mjs +2 -2
  95. package/dist/server/{loadContent-D7LQwI0o.d.ts → loadContent-BUK6IVJf.d.ts} +26 -4
  96. package/dist/server/{loadContent-DVfuBLiZ.d.mts → loadContent-au9Weoy0.d.mts} +26 -4
  97. package/dist/server/loadPage-AWYZ2QA2.mjs +11 -0
  98. package/dist/server/loadPage-CMHYAW2J.js +11 -0
  99. package/dist/server/{loadPage-AXNAERDS.js.map → loadPage-CMHYAW2J.js.map} +1 -1
  100. package/dist/server/{loadPage-BucnLHmE.d.mts → loadPage-DiHEl8BA.d.mts} +3 -3
  101. package/dist/server/{loadPage-BmYJCe_V.d.ts → loadPage-JOIbF7ih.d.ts} +3 -3
  102. package/dist/server/metadata.d.mts +5 -5
  103. package/dist/server/metadata.d.ts +5 -5
  104. package/dist/server/metadata.js +1 -1
  105. package/dist/server/metadata.mjs +1 -1
  106. package/dist/server/navigation.d.mts +4 -8
  107. package/dist/server/navigation.d.ts +4 -8
  108. package/dist/server/navigation.js +3 -7
  109. package/dist/server/navigation.js.map +1 -1
  110. package/dist/server/navigation.mjs +2 -6
  111. package/dist/server/next/revalidate.js +1 -1
  112. package/dist/server/next/revalidate.mjs +1 -1
  113. package/dist/server/next/tags.js +1 -1
  114. package/dist/server/next/tags.mjs +1 -1
  115. package/dist/server/next.d.mts +39 -8
  116. package/dist/server/next.d.ts +39 -8
  117. package/dist/server/next.js +47 -25
  118. package/dist/server/next.js.map +1 -1
  119. package/dist/server/next.mjs +40 -18
  120. package/dist/server/next.mjs.map +1 -1
  121. package/dist/server/rendering/server.d.mts +5 -5
  122. package/dist/server/rendering/server.d.ts +5 -5
  123. package/dist/server/rendering/server.js +9 -9
  124. package/dist/server/rendering/server.mjs +8 -8
  125. package/dist/server/rendering.d.mts +8 -8
  126. package/dist/server/rendering.d.ts +8 -8
  127. package/dist/server/rendering.js +11 -11
  128. package/dist/server/rendering.mjs +10 -10
  129. package/dist/server/routing.d.mts +5 -5
  130. package/dist/server/routing.d.ts +5 -5
  131. package/dist/server/routing.js +2 -2
  132. package/dist/server/routing.mjs +2 -2
  133. package/dist/server/{schema-Z6-afHJG.d.mts → schema-DYtW0zEu.d.mts} +40 -0
  134. package/dist/server/{schema-Z6-afHJG.d.ts → schema-DYtW0zEu.d.ts} +40 -0
  135. package/dist/server/server.d.mts +6 -6
  136. package/dist/server/server.d.ts +6 -6
  137. package/dist/server/server.js +7 -7
  138. package/dist/server/server.mjs +6 -6
  139. package/dist/server/theme-bridge.js +9 -9
  140. package/dist/server/theme-bridge.mjs +3 -3
  141. package/dist/server/theme.js +1 -1
  142. package/dist/server/theme.mjs +1 -1
  143. package/dist/server/{types-BSV6Vc-P.d.mts → types-BAM1kcGA.d.mts} +9 -1
  144. package/dist/server/{types-C-LShyIg.d.mts → types-CmBB0Osp.d.ts} +44 -2
  145. package/dist/server/{types-BRQyLrQU.d.ts → types-DDNKxQXw.d.mts} +44 -2
  146. package/dist/server/{types-DLBhEPSt.d.ts → types-DVesWaB7.d.ts} +38 -1
  147. package/dist/server/{types-BjgZt8xJ.d.mts → types-M0CviVW2.d.mts} +38 -1
  148. package/dist/server/{types-Dt98DeYa.d.ts → types-_SNCu2ZZ.d.ts} +9 -1
  149. package/dist/server/{validation-BGuRo8P1.d.mts → validation-BA1TKthZ.d.mts} +2 -2
  150. package/dist/server/{validation-DU2YE7u5.d.ts → validation-js7BCPN8.d.ts} +2 -2
  151. package/dist/server/webhooks.js +1 -1
  152. package/dist/server/webhooks.mjs +1 -1
  153. package/package.json +2 -1
  154. package/dist/server/chunk-2NBNOY3C.mjs.map +0 -1
  155. package/dist/server/chunk-4CV4JOE5.js.map +0 -1
  156. package/dist/server/chunk-4YQJUL5W.mjs.map +0 -1
  157. package/dist/server/chunk-7UPVCT3K.js.map +0 -1
  158. package/dist/server/chunk-BYBJA6SP.mjs.map +0 -1
  159. package/dist/server/chunk-EIJ27EZQ.js.map +0 -1
  160. package/dist/server/chunk-EIVISR62.js.map +0 -1
  161. package/dist/server/chunk-KH3EXBJM.js.map +0 -1
  162. package/dist/server/chunk-LNOUXALA.mjs.map +0 -1
  163. package/dist/server/chunk-YYO3RIFO.js.map +0 -1
  164. package/dist/server/loadPage-AXNAERDS.js +0 -11
  165. package/dist/server/loadPage-XR7ORQ2E.mjs +0 -11
  166. /package/dist/server/{chunk-OSF34JTQ.mjs.map → chunk-ADD3O2QO.mjs.map} +0 -0
  167. /package/dist/server/{chunk-C6FIJC7T.mjs.map → chunk-BNHK7YOC.mjs.map} +0 -0
  168. /package/dist/server/{chunk-AEFWG657.mjs.map → chunk-HDHY4236.mjs.map} +0 -0
  169. /package/dist/server/{chunk-RBJFXNDM.mjs.map → chunk-PGZJUNCY.mjs.map} +0 -0
  170. /package/dist/server/{chunk-ARNCLSQT.mjs.map → chunk-T5PAA22U.mjs.map} +0 -0
  171. /package/dist/server/{chunk-P4K63SBZ.mjs.map → chunk-TR7MSLWL.mjs.map} +0 -0
  172. /package/dist/server/{chunk-NFEGQTCC.mjs.map → chunk-WMJKH4XE.mjs.map} +0 -0
  173. /package/dist/server/{loadPage-XR7ORQ2E.mjs.map → loadPage-AWYZ2QA2.mjs.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -3,16 +3,20 @@
3
3
 
4
4
  var jiti = require('jiti');
5
5
  var path9 = require('path');
6
- var fs = require('fs');
6
+ var fs6 = require('fs');
7
7
  var dotenv = require('dotenv');
8
8
  var commander = require('commander');
9
9
  var zod = require('zod');
10
- var prompts = require('prompts');
11
10
  var fs3 = require('fs/promises');
12
11
  var readline = require('readline');
12
+ var prompts = require('prompts');
13
13
  var equal = require('fast-deep-equal');
14
14
  var os = require('os');
15
15
  var child_process = require('child_process');
16
+ var simpleGit = require('simple-git');
17
+ require('react');
18
+ require('react/jsx-runtime');
19
+ var crypto2 = require('crypto');
16
20
 
17
21
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
18
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -36,14 +40,19 @@ function _interopNamespace(e) {
36
40
  }
37
41
 
38
42
  var path9__namespace = /*#__PURE__*/_interopNamespace(path9);
39
- var prompts__default = /*#__PURE__*/_interopDefault(prompts);
43
+ var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
40
44
  var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
41
45
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
46
+ var prompts__default = /*#__PURE__*/_interopDefault(prompts);
42
47
  var equal__default = /*#__PURE__*/_interopDefault(equal);
43
48
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
49
+ var simpleGit__default = /*#__PURE__*/_interopDefault(simpleGit);
50
+ var crypto2__namespace = /*#__PURE__*/_interopNamespace(crypto2);
44
51
 
45
52
  var __defProp = Object.defineProperty;
53
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
46
54
  var __getOwnPropNames = Object.getOwnPropertyNames;
55
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
47
56
  var __esm = (fn, res) => function __init() {
48
57
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
49
58
  };
@@ -51,6 +60,15 @@ var __export = (target, all) => {
51
60
  for (var name in all)
52
61
  __defProp(target, name, { get: all[name], enumerable: true });
53
62
  };
63
+ var __copyProps = (to, from, except, desc) => {
64
+ if (from && typeof from === "object" || typeof from === "function") {
65
+ for (let key of __getOwnPropNames(from))
66
+ if (!__hasOwnProp.call(to, key) && key !== except)
67
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
68
+ }
69
+ return to;
70
+ };
71
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
54
72
 
55
73
  // src/cli/load-config.ts
56
74
  var load_config_exports = {};
@@ -59,7 +77,7 @@ __export(load_config_exports, {
59
77
  });
60
78
  async function loadConfigFile(configPath) {
61
79
  const resolvedPath = resolveConfigPath(configPath);
62
- if (!fs.existsSync(resolvedPath)) {
80
+ if (!fs6.existsSync(resolvedPath)) {
63
81
  throw new Error(
64
82
  `Config file not found: ${resolvedPath}
65
83
  Create a riverbank.config.ts file or specify a path with --config`
@@ -90,7 +108,7 @@ function resolveConfigPath(configPath) {
90
108
  return path9.resolve(process.cwd(), DEFAULT_CONFIG_FILENAME);
91
109
  }
92
110
  const resolved = path9.resolve(configPath);
93
- if (fs.existsSync(resolved) && !resolved.endsWith(".ts") && !resolved.endsWith(".js")) {
111
+ if (fs6.existsSync(resolved) && !resolved.endsWith(".ts") && !resolved.endsWith(".js")) {
94
112
  return path9.resolve(resolved, DEFAULT_CONFIG_FILENAME);
95
113
  }
96
114
  return resolved;
@@ -102,6 +120,240 @@ var init_load_config = __esm({
102
120
  }
103
121
  });
104
122
 
123
+ // src/constants.ts
124
+ var PREBUILD_PAGE_SIZE, DEFAULT_MAX_PREBUILD_AGE_SEC, DEFAULT_PREBUILD_DIR;
125
+ var init_constants = __esm({
126
+ "src/constants.ts"() {
127
+ PREBUILD_PAGE_SIZE = 50;
128
+ DEFAULT_MAX_PREBUILD_AGE_SEC = 86400;
129
+ DEFAULT_PREBUILD_DIR = ".riverbank-cache";
130
+ }
131
+ });
132
+
133
+ // src/prebuild/loader.ts
134
+ var loader_exports = {};
135
+ __export(loader_exports, {
136
+ PrebuildLoader: () => PrebuildLoader,
137
+ canUsePrebuild: () => canUsePrebuild,
138
+ createPrebuildLoader: () => createPrebuildLoader
139
+ });
140
+ function loadManifest(prebuildDir) {
141
+ if (cachedManifest?.dir === prebuildDir) {
142
+ return cachedManifest.manifest;
143
+ }
144
+ const manifestPath = path9__namespace.join(prebuildDir, "manifest.json");
145
+ if (!fs6__namespace.existsSync(manifestPath)) {
146
+ return null;
147
+ }
148
+ try {
149
+ const content = fs6__namespace.readFileSync(manifestPath, "utf-8");
150
+ const manifest = JSON.parse(content);
151
+ cachedManifest = { manifest, dir: prebuildDir };
152
+ return manifest;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function loadJsonFile(prebuildDir, relativePath) {
158
+ const filePath = path9__namespace.join(prebuildDir, relativePath);
159
+ if (!fs6__namespace.existsSync(filePath)) {
160
+ return null;
161
+ }
162
+ try {
163
+ const content = fs6__namespace.readFileSync(filePath, "utf-8");
164
+ return JSON.parse(content);
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+ function isPrebuildExpired(manifest, maxAgeSec) {
170
+ const ageMs = Date.now() - new Date(manifest.generatedAt).getTime();
171
+ return ageMs > maxAgeSec * 1e3;
172
+ }
173
+ function getPrebuildAgeSec(manifest) {
174
+ const ageMs = Date.now() - new Date(manifest.generatedAt).getTime();
175
+ return Math.floor(ageMs / 1e3);
176
+ }
177
+ function canUsePrebuild() {
178
+ if (typeof process === "undefined" || !process.versions?.node) {
179
+ return false;
180
+ }
181
+ try {
182
+ return typeof fs6__namespace.existsSync === "function";
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+ function createPrebuildLoader(config3) {
188
+ return new PrebuildLoader(config3);
189
+ }
190
+ var cachedManifest, PrebuildLoader;
191
+ var init_loader = __esm({
192
+ "src/prebuild/loader.ts"() {
193
+ init_constants();
194
+ cachedManifest = null;
195
+ PrebuildLoader = class {
196
+ constructor(config3) {
197
+ this.prebuildDir = config3.prebuildDir ?? DEFAULT_PREBUILD_DIR;
198
+ this.maxPrebuildAgeSec = config3.maxPrebuildAgeSec ?? DEFAULT_MAX_PREBUILD_AGE_SEC;
199
+ }
200
+ /**
201
+ * Check if prebuild is available and not expired.
202
+ */
203
+ isAvailable() {
204
+ if (!canUsePrebuild()) {
205
+ return false;
206
+ }
207
+ const manifest = loadManifest(this.prebuildDir);
208
+ if (!manifest) {
209
+ return false;
210
+ }
211
+ return !isPrebuildExpired(manifest, this.maxPrebuildAgeSec);
212
+ }
213
+ /**
214
+ * Load site data from prebuild cache.
215
+ */
216
+ loadSite(siteId) {
217
+ const manifest = loadManifest(this.prebuildDir);
218
+ if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {
219
+ return null;
220
+ }
221
+ const cacheFile = loadJsonFile(this.prebuildDir, "site.json");
222
+ if (!cacheFile || cacheFile.data.site.id !== siteId) {
223
+ return null;
224
+ }
225
+ return {
226
+ data: cacheFile.data,
227
+ prebuildAgeSec: getPrebuildAgeSec(manifest)
228
+ };
229
+ }
230
+ /**
231
+ * Load page data from prebuild cache.
232
+ */
233
+ loadPage(siteId, pagePath) {
234
+ const manifest = loadManifest(this.prebuildDir);
235
+ if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {
236
+ return null;
237
+ }
238
+ const cacheKey = `page:${siteId}:${pagePath}:false`;
239
+ const relativePath = manifest.keyToFile[cacheKey];
240
+ if (!relativePath) {
241
+ return null;
242
+ }
243
+ const cacheFile = loadJsonFile(this.prebuildDir, relativePath);
244
+ if (!cacheFile) {
245
+ return null;
246
+ }
247
+ return {
248
+ data: cacheFile.data,
249
+ prebuildAgeSec: getPrebuildAgeSec(manifest)
250
+ };
251
+ }
252
+ /**
253
+ * Load entries from prebuild cache with runtime filtering.
254
+ *
255
+ * The prebuild stores ALL entries for each content type.
256
+ * Filtering, ordering, and pagination are applied at runtime.
257
+ */
258
+ loadEntries(siteId, params) {
259
+ if (params.preview) {
260
+ return null;
261
+ }
262
+ if (params.mode === "manual" && params.entryIds?.length) {
263
+ return null;
264
+ }
265
+ const manifest = loadManifest(this.prebuildDir);
266
+ if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {
267
+ return null;
268
+ }
269
+ const cacheKey = `entries-all:${siteId}:${params.contentType}`;
270
+ const relativePath = manifest.keyToFile[cacheKey];
271
+ if (!relativePath) {
272
+ return null;
273
+ }
274
+ const cacheFile = loadJsonFile(this.prebuildDir, relativePath);
275
+ if (!cacheFile) {
276
+ return null;
277
+ }
278
+ let entries = [...cacheFile.entries];
279
+ if (params.order === "newest") {
280
+ entries.sort((a, b) => {
281
+ const aTime = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;
282
+ const bTime = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;
283
+ return bTime - aTime;
284
+ });
285
+ } else if (params.order === "oldest") {
286
+ entries.sort((a, b) => {
287
+ const aTime = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;
288
+ const bTime = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;
289
+ return aTime - bTime;
290
+ });
291
+ } else if (params.order === "title") {
292
+ entries.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
293
+ }
294
+ const total = entries.length;
295
+ const offset = params.offset ?? 0;
296
+ const limit = params.limit ?? 10;
297
+ if (offset > 0) {
298
+ entries = entries.slice(offset);
299
+ }
300
+ entries = entries.slice(0, limit);
301
+ const prebuildAgeSec = getPrebuildAgeSec(manifest);
302
+ if (params.includeMeta) {
303
+ return {
304
+ data: {
305
+ entries,
306
+ total,
307
+ hasMore: offset + entries.length < total,
308
+ limit,
309
+ offset,
310
+ totalPages: Math.ceil(total / limit),
311
+ currentPage: Math.floor(offset / limit) + 1
312
+ },
313
+ prebuildAgeSec
314
+ };
315
+ }
316
+ return {
317
+ data: { entries },
318
+ prebuildAgeSec
319
+ };
320
+ }
321
+ /**
322
+ * Load navigation data from prebuild cache.
323
+ */
324
+ loadNavigation() {
325
+ const manifest = loadManifest(this.prebuildDir);
326
+ if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {
327
+ return null;
328
+ }
329
+ const cacheFile = loadJsonFile(
330
+ this.prebuildDir,
331
+ "navigation/menus.json"
332
+ );
333
+ if (!cacheFile) {
334
+ return null;
335
+ }
336
+ return {
337
+ data: cacheFile.menus,
338
+ prebuildAgeSec: getPrebuildAgeSec(manifest)
339
+ };
340
+ }
341
+ /**
342
+ * Get the manifest for inspection.
343
+ */
344
+ getManifest() {
345
+ return loadManifest(this.prebuildDir);
346
+ }
347
+ /**
348
+ * Clear the cached manifest (for testing).
349
+ */
350
+ clearCache() {
351
+ cachedManifest = null;
352
+ }
353
+ };
354
+ }
355
+ });
356
+
105
357
  // ../blocks/src/system/manifest/augmentManifest.ts
106
358
  function augmentManifest(manifest) {
107
359
  let augmentedFields = manifest.fields ?? [];
@@ -254,7 +506,11 @@ var uiSchema = zod.z.object({
254
506
  layout: zod.z.enum(["stack", "grid"]).optional(),
255
507
  columns: zod.z.number().int().min(2).max(4).optional(),
256
508
  // Entry picker configuration
257
- contentTypeField: zod.z.string().optional()
509
+ contentTypeField: zod.z.string().optional(),
510
+ // Extras pattern: fields marked as extras are hidden behind a modal toggle
511
+ extras: zod.z.boolean().optional(),
512
+ // Render in block header instead of form body (used for section styles)
513
+ renderInHeader: zod.z.boolean().optional()
258
514
  }).partial();
259
515
  var baseFieldSchema = zod.z.object({
260
516
  id: zod.z.string().min(1, "Field id is required"),
@@ -574,11 +830,11 @@ function bind(from, options) {
574
830
  }
575
831
  });
576
832
  }
577
- function when(path11, options) {
833
+ function when(path13, options) {
578
834
  return (node) => ({
579
835
  ...node,
580
836
  $when: {
581
- when: { from: path11 },
837
+ when: { from: path13 },
582
838
  ...options?.equals !== void 0 ? { equals: options.equals } : {},
583
839
  ...options?.not ? { not: true } : {}
584
840
  }
@@ -651,25 +907,25 @@ function devValidate(node) {
651
907
  }
652
908
 
653
909
  // ../blocks/src/system/node/fragments/backgroundLayer.ts
654
- function backgroundLayer(path11, options = {}) {
910
+ function backgroundLayer(path13, options = {}) {
655
911
  const {
656
912
  styleClassName = "absolute inset-0 -z-10 h-full w-full pointer-events-none",
657
913
  imageClassName
658
914
  } = options;
659
915
  const styleLayer = el("div", {
660
- className: { $bind: { from: path11, transforms: [{ id: "background.resolveClass", options: { baseClass: styleClassName } }] } },
661
- style: { $bind: { from: path11, transforms: [{ id: "background.resolveStyle" }] } }
916
+ className: { $bind: { from: path13, transforms: [{ id: "background.resolveClass", options: { baseClass: styleClassName } }] } },
917
+ style: { $bind: { from: path13, transforms: [{ id: "background.resolveStyle" }] } }
662
918
  });
663
- const imageLayer = createBackgroundImageNode(path11, imageClassName);
919
+ const imageLayer = createBackgroundImageNode(path13, imageClassName);
664
920
  return [styleLayer, imageLayer];
665
921
  }
666
- function createBackgroundImageNode(path11, baseClassName = "absolute -z-10") {
667
- const imagePath = `${path11}.image`;
922
+ function createBackgroundImageNode(path13, baseClassName = "absolute -z-10") {
923
+ const imagePath = `${path13}.image`;
668
924
  return media(
669
925
  {
670
926
  className: {
671
927
  $bind: {
672
- from: path11,
928
+ from: path13,
673
929
  transforms: [{
674
930
  id: "background.resolveImageClassName",
675
931
  options: { baseClass: `background-image ${baseClassName}` }
@@ -678,7 +934,7 @@ function createBackgroundImageNode(path11, baseClassName = "absolute -z-10") {
678
934
  },
679
935
  style: {
680
936
  $bind: {
681
- from: path11,
937
+ from: path13,
682
938
  transforms: [{ id: "background.resolveImageStyle" }]
683
939
  }
684
940
  }
@@ -1106,27 +1362,27 @@ function scopePropValue(value, scope) {
1106
1362
  }
1107
1363
  return value;
1108
1364
  }
1109
- function scopeContentPath(path11, scope) {
1365
+ function scopeContentPath(path13, scope) {
1110
1366
  if (!scope || scope.length === 0) {
1111
- return path11;
1367
+ return path13;
1112
1368
  }
1113
- if (path11 === "content") {
1369
+ if (path13 === "content") {
1114
1370
  return `content.${scope}`;
1115
1371
  }
1116
- if (path11.startsWith("content.")) {
1117
- const remainder = path11.slice("content.".length);
1372
+ if (path13.startsWith("content.")) {
1373
+ const remainder = path13.slice("content.".length);
1118
1374
  return remainder.length > 0 ? `content.${scope}.${remainder}` : `content.${scope}`;
1119
1375
  }
1120
- if (path11.startsWith("content[")) {
1121
- return path11.replace(/^content/, `content.${scope}`);
1376
+ if (path13.startsWith("content[")) {
1377
+ return path13.replace(/^content/, `content.${scope}`);
1122
1378
  }
1123
- if (path11.startsWith("$root.")) {
1124
- return path11;
1379
+ if (path13.startsWith("$root.")) {
1380
+ return path13;
1125
1381
  }
1126
- if (path11.includes(".")) {
1127
- return path11;
1382
+ if (path13.includes(".")) {
1383
+ return path13;
1128
1384
  }
1129
- return `content.${scope}.${path11}`;
1385
+ return `content.${scope}.${path13}`;
1130
1386
  }
1131
1387
 
1132
1388
  // ../blocks/src/system/fragments/builder.ts
@@ -1424,25 +1680,30 @@ function createButtonGroup(options = {}) {
1424
1680
  ui: { colSpan: 2 }
1425
1681
  }
1426
1682
  ];
1427
- const iconsGroup = {
1428
- id: "icons",
1429
- type: "group",
1430
- label: "Icons",
1431
- required: false,
1432
- ui: { preset: "disclosure", colSpan: 2 },
1433
- schema: {
1434
- fields: [
1435
- { id: "iconLeft", type: "media", label: "Left icon", required: false, mediaKinds: ["image"] },
1436
- { id: "iconRight", type: "media", label: "Right icon", required: false, mediaKinds: ["image"] }
1437
- ]
1683
+ const iconFields = [
1684
+ {
1685
+ id: "iconLeft",
1686
+ type: "media",
1687
+ label: "Left icon",
1688
+ required: false,
1689
+ mediaKinds: ["image"],
1690
+ ui: { extras: true }
1691
+ },
1692
+ {
1693
+ id: "iconRight",
1694
+ type: "media",
1695
+ label: "Right icon",
1696
+ required: false,
1697
+ mediaKinds: ["image"],
1698
+ ui: { extras: true }
1438
1699
  }
1439
- };
1700
+ ];
1440
1701
  return {
1441
1702
  id: groupId,
1442
1703
  type: "group",
1443
1704
  label: groupLabel,
1444
1705
  ui: { layout: "grid", columns: 2, flattenInRepeater, hideLabel: !showGroupLabel },
1445
- schema: { fields: [...mainFields, iconsGroup] },
1706
+ schema: { fields: [...mainFields, ...iconFields] },
1446
1707
  required: false
1447
1708
  };
1448
1709
  }
@@ -2738,6 +2999,10 @@ var BACKGROUND_POSITION_PRESETS = [
2738
2999
  BACKGROUND_POSITION_PRESETS.map((p) => p.value);
2739
3000
 
2740
3001
  // ../blocks/src/system/fields/background.ts
3002
+ var BACKGROUND_WIDGETS = {
3003
+ COLOR: "backgroundColor",
3004
+ GRADIENT: "backgroundGradient"
3005
+ };
2741
3006
  function createBackgroundField(options = {}) {
2742
3007
  const {
2743
3008
  id = "background",
@@ -2763,8 +3028,7 @@ function createBackgroundField(options = {}) {
2763
3028
  required: false,
2764
3029
  multiline: false,
2765
3030
  ui: {
2766
- // Use BackgroundColorWidget via widget override
2767
- widget: "backgroundColor"
3031
+ widget: BACKGROUND_WIDGETS.COLOR
2768
3032
  }
2769
3033
  }
2770
3034
  ]
@@ -2781,11 +3045,11 @@ function createBackgroundField(options = {}) {
2781
3045
  id: "gradient",
2782
3046
  type: "text",
2783
3047
  label: "Gradient",
2784
- description: "CSS gradient value (e.g., linear-gradient(to right, #ff0000, #00ff00)).",
3048
+ description: "Select a gradient from theme presets.",
2785
3049
  required: false,
2786
- multiline: true,
3050
+ multiline: false,
2787
3051
  ui: {
2788
- placeholder: "linear-gradient(to right, #ff0000, #00ff00)"
3052
+ widget: BACKGROUND_WIDGETS.GRADIENT
2789
3053
  }
2790
3054
  }
2791
3055
  ]
@@ -2844,7 +3108,7 @@ function createBackgroundField(options = {}) {
2844
3108
  id: "position",
2845
3109
  type: "presetOrCustom",
2846
3110
  label: "Position",
2847
- description: 'Anchor point for the image. Relevant for "Fill" and "Custom size" options.',
3111
+ description: 'Anchor point for scaled images. For "Fill" mode, the image focus point (if set) takes precedence.',
2848
3112
  required: false,
2849
3113
  presets: [...BACKGROUND_POSITION_PRESETS],
2850
3114
  customInput: {
@@ -2935,6 +3199,8 @@ function sectionStylesField(options = {}) {
2935
3199
  required: false,
2936
3200
  schema: { fields: fields4 },
2937
3201
  ui: {
3202
+ // Render in block header instead of form body
3203
+ renderInHeader: true,
2938
3204
  modalConfig: {
2939
3205
  buttonLabel: label,
2940
3206
  description: "Configure background and spacing for this section.",
@@ -5133,7 +5399,7 @@ var contentConfigSchema = contentConfigBaseSchema.superRefine((data, ctx) => {
5133
5399
  });
5134
5400
  }
5135
5401
  const paths = data.pages.map((p) => p.path);
5136
- const duplicatePaths = paths.filter((path11, i) => paths.indexOf(path11) !== i);
5402
+ const duplicatePaths = paths.filter((path13, i) => paths.indexOf(path13) !== i);
5137
5403
  if (duplicatePaths.length > 0) {
5138
5404
  ctx.addIssue({
5139
5405
  code: zod.z.ZodIssueCode.custom,
@@ -5391,111 +5657,6 @@ var riverbankSiteConfigSchema = zod.z.object({
5391
5657
 
5392
5658
  // src/cli/push-config.ts
5393
5659
  init_load_config();
5394
- async function pushToDashboard(dashboardUrl, siteId, apiKey, config3) {
5395
- const pushUrl = `${dashboardUrl}/api/sites/${siteId}/sdk-config`;
5396
- console.log(`Pushing config to ${pushUrl}...`);
5397
- let response;
5398
- try {
5399
- response = await fetch(pushUrl, {
5400
- method: "POST",
5401
- headers: {
5402
- "Content-Type": "application/json",
5403
- "Authorization": `Bearer ${apiKey}`
5404
- },
5405
- body: JSON.stringify({ config: config3 }),
5406
- signal: AbortSignal.timeout(3e4)
5407
- });
5408
- } catch (error) {
5409
- const message = error instanceof Error ? error.message : "Unknown error";
5410
- throw new Error(`Failed to connect to dashboard: ${message}`);
5411
- }
5412
- if (!response.ok) {
5413
- let errorMessage = `Dashboard returned ${response.status}`;
5414
- try {
5415
- const errorBody = await response.json();
5416
- if (errorBody.error) {
5417
- errorMessage = errorBody.error;
5418
- if (errorBody.details) {
5419
- errorMessage += ":\n" + errorBody.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
5420
- }
5421
- }
5422
- } catch {
5423
- }
5424
- throw new Error(errorMessage);
5425
- }
5426
- }
5427
- async function pushConfigAction(options) {
5428
- try {
5429
- const rawConfig = await loadConfigFile(options.config);
5430
- console.log("Validating config...");
5431
- const parseResult = riverbankSiteConfigSchema.safeParse(rawConfig);
5432
- if (!parseResult.success) {
5433
- console.error("Invalid config:");
5434
- for (const issue of parseResult.error.issues) {
5435
- console.error(` - ${issue.path.join(".")}: ${issue.message}`);
5436
- }
5437
- process.exit(1);
5438
- }
5439
- const conflicts = validateFieldIdConflicts(parseResult.data.blockFieldExtensions);
5440
- if (conflicts.length > 0) {
5441
- console.error("Field ID conflicts detected in blockFieldExtensions:");
5442
- for (const conflict of conflicts) {
5443
- console.error(` - ${conflict.message}`);
5444
- }
5445
- process.exit(1);
5446
- }
5447
- const { siteId } = parseResult.data;
5448
- const apiKey = resolveManagementApiKey(options.apiKey, options.isRemote);
5449
- await pushToDashboard(options.dashboard, siteId, apiKey, parseResult.data);
5450
- console.log("Config pushed successfully!");
5451
- } catch (error) {
5452
- console.error("Error:", error instanceof Error ? error.message : error);
5453
- process.exit(1);
5454
- }
5455
- }
5456
- function resolveDashboardUrl(cliOption, isRemote) {
5457
- const envVar = isRemote ? "RIVERBANK_REMOTE_DASHBOARD_URL" : "RIVERBANK_LOCAL_DASHBOARD_URL";
5458
- const url = cliOption || process.env[envVar];
5459
- if (!url) {
5460
- console.error("Error: Dashboard URL is required.");
5461
- console.error(`Provide --dashboard <url> or set ${envVar} environment variable.`);
5462
- process.exit(1);
5463
- }
5464
- return url;
5465
- }
5466
- function resolveManagementApiKey(cliOption, isRemote) {
5467
- const envVar = isRemote ? "RIVERBANK_REMOTE_MGMT_API_KEY" : "RIVERBANK_LOCAL_MGMT_API_KEY";
5468
- const apiKey = cliOption || process.env[envVar];
5469
- if (!apiKey) {
5470
- console.error("Error: Management API key is required.");
5471
- console.error(`Provide --api-key <key> or set ${envVar} environment variable.`);
5472
- process.exit(1);
5473
- }
5474
- if (!apiKey.startsWith("bld_mgmt_sk_")) {
5475
- console.error(`Error: Invalid management API key format for ${envVar}.`);
5476
- console.error("Expected key starting with bld_mgmt_sk_.");
5477
- process.exit(1);
5478
- }
5479
- return apiKey;
5480
- }
5481
- var pushConfigCommand = new commander.Command("push-config").description("Push SDK config to dashboard").option("--api-key <key>", "Management API key (or set RIVERBANK_*_MGMT_API_KEY)").option("--dashboard <url>", "Dashboard URL (or set RIVERBANK_*_DASHBOARD_URL env var)").option("--config <path>", "Path to config file (default: ./riverbank.config.ts)").addHelpText("after", `
5482
- Description:
5483
- Syncs your local riverbank.config.ts to the CMS dashboard, including:
5484
- - Custom blocks
5485
- - Block field extensions
5486
- - Block field options
5487
- - Content types, pages, entries, and navigation
5488
-
5489
- Examples:
5490
- $ npx riverbankcms push-config
5491
- $ npx riverbankcms push-config --api-key bld_mgmt_sk_... --dashboard https://www.riverbankcms.com
5492
- $ npx riverbankcms push-config --config ./src/riverbank.config.ts
5493
- `).action((options, command) => {
5494
- const globalOpts = command.optsWithGlobals();
5495
- const isRemote = globalOpts.remote ?? false;
5496
- const dashboard = resolveDashboardUrl(options.dashboard, isRemote);
5497
- return pushConfigAction({ ...options, dashboard, isRemote });
5498
- });
5499
5660
 
5500
5661
  // src/client/management/http.ts
5501
5662
  var ManagementApiError = class extends Error {
@@ -5507,11 +5668,14 @@ var ManagementApiError = class extends Error {
5507
5668
  this.statusCode = statusCode;
5508
5669
  }
5509
5670
  };
5671
+ function is404Error(error) {
5672
+ return error instanceof ManagementApiError && error.statusCode === 404;
5673
+ }
5510
5674
  function createHttpClient(config3) {
5511
5675
  const baseUrl = `${config3.dashboardUrl}/api/sdk/${config3.siteId}`;
5512
5676
  const timeout = config3.timeout ?? 3e4;
5513
- async function request(method, path11, options) {
5514
- let url = `${baseUrl}${path11}`;
5677
+ async function request(method, path13, options) {
5678
+ let url = `${baseUrl}${path13}`;
5515
5679
  if (options?.params && Object.keys(options.params).length > 0) {
5516
5680
  const searchParams = new URLSearchParams(options.params);
5517
5681
  url = `${url}?${searchParams.toString()}`;
@@ -5579,17 +5743,17 @@ function createHttpClient(config3) {
5579
5743
  return json.data;
5580
5744
  }
5581
5745
  return {
5582
- async get(path11, params) {
5583
- return request("GET", path11, { params });
5746
+ async get(path13, params) {
5747
+ return request("GET", path13, { params });
5584
5748
  },
5585
- async post(path11, body) {
5586
- return request("POST", path11, { body });
5749
+ async post(path13, body) {
5750
+ return request("POST", path13, { body });
5587
5751
  },
5588
- async patch(path11, body) {
5589
- return request("PATCH", path11, { body });
5752
+ async patch(path13, body) {
5753
+ return request("PATCH", path13, { body });
5590
5754
  },
5591
- async delete(path11, body) {
5592
- await request("DELETE", path11, { body });
5755
+ async delete(path13, body) {
5756
+ await request("DELETE", path13, { body });
5593
5757
  }
5594
5758
  };
5595
5759
  }
@@ -5612,7 +5776,7 @@ function createEntryOperations(http) {
5612
5776
  `/entries/${encodeURIComponent(contentType)}/${encodeURIComponent(identifier)}`
5613
5777
  );
5614
5778
  } catch (error) {
5615
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5779
+ if (is404Error(error)) {
5616
5780
  return null;
5617
5781
  }
5618
5782
  throw error;
@@ -5654,7 +5818,7 @@ function createPageOperations(http) {
5654
5818
  `/pages/${encodeURIComponent(identifier)}`
5655
5819
  );
5656
5820
  } catch (error) {
5657
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5821
+ if (is404Error(error)) {
5658
5822
  return null;
5659
5823
  }
5660
5824
  throw error;
@@ -5687,7 +5851,7 @@ function createBlockOperations(http) {
5687
5851
  `/pages/${encodeURIComponent(pageIdentifier)}/blocks/${encodeURIComponent(blockIdentifier)}`
5688
5852
  );
5689
5853
  } catch (error) {
5690
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5854
+ if (is404Error(error)) {
5691
5855
  return null;
5692
5856
  }
5693
5857
  throw error;
@@ -5726,7 +5890,7 @@ function createNavigationOperations(http) {
5726
5890
  `/navigation/${encodeURIComponent(name)}`
5727
5891
  );
5728
5892
  } catch (error) {
5729
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5893
+ if (is404Error(error)) {
5730
5894
  return null;
5731
5895
  }
5732
5896
  throw error;
@@ -5795,6 +5959,9 @@ function createPullOperations(http) {
5795
5959
  truncationMessage: entriesResult?.meta?.truncationMessage
5796
5960
  }
5797
5961
  };
5962
+ },
5963
+ async siteInfo() {
5964
+ return http.get("/pull/site-info");
5798
5965
  }
5799
5966
  };
5800
5967
  }
@@ -5817,43 +5984,164 @@ function createIdentifiersOperations(http) {
5817
5984
  };
5818
5985
  }
5819
5986
 
5820
- // src/client/management/index.ts
5821
- function createManagementClient(config3) {
5822
- if (!config3.dashboardUrl) {
5823
- throw new Error(
5824
- "dashboardUrl is required when creating a management client. Example: http://localhost:4000 or https://dashboard.riverbankcms.com"
5825
- );
5826
- }
5827
- if (!config3.managementApiKey) {
5828
- throw new Error(
5829
- "managementApiKey is required when creating a management client. A management API key starts with bld_mgmt_sk_"
5830
- );
5831
- }
5832
- if (!config3.managementApiKey.startsWith("bld_mgmt_sk_")) {
5833
- throw new Error(
5834
- "Invalid management API key format. A management API key must start with bld_mgmt_sk_"
5835
- );
5836
- }
5837
- if (!config3.siteId) {
5838
- throw new Error(
5839
- "siteId is required when creating a management client."
5840
- );
5987
+ // src/client/management/media.ts
5988
+ function createMediaOperations(config3) {
5989
+ const baseUrl = `${config3.dashboardUrl}/api/sdk/${config3.siteId}`;
5990
+ const timeout = config3.timeout ?? 6e4;
5991
+ async function formDataRequest(path13, formData) {
5992
+ const url = `${baseUrl}${path13}`;
5993
+ const headers = {
5994
+ "Authorization": `Bearer ${config3.managementApiKey}`
5995
+ // Note: Don't set Content-Type - let fetch set it with boundary
5996
+ };
5997
+ let response;
5998
+ try {
5999
+ response = await fetch(url, {
6000
+ method: "POST",
6001
+ headers,
6002
+ body: formData,
6003
+ signal: AbortSignal.timeout(timeout)
6004
+ });
6005
+ } catch (error) {
6006
+ if (error instanceof Error && error.name === "TimeoutError") {
6007
+ throw new ManagementApiError(
6008
+ `Request timed out after ${timeout}ms`,
6009
+ "sdk:timeout",
6010
+ void 0,
6011
+ void 0
6012
+ );
6013
+ }
6014
+ throw new ManagementApiError(
6015
+ error instanceof Error ? error.message : "Network request failed",
6016
+ "sdk:network-error",
6017
+ void 0,
6018
+ void 0
6019
+ );
6020
+ }
6021
+ const json = await response.json();
6022
+ if (!response.ok || !json.success) {
6023
+ if (json.error) {
6024
+ throw new ManagementApiError(
6025
+ json.error.message,
6026
+ json.error.code,
6027
+ json.error.details,
6028
+ response.status
6029
+ );
6030
+ }
6031
+ throw new ManagementApiError(
6032
+ `Request failed with status ${response.status}`,
6033
+ "sdk:http-error",
6034
+ void 0,
6035
+ response.status
6036
+ );
6037
+ }
6038
+ return json.data;
5841
6039
  }
5842
- const http = createHttpClient(config3);
5843
- return {
5844
- entries: createEntryOperations(http),
5845
- pages: createPageOperations(http),
5846
- blocks: createBlockOperations(http),
5847
- navigation: createNavigationOperations(http),
5848
- settings: createSettingsOperations(http),
5849
- pull: createPullOperations(http),
5850
- preview: createPreviewOperations(http),
5851
- identifiers: createIdentifiersOperations(http)
5852
- };
5853
- }
5854
-
5855
- // src/cli/env.ts
5856
- function getEnvPrefix(target) {
6040
+ async function jsonRequest(path13, body) {
6041
+ const url = `${baseUrl}${path13}`;
6042
+ let response;
6043
+ try {
6044
+ response = await fetch(url, {
6045
+ method: "POST",
6046
+ headers: {
6047
+ "Authorization": `Bearer ${config3.managementApiKey}`,
6048
+ "Content-Type": "application/json"
6049
+ },
6050
+ body: JSON.stringify(body),
6051
+ signal: AbortSignal.timeout(3e4)
6052
+ });
6053
+ } catch (error) {
6054
+ if (error instanceof Error && error.name === "TimeoutError") {
6055
+ throw new ManagementApiError(
6056
+ "Request timed out after 30000ms",
6057
+ "sdk:timeout",
6058
+ void 0,
6059
+ void 0
6060
+ );
6061
+ }
6062
+ throw new ManagementApiError(
6063
+ error instanceof Error ? error.message : "Network request failed",
6064
+ "sdk:network-error",
6065
+ void 0,
6066
+ void 0
6067
+ );
6068
+ }
6069
+ const json = await response.json();
6070
+ if (!response.ok || !json.success) {
6071
+ if (json.error) {
6072
+ throw new ManagementApiError(
6073
+ json.error.message,
6074
+ json.error.code,
6075
+ json.error.details,
6076
+ response.status
6077
+ );
6078
+ }
6079
+ throw new ManagementApiError(
6080
+ `Request failed with status ${response.status}`,
6081
+ "sdk:http-error",
6082
+ void 0,
6083
+ response.status
6084
+ );
6085
+ }
6086
+ return json.data;
6087
+ }
6088
+ return {
6089
+ async upload(file) {
6090
+ const formData = new FormData();
6091
+ const blob = new Blob([new Uint8Array(file.data)], { type: file.contentType });
6092
+ formData.append("file", blob, file.filename);
6093
+ formData.append("storagePath", file.storagePath);
6094
+ return formDataRequest("/media/upload", formData);
6095
+ },
6096
+ async exists(storagePath) {
6097
+ const result = await jsonRequest("/media/exists", { storagePath });
6098
+ return result.exists;
6099
+ }
6100
+ };
6101
+ }
6102
+
6103
+ // src/client/management/index.ts
6104
+ function createManagementClient(config3) {
6105
+ if (!config3.dashboardUrl) {
6106
+ throw new Error(
6107
+ "dashboardUrl is required when creating a management client. Example: http://localhost:4000 or https://dashboard.riverbankcms.com"
6108
+ );
6109
+ }
6110
+ if (!config3.managementApiKey) {
6111
+ throw new Error(
6112
+ "managementApiKey is required when creating a management client. A management API key starts with bld_mgmt_sk_"
6113
+ );
6114
+ }
6115
+ if (!config3.managementApiKey.startsWith("bld_mgmt_sk_")) {
6116
+ throw new Error(
6117
+ "Invalid management API key format. A management API key must start with bld_mgmt_sk_"
6118
+ );
6119
+ }
6120
+ if (!config3.siteId) {
6121
+ throw new Error(
6122
+ "siteId is required when creating a management client."
6123
+ );
6124
+ }
6125
+ const http = createHttpClient(config3);
6126
+ return {
6127
+ entries: createEntryOperations(http),
6128
+ pages: createPageOperations(http),
6129
+ blocks: createBlockOperations(http),
6130
+ navigation: createNavigationOperations(http),
6131
+ settings: createSettingsOperations(http),
6132
+ pull: createPullOperations(http),
6133
+ preview: createPreviewOperations(http),
6134
+ identifiers: createIdentifiersOperations(http),
6135
+ media: createMediaOperations(config3)
6136
+ };
6137
+ }
6138
+
6139
+ // src/cli/env.ts
6140
+ function getEnvVarName(key, remote) {
6141
+ const target = remote ? "REMOTE" : "LOCAL";
6142
+ return `RIVERBANK_${target}_${key}`;
6143
+ }
6144
+ function getEnvPrefix(target) {
5857
6145
  return target === "remote" ? "RIVERBANK_REMOTE" : "RIVERBANK_LOCAL";
5858
6146
  }
5859
6147
  function requireEnv(name) {
@@ -5863,12 +6151,16 @@ function requireEnv(name) {
5863
6151
  }
5864
6152
  return value;
5865
6153
  }
6154
+ function getEnv(name, fallback) {
6155
+ return process.env[name] ?? fallback;
6156
+ }
5866
6157
  function loadEnvironment(remote) {
5867
6158
  const target = remote ? "remote" : "local";
5868
6159
  const prefix = getEnvPrefix(target);
5869
6160
  const siteId = requireEnv(`${prefix}_SITE_ID`);
5870
6161
  const dashboardUrl = requireEnv(`${prefix}_DASHBOARD_URL`);
5871
6162
  const managementApiKey = requireEnv(`${prefix}_MGMT_API_KEY`);
6163
+ const supabaseUrl = getEnv(`${prefix}_SUPABASE_URL`);
5872
6164
  if (!managementApiKey.startsWith("bld_mgmt_sk_")) {
5873
6165
  throw new Error(
5874
6166
  `Invalid management API key format for ${prefix}_MGMT_API_KEY. Expected key starting with bld_mgmt_sk_`
@@ -5881,10 +6173,20 @@ function loadEnvironment(remote) {
5881
6173
  `Invalid dashboard URL in ${prefix}_DASHBOARD_URL: ${dashboardUrl}. Expected format: http://localhost:4000 or https://dashboard.example.com`
5882
6174
  );
5883
6175
  }
6176
+ if (supabaseUrl) {
6177
+ try {
6178
+ new URL(supabaseUrl);
6179
+ } catch {
6180
+ throw new Error(
6181
+ `Invalid Supabase URL in ${prefix}_SUPABASE_URL: ${supabaseUrl}. Expected format: http://127.0.0.1:54321 or https://xxx.supabase.co`
6182
+ );
6183
+ }
6184
+ }
5884
6185
  return {
5885
6186
  siteId,
5886
6187
  dashboardUrl,
5887
- managementApiKey
6188
+ managementApiKey,
6189
+ supabaseUrl
5888
6190
  };
5889
6191
  }
5890
6192
 
@@ -6261,6 +6563,112 @@ function createListCommand(config3) {
6261
6563
  );
6262
6564
  }
6263
6565
 
6566
+ // src/cli/push-config.ts
6567
+ async function pushToDashboard(output, dashboardUrl, siteId, apiKey, config3) {
6568
+ const pushUrl = `${dashboardUrl}/api/sites/${siteId}/sdk-config`;
6569
+ output.info(`Pushing config to ${pushUrl}...`);
6570
+ let response;
6571
+ try {
6572
+ response = await fetch(pushUrl, {
6573
+ method: "POST",
6574
+ headers: {
6575
+ "Content-Type": "application/json",
6576
+ "Authorization": `Bearer ${apiKey}`
6577
+ },
6578
+ body: JSON.stringify({ config: config3 }),
6579
+ signal: AbortSignal.timeout(3e4)
6580
+ });
6581
+ } catch (error) {
6582
+ const message = error instanceof Error ? error.message : "Unknown error";
6583
+ throw new Error(`Failed to connect to dashboard: ${message}`);
6584
+ }
6585
+ if (!response.ok) {
6586
+ let errorMessage = `Dashboard returned ${response.status}`;
6587
+ try {
6588
+ const errorBody = await response.json();
6589
+ if (errorBody.error) {
6590
+ errorMessage = errorBody.error;
6591
+ if (errorBody.details) {
6592
+ errorMessage += ":\n" + errorBody.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
6593
+ }
6594
+ }
6595
+ } catch {
6596
+ }
6597
+ throw new Error(errorMessage);
6598
+ }
6599
+ }
6600
+ async function pushConfigAction(output, options) {
6601
+ try {
6602
+ const rawConfig = await loadConfigFile(options.config);
6603
+ output.info("Validating config...");
6604
+ const parseResult = riverbankSiteConfigSchema.safeParse(rawConfig);
6605
+ if (!parseResult.success) {
6606
+ output.error("Invalid config", {
6607
+ issues: parseResult.error.issues.map((issue) => ({
6608
+ path: issue.path.join("."),
6609
+ message: issue.message
6610
+ }))
6611
+ });
6612
+ }
6613
+ const conflicts = validateFieldIdConflicts(parseResult.data.blockFieldExtensions);
6614
+ if (conflicts.length > 0) {
6615
+ output.error("Field ID conflicts detected in blockFieldExtensions", {
6616
+ conflicts: conflicts.map((c) => c.message)
6617
+ });
6618
+ }
6619
+ const { siteId } = parseResult.data;
6620
+ const apiKey = resolveManagementApiKey(output, options.apiKey, options.isRemote);
6621
+ await pushToDashboard(output, options.dashboard, siteId, apiKey, parseResult.data);
6622
+ output.success("Config pushed successfully!");
6623
+ } catch (error) {
6624
+ const message = error instanceof Error ? error.message : String(error);
6625
+ output.error(message);
6626
+ }
6627
+ }
6628
+ function resolveDashboardUrl(output, cliOption, isRemote) {
6629
+ const envVar = getEnvVarName("DASHBOARD_URL", isRemote);
6630
+ const url = cliOption || process.env[envVar];
6631
+ if (!url) {
6632
+ output.error("Dashboard URL is required", {
6633
+ suggestion: `Provide --dashboard <url> or set ${envVar} environment variable.`
6634
+ });
6635
+ }
6636
+ return url;
6637
+ }
6638
+ function resolveManagementApiKey(output, cliOption, isRemote) {
6639
+ const envVar = getEnvVarName("MGMT_API_KEY", isRemote);
6640
+ const apiKey = cliOption || process.env[envVar];
6641
+ if (!apiKey) {
6642
+ output.error("Management API key is required", {
6643
+ suggestion: `Provide --api-key <key> or set ${envVar} environment variable.`
6644
+ });
6645
+ }
6646
+ if (!apiKey.startsWith("bld_mgmt_sk_")) {
6647
+ output.error("Invalid management API key format", {
6648
+ expected: "Key starting with bld_mgmt_sk_",
6649
+ suggestion: `Check your ${envVar} environment variable.`
6650
+ });
6651
+ }
6652
+ return apiKey;
6653
+ }
6654
+ var pushConfigCommand = new commander.Command("push-config").description("Push SDK config to dashboard").option("--api-key <key>", "Management API key (or set RIVERBANK_*_MGMT_API_KEY)").option("--dashboard <url>", "Dashboard URL (or set RIVERBANK_*_DASHBOARD_URL env var)").option("--config <path>", "Path to config file (default: ./riverbank.config.ts)").addHelpText("after", `
6655
+ Description:
6656
+ Syncs your local riverbank.config.ts to the CMS dashboard, including:
6657
+ - Custom blocks
6658
+ - Block field extensions
6659
+ - Block field options
6660
+ - Content types, pages, entries, and navigation
6661
+
6662
+ Examples:
6663
+ $ npx riverbankcms push-config
6664
+ $ npx riverbankcms push-config --api-key bld_mgmt_sk_... --dashboard https://www.riverbankcms.com
6665
+ $ npx riverbankcms push-config --config ./src/riverbank.config.ts
6666
+ `).action((options, command) => {
6667
+ const { output, isRemote } = getOutputContext(command);
6668
+ const dashboard = resolveDashboardUrl(output, options.dashboard, isRemote);
6669
+ return pushConfigAction(output, { ...options, dashboard, isRemote });
6670
+ });
6671
+
6264
6672
  // src/cli/sync/mapper.ts
6265
6673
  function stripNavigationItemIds(items) {
6266
6674
  return items.map((item) => {
@@ -6364,6 +6772,53 @@ async function writeSettings(contentDir, pulledSettings) {
6364
6772
  await writeJsonFile(filePath, pulledSettings.settings);
6365
6773
  return filePath;
6366
6774
  }
6775
+ async function updateMetadataAfterPush(contentDir, remoteContent) {
6776
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6777
+ await ensureDir(metaDir);
6778
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6779
+ for (const [contentType, entries] of Object.entries(remoteContent.entries)) {
6780
+ const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
6781
+ const entriesMeta = {};
6782
+ for (const entry of entries) {
6783
+ entriesMeta[entry.identifier] = {
6784
+ createdAt: entry.createdAt,
6785
+ updatedAt: entry.updatedAt
6786
+ };
6787
+ }
6788
+ await writeJsonFile(metaPath, {
6789
+ pulledAt: now,
6790
+ entries: entriesMeta
6791
+ });
6792
+ }
6793
+ if (remoteContent.pages.length > 0) {
6794
+ const metaPath = path9__namespace.join(metaDir, "pages.json");
6795
+ const pagesMeta = {};
6796
+ for (const page of remoteContent.pages) {
6797
+ pagesMeta[page.identifier] = {
6798
+ createdAt: page.createdAt,
6799
+ updatedAt: page.updatedAt
6800
+ };
6801
+ }
6802
+ await writeJsonFile(metaPath, {
6803
+ pulledAt: now,
6804
+ pages: pagesMeta
6805
+ });
6806
+ }
6807
+ if (remoteContent.navigation.length > 0) {
6808
+ const metaPath = path9__namespace.join(metaDir, "navigation.json");
6809
+ const menusMeta = {};
6810
+ for (const menu of remoteContent.navigation) {
6811
+ menusMeta[menu.name] = {
6812
+ createdAt: menu.createdAt,
6813
+ updatedAt: menu.updatedAt
6814
+ };
6815
+ }
6816
+ await writeJsonFile(metaPath, {
6817
+ pulledAt: now,
6818
+ menus: menusMeta
6819
+ });
6820
+ }
6821
+ }
6367
6822
  async function fileExists(filePath) {
6368
6823
  try {
6369
6824
  await fs3__namespace.access(filePath);
@@ -6512,6 +6967,107 @@ async function readNavigationMeta(contentDir) {
6512
6967
  }
6513
6968
  }
6514
6969
 
6970
+ // src/cli/sync/media.ts
6971
+ var DEFAULT_RETRY_CONFIG = {
6972
+ maxRetries: 3,
6973
+ baseDelayMs: 1e3,
6974
+ // 1 second
6975
+ maxDelayMs: 1e4
6976
+ // 10 seconds
6977
+ };
6978
+ function sleep(ms) {
6979
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
6980
+ }
6981
+ function getBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
6982
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
6983
+ const jitter = exponentialDelay * (0.75 + Math.random() * 0.5);
6984
+ return Math.min(jitter, maxDelayMs);
6985
+ }
6986
+ async function withRetry(fn, options = {}) {
6987
+ const { maxRetries, baseDelayMs, maxDelayMs } = { ...DEFAULT_RETRY_CONFIG, ...options };
6988
+ let lastError;
6989
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
6990
+ try {
6991
+ return await fn();
6992
+ } catch (error) {
6993
+ lastError = error instanceof Error ? error : new Error(String(error));
6994
+ if (attempt < maxRetries) {
6995
+ const delay = getBackoffDelay(attempt, baseDelayMs, maxDelayMs);
6996
+ options.onRetry?.(attempt + 1, lastError);
6997
+ await sleep(delay);
6998
+ }
6999
+ }
7000
+ }
7001
+ throw lastError;
7002
+ }
7003
+ function extractMediaPaths(data) {
7004
+ const paths = /* @__PURE__ */ new Set();
7005
+ function walk(value) {
7006
+ if (value === null || value === void 0) {
7007
+ return;
7008
+ }
7009
+ if (Array.isArray(value)) {
7010
+ for (const item of value) {
7011
+ walk(item);
7012
+ }
7013
+ return;
7014
+ }
7015
+ if (typeof value === "object") {
7016
+ const obj = value;
7017
+ if (typeof obj.storagePath === "string" && obj.storagePath) {
7018
+ paths.add(obj.storagePath);
7019
+ }
7020
+ for (const key of Object.keys(obj)) {
7021
+ walk(obj[key]);
7022
+ }
7023
+ }
7024
+ }
7025
+ walk(data);
7026
+ return paths;
7027
+ }
7028
+ function buildStorageUrl(supabaseUrl, relativePath, siteId, bucket = "media") {
7029
+ const baseUrl = supabaseUrl.replace(/\/$/, "");
7030
+ const fullPath = `sites/${siteId}/${relativePath}`;
7031
+ return `${baseUrl}/storage/v1/object/public/${bucket}/${fullPath}`;
7032
+ }
7033
+ async function downloadMedia(url, options) {
7034
+ try {
7035
+ return await withRetry(
7036
+ async () => {
7037
+ const response = await fetch(url);
7038
+ if (!response.ok) {
7039
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
7040
+ throw new DownloadError(`HTTP ${response.status}`, response.status, false);
7041
+ }
7042
+ throw new DownloadError(`HTTP ${response.status}`, response.status, true);
7043
+ }
7044
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
7045
+ const arrayBuffer = await response.arrayBuffer();
7046
+ const data = Buffer.from(arrayBuffer);
7047
+ return { data, contentType };
7048
+ },
7049
+ {
7050
+ maxRetries: options?.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
7051
+ onRetry: (attempt, error) => {
7052
+ console.warn(`[media-sync] Retry ${attempt} for ${url}: ${error.message}`);
7053
+ }
7054
+ }
7055
+ );
7056
+ } catch (error) {
7057
+ const message = error instanceof Error ? error.message : String(error);
7058
+ console.error(`[media-sync] Download failed after retries: ${url} - ${message}`);
7059
+ return null;
7060
+ }
7061
+ }
7062
+ var DownloadError = class extends Error {
7063
+ constructor(message, status, retryable) {
7064
+ super(message);
7065
+ this.status = status;
7066
+ this.retryable = retryable;
7067
+ this.name = "DownloadError";
7068
+ }
7069
+ };
7070
+
6515
7071
  // src/cli/commands/pull.ts
6516
7072
  var DEFAULT_PAGE_LIMIT = 500;
6517
7073
  async function pullEntriesWithPagination(client, contentType, output) {
@@ -6596,21 +7152,105 @@ async function fetchAllContentPaginated(client, contentTypes, output) {
6596
7152
  meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: allMeta }
6597
7153
  };
6598
7154
  }
6599
- var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").option("--force", "Overwrite existing files without prompting").option("--yes", "Skip confirmation prompt (same as --force)").addHelpText("after", `
7155
+ async function syncMediaFiles(content, sourceSupabaseUrl, sourceSiteId, targetSiteId, targetClient, output) {
7156
+ const mediaPaths = extractMediaPaths(content);
7157
+ if (mediaPaths.size === 0) {
7158
+ output.info("No media files found in content");
7159
+ return;
7160
+ }
7161
+ output.info(`Found ${mediaPaths.size} media files to sync`);
7162
+ let synced = 0;
7163
+ let skipped = 0;
7164
+ let failed = 0;
7165
+ for (const relativePath of mediaPaths) {
7166
+ const filename = relativePath.split("/").pop() ?? "file";
7167
+ const targetPath = `sites/${targetSiteId}/${relativePath}`;
7168
+ try {
7169
+ const exists = await targetClient.media.exists(targetPath);
7170
+ if (exists) {
7171
+ skipped++;
7172
+ continue;
7173
+ }
7174
+ } catch (error) {
7175
+ if (process.env.DEBUG) {
7176
+ const message = error instanceof Error ? error.message : "Unknown error";
7177
+ console.warn(`[media-sync] Could not check if ${filename} exists: ${message}`);
7178
+ }
7179
+ }
7180
+ const url = buildStorageUrl(sourceSupabaseUrl, relativePath, sourceSiteId);
7181
+ const downloaded = await downloadMedia(url);
7182
+ if (!downloaded) {
7183
+ output.warn(` Failed to download: ${filename}`);
7184
+ failed++;
7185
+ continue;
7186
+ }
7187
+ try {
7188
+ await targetClient.media.upload({
7189
+ data: downloaded.data,
7190
+ filename,
7191
+ contentType: downloaded.contentType,
7192
+ storagePath: targetPath
7193
+ });
7194
+ synced++;
7195
+ } catch (error) {
7196
+ output.warn(` Failed to upload: ${filename}`);
7197
+ failed++;
7198
+ }
7199
+ }
7200
+ output.info(`Media sync: ${synced} synced, ${skipped} skipped (already exist), ${failed} failed`);
7201
+ }
7202
+ var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").option("--force", "Overwrite existing files without prompting").option("--yes", "Skip confirmation prompt (same as --force)").option("--sync-media", "Sync media files from source to target environment").addHelpText("after", `
6600
7203
  Examples:
6601
7204
  $ riverbankcms pull # Pull all content
6602
7205
  $ riverbankcms pull --remote # Pull from production
7206
+ $ riverbankcms pull --remote --sync-media # Pull from production and sync media to local
6603
7207
  $ riverbankcms pull entries # Pull all entries
6604
7208
  $ riverbankcms pull entries blog-post # Pull specific content type
6605
7209
  $ riverbankcms pull pages # Pull pages with blocks
6606
7210
  $ riverbankcms pull navigation # Pull navigation menus
6607
7211
  $ riverbankcms pull settings # Pull site settings
6608
7212
  $ riverbankcms pull --output ./src/content # Custom output directory
7213
+
7214
+ Media Sync:
7215
+ When using --sync-media, media files are:
7216
+ 1. Downloaded from the source environment's Supabase storage
7217
+ 2. Uploaded to the target environment via the management API
7218
+
7219
+ The storage URL is automatically fetched from the source CMS API.
7220
+ No additional environment variables are required beyond the standard
7221
+ RIVERBANK_*_DASHBOARD_URL, RIVERBANK_*_SITE_ID, and RIVERBANK_*_MGMT_API_KEY.
6609
7222
  `).action(
6610
7223
  withErrorHandling(
6611
7224
  async (scope, type, options, command) => {
6612
- const { output, client } = createCommandContext(command);
7225
+ const { output, client, isRemote } = createCommandContext(command);
6613
7226
  const contentDir = path9__namespace.resolve(options.output ?? "./content");
7227
+ let targetClient = null;
7228
+ let sourceSupabaseUrl = null;
7229
+ let sourceSiteId = null;
7230
+ let targetSiteId = null;
7231
+ if (options.syncMedia) {
7232
+ const sourceEnv = loadEnvironment(isRemote);
7233
+ const targetEnv = loadEnvironment(!isRemote);
7234
+ output.info("Fetching storage configuration from source...");
7235
+ try {
7236
+ const siteInfo = await client.pull.siteInfo();
7237
+ sourceSupabaseUrl = siteInfo.supabaseUrl;
7238
+ } catch (error) {
7239
+ const message = error instanceof Error ? error.message : "Unknown error";
7240
+ output.error(`Failed to get storage configuration from source: ${message}`, {
7241
+ suggestion: "Ensure the source CMS is running and accessible"
7242
+ });
7243
+ return;
7244
+ }
7245
+ sourceSiteId = sourceEnv.siteId;
7246
+ targetSiteId = targetEnv.siteId;
7247
+ targetClient = createManagementClient({
7248
+ dashboardUrl: targetEnv.dashboardUrl,
7249
+ managementApiKey: targetEnv.managementApiKey,
7250
+ siteId: targetEnv.siteId
7251
+ });
7252
+ output.info(`Media sync enabled: ${isRemote ? "remote" : "local"} -> ${isRemote ? "local" : "remote"}`);
7253
+ }
6614
7254
  if (await contentDirExists(contentDir) && !options.force && !options.yes) {
6615
7255
  if (!process.stdin.isTTY) {
6616
7256
  output.error("Content directory already exists and --yes not specified", {
@@ -6693,6 +7333,17 @@ Examples:
6693
7333
  const contentTypes = Object.keys(result.entries);
6694
7334
  result = await fetchAllContentPaginated(client, contentTypes, output);
6695
7335
  }
7336
+ if (targetClient && sourceSupabaseUrl && sourceSiteId && targetSiteId) {
7337
+ output.info("Syncing media files...");
7338
+ await syncMediaFiles(
7339
+ result,
7340
+ sourceSupabaseUrl,
7341
+ sourceSiteId,
7342
+ targetSiteId,
7343
+ targetClient,
7344
+ output
7345
+ );
7346
+ }
6696
7347
  const { totalCount: totalEntries } = await writeAllEntries(
6697
7348
  contentDir,
6698
7349
  result.entries,
@@ -6748,11 +7399,11 @@ function findChangedFields(local, remote, prefix = "") {
6748
7399
  const changes = [];
6749
7400
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(local), ...Object.keys(remote)]);
6750
7401
  for (const key of allKeys) {
6751
- const path11 = prefix ? `${prefix}.${key}` : key;
7402
+ const path13 = prefix ? `${prefix}.${key}` : key;
6752
7403
  const localVal = local[key];
6753
7404
  const remoteVal = remote[key];
6754
7405
  if (!equal__default.default(localVal, remoteVal)) {
6755
- changes.push(path11);
7406
+ changes.push(path13);
6756
7407
  }
6757
7408
  }
6758
7409
  return changes;
@@ -7691,6 +8342,43 @@ Safety:
7691
8342
  output
7692
8343
  );
7693
8344
  reportSyncResults(output, result, result.errors.length > 0);
8345
+ if (result.errors.length === 0) {
8346
+ try {
8347
+ output.info("Updating local metadata...");
8348
+ const freshRemote = await client.pull.all();
8349
+ const entriesForMeta = {};
8350
+ if (freshRemote.meta.entries) {
8351
+ for (const [key, meta] of Object.entries(freshRemote.meta.entries)) {
8352
+ const [contentType, identifier] = key.split(":");
8353
+ if (contentType && identifier) {
8354
+ if (!entriesForMeta[contentType]) {
8355
+ entriesForMeta[contentType] = [];
8356
+ }
8357
+ entriesForMeta[contentType].push({
8358
+ identifier,
8359
+ updatedAt: meta.updatedAt,
8360
+ createdAt: meta.createdAt
8361
+ });
8362
+ }
8363
+ }
8364
+ }
8365
+ await updateMetadataAfterPush(contentDir, {
8366
+ entries: entriesForMeta,
8367
+ pages: freshRemote.pages.map((p) => ({
8368
+ identifier: p.identifier,
8369
+ updatedAt: p.updatedAt,
8370
+ createdAt: p.createdAt
8371
+ })),
8372
+ navigation: freshRemote.navigation.map((n) => ({
8373
+ name: n.name,
8374
+ updatedAt: n.updatedAt,
8375
+ createdAt: n.createdAt
8376
+ }))
8377
+ });
8378
+ } catch (metaError) {
8379
+ output.warn('Push succeeded but metadata update failed. Run "pull" to sync metadata.');
8380
+ }
8381
+ }
7694
8382
  } catch (error) {
7695
8383
  handleCommandError(error, output);
7696
8384
  }
@@ -8505,7 +9193,7 @@ function getContentDir() {
8505
9193
  function loadTemplate(name) {
8506
9194
  const contentDir = getContentDir();
8507
9195
  const filePath = path9__namespace.join(contentDir, `${name}.md`);
8508
- return fs.readFileSync(filePath, "utf-8");
9196
+ return fs6.readFileSync(filePath, "utf-8");
8509
9197
  }
8510
9198
  async function initDocs(options) {
8511
9199
  const { rootDir, configPath, agentsPath } = options;
@@ -8692,6 +9380,3407 @@ Examples:
8692
9380
  $ riverbankcms identifiers backfill
8693
9381
  `).addCommand(backfillCommand);
8694
9382
 
9383
+ // src/cli/commands/deploy.ts
9384
+ init_load_config();
9385
+
9386
+ // ../api/src/endpoints.ts
9387
+ var ENDPOINT_DEFINITIONS = {
9388
+ // AI endpoints - no cache due to dynamic nature
9389
+ aiContentUpdateChat: {
9390
+ path: "/ai/content-update-chat",
9391
+ method: "POST",
9392
+ auth: "user",
9393
+ responseKind: "stream"
9394
+ },
9395
+ aiChat: {
9396
+ path: "/ai/chat",
9397
+ method: "POST",
9398
+ auth: "user",
9399
+ responseKind: "stream"
9400
+ },
9401
+ applySeoChanges: {
9402
+ path: "/seo/apply",
9403
+ method: "POST",
9404
+ auth: "user",
9405
+ responseKind: "json"
9406
+ },
9407
+ aiCreateBriefChat: {
9408
+ path: "/ai/chat/create-brief",
9409
+ method: "POST",
9410
+ auth: "user",
9411
+ responseKind: "json"
9412
+ },
9413
+ aiPrototypeChat: {
9414
+ path: "/ai/chat/create-prototype",
9415
+ method: "POST",
9416
+ revalidate: 30,
9417
+ // Short cache for AI responses to avoid duplicate calls
9418
+ tags: ["ai-prototype"],
9419
+ auth: "user",
9420
+ responseKind: "json"
9421
+ },
9422
+ aiPatchDryRun: {
9423
+ path: "/ai/patch/dry-run",
9424
+ method: "POST",
9425
+ auth: "user",
9426
+ responseKind: "json"
9427
+ },
9428
+ aiPatchApply: {
9429
+ path: "/ai/patch/apply",
9430
+ method: "POST",
9431
+ auth: "user",
9432
+ responseKind: "json"
9433
+ },
9434
+ aiPlaygroundPropose: {
9435
+ path: "/sites/{siteId}/ai/playground/propose",
9436
+ method: "POST",
9437
+ auth: "user",
9438
+ responseKind: "json"
9439
+ },
9440
+ // Admin SEO
9441
+ listGscPropertiesAdmin: {
9442
+ path: "/admin/seo/gsc/properties",
9443
+ method: "GET",
9444
+ auth: "admin",
9445
+ responseKind: "json"
9446
+ },
9447
+ adminSetGscPersist: {
9448
+ path: "/admin/seo/gsc/meta/persist",
9449
+ method: "POST",
9450
+ auth: "admin",
9451
+ responseKind: "json"
9452
+ },
9453
+ adminStartGscVerification: {
9454
+ path: "/admin/seo/gsc/properties/verify/start",
9455
+ method: "POST",
9456
+ auth: "admin",
9457
+ responseKind: "json"
9458
+ },
9459
+ adminConfirmGscVerification: {
9460
+ path: "/admin/seo/gsc/properties/verify/confirm",
9461
+ method: "POST",
9462
+ auth: "admin",
9463
+ responseKind: "json"
9464
+ },
9465
+ adminRunGscIngest: {
9466
+ path: "/admin/seo/ingest/run",
9467
+ method: "POST",
9468
+ auth: "admin",
9469
+ responseKind: "json"
9470
+ },
9471
+ getSeoVerificationMeta: {
9472
+ path: "/public/seo/verification/meta",
9473
+ method: "GET",
9474
+ auth: "public",
9475
+ responseKind: "json"
9476
+ },
9477
+ checkRedirect: {
9478
+ path: "/api/public/content/redirect",
9479
+ method: "GET",
9480
+ revalidate: 86400,
9481
+ // 24 hours - redirects rarely change
9482
+ tags: ["redirect"],
9483
+ auth: "public",
9484
+ responseKind: "json"
9485
+ },
9486
+ listRedirectRules: {
9487
+ path: "/sites/{siteId}/redirects",
9488
+ method: "GET",
9489
+ auth: "user",
9490
+ responseKind: "json"
9491
+ },
9492
+ createRedirectRule: {
9493
+ path: "/sites/{siteId}/redirects",
9494
+ method: "POST",
9495
+ auth: "user",
9496
+ responseKind: "json"
9497
+ },
9498
+ deleteRedirectRule: {
9499
+ path: "/sites/{siteId}/redirects/{ruleId}",
9500
+ method: "DELETE",
9501
+ auth: "user",
9502
+ responseKind: "json"
9503
+ },
9504
+ // API Keys (Account-level - DEPRECATED, use site-scoped endpoints)
9505
+ listApiKeys: {
9506
+ path: "/account/api-keys",
9507
+ method: "GET",
9508
+ auth: "user",
9509
+ responseKind: "json"
9510
+ },
9511
+ createApiKey: {
9512
+ path: "/account/api-keys",
9513
+ method: "POST",
9514
+ auth: "user",
9515
+ responseKind: "json"
9516
+ },
9517
+ revokeApiKey: {
9518
+ path: "/account/api-keys/{keyId}",
9519
+ method: "DELETE",
9520
+ auth: "user",
9521
+ responseKind: "json"
9522
+ },
9523
+ // API Keys (Site-scoped - preferred)
9524
+ listSiteApiKeys: {
9525
+ path: "/sites/{siteId}/api-keys",
9526
+ method: "GET",
9527
+ auth: "user",
9528
+ tags: ["site-{siteId}", "api-keys"],
9529
+ responseKind: "json"
9530
+ },
9531
+ createSiteApiKey: {
9532
+ path: "/sites/{siteId}/api-keys",
9533
+ method: "POST",
9534
+ auth: "user",
9535
+ tags: ["site-{siteId}", "api-keys"],
9536
+ responseKind: "json"
9537
+ },
9538
+ revokeSiteApiKey: {
9539
+ path: "/sites/{siteId}/api-keys/{keyId}",
9540
+ method: "DELETE",
9541
+ auth: "user",
9542
+ tags: ["site-{siteId}", "api-keys"],
9543
+ responseKind: "json"
9544
+ },
9545
+ getSitePreviewKey: {
9546
+ path: "/sites/{siteId}/api-keys/preview",
9547
+ method: "GET",
9548
+ auth: "user",
9549
+ tags: ["site-{siteId}", "api-keys", "preview-key"],
9550
+ responseKind: "json"
9551
+ },
9552
+ regenerateSitePreviewKey: {
9553
+ path: "/sites/{siteId}/api-keys/preview/regenerate",
9554
+ method: "POST",
9555
+ auth: "user",
9556
+ tags: ["site-{siteId}", "api-keys", "preview-key"],
9557
+ responseKind: "json"
9558
+ },
9559
+ getSiteApiKeyAccessLogs: {
9560
+ path: "/sites/{siteId}/api-keys/access-logs",
9561
+ method: "GET",
9562
+ auth: "user",
9563
+ tags: ["site-{siteId}", "api-keys", "access-logs"],
9564
+ responseKind: "json"
9565
+ },
9566
+ // Management API Keys (SDK write operations)
9567
+ listManagementKeys: {
9568
+ path: "/sites/{siteId}/api-keys/management",
9569
+ method: "GET",
9570
+ auth: "user",
9571
+ tags: ["site-{siteId}", "api-keys", "management-keys"],
9572
+ responseKind: "json"
9573
+ },
9574
+ createManagementKey: {
9575
+ path: "/sites/{siteId}/api-keys/management",
9576
+ method: "POST",
9577
+ auth: "user",
9578
+ tags: ["site-{siteId}", "api-keys", "management-keys"],
9579
+ responseKind: "json"
9580
+ },
9581
+ revokeManagementKey: {
9582
+ path: "/sites/{siteId}/api-keys/management",
9583
+ method: "DELETE",
9584
+ auth: "user",
9585
+ tags: ["site-{siteId}", "api-keys", "management-keys"],
9586
+ responseKind: "json"
9587
+ },
9588
+ getBookingSettings: {
9589
+ path: "/sites/{siteId}/bookings/settings",
9590
+ method: "GET",
9591
+ auth: "user",
9592
+ responseKind: "json"
9593
+ },
9594
+ updateBookingSettings: {
9595
+ path: "/sites/{siteId}/bookings/settings",
9596
+ method: "PUT",
9597
+ auth: "user",
9598
+ responseKind: "json"
9599
+ },
9600
+ listAppointmentResources: {
9601
+ path: "/sites/{siteId}/bookings/resources",
9602
+ method: "GET",
9603
+ auth: "user",
9604
+ responseKind: "json"
9605
+ },
9606
+ createAppointmentResource: {
9607
+ path: "/sites/{siteId}/bookings/resources",
9608
+ method: "POST",
9609
+ auth: "user",
9610
+ responseKind: "json"
9611
+ },
9612
+ getAppointmentResource: {
9613
+ path: "/sites/{siteId}/bookings/resources/{resourceId}",
9614
+ method: "GET",
9615
+ auth: "user",
9616
+ responseKind: "json"
9617
+ },
9618
+ updateAppointmentResource: {
9619
+ path: "/sites/{siteId}/bookings/resources/{resourceId}",
9620
+ method: "PUT",
9621
+ auth: "user",
9622
+ responseKind: "json"
9623
+ },
9624
+ deleteAppointmentResource: {
9625
+ path: "/sites/{siteId}/bookings/resources/{resourceId}",
9626
+ method: "DELETE",
9627
+ auth: "user",
9628
+ responseKind: "json"
9629
+ },
9630
+ listAppointmentServices: {
9631
+ path: "/sites/{siteId}/bookings/services",
9632
+ method: "GET",
9633
+ auth: "user",
9634
+ responseKind: "json"
9635
+ },
9636
+ createAppointmentService: {
9637
+ path: "/sites/{siteId}/bookings/services",
9638
+ method: "POST",
9639
+ auth: "user",
9640
+ responseKind: "json"
9641
+ },
9642
+ getAppointmentService: {
9643
+ path: "/sites/{siteId}/bookings/services/{serviceId}",
9644
+ method: "GET",
9645
+ auth: "user",
9646
+ responseKind: "json"
9647
+ },
9648
+ updateAppointmentService: {
9649
+ path: "/sites/{siteId}/bookings/services/{serviceId}",
9650
+ method: "PUT",
9651
+ auth: "user",
9652
+ responseKind: "json"
9653
+ },
9654
+ deleteAppointmentService: {
9655
+ path: "/sites/{siteId}/bookings/services/{serviceId}",
9656
+ method: "DELETE",
9657
+ auth: "user",
9658
+ responseKind: "json"
9659
+ },
9660
+ getAppointmentServicesReference: {
9661
+ path: "/sites/{siteId}/bookings/services/reference",
9662
+ method: "GET",
9663
+ auth: "user",
9664
+ responseKind: "json"
9665
+ },
9666
+ getAppointmentResourcesReference: {
9667
+ path: "/sites/{siteId}/bookings/resources/reference",
9668
+ method: "GET",
9669
+ auth: "user",
9670
+ responseKind: "json"
9671
+ },
9672
+ // Service-Resource linking
9673
+ getResourceServices: {
9674
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/services",
9675
+ method: "GET",
9676
+ auth: "user",
9677
+ responseKind: "json"
9678
+ },
9679
+ updateResourceServices: {
9680
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/services",
9681
+ method: "PUT",
9682
+ auth: "user",
9683
+ responseKind: "json"
9684
+ },
9685
+ getServiceResources: {
9686
+ path: "/sites/{siteId}/bookings/services/{serviceId}/resources",
9687
+ method: "GET",
9688
+ auth: "user",
9689
+ responseKind: "json"
9690
+ },
9691
+ updateServiceResources: {
9692
+ path: "/sites/{siteId}/bookings/services/{serviceId}/resources",
9693
+ method: "PUT",
9694
+ auth: "user",
9695
+ responseKind: "json"
9696
+ },
9697
+ // Availability management
9698
+ listAvailabilityRules: {
9699
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/availability",
9700
+ method: "GET",
9701
+ auth: "user",
9702
+ responseKind: "json"
9703
+ },
9704
+ upsertAvailabilityRule: {
9705
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/availability",
9706
+ method: "POST",
9707
+ auth: "user",
9708
+ responseKind: "json"
9709
+ },
9710
+ deleteAvailabilityRule: {
9711
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/availability/{ruleId}",
9712
+ method: "DELETE",
9713
+ auth: "user",
9714
+ responseKind: "json"
9715
+ },
9716
+ listBlackouts: {
9717
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/blackouts",
9718
+ method: "GET",
9719
+ auth: "user",
9720
+ responseKind: "json"
9721
+ },
9722
+ createBlackout: {
9723
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/blackouts",
9724
+ method: "POST",
9725
+ auth: "user",
9726
+ responseKind: "json"
9727
+ },
9728
+ deleteBlackout: {
9729
+ path: "/sites/{siteId}/bookings/resources/{resourceId}/blackouts/{blackoutId}",
9730
+ method: "DELETE",
9731
+ auth: "user",
9732
+ responseKind: "json"
9733
+ },
9734
+ getAvailableSlots: {
9735
+ path: "/sites/{siteId}/bookings/availability/slots",
9736
+ method: "GET",
9737
+ auth: "user",
9738
+ responseKind: "json"
9739
+ },
9740
+ createAppointment: {
9741
+ path: "/sites/{siteId}/bookings/appointments",
9742
+ method: "POST",
9743
+ auth: "user",
9744
+ responseKind: "json"
9745
+ },
9746
+ // Data retrieval endpoints - good candidates for caching
9747
+ getBrief: {
9748
+ path: "/briefs",
9749
+ method: "GET",
9750
+ revalidate: 120,
9751
+ // 2 minutes
9752
+ tags: ["brief"],
9753
+ auth: "user",
9754
+ responseKind: "json"
9755
+ },
9756
+ // Unified site data endpoint - use this for all site lookups
9757
+ getSite: {
9758
+ path: "/sites",
9759
+ method: "GET",
9760
+ revalidate: 900,
9761
+ // 15 minutes - site data changes less frequently
9762
+ tags: ["site"],
9763
+ auth: "user",
9764
+ responseKind: "json"
9765
+ },
9766
+ // DEPRECATED: Use getSite with ?slug={slug} instead
9767
+ getSiteBySlug: {
9768
+ path: "/sites/by-slug/{slug}",
9769
+ method: "GET",
9770
+ revalidate: 900,
9771
+ // 15 minutes - site data changes less frequently
9772
+ tags: ["site", "site-{slug}"],
9773
+ auth: "user",
9774
+ responseKind: "json"
9775
+ },
9776
+ // DEPRECATED: Use getSite with ?domain={domain} instead
9777
+ getSiteByDomain: {
9778
+ path: "/sites/by-domain/{domain}",
9779
+ method: "GET",
9780
+ revalidate: 900,
9781
+ // 15 minutes - site data changes less frequently
9782
+ tags: ["site", "site-domain-{domain}"],
9783
+ auth: "user",
9784
+ responseKind: "json"
9785
+ },
9786
+ createBriefTurn: {
9787
+ path: "/brief-turns",
9788
+ method: "POST",
9789
+ tags: ["brief"],
9790
+ // Tags for invalidation after mutation
9791
+ auth: "user",
9792
+ responseKind: "json"
9793
+ },
9794
+ upsertBrief: {
9795
+ path: "/briefs",
9796
+ method: "PUT",
9797
+ tags: ["brief"],
9798
+ auth: "user",
9799
+ responseKind: "json"
9800
+ },
9801
+ aiBriefToSpec: {
9802
+ path: "/ai/actions/brief-to-spec",
9803
+ method: "POST",
9804
+ tags: ["brief", "spec"],
9805
+ auth: "user",
9806
+ responseKind: "json"
9807
+ },
9808
+ createSite: {
9809
+ path: "/sites",
9810
+ method: "POST",
9811
+ tags: ["site"],
9812
+ auth: "user",
9813
+ responseKind: "json"
9814
+ },
9815
+ createSiteManual: {
9816
+ path: "/sites/manual",
9817
+ method: "POST",
9818
+ tags: ["site"],
9819
+ auth: "user",
9820
+ responseKind: "json"
9821
+ },
9822
+ updateSite: {
9823
+ path: "/sites",
9824
+ method: "PUT",
9825
+ tags: ["site"],
9826
+ auth: "user",
9827
+ responseKind: "json"
9828
+ },
9829
+ deleteSite: {
9830
+ path: "/sites/{siteId}",
9831
+ method: "DELETE",
9832
+ tags: ["site", "site-{siteId}"],
9833
+ auth: "user",
9834
+ responseKind: "json"
9835
+ },
9836
+ listSiteMembers: {
9837
+ path: "/sites/{siteId}/members",
9838
+ method: "GET",
9839
+ tags: ["site-{siteId}", "site-members-{siteId}"],
9840
+ auth: "user",
9841
+ responseKind: "json"
9842
+ },
9843
+ inviteSiteMember: {
9844
+ path: "/sites/{siteId}/members",
9845
+ method: "POST",
9846
+ tags: ["site-{siteId}", "site-members-{siteId}"],
9847
+ auth: "user",
9848
+ responseKind: "json"
9849
+ },
9850
+ updateSiteMemberRole: {
9851
+ path: "/sites/{siteId}/members/{memberId}",
9852
+ method: "PATCH",
9853
+ tags: ["site-{siteId}", "site-members-{siteId}", "site-member-{memberId}"],
9854
+ auth: "user",
9855
+ responseKind: "json"
9856
+ },
9857
+ authLogin: {
9858
+ path: "/auth/login/submit",
9859
+ method: "POST",
9860
+ tags: ["auth"],
9861
+ auth: "public",
9862
+ responseKind: "json"
9863
+ },
9864
+ authForgotPassword: {
9865
+ path: "/auth/forgot/submit",
9866
+ method: "POST",
9867
+ tags: ["auth"],
9868
+ auth: "public",
9869
+ responseKind: "json"
9870
+ },
9871
+ authRegister: {
9872
+ path: "/auth/register/submit",
9873
+ method: "POST",
9874
+ tags: ["auth"],
9875
+ auth: "public",
9876
+ responseKind: "json"
9877
+ },
9878
+ authReauthenticate: {
9879
+ path: "/auth/reauth/submit",
9880
+ method: "POST",
9881
+ tags: ["auth"],
9882
+ auth: "public",
9883
+ responseKind: "json"
9884
+ },
9885
+ lookupSiteDomains: {
9886
+ path: "/sites/{siteId}/domains/lookup",
9887
+ method: "POST",
9888
+ tags: ["site-{siteId}", "site-domains"],
9889
+ auth: "user",
9890
+ responseKind: "json"
9891
+ },
9892
+ registerSiteDomain: {
9893
+ path: "/sites/{siteId}/domains/register",
9894
+ method: "POST",
9895
+ tags: ["site-{siteId}", "site-domains"],
9896
+ auth: "user",
9897
+ responseKind: "json"
9898
+ },
9899
+ addCustomDomain: {
9900
+ path: "/sites/{siteId}/domains/custom",
9901
+ method: "POST",
9902
+ tags: ["site-{siteId}", "site-domains"],
9903
+ auth: "user",
9904
+ responseKind: "json"
9905
+ },
9906
+ removeCustomDomain: {
9907
+ path: "/sites/{siteId}/domains/custom",
9908
+ method: "DELETE",
9909
+ tags: ["site-{siteId}", "site-domains"],
9910
+ auth: "user",
9911
+ responseKind: "json"
9912
+ },
9913
+ syncCustomDomainToEdgeConfig: {
9914
+ path: "/sites/{siteId}/domains/custom/sync",
9915
+ method: "POST",
9916
+ tags: ["site-{siteId}", "site-domains"],
9917
+ auth: "user",
9918
+ responseKind: "json"
9919
+ },
9920
+ removeSiteMember: {
9921
+ path: "/sites/{siteId}/members/{memberId}",
9922
+ method: "DELETE",
9923
+ tags: ["site-{siteId}", "site-members-{siteId}", "site-member-{memberId}"],
9924
+ auth: "user",
9925
+ responseKind: "json"
9926
+ },
9927
+ revokeSiteInvitation: {
9928
+ path: "/sites/{siteId}/members/invitations/{invitationId}",
9929
+ method: "DELETE",
9930
+ tags: ["site-{siteId}", "site-members-{siteId}", "site-invite-{invitationId}"],
9931
+ auth: "user",
9932
+ responseKind: "json"
9933
+ },
9934
+ transferSiteOwnership: {
9935
+ path: "/sites/{siteId}/members/transfer-ownership",
9936
+ method: "POST",
9937
+ tags: ["site-{siteId}", "site-members-{siteId}"],
9938
+ auth: "user",
9939
+ responseKind: "json"
9940
+ },
9941
+ listContentEntries: {
9942
+ path: "/sites/{siteId}/content/{type}",
9943
+ method: "GET",
9944
+ revalidate: 60,
9945
+ tags: ["site-{siteId}", "content-{siteId}-{type}"],
9946
+ auth: "user",
9947
+ responseKind: "json"
9948
+ },
9949
+ getContentTemplate: {
9950
+ path: "/sites/{siteId}/content/types/{type}/template",
9951
+ method: "GET",
9952
+ revalidate: 60,
9953
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}"],
9954
+ auth: "user",
9955
+ responseKind: "json"
9956
+ },
9957
+ updateContentTemplateBlock: {
9958
+ path: "/sites/{siteId}/content/types/{type}/template/blocks/{blockId}",
9959
+ method: "PATCH",
9960
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}", "content-template-block-{blockId}"],
9961
+ auth: "user",
9962
+ responseKind: "json"
9963
+ },
9964
+ applyContentTemplateAddon: {
9965
+ path: "/sites/{siteId}/content/types/{type}/template/addons",
9966
+ method: "POST",
9967
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}"],
9968
+ auth: "user",
9969
+ responseKind: "json"
9970
+ },
9971
+ updateContentTemplateBlockBindings: {
9972
+ path: "/sites/{siteId}/content/types/{type}/template/blocks/{blockId}/bindings",
9973
+ method: "PATCH",
9974
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}", "content-template-block-{blockId}"],
9975
+ auth: "user",
9976
+ responseKind: "json"
9977
+ },
9978
+ createTemplateBlock: {
9979
+ path: "/sites/{siteId}/content/types/{type}/template/blocks",
9980
+ method: "POST",
9981
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}"],
9982
+ auth: "user",
9983
+ responseKind: "json"
9984
+ },
9985
+ deleteTemplateBlock: {
9986
+ path: "/sites/{siteId}/content/types/{type}/template/blocks/{blockId}",
9987
+ method: "DELETE",
9988
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}", "content-template-block-{blockId}"],
9989
+ auth: "user",
9990
+ responseKind: "json"
9991
+ },
9992
+ reorderTemplateBlocks: {
9993
+ path: "/sites/{siteId}/content/types/{type}/template/blocks/reorder",
9994
+ method: "POST",
9995
+ tags: ["site-{siteId}", "content-template-{siteId}-{type}"],
9996
+ auth: "user",
9997
+ responseKind: "json"
9998
+ },
9999
+ getTransforms: {
10000
+ path: "/transforms",
10001
+ method: "GET",
10002
+ revalidate: 3600,
10003
+ tags: ["transforms"],
10004
+ auth: "public",
10005
+ responseKind: "json"
10006
+ },
10007
+ createContentEntry: {
10008
+ path: "/sites/{siteId}/content/{type}",
10009
+ method: "POST",
10010
+ tags: ["site-{siteId}", "content-{siteId}-{type}"],
10011
+ auth: "user",
10012
+ responseKind: "json"
10013
+ },
10014
+ getContentEntry: {
10015
+ path: "/sites/{siteId}/content/{type}/{entryId}",
10016
+ method: "GET",
10017
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10018
+ auth: "user",
10019
+ responseKind: "json"
10020
+ },
10021
+ updateContentEntry: {
10022
+ path: "/sites/{siteId}/content/{type}/{entryId}",
10023
+ method: "PUT",
10024
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10025
+ auth: "user",
10026
+ responseKind: "json"
10027
+ },
10028
+ updateContentEntryContent: {
10029
+ path: "/sites/{siteId}/content/{type}/{entryId}/content",
10030
+ method: "PUT",
10031
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10032
+ auth: "user",
10033
+ responseKind: "json"
10034
+ },
10035
+ updateRouteMetadata: {
10036
+ path: "/sites/{siteId}/routes/{routeId}/metadata",
10037
+ method: "PATCH",
10038
+ tags: ["site-{siteId}", "route-{routeId}"],
10039
+ auth: "user",
10040
+ responseKind: "json"
10041
+ },
10042
+ publishContentEntry: {
10043
+ path: "/sites/{siteId}/content/{type}/{entryId}/publish",
10044
+ method: "POST",
10045
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10046
+ auth: "user",
10047
+ responseKind: "json"
10048
+ },
10049
+ discardContentEntry: {
10050
+ path: "/sites/{siteId}/content/{type}/{entryId}/discard",
10051
+ method: "POST",
10052
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10053
+ auth: "user",
10054
+ responseKind: "json"
10055
+ },
10056
+ unpublishContentEntry: {
10057
+ path: "/sites/{siteId}/content/{type}/{entryId}/unpublish",
10058
+ method: "POST",
10059
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10060
+ auth: "user",
10061
+ responseKind: "json"
10062
+ },
10063
+ // Content types: enable + setup
10064
+ listContentTypes: {
10065
+ path: "/sites/{siteId}/content-types",
10066
+ method: "GET",
10067
+ revalidate: 120,
10068
+ // 2 minutes
10069
+ tags: ["site-{siteId}", "content-types"],
10070
+ auth: "user",
10071
+ responseKind: "json"
10072
+ },
10073
+ enableContentType: {
10074
+ path: "/sites/{siteId}/content-types/{key}/enable",
10075
+ method: "POST",
10076
+ tags: ["site-{siteId}", "content-types", "content-type-{key}"],
10077
+ auth: "user",
10078
+ responseKind: "json"
10079
+ },
10080
+ setupContentType: {
10081
+ path: "/sites/{siteId}/content-types/{key}/setup",
10082
+ method: "POST",
10083
+ tags: ["site-{siteId}", "content-types", "content-type-{key}"],
10084
+ auth: "user",
10085
+ responseKind: "json"
10086
+ },
10087
+ deleteContentEntry: {
10088
+ path: "/sites/{siteId}/content/{type}/{entryId}",
10089
+ method: "DELETE",
10090
+ tags: ["site-{siteId}", "content-{siteId}-{type}", "content-entry-{entryId}"],
10091
+ auth: "user",
10092
+ responseKind: "json"
10093
+ },
10094
+ updateSiteGeneralSettings: {
10095
+ path: "/sites/{siteId}/settings/general",
10096
+ method: "POST",
10097
+ tags: ["site-{siteId}", "site-settings-{siteId}"],
10098
+ auth: "user",
10099
+ responseKind: "json"
10100
+ },
10101
+ getSiteLayoutSettings: {
10102
+ path: "/sites/{siteId}/settings/layout",
10103
+ method: "GET",
10104
+ revalidate: 120,
10105
+ tags: ["site-{siteId}", "site-settings-{siteId}"],
10106
+ auth: "user",
10107
+ responseKind: "json"
10108
+ },
10109
+ updateSiteLayoutSettings: {
10110
+ path: "/sites/{siteId}/settings/layout",
10111
+ method: "POST",
10112
+ tags: ["site-{siteId}", "site-settings-{siteId}"],
10113
+ auth: "user",
10114
+ responseKind: "json"
10115
+ },
10116
+ getMaintenanceSettings: {
10117
+ path: "/sites/{siteId}/settings/maintenance",
10118
+ method: "GET",
10119
+ revalidate: 120,
10120
+ tags: ["site-{siteId}", "site-settings-{siteId}"],
10121
+ auth: "user",
10122
+ responseKind: "json"
10123
+ },
10124
+ updateMaintenanceSettings: {
10125
+ path: "/sites/{siteId}/settings/maintenance",
10126
+ method: "POST",
10127
+ tags: ["site-{siteId}", "site-settings-{siteId}"],
10128
+ auth: "user",
10129
+ responseKind: "json"
10130
+ },
10131
+ setHomepage: {
10132
+ path: "/sites/{siteId}/settings/homepage",
10133
+ method: "POST",
10134
+ tags: ["site-{siteId}", "site-homepage"],
10135
+ auth: "user",
10136
+ responseKind: "json"
10137
+ },
10138
+ listAccountDomains: {
10139
+ path: "/domains",
10140
+ method: "GET",
10141
+ tags: ["domains"],
10142
+ auth: "user",
10143
+ responseKind: "json"
10144
+ },
10145
+ searchDomains: {
10146
+ path: "/domains/search",
10147
+ method: "POST",
10148
+ tags: ["domains"],
10149
+ auth: "user",
10150
+ responseKind: "json"
10151
+ },
10152
+ registerDomain: {
10153
+ path: "/domains/register",
10154
+ method: "POST",
10155
+ tags: ["domains"],
10156
+ auth: "user",
10157
+ responseKind: "json"
10158
+ },
10159
+ assignDomain: {
10160
+ path: "/domains/{domainId}/assign",
10161
+ method: "POST",
10162
+ tags: ["domains", "domain-{domainId}"],
10163
+ auth: "user",
10164
+ responseKind: "json"
10165
+ },
10166
+ adminStartImpersonation: {
10167
+ path: "/admin/impersonation/start",
10168
+ method: "POST",
10169
+ tags: ["admin", "impersonation"],
10170
+ auth: "user",
10171
+ responseKind: "json"
10172
+ },
10173
+ adminStopImpersonation: {
10174
+ path: "/admin/impersonation/stop",
10175
+ method: "POST",
10176
+ tags: ["admin", "impersonation"],
10177
+ auth: "user",
10178
+ responseKind: "json"
10179
+ },
10180
+ adminAssignRole: {
10181
+ path: "/admin/roles/assign",
10182
+ method: "POST",
10183
+ tags: ["admin", "roles"],
10184
+ auth: "user",
10185
+ responseKind: "json"
10186
+ },
10187
+ adminRevokeRole: {
10188
+ path: "/admin/roles/revoke",
10189
+ method: "POST",
10190
+ tags: ["admin", "roles"],
10191
+ auth: "user",
10192
+ responseKind: "json"
10193
+ },
10194
+ adminChangePlan: {
10195
+ path: "/admin/billing/plan",
10196
+ method: "POST",
10197
+ tags: ["admin", "billing"],
10198
+ auth: "user",
10199
+ responseKind: "json"
10200
+ },
10201
+ changePlan: {
10202
+ path: "/billing/plan/change",
10203
+ method: "POST",
10204
+ tags: ["billing"],
10205
+ auth: "user",
10206
+ responseKind: "json"
10207
+ },
10208
+ mfaTotpEnroll: {
10209
+ path: "/auth/mfa/totp/enroll",
10210
+ method: "POST",
10211
+ tags: ["mfa"],
10212
+ auth: "user",
10213
+ responseKind: "json"
10214
+ },
10215
+ mfaTotpVerify: {
10216
+ path: "/auth/mfa/totp/verify",
10217
+ method: "POST",
10218
+ tags: ["mfa"],
10219
+ auth: "user",
10220
+ responseKind: "json"
10221
+ },
10222
+ mfaTotpActivate: {
10223
+ path: "/auth/mfa/totp/activate",
10224
+ method: "POST",
10225
+ tags: ["mfa"],
10226
+ auth: "user",
10227
+ responseKind: "json"
10228
+ },
10229
+ mfaDeleteFactor: {
10230
+ path: "/auth/mfa/factors/{factorId}",
10231
+ method: "DELETE",
10232
+ tags: ["mfa"],
10233
+ auth: "user",
10234
+ responseKind: "json"
10235
+ },
10236
+ mfaBackupCodesGet: {
10237
+ path: "/auth/mfa/backup-codes",
10238
+ method: "GET",
10239
+ tags: ["mfa"],
10240
+ auth: "user",
10241
+ responseKind: "json"
10242
+ },
10243
+ mfaBackupCodesRotate: {
10244
+ path: "/auth/mfa/backup-codes/rotate",
10245
+ method: "POST",
10246
+ tags: ["mfa"],
10247
+ auth: "user",
10248
+ responseKind: "json"
10249
+ },
10250
+ mfaPhoneEnroll: {
10251
+ path: "/auth/mfa/phone/enroll",
10252
+ method: "POST",
10253
+ tags: ["mfa"],
10254
+ auth: "user",
10255
+ responseKind: "json"
10256
+ },
10257
+ mfaPhoneChallenge: {
10258
+ path: "/auth/mfa/phone/challenge",
10259
+ method: "POST",
10260
+ tags: ["mfa"],
10261
+ auth: "user",
10262
+ responseKind: "json"
10263
+ },
10264
+ mfaPhoneVerify: {
10265
+ path: "/auth/mfa/phone/verify",
10266
+ method: "POST",
10267
+ tags: ["mfa"],
10268
+ auth: "user",
10269
+ responseKind: "json"
10270
+ },
10271
+ accountUpdatePassword: {
10272
+ path: "/account/password/update",
10273
+ method: "POST",
10274
+ tags: ["account"],
10275
+ auth: "user",
10276
+ responseKind: "json"
10277
+ },
10278
+ accountRevokeSessions: {
10279
+ path: "/account/sessions/revoke",
10280
+ method: "POST",
10281
+ tags: ["account"],
10282
+ auth: "user",
10283
+ responseKind: "json"
10284
+ },
10285
+ adminListInvites: {
10286
+ path: "/admin/invites",
10287
+ method: "GET",
10288
+ tags: ["admin", "invites"],
10289
+ auth: "user",
10290
+ responseKind: "json"
10291
+ },
10292
+ adminCreateInvite: {
10293
+ path: "/admin/invites",
10294
+ method: "POST",
10295
+ tags: ["admin", "invites"],
10296
+ auth: "user",
10297
+ responseKind: "json"
10298
+ },
10299
+ adminRevokeInvite: {
10300
+ path: "/admin/invites/{inviteId}",
10301
+ method: "DELETE",
10302
+ tags: ["admin", "invites"],
10303
+ auth: "user",
10304
+ responseKind: "json"
10305
+ },
10306
+ adminCreateUser: {
10307
+ path: "/admin/users",
10308
+ method: "POST",
10309
+ tags: ["admin", "users"],
10310
+ auth: "admin",
10311
+ responseKind: "json"
10312
+ },
10313
+ adminListAllowedDomains: {
10314
+ path: "/admin/allowed-domains",
10315
+ method: "GET",
10316
+ tags: ["admin", "allowed-domains"],
10317
+ auth: "user",
10318
+ responseKind: "json"
10319
+ },
10320
+ adminAddAllowedDomain: {
10321
+ path: "/admin/allowed-domains",
10322
+ method: "POST",
10323
+ tags: ["admin", "allowed-domains"],
10324
+ auth: "user",
10325
+ responseKind: "json"
10326
+ },
10327
+ adminDeleteAllowedDomain: {
10328
+ path: "/admin/allowed-domains/{domainId}",
10329
+ method: "DELETE",
10330
+ tags: ["admin", "allowed-domains"],
10331
+ auth: "user",
10332
+ responseKind: "json"
10333
+ },
10334
+ authAcceptInvite: {
10335
+ path: "/auth/invite/accept",
10336
+ method: "POST",
10337
+ tags: ["auth", "invite"],
10338
+ auth: "user",
10339
+ responseKind: "json"
10340
+ },
10341
+ acceptSiteInvitation: {
10342
+ path: "/sites/invitations/accept",
10343
+ method: "POST",
10344
+ tags: ["site-invitations"],
10345
+ auth: "user",
10346
+ responseKind: "json"
10347
+ },
10348
+ getNavigationMenus: {
10349
+ path: "/sites/{siteId}/navigation/menus",
10350
+ method: "GET",
10351
+ tags: ["site-{siteId}", "navigation"],
10352
+ auth: "user",
10353
+ responseKind: "json"
10354
+ },
10355
+ createNavigationMenu: {
10356
+ path: "/sites/{siteId}/navigation/menus",
10357
+ method: "POST",
10358
+ tags: ["site-{siteId}", "navigation"],
10359
+ auth: "user",
10360
+ responseKind: "json"
10361
+ },
10362
+ updateNavigationMenu: {
10363
+ path: "/sites/{siteId}/navigation/menus/{menuId}",
10364
+ method: "PATCH",
10365
+ tags: ["site-{siteId}", "navigation", "navigation-menu-{menuId}"],
10366
+ auth: "user",
10367
+ responseKind: "json"
10368
+ },
10369
+ deleteNavigationMenu: {
10370
+ path: "/sites/{siteId}/navigation/menus/{menuId}",
10371
+ method: "DELETE",
10372
+ tags: ["site-{siteId}", "navigation", "navigation-menu-{menuId}"],
10373
+ auth: "user",
10374
+ responseKind: "json"
10375
+ },
10376
+ createNavigationItem: {
10377
+ path: "/sites/{siteId}/navigation/menus/{menuId}/items",
10378
+ method: "POST",
10379
+ tags: ["site-{siteId}", "navigation", "navigation-menu-{menuId}"],
10380
+ auth: "user",
10381
+ responseKind: "json"
10382
+ },
10383
+ updateNavigationItem: {
10384
+ path: "/sites/{siteId}/navigation/menus/{menuId}/items/{itemId}",
10385
+ method: "PATCH",
10386
+ tags: ["site-{siteId}", "navigation", "navigation-menu-{menuId}", "navigation-item-{itemId}"],
10387
+ auth: "user",
10388
+ responseKind: "json"
10389
+ },
10390
+ deleteNavigationItem: {
10391
+ path: "/sites/{siteId}/navigation/menus/{menuId}/items/{itemId}",
10392
+ method: "DELETE",
10393
+ tags: ["site-{siteId}", "navigation", "navigation-menu-{menuId}", "navigation-item-{itemId}"],
10394
+ auth: "user",
10395
+ responseKind: "json"
10396
+ },
10397
+ reorderNavigationItems: {
10398
+ path: "/sites/{siteId}/navigation/menus/{menuId}/items/reorder",
10399
+ method: "POST",
10400
+ tags: ["site-{siteId}", "navigation", "navigation-menu-{menuId}"],
10401
+ auth: "user",
10402
+ responseKind: "json"
10403
+ },
10404
+ getRoutableContent: {
10405
+ path: "/sites/{siteId}/routable-content",
10406
+ method: "GET",
10407
+ revalidate: 60,
10408
+ tags: ["site-{siteId}", "routable-content-{siteId}"],
10409
+ auth: "user",
10410
+ responseKind: "json"
10411
+ },
10412
+ // Public routable content for SDK SSG
10413
+ getPublicRoutableContent: {
10414
+ path: "/public/sites/{siteId}/routable-content",
10415
+ method: "GET",
10416
+ revalidate: 60,
10417
+ tags: ["site-{siteId}", "routable-content-{siteId}"],
10418
+ auth: "public",
10419
+ responseKind: "json"
10420
+ },
10421
+ // Generic public content preview (preferred)
10422
+ getPublishedEntryPreview: {
10423
+ path: "/public/content/{siteId}/{type}/{slug}/preview",
10424
+ method: "GET",
10425
+ revalidate: 60,
10426
+ tags: ["content-{siteId}-{type}-{slug}"],
10427
+ auth: "public",
10428
+ responseKind: "json"
10429
+ },
10430
+ listPublishedEntries: {
10431
+ path: "/public/content/{siteId}/{type}/entries",
10432
+ method: "GET",
10433
+ revalidate: 60,
10434
+ tags: ["content-{siteId}-{type}"],
10435
+ auth: "public",
10436
+ responseKind: "json"
10437
+ },
10438
+ getPublishedPostPreview: {
10439
+ path: "/public/posts/{siteId}/{slug}/preview",
10440
+ method: "GET",
10441
+ revalidate: 60,
10442
+ tags: ["blog-post:{siteId}:{slug}"],
10443
+ auth: "public",
10444
+ responseKind: "json"
10445
+ },
10446
+ proposalsSelect: {
10447
+ path: "/proposals/select",
10448
+ method: "POST",
10449
+ tags: ["proposal", "site"],
10450
+ auth: "user",
10451
+ responseKind: "json"
10452
+ },
10453
+ createTheme: {
10454
+ path: "/ai/chat/create-theme",
10455
+ method: "POST",
10456
+ revalidate: 60,
10457
+ // 1 minute cache for theme creation to avoid duplicate requests
10458
+ tags: ["theme", "ai-theme"],
10459
+ auth: "user",
10460
+ responseKind: "json"
10461
+ },
10462
+ generateThemes: {
10463
+ path: "/theme/generate",
10464
+ method: "POST",
10465
+ tags: ["theme"],
10466
+ auth: "user",
10467
+ responseKind: "json"
10468
+ },
10469
+ extractThemeFromInspiration: {
10470
+ path: "/theme/extract-from-inspiration",
10471
+ method: "POST",
10472
+ tags: ["theme", "preferences"],
10473
+ auth: "user",
10474
+ responseKind: "json"
10475
+ },
10476
+ saveSiteTheme: {
10477
+ path: "/sites/{siteId}/theme/save",
10478
+ method: "POST",
10479
+ tags: ["site-{siteId}", "theme"],
10480
+ auth: "user",
10481
+ responseKind: "json"
10482
+ },
10483
+ uploadSiteLogo: {
10484
+ path: "/storage/upload-site-logo",
10485
+ method: "POST",
10486
+ tags: ["site", "logo"],
10487
+ auth: "user",
10488
+ responseKind: "json"
10489
+ },
10490
+ upsertThemePreferences: {
10491
+ path: "/theme-preferences/upsert",
10492
+ method: "POST",
10493
+ tags: ["theme", "preferences"],
10494
+ auth: "user",
10495
+ responseKind: "json"
10496
+ },
10497
+ finalizeSite: {
10498
+ path: "/sites/finalize",
10499
+ method: "POST",
10500
+ tags: ["site"],
10501
+ auth: "user",
10502
+ responseKind: "json"
10503
+ },
10504
+ instagramUploadZip: {
10505
+ path: "/api/instagram/upload-zip",
10506
+ method: "POST",
10507
+ tags: ["instagram-import"],
10508
+ auth: "user",
10509
+ responseKind: "json"
10510
+ },
10511
+ getAnalyticsReport: {
10512
+ path: "/sites/{siteId}/analytics/report",
10513
+ method: "GET",
10514
+ revalidate: 300,
10515
+ tags: ["site-{siteId}", "analytics-{siteId}"],
10516
+ auth: "user",
10517
+ responseKind: "json"
10518
+ },
10519
+ getSeoOverview: {
10520
+ path: "/sites/{siteId}/seo/overview",
10521
+ method: "GET",
10522
+ revalidate: 300,
10523
+ tags: ["site-{siteId}", "seo-overview-{siteId}"],
10524
+ auth: "user",
10525
+ responseKind: "json"
10526
+ },
10527
+ getSeoPages: {
10528
+ path: "/sites/{siteId}/seo/pages",
10529
+ method: "GET",
10530
+ revalidate: 300,
10531
+ tags: ["site-{siteId}", "seo-pages-{siteId}"],
10532
+ auth: "user",
10533
+ responseKind: "json"
10534
+ },
10535
+ getSeoQueries: {
10536
+ path: "/sites/{siteId}/seo/queries",
10537
+ method: "GET",
10538
+ revalidate: 300,
10539
+ tags: ["site-{siteId}", "seo-queries-{siteId}"],
10540
+ auth: "user",
10541
+ responseKind: "json"
10542
+ },
10543
+ getPerformanceOverview: {
10544
+ path: "/sites/{siteId}/performance/overview",
10545
+ method: "GET",
10546
+ revalidate: 300,
10547
+ tags: ["site-{siteId}", "performance-overview-{siteId}"],
10548
+ auth: "user",
10549
+ responseKind: "json"
10550
+ },
10551
+ createMediaAsset: {
10552
+ path: "/media",
10553
+ method: "POST",
10554
+ tags: ["media"],
10555
+ auth: "user",
10556
+ responseKind: "json"
10557
+ },
10558
+ mediaList: {
10559
+ path: "/media",
10560
+ method: "GET",
10561
+ tags: ["media"],
10562
+ auth: "user",
10563
+ responseKind: "json"
10564
+ },
10565
+ mediaGet: {
10566
+ path: "/media/{assetId}",
10567
+ method: "GET",
10568
+ tags: ["media", "media-{assetId}"],
10569
+ auth: "user",
10570
+ responseKind: "json"
10571
+ },
10572
+ mediaUpdate: {
10573
+ path: "/media/{assetId}",
10574
+ method: "PATCH",
10575
+ tags: ["media", "media-{assetId}"],
10576
+ auth: "user",
10577
+ responseKind: "json"
10578
+ },
10579
+ mediaDelete: {
10580
+ path: "/media/{assetId}",
10581
+ method: "DELETE",
10582
+ tags: ["media", "media-{assetId}"],
10583
+ auth: "user",
10584
+ responseKind: "void"
10585
+ },
10586
+ mediaBulkDelete: {
10587
+ path: "/media/bulk-delete",
10588
+ method: "POST",
10589
+ tags: ["media"],
10590
+ auth: "user",
10591
+ responseKind: "json"
10592
+ },
10593
+ mediaGetSignedUrl: {
10594
+ path: "/media/{assetId}/signed-url",
10595
+ method: "GET",
10596
+ tags: ["media", "media-{assetId}"],
10597
+ auth: "user",
10598
+ responseKind: "json"
10599
+ },
10600
+ mediaUpload: {
10601
+ path: "/media/upload",
10602
+ method: "POST",
10603
+ tags: ["media"],
10604
+ auth: "user",
10605
+ responseKind: "json"
10606
+ },
10607
+ getMediaLabels: {
10608
+ path: "/media/labels",
10609
+ method: "GET",
10610
+ tags: ["media", "media-labels"],
10611
+ auth: "user",
10612
+ responseKind: "json"
10613
+ },
10614
+ getMediaSettings: {
10615
+ path: "/media/settings",
10616
+ method: "GET",
10617
+ tags: ["media-settings"],
10618
+ auth: "user",
10619
+ responseKind: "json"
10620
+ },
10621
+ updateMediaSettings: {
10622
+ path: "/media/settings",
10623
+ method: "POST",
10624
+ tags: ["media-settings"],
10625
+ auth: "user",
10626
+ responseKind: "json"
10627
+ },
10628
+ // Media endpoints
10629
+ mediaSearch: {
10630
+ path: "/media/search",
10631
+ method: "POST",
10632
+ tags: ["media-search"],
10633
+ auth: "user",
10634
+ responseKind: "json"
10635
+ },
10636
+ classifyMediaAsset: {
10637
+ path: "/media/{assetId}/classify",
10638
+ method: "POST",
10639
+ tags: ["media", "media-{assetId}"],
10640
+ auth: "user",
10641
+ responseKind: "json"
10642
+ },
10643
+ classifyMediaBatch: {
10644
+ path: "/media/classify",
10645
+ method: "POST",
10646
+ tags: ["media"],
10647
+ auth: "user",
10648
+ responseKind: "json"
10649
+ },
10650
+ mediaJobsStatus: {
10651
+ path: "/media/jobs/status",
10652
+ method: "GET",
10653
+ tags: ["media"],
10654
+ auth: "user",
10655
+ responseKind: "json"
10656
+ },
10657
+ enqueueMediaClassifyJob: {
10658
+ path: "/media/jobs",
10659
+ method: "POST",
10660
+ tags: ["media"],
10661
+ auth: "user",
10662
+ responseKind: "json"
10663
+ },
10664
+ runMediaClassifyJob: {
10665
+ path: "/media/jobs/run",
10666
+ method: "POST",
10667
+ tags: ["media"],
10668
+ auth: "user",
10669
+ responseKind: "json"
10670
+ },
10671
+ runAllMediaClassifyJobs: {
10672
+ path: "/media/jobs/run-all",
10673
+ method: "POST",
10674
+ tags: ["media"],
10675
+ auth: "user",
10676
+ responseKind: "json"
10677
+ },
10678
+ initSiteContent: {
10679
+ path: "/sites/{siteId}/content/init",
10680
+ method: "POST",
10681
+ tags: ["site", "content"],
10682
+ auth: "user",
10683
+ responseKind: "json"
10684
+ },
10685
+ updateBlockContent: {
10686
+ path: "/blocks/{blockId}/content",
10687
+ method: "POST",
10688
+ tags: ["block", "block-{blockId}", "content"],
10689
+ auth: "user",
10690
+ responseKind: "json"
10691
+ },
10692
+ saveBlockContent: {
10693
+ path: "/blocks/{blockId}/content",
10694
+ method: "POST",
10695
+ tags: ["block", "block-{blockId}", "content"],
10696
+ auth: "user",
10697
+ responseKind: "json"
10698
+ },
10699
+ createBlock: {
10700
+ path: "/sites/{siteId}/pages/{pageId}/blocks",
10701
+ method: "POST",
10702
+ tags: ["site-{siteId}", "page-{pageId}", "blocks"],
10703
+ auth: "user",
10704
+ responseKind: "json"
10705
+ },
10706
+ reorderBlocks: {
10707
+ path: "/sites/{siteId}/pages/{pageId}/blocks/reorder",
10708
+ method: "POST",
10709
+ tags: ["site-{siteId}", "page-{pageId}", "blocks"],
10710
+ auth: "user",
10711
+ responseKind: "json"
10712
+ },
10713
+ deleteBlock: {
10714
+ path: "/sites/{siteId}/pages/{pageId}/blocks/{blockId}",
10715
+ method: "DELETE",
10716
+ tags: ["site-{siteId}", "page-{pageId}", "blocks"],
10717
+ auth: "user",
10718
+ responseKind: "json"
10719
+ },
10720
+ listBlocks: {
10721
+ path: "/sites/{siteId}/pages/{pageId}/blocks",
10722
+ method: "GET",
10723
+ tags: ["site-{siteId}", "page-{pageId}", "blocks"],
10724
+ auth: "user",
10725
+ responseKind: "json"
10726
+ },
10727
+ getBlockContent: {
10728
+ path: "/blocks/{blockId}/content",
10729
+ method: "GET",
10730
+ tags: ["block-{blockId}", "content"],
10731
+ auth: "user",
10732
+ responseKind: "json"
10733
+ },
10734
+ // DEPRECATED: Use getSite with ?id={id} instead
10735
+ getSiteById: {
10736
+ path: "/sites/by-id/{id}",
10737
+ method: "GET",
10738
+ revalidate: 900,
10739
+ // 15 minutes
10740
+ tags: ["site-{id}"],
10741
+ auth: "user",
10742
+ responseKind: "json"
10743
+ },
10744
+ // Page data endpoints
10745
+ getPageById: {
10746
+ path: "/sites/{siteId}/pages/{pageId}",
10747
+ method: "GET",
10748
+ revalidate: 900,
10749
+ // 15 minutes
10750
+ tags: ["site-{siteId}", "page-{pageId}"],
10751
+ auth: "user",
10752
+ responseKind: "json"
10753
+ },
10754
+ getContentByPath: {
10755
+ path: "/sites/{siteId}/pages",
10756
+ method: "GET",
10757
+ tags: ["site-{siteId}", "routable-content-{siteId}"],
10758
+ auth: "public",
10759
+ responseKind: "json"
10760
+ },
10761
+ getPageByPath: {
10762
+ path: "/sites/{siteId}/pages",
10763
+ method: "GET",
10764
+ tags: ["site-{siteId}"],
10765
+ auth: "user",
10766
+ responseKind: "json"
10767
+ },
10768
+ // Forms CRUD
10769
+ listForms: {
10770
+ path: "/sites/{siteId}/forms",
10771
+ method: "GET",
10772
+ tags: ["site-{siteId}", "forms"],
10773
+ auth: "user",
10774
+ responseKind: "json"
10775
+ },
10776
+ listBookingForms: {
10777
+ path: "/sites/{siteId}/bookings/forms",
10778
+ method: "GET",
10779
+ tags: ["site-{siteId}", "forms"],
10780
+ auth: "user",
10781
+ responseKind: "json"
10782
+ },
10783
+ createForm: {
10784
+ path: "/sites/{siteId}/forms",
10785
+ method: "POST",
10786
+ tags: ["site-{siteId}", "forms"],
10787
+ auth: "user",
10788
+ responseKind: "json"
10789
+ },
10790
+ updateForm: {
10791
+ path: "/sites/{siteId}/forms/{slug}",
10792
+ method: "PATCH",
10793
+ tags: ["site-{siteId}", "forms", "form-{slug}"],
10794
+ auth: "user",
10795
+ responseKind: "json"
10796
+ },
10797
+ deleteForm: {
10798
+ path: "/sites/{siteId}/forms/{slug}",
10799
+ method: "DELETE",
10800
+ tags: ["site-{siteId}", "forms", "form-{slug}"],
10801
+ auth: "user",
10802
+ responseKind: "json"
10803
+ },
10804
+ listFormSubmissions: {
10805
+ path: "/sites/{siteId}/forms/{slug}/submissions",
10806
+ method: "GET",
10807
+ revalidate: 30,
10808
+ tags: ["site-{siteId}", "form-{slug}", "form-submissions-{slug}"],
10809
+ auth: "user",
10810
+ responseKind: "json"
10811
+ },
10812
+ // Public submit
10813
+ submitForm: {
10814
+ path: "/forms/submit",
10815
+ method: "POST",
10816
+ tags: ["forms-submit"],
10817
+ auth: "public",
10818
+ responseKind: "json"
10819
+ },
10820
+ // Public forms
10821
+ getPublicFormById: {
10822
+ path: "/public/forms/{formId}",
10823
+ method: "GET",
10824
+ revalidate: 60,
10825
+ tags: ["form-{formId}"],
10826
+ auth: "public",
10827
+ responseKind: "json"
10828
+ },
10829
+ // Public booking services
10830
+ getPublicBookingServices: {
10831
+ path: "/public/bookings/services",
10832
+ method: "GET",
10833
+ revalidate: 60,
10834
+ tags: ["site-{siteId}"],
10835
+ auth: "public",
10836
+ responseKind: "json"
10837
+ },
10838
+ devToolsImpersonateAdmin: {
10839
+ path: "/dev-tools/impersonate/admin",
10840
+ method: "POST",
10841
+ auth: "admin",
10842
+ responseKind: "json"
10843
+ },
10844
+ devToolsImpersonateRandom: {
10845
+ path: "/dev-tools/impersonate/random",
10846
+ method: "POST",
10847
+ auth: "admin",
10848
+ responseKind: "json"
10849
+ },
10850
+ devToolsSeedDemoData: {
10851
+ path: "/dev-tools/seed",
10852
+ method: "POST",
10853
+ auth: "admin",
10854
+ responseKind: "json"
10855
+ },
10856
+ // Public analytics collection endpoint
10857
+ analyticsCollect: {
10858
+ path: "/api/analytics/collect",
10859
+ method: "POST",
10860
+ auth: "public",
10861
+ responseKind: "json"
10862
+ },
10863
+ // Public events for event calendar block
10864
+ listPublicEvents: {
10865
+ path: "/public/sites/{siteId}/events",
10866
+ method: "GET",
10867
+ revalidate: 60,
10868
+ tags: ["public-events-{siteId}"],
10869
+ auth: "public",
10870
+ responseKind: "json"
10871
+ },
10872
+ // Resolve event occurrence by URL segment (date or UUID)
10873
+ resolveEventOccurrence: {
10874
+ path: "/public/sites/{siteId}/events/occurrences/resolve",
10875
+ method: "GET",
10876
+ revalidate: 60,
10877
+ tags: ["public-events-{siteId}", "event-occurrence"],
10878
+ auth: "public",
10879
+ responseKind: "json"
10880
+ },
10881
+ // Public event registration
10882
+ registerForEvent: {
10883
+ path: "/public/sites/{siteId}/events/register",
10884
+ method: "POST",
10885
+ tags: ["public-events-{siteId}", "event-registration"],
10886
+ auth: "public",
10887
+ responseKind: "json"
10888
+ },
10889
+ // Content Types CRUD
10890
+ listSiteContentTypes: {
10891
+ path: "/sites/{siteId}/content-types",
10892
+ method: "GET",
10893
+ revalidate: 60,
10894
+ tags: ["site-{siteId}", "content-types-{siteId}"],
10895
+ auth: "user",
10896
+ responseKind: "json"
10897
+ },
10898
+ getContentType: {
10899
+ path: "/sites/{siteId}/content-types/by-id/{typeId}",
10900
+ method: "GET",
10901
+ revalidate: 60,
10902
+ tags: ["site-{siteId}", "content-type-{typeId}"],
10903
+ auth: "user",
10904
+ responseKind: "json"
10905
+ },
10906
+ createContentType: {
10907
+ path: "/sites/{siteId}/content-types",
10908
+ method: "POST",
10909
+ tags: ["site-{siteId}", "content-types-{siteId}"],
10910
+ auth: "user",
10911
+ responseKind: "json"
10912
+ },
10913
+ updateContentType: {
10914
+ path: "/sites/{siteId}/content-types/by-id/{typeId}",
10915
+ method: "PATCH",
10916
+ tags: ["site-{siteId}", "content-type-{typeId}", "content-types-{siteId}"],
10917
+ auth: "user",
10918
+ responseKind: "json"
10919
+ },
10920
+ deleteContentType: {
10921
+ path: "/sites/{siteId}/content-types/by-id/{typeId}",
10922
+ method: "DELETE",
10923
+ tags: ["site-{siteId}", "content-type-{typeId}", "content-types-{sideId}"],
10924
+ auth: "user",
10925
+ responseKind: "json"
10926
+ },
10927
+ duplicateContentType: {
10928
+ path: "/sites/{siteId}/content-types/by-id/{typeId}/duplicate",
10929
+ method: "POST",
10930
+ tags: ["site-{siteId}", "content-type-{typeId}", "content-types-{siteId}"],
10931
+ auth: "user",
10932
+ responseKind: "json"
10933
+ },
10934
+ // Admin site creation
10935
+ adminCreateSite: {
10936
+ path: "/admin/sites",
10937
+ method: "POST",
10938
+ tags: ["admin", "sites"],
10939
+ auth: "admin",
10940
+ responseKind: "json"
10941
+ },
10942
+ // SDK Config
10943
+ refreshSdkConfig: {
10944
+ path: "/sites/{siteId}/refresh-sdk-config",
10945
+ method: "POST",
10946
+ tags: ["site-{siteId}", "sdk-config"],
10947
+ auth: "user",
10948
+ responseKind: "json"
10949
+ },
10950
+ // Stripe Connect - Site billing
10951
+ stripeConnectAuthorize: {
10952
+ path: "/sites/{siteId}/billing/connect/authorize",
10953
+ method: "POST",
10954
+ tags: ["site-{siteId}", "stripe-connect"],
10955
+ auth: "user",
10956
+ responseKind: "json"
10957
+ },
10958
+ stripeConnectStatus: {
10959
+ path: "/sites/{siteId}/billing/connect/status",
10960
+ method: "GET",
10961
+ tags: ["site-{siteId}", "stripe-connect"],
10962
+ auth: "user",
10963
+ responseKind: "json"
10964
+ },
10965
+ stripeConnectDisconnect: {
10966
+ path: "/sites/{siteId}/billing/connect/disconnect",
10967
+ method: "DELETE",
10968
+ tags: ["site-{siteId}", "stripe-connect"],
10969
+ auth: "user",
10970
+ responseKind: "json"
10971
+ },
10972
+ // Backup
10973
+ importSiteBackup: {
10974
+ path: "/sites/{siteId}/backup/import",
10975
+ method: "POST",
10976
+ tags: ["site-{siteId}", "backup"],
10977
+ auth: "user",
10978
+ responseKind: "json"
10979
+ },
10980
+ previewBackup: {
10981
+ path: "/backup/preview",
10982
+ method: "POST",
10983
+ tags: ["backup"],
10984
+ auth: "user",
10985
+ responseKind: "json"
10986
+ },
10987
+ importBackupAsNewSite: {
10988
+ path: "/sites/backup/import-as-new",
10989
+ method: "POST",
10990
+ tags: ["site", "backup"],
10991
+ auth: "user",
10992
+ responseKind: "json"
10993
+ },
10994
+ // Admin billing price overrides
10995
+ adminGetPriceOverride: {
10996
+ path: "/admin/billing/price-override",
10997
+ method: "GET",
10998
+ tags: ["admin", "billing", "price-override"],
10999
+ auth: "admin",
11000
+ responseKind: "json"
11001
+ },
11002
+ adminUpsertPriceOverride: {
11003
+ path: "/admin/billing/price-override",
11004
+ method: "POST",
11005
+ tags: ["admin", "billing", "price-override"],
11006
+ auth: "admin",
11007
+ responseKind: "json"
11008
+ },
11009
+ adminDeletePriceOverride: {
11010
+ path: "/admin/billing/price-override",
11011
+ method: "DELETE",
11012
+ tags: ["admin", "billing", "price-override"],
11013
+ auth: "admin",
11014
+ responseKind: "json"
11015
+ }
11016
+ };
11017
+ var API_ENDPOINTS = ENDPOINT_DEFINITIONS;
11018
+
11019
+ // ../api/src/url.ts
11020
+ function getCmsApiUrl() {
11021
+ if (typeof window !== "undefined") {
11022
+ return "/api";
11023
+ }
11024
+ const internalUrl = process.env.CMS_API_URL;
11025
+ if (internalUrl) {
11026
+ return internalUrl.replace(/\/$/, "");
11027
+ }
11028
+ const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
11029
+ if (dashboardUrl) {
11030
+ const base = dashboardUrl.replace(/\/$/, "");
11031
+ return `${base}/api`;
11032
+ }
11033
+ const legacyApiUrl = process.env.NEXT_PUBLIC_CMS_API_URL;
11034
+ if (legacyApiUrl) {
11035
+ return legacyApiUrl.replace(/\/$/, "");
11036
+ }
11037
+ throw new Error(
11038
+ "NEXT_PUBLIC_DASHBOARD_URL is not configured. Set it to your dashboard URL (e.g., http://dashboard.local:4000)"
11039
+ );
11040
+ }
11041
+ function resolveApiBaseUrl() {
11042
+ return getCmsApiUrl();
11043
+ }
11044
+
11045
+ // ../api/src/request.ts
11046
+ var revalidateTag = null;
11047
+ if (typeof window === "undefined") {
11048
+ try {
11049
+ const dynamicRequire = new Function("modulePath", "return require(modulePath)");
11050
+ const nextCache = dynamicRequire("next/cache");
11051
+ revalidateTag = nextCache.revalidateTag ?? null;
11052
+ } catch {
11053
+ }
11054
+ }
11055
+ var sdkVersion;
11056
+ function generateRequestId() {
11057
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
11058
+ return crypto.randomUUID();
11059
+ }
11060
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
11061
+ const r = Math.random() * 16 | 0;
11062
+ const v = c === "x" ? r : r & 3 | 8;
11063
+ return v.toString(16);
11064
+ });
11065
+ }
11066
+ function setSdkVersion(version) {
11067
+ sdkVersion = version;
11068
+ }
11069
+ var ApiRequestError = class extends Error {
11070
+ constructor(message, options) {
11071
+ super(message);
11072
+ this.name = "ApiRequestError";
11073
+ this.endpoint = options.endpoint;
11074
+ this.status = options.status;
11075
+ this.method = options.method;
11076
+ this.auth = options.auth;
11077
+ this.requestId = options.requestId;
11078
+ this.body = options.body;
11079
+ this.cause = options.cause;
11080
+ this.errorCode = options.errorCode;
11081
+ this.retryAfterMs = options.retryAfterMs;
11082
+ }
11083
+ };
11084
+ function parseRetryAfterHeader(headerValue) {
11085
+ if (!headerValue) return void 0;
11086
+ if (/^\d+$/.test(headerValue)) {
11087
+ const seconds = parseInt(headerValue, 10);
11088
+ return seconds * 1e3;
11089
+ }
11090
+ const date = new Date(headerValue);
11091
+ if (!isNaN(date.getTime())) {
11092
+ const delayMs = date.getTime() - Date.now();
11093
+ return delayMs > 0 ? delayMs : void 0;
11094
+ }
11095
+ return void 0;
11096
+ }
11097
+ function buildEndpointURL(baseURL, endpoint) {
11098
+ return baseURL + API_ENDPOINTS[endpoint].path;
11099
+ }
11100
+ function invalidateCacheTags(tags, params) {
11101
+ if (typeof window !== "undefined" || !revalidateTag || !tags) return;
11102
+ tags.forEach((tag) => {
11103
+ let processedTag = tag;
11104
+ if (params) {
11105
+ Object.entries(params).forEach(([key, value]) => {
11106
+ processedTag = processedTag.replace(`{${key}}`, String(value));
11107
+ });
11108
+ }
11109
+ try {
11110
+ revalidateTag(processedTag, "max");
11111
+ } catch {
11112
+ }
11113
+ });
11114
+ }
11115
+ async function parseErrorBody(response) {
11116
+ const clone = response.clone();
11117
+ const contentType = clone.headers.get("content-type") ?? "";
11118
+ if (contentType.includes("application/json")) {
11119
+ try {
11120
+ return await clone.json();
11121
+ } catch {
11122
+ }
11123
+ }
11124
+ try {
11125
+ const text2 = await clone.text();
11126
+ return text2.length ? text2 : null;
11127
+ } catch {
11128
+ return null;
11129
+ }
11130
+ }
11131
+ function buildSuccessEnvelope(data, requestId) {
11132
+ return {
11133
+ success: true,
11134
+ data,
11135
+ meta: {
11136
+ requestId: requestId ?? generateRequestId(),
11137
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11138
+ apiVersion: "2025-01-01"
11139
+ }
11140
+ };
11141
+ }
11142
+ function buildErrorEnvelope(code, message, status, requestId) {
11143
+ return {
11144
+ success: false,
11145
+ error: {
11146
+ code,
11147
+ message,
11148
+ requestId: requestId ?? generateRequestId(),
11149
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11150
+ status
11151
+ }
11152
+ };
11153
+ }
11154
+ async function parseSuccessResponse(endpoint, response, config3) {
11155
+ const responseKind = config3.responseKind ?? "json";
11156
+ const auth = config3.auth ?? "user";
11157
+ const requestId = response.headers.get("x-request-id") ?? void 0;
11158
+ switch (responseKind) {
11159
+ case "json": {
11160
+ if (response.status === 204 || response.status === 205 || response.status === 304) {
11161
+ return buildSuccessEnvelope(void 0, requestId);
11162
+ }
11163
+ const raw = await response.text();
11164
+ if (!raw.trim()) {
11165
+ return buildSuccessEnvelope(void 0, requestId);
11166
+ }
11167
+ let parsed;
11168
+ try {
11169
+ parsed = JSON.parse(raw);
11170
+ } catch (cause) {
11171
+ throw new ApiRequestError(
11172
+ `Failed to parse JSON response for endpoint ${String(endpoint)}`,
11173
+ {
11174
+ endpoint,
11175
+ status: response.status,
11176
+ method: config3.method,
11177
+ auth,
11178
+ requestId,
11179
+ body: raw,
11180
+ cause
11181
+ }
11182
+ );
11183
+ }
11184
+ if (parsed && typeof parsed === "object" && "success" in parsed && typeof parsed.success === "boolean") {
11185
+ return parsed;
11186
+ }
11187
+ return buildSuccessEnvelope(parsed, requestId);
11188
+ }
11189
+ case "text": {
11190
+ const text2 = await response.text();
11191
+ return buildSuccessEnvelope(text2, requestId);
11192
+ }
11193
+ case "stream": {
11194
+ const body = response.body;
11195
+ if (!body) {
11196
+ return buildErrorEnvelope(
11197
+ "server:internal_error",
11198
+ `Expected a streamed body for endpoint ${String(endpoint)}`,
11199
+ response.status,
11200
+ requestId
11201
+ );
11202
+ }
11203
+ const stream = body;
11204
+ return buildSuccessEnvelope(stream, requestId);
11205
+ }
11206
+ case "void": {
11207
+ return buildSuccessEnvelope(void 0, requestId);
11208
+ }
11209
+ default: {
11210
+ return buildSuccessEnvelope(void 0, requestId);
11211
+ }
11212
+ }
11213
+ }
11214
+ function createRawCMSClient(headers = {}, baseUrl) {
11215
+ return ({
11216
+ endpoint,
11217
+ body,
11218
+ params,
11219
+ options = {}
11220
+ }) => {
11221
+ const resolvedBaseUrl = baseUrl ?? resolveApiBaseUrl();
11222
+ let url = buildEndpointURL(resolvedBaseUrl, endpoint);
11223
+ const originalPath = API_ENDPOINTS[endpoint].path;
11224
+ const unusedParams = {};
11225
+ if (params) {
11226
+ Object.entries(params).forEach(([key, value]) => {
11227
+ const placeholder = `{${key}}`;
11228
+ if (originalPath.includes(placeholder)) {
11229
+ url = url.replace(placeholder, value);
11230
+ } else {
11231
+ unusedParams[key] = value;
11232
+ }
11233
+ });
11234
+ }
11235
+ const endpointConfig = API_ENDPOINTS[endpoint];
11236
+ const method = endpointConfig.method;
11237
+ const isGetOrHead = method === "GET" || method === "HEAD";
11238
+ if (isGetOrHead) {
11239
+ const queryParams = new URLSearchParams();
11240
+ Object.entries(unusedParams).forEach(([key, value]) => {
11241
+ if (value !== void 0 && value !== null) {
11242
+ queryParams.append(key, String(value));
11243
+ }
11244
+ });
11245
+ if (body) {
11246
+ Object.entries(body).forEach(([key, value]) => {
11247
+ if (value !== void 0 && value !== null) {
11248
+ queryParams.append(key, String(value));
11249
+ }
11250
+ });
11251
+ }
11252
+ const queryString = queryParams.toString();
11253
+ if (queryString) {
11254
+ url += (url.includes("?") ? "&" : "?") + queryString;
11255
+ }
11256
+ }
11257
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
11258
+ const nextOptions = {};
11259
+ if (isGetOrHead && "revalidate" in endpointConfig && typeof endpointConfig.revalidate === "number") {
11260
+ nextOptions.revalidate = endpointConfig.revalidate;
11261
+ }
11262
+ if ("tags" in endpointConfig && Array.isArray(endpointConfig.tags)) {
11263
+ nextOptions.tags = endpointConfig.tags.map((tag) => {
11264
+ let processedTag = tag;
11265
+ if (params) {
11266
+ Object.entries(params).forEach(([key, value]) => {
11267
+ processedTag = processedTag.replace(`{${key}}`, String(value));
11268
+ });
11269
+ }
11270
+ return processedTag;
11271
+ });
11272
+ }
11273
+ const requestInit = {
11274
+ method,
11275
+ ...options,
11276
+ // Include credentials for same-origin requests (sends cookies for auth)
11277
+ credentials: "same-origin",
11278
+ // Don't include body for GET/HEAD requests
11279
+ body: isGetOrHead ? void 0 : isFormData ? body : body ? JSON.stringify(body) : void 0,
11280
+ headers: {
11281
+ ...options.headers,
11282
+ ...headers,
11283
+ // Include SDK version if set
11284
+ ...sdkVersion && { "x-sdk-version": sdkVersion },
11285
+ // Don't set Content-Type for GET/HEAD requests without body
11286
+ ...isGetOrHead ? {} : isFormData ? {} : { "Content-Type": "application/json" }
11287
+ },
11288
+ // Add Next.js caching options
11289
+ next: Object.keys(nextOptions).length > 0 ? nextOptions : void 0
11290
+ };
11291
+ const fetchPromise = fetch(url, requestInit);
11292
+ if (!isGetOrHead && "tags" in endpointConfig && Array.isArray(endpointConfig.tags)) {
11293
+ const tags = endpointConfig.tags.map((tag) => tag);
11294
+ return fetchPromise.then((response) => {
11295
+ if (response.ok) {
11296
+ invalidateCacheTags(tags, params);
11297
+ }
11298
+ return response;
11299
+ });
11300
+ }
11301
+ return fetchPromise;
11302
+ };
11303
+ }
11304
+ function createParsedClient(rawClient) {
11305
+ return async (params) => {
11306
+ const response = await rawClient(params);
11307
+ const endpoint = params.endpoint;
11308
+ const config3 = API_ENDPOINTS[endpoint];
11309
+ const auth = config3.auth ?? "user";
11310
+ if (!response.ok) {
11311
+ const body = await parseErrorBody(response);
11312
+ const requestId = response.headers.get("x-request-id") ?? void 0;
11313
+ const retryAfterMs = parseRetryAfterHeader(response.headers.get("retry-after"));
11314
+ throw new ApiRequestError(
11315
+ `Request to ${String(endpoint)} failed with status ${response.status}`,
11316
+ {
11317
+ endpoint,
11318
+ status: response.status,
11319
+ method: config3.method,
11320
+ auth,
11321
+ requestId,
11322
+ body,
11323
+ retryAfterMs
11324
+ }
11325
+ );
11326
+ }
11327
+ return parseSuccessResponse(endpoint, response, config3);
11328
+ };
11329
+ }
11330
+ function createCMSClient(headers = {}, baseUrl) {
11331
+ return createParsedClient(createRawCMSClient(headers, baseUrl));
11332
+ }
11333
+ function createBearerAPIClient(token, baseUrl) {
11334
+ const authHeaders = {
11335
+ Authorization: `Bearer ${token}`
11336
+ };
11337
+ return createCMSClient(authHeaders, baseUrl);
11338
+ }
11339
+
11340
+ // ../api/src/common/envelope.ts
11341
+ function isApiError(result) {
11342
+ return result.success === false;
11343
+ }
11344
+ function isApiSuccess(result) {
11345
+ return result.success === true;
11346
+ }
11347
+ var ApiEnvelopeError = class extends Error {
11348
+ constructor(error) {
11349
+ super(error.message);
11350
+ this.name = "ApiEnvelopeError";
11351
+ this.code = error.code;
11352
+ this.requestId = error.requestId;
11353
+ this.timestamp = error.timestamp;
11354
+ this.status = error.status;
11355
+ this.fieldErrors = error.fieldErrors;
11356
+ }
11357
+ };
11358
+ function unwrapResponse(result) {
11359
+ if (isApiSuccess(result)) {
11360
+ return result.data;
11361
+ }
11362
+ throw new ApiEnvelopeError(result.error);
11363
+ }
11364
+
11365
+ // src/client/cache.ts
11366
+ var SimpleCache = class {
11367
+ constructor(options = {}) {
11368
+ this.cache = /* @__PURE__ */ new Map();
11369
+ this.maxSize = options.maxSize ?? 100;
11370
+ this.ttl = options.ttl ?? 3e5;
11371
+ this.staleTtl = options.staleTtl ?? 3e5;
11372
+ }
11373
+ /**
11374
+ * Get a fresh value (within TTL)
11375
+ * @returns The value if fresh, null otherwise
11376
+ */
11377
+ getFresh(key) {
11378
+ const entry = this.cache.get(key);
11379
+ if (!entry) return null;
11380
+ const now = Date.now();
11381
+ if (now <= entry.freshUntil) {
11382
+ return entry.value;
11383
+ }
11384
+ return null;
11385
+ }
11386
+ /**
11387
+ * Get a value that may be stale (past TTL but within staleTtl)
11388
+ * @returns Object with value and stale age, or null if expired
11389
+ */
11390
+ getStale(key) {
11391
+ const entry = this.cache.get(key);
11392
+ if (!entry) return null;
11393
+ const now = Date.now();
11394
+ if (now > entry.staleUntil) {
11395
+ this.cache.delete(key);
11396
+ return null;
11397
+ }
11398
+ const staleAgeSec = now <= entry.freshUntil ? 0 : Math.floor((now - entry.freshUntil) / 1e3);
11399
+ return {
11400
+ value: entry.value,
11401
+ staleAgeSec
11402
+ };
11403
+ }
11404
+ /**
11405
+ * Store a value with TTL and stale window
11406
+ */
11407
+ set(key, value, options) {
11408
+ const ttl = options?.ttl ?? this.ttl;
11409
+ const staleTtl = options?.staleTtl ?? this.staleTtl;
11410
+ const now = Date.now();
11411
+ while (this.cache.size >= this.maxSize && !this.cache.has(key)) {
11412
+ this.evictOne(now);
11413
+ }
11414
+ this.cache.set(key, {
11415
+ value,
11416
+ createdAt: now,
11417
+ freshUntil: now + ttl,
11418
+ staleUntil: now + ttl + staleTtl
11419
+ });
11420
+ }
11421
+ /**
11422
+ * Evict one entry to make room for a new one
11423
+ * Priority: oldest stale entry, then oldest fresh entry
11424
+ */
11425
+ evictOne(now) {
11426
+ let oldestStaleKey = null;
11427
+ let oldestStaleTime = Infinity;
11428
+ let oldestFreshKey = null;
11429
+ let oldestFreshTime = Infinity;
11430
+ for (const [key, entry] of this.cache) {
11431
+ if (now > entry.freshUntil) {
11432
+ if (entry.createdAt < oldestStaleTime) {
11433
+ oldestStaleTime = entry.createdAt;
11434
+ oldestStaleKey = key;
11435
+ }
11436
+ } else {
11437
+ if (entry.createdAt < oldestFreshTime) {
11438
+ oldestFreshTime = entry.createdAt;
11439
+ oldestFreshKey = key;
11440
+ }
11441
+ }
11442
+ }
11443
+ const keyToEvict = oldestStaleKey ?? oldestFreshKey;
11444
+ if (keyToEvict) {
11445
+ this.cache.delete(keyToEvict);
11446
+ }
11447
+ }
11448
+ /**
11449
+ * Remove all fully expired entries (past staleUntil)
11450
+ */
11451
+ prune() {
11452
+ const now = Date.now();
11453
+ for (const [key, entry] of this.cache) {
11454
+ if (now > entry.staleUntil) {
11455
+ this.cache.delete(key);
11456
+ }
11457
+ }
11458
+ }
11459
+ /**
11460
+ * Clear all entries
11461
+ */
11462
+ clear() {
11463
+ this.cache.clear();
11464
+ }
11465
+ /**
11466
+ * Check if a key exists and is not fully expired
11467
+ */
11468
+ has(key) {
11469
+ const entry = this.cache.get(key);
11470
+ if (!entry) return false;
11471
+ const now = Date.now();
11472
+ if (now > entry.staleUntil) {
11473
+ this.cache.delete(key);
11474
+ return false;
11475
+ }
11476
+ return now <= entry.freshUntil;
11477
+ }
11478
+ };
11479
+
11480
+ // src/version.ts
11481
+ var SDK_VERSION = "0.8.1";
11482
+
11483
+ // src/client/error.ts
11484
+ var RiverbankApiError = class _RiverbankApiError extends Error {
11485
+ constructor(apiError) {
11486
+ super(apiError.message);
11487
+ this.name = "RiverbankApiError";
11488
+ if ("cause" in apiError && apiError.cause) {
11489
+ this.cause = apiError.cause;
11490
+ }
11491
+ this.code = apiError.code;
11492
+ this.requestId = apiError.requestId;
11493
+ this.status = apiError.status;
11494
+ this.fieldErrors = apiError.fieldErrors;
11495
+ this.timestamp = apiError.timestamp;
11496
+ this.retryAfterMs = "retryAfterMs" in apiError ? apiError.retryAfterMs : void 0;
11497
+ this.isRetryable = this.computeRetryable();
11498
+ Object.setPrototypeOf(this, _RiverbankApiError.prototype);
11499
+ }
11500
+ /**
11501
+ * Compute whether this error is retryable based on HTTP status code.
11502
+ * - 0 (network errors - no HTTP response): retryable
11503
+ * - 429 (rate limit): retryable
11504
+ * - 5xx (server errors): retryable
11505
+ * - 4xx (client errors, except 429): NOT retryable
11506
+ */
11507
+ computeRetryable() {
11508
+ if (this.status === 0) return true;
11509
+ if (this.status === 429) return true;
11510
+ if (this.status >= 500) return true;
11511
+ return false;
11512
+ }
11513
+ /**
11514
+ * Check if this is a network error (no HTTP response received)
11515
+ *
11516
+ * Matches: network:connection_error, network:timeout, network:dns_error
11517
+ */
11518
+ isNetworkError() {
11519
+ return this.code.startsWith("network:");
11520
+ }
11521
+ /**
11522
+ * Check if this error matches a specific error code
11523
+ *
11524
+ * @example
11525
+ * ```ts
11526
+ * if (error.is('auth:unauthenticated')) {
11527
+ * // Redirect to login
11528
+ * }
11529
+ * ```
11530
+ */
11531
+ is(code) {
11532
+ return this.code === code;
11533
+ }
11534
+ /**
11535
+ * Check if this is an authentication or authorization error
11536
+ *
11537
+ * Matches: auth:unauthenticated, auth:token_expired, auth:token_invalid,
11538
+ * auth:forbidden, auth:mfa_required, auth:insufficient_permissions
11539
+ */
11540
+ isAuthError() {
11541
+ return this.code.startsWith("auth:");
11542
+ }
11543
+ /**
11544
+ * Check if this is a validation error
11545
+ *
11546
+ * Matches: validation:invalid_input, validation:missing_field, validation:invalid_format
11547
+ */
11548
+ isValidationError() {
11549
+ return this.code.startsWith("validation:");
11550
+ }
11551
+ /**
11552
+ * Check if this is a resource error (not found, conflict, etc.)
11553
+ *
11554
+ * Matches: resource:not_found, resource:already_exists, resource:conflict, resource:gone
11555
+ */
11556
+ isResourceError() {
11557
+ return this.code.startsWith("resource:");
11558
+ }
11559
+ /**
11560
+ * Check if this is a rate limiting error
11561
+ */
11562
+ isRateLimitError() {
11563
+ return this.code.startsWith("rate_limit:");
11564
+ }
11565
+ /**
11566
+ * Check if this is a billing/payment error
11567
+ */
11568
+ isBillingError() {
11569
+ return this.code.startsWith("billing:");
11570
+ }
11571
+ /**
11572
+ * Check if this is a server error
11573
+ */
11574
+ isServerError() {
11575
+ return this.code.startsWith("server:");
11576
+ }
11577
+ /**
11578
+ * Returns a human-readable string representation of the error.
11579
+ * Includes all key details for debugging.
11580
+ *
11581
+ * @example
11582
+ * "RiverbankApiError: Content keys cannot access preview content | Code: auth:forbidden | Status: 401 | RequestId: req-abc123"
11583
+ */
11584
+ toString() {
11585
+ const parts = [`RiverbankApiError: ${this.message}`];
11586
+ if (this.code) parts.push(`Code: ${this.code}`);
11587
+ if (this.status) parts.push(`Status: ${this.status}`);
11588
+ if (this.requestId) parts.push(`RequestId: ${this.requestId}`);
11589
+ return parts.join(" | ");
11590
+ }
11591
+ /**
11592
+ * Custom Node.js inspect output for better console.log display.
11593
+ * This ensures that console.log(error) shows all relevant details
11594
+ * instead of just "[Object]" for nested properties.
11595
+ */
11596
+ [Symbol.for("nodejs.util.inspect.custom")]() {
11597
+ return this.toDetailedString();
11598
+ }
11599
+ /**
11600
+ * Returns a detailed multi-line string for debugging.
11601
+ * Used by the Node.js inspect symbol for console output.
11602
+ */
11603
+ toDetailedString() {
11604
+ const lines = [
11605
+ `RiverbankApiError: ${this.message}`,
11606
+ ` Code: ${this.code}`,
11607
+ ` Status: ${this.status}`,
11608
+ ` RequestId: ${this.requestId}`,
11609
+ ` Timestamp: ${this.timestamp}`
11610
+ ];
11611
+ if (this.isRetryable) {
11612
+ lines.push(` Retryable: true`);
11613
+ if (this.retryAfterMs) {
11614
+ lines.push(` RetryAfter: ${this.retryAfterMs}ms`);
11615
+ }
11616
+ }
11617
+ if (this.fieldErrors && this.fieldErrors.length > 0) {
11618
+ lines.push(" FieldErrors:");
11619
+ this.fieldErrors.forEach((fe) => {
11620
+ lines.push(` - ${fe.field}: ${fe.message}`);
11621
+ });
11622
+ }
11623
+ return lines.join("\n");
11624
+ }
11625
+ };
11626
+
11627
+ // src/client/resilience.ts
11628
+ var DEFAULT_RETRY_CONFIG2 = {
11629
+ maxAttempts: 3,
11630
+ baseDelayMs: 200,
11631
+ maxDelayMs: 2e3,
11632
+ jitter: "full"
11633
+ };
11634
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
11635
+ failureThreshold: 5,
11636
+ resetTimeoutMs: 3e4,
11637
+ halfOpenMaxRequests: 2
11638
+ };
11639
+ var PERMANENT_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
11640
+ "ECONNREFUSED",
11641
+ // Server is not running / port not listening
11642
+ "ENOTFOUND",
11643
+ // DNS lookup failed - hostname doesn't exist
11644
+ "EAI_AGAIN"
11645
+ // DNS lookup timeout (usually permanent for invalid hosts)
11646
+ ]);
11647
+ var TRANSIENT_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
11648
+ "ECONNRESET",
11649
+ // Connection was reset mid-request (server dropped it)
11650
+ "EPIPE",
11651
+ // Broken pipe (connection closed while writing)
11652
+ "ETIMEDOUT",
11653
+ // Connection timed out (could be temporary congestion)
11654
+ "ESOCKETTIMEDOUT"
11655
+ // Socket timeout
11656
+ ]);
11657
+ var NODE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
11658
+ // Permanent
11659
+ "ECONNREFUSED",
11660
+ "ENOTFOUND",
11661
+ "EAI_AGAIN",
11662
+ // Transient
11663
+ "ECONNRESET",
11664
+ "EPIPE",
11665
+ "ETIMEDOUT",
11666
+ "ESOCKETTIMEDOUT"
11667
+ ]);
11668
+ function isNodeNetworkErrorCode(code) {
11669
+ return !code.includes(":") && NODE_NETWORK_ERROR_CODES.has(code);
11670
+ }
11671
+ function getErrorCodeFromCause(error) {
11672
+ let current = error;
11673
+ while (current) {
11674
+ const nodeError = current;
11675
+ if (nodeError.code && typeof nodeError.code === "string") {
11676
+ if (isNodeNetworkErrorCode(nodeError.code)) {
11677
+ return nodeError.code;
11678
+ }
11679
+ }
11680
+ const errorWithCause = current;
11681
+ current = errorWithCause.cause;
11682
+ }
11683
+ return void 0;
11684
+ }
11685
+ function isTransientError(error) {
11686
+ if (error instanceof RiverbankApiError) {
11687
+ const errorCode = getErrorCodeFromCause(error);
11688
+ if (errorCode && PERMANENT_NETWORK_ERROR_CODES.has(errorCode)) {
11689
+ return false;
11690
+ }
11691
+ if (error.status === 0) return true;
11692
+ if (error.status === 429) return true;
11693
+ if (error.status >= 500) return true;
11694
+ return false;
11695
+ }
11696
+ if (error instanceof TypeError) {
11697
+ const errorCode = getErrorCodeFromCause(error);
11698
+ if (errorCode) {
11699
+ if (PERMANENT_NETWORK_ERROR_CODES.has(errorCode)) {
11700
+ return false;
11701
+ }
11702
+ if (TRANSIENT_NETWORK_ERROR_CODES.has(errorCode)) {
11703
+ return true;
11704
+ }
11705
+ }
11706
+ return true;
11707
+ }
11708
+ return true;
11709
+ }
11710
+ function calculateBackoff(attempt, config3) {
11711
+ const baseDelayMs = config3.baseDelayMs ?? DEFAULT_RETRY_CONFIG2.baseDelayMs;
11712
+ const maxDelayMs = config3.maxDelayMs ?? DEFAULT_RETRY_CONFIG2.maxDelayMs;
11713
+ const jitter = config3.jitter ?? DEFAULT_RETRY_CONFIG2.jitter;
11714
+ const exponential = baseDelayMs * Math.pow(2, attempt - 1);
11715
+ const capped = Math.min(exponential, maxDelayMs);
11716
+ if (jitter === "full") {
11717
+ return Math.random() * capped;
11718
+ }
11719
+ return capped;
11720
+ }
11721
+ var CircuitBreaker = class {
11722
+ constructor(config3) {
11723
+ this.state = "closed";
11724
+ this.failureCount = 0;
11725
+ this.successCount = 0;
11726
+ this.openUntil = 0;
11727
+ this.halfOpenRequests = 0;
11728
+ this.config = {
11729
+ failureThreshold: config3?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
11730
+ resetTimeoutMs: config3?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
11731
+ halfOpenMaxRequests: config3?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
11732
+ };
11733
+ }
11734
+ /**
11735
+ * Check if circuit is open (requests should be blocked)
11736
+ * Also handles automatic transition from open to half-open after timeout
11737
+ */
11738
+ isOpen() {
11739
+ if (this.state === "open" && Date.now() >= this.openUntil) {
11740
+ this.transitionTo("half-open");
11741
+ }
11742
+ return this.state === "open";
11743
+ }
11744
+ /**
11745
+ * Check if a request can be attempted
11746
+ * - closed: always yes
11747
+ * - open: always no
11748
+ * - half-open: limited number of probes
11749
+ */
11750
+ canAttempt() {
11751
+ if (this.state === "closed") return true;
11752
+ if (this.state === "open") return false;
11753
+ return this.halfOpenRequests < this.config.halfOpenMaxRequests;
11754
+ }
11755
+ /**
11756
+ * Increment half-open request counter (call before making request in half-open)
11757
+ */
11758
+ incrementHalfOpenRequests() {
11759
+ if (this.state === "half-open") {
11760
+ this.halfOpenRequests++;
11761
+ }
11762
+ }
11763
+ /**
11764
+ * Record a successful request
11765
+ */
11766
+ recordSuccess() {
11767
+ if (this.state === "half-open") {
11768
+ this.successCount++;
11769
+ if (this.successCount >= this.config.halfOpenMaxRequests) {
11770
+ this.transitionTo("closed");
11771
+ }
11772
+ } else {
11773
+ this.failureCount = 0;
11774
+ }
11775
+ }
11776
+ /**
11777
+ * Record a failed request
11778
+ * Only counts transient failures toward circuit breaker threshold
11779
+ */
11780
+ recordFailure(error) {
11781
+ if (!isTransientError(error)) return;
11782
+ this.failureCount++;
11783
+ if (this.state === "half-open") {
11784
+ this.transitionTo("open");
11785
+ } else if (this.failureCount >= this.config.failureThreshold) {
11786
+ this.transitionTo("open");
11787
+ }
11788
+ }
11789
+ /**
11790
+ * Get current circuit state
11791
+ */
11792
+ getState() {
11793
+ return {
11794
+ state: this.state,
11795
+ failureCount: this.failureCount,
11796
+ openUntil: this.state === "open" ? this.openUntil : void 0
11797
+ };
11798
+ }
11799
+ /**
11800
+ * Transition to a new state
11801
+ */
11802
+ transitionTo(newState) {
11803
+ this.state = newState;
11804
+ if (newState === "open") {
11805
+ this.openUntil = Date.now() + this.config.resetTimeoutMs;
11806
+ } else if (newState === "half-open") {
11807
+ this.halfOpenRequests = 0;
11808
+ this.successCount = 0;
11809
+ } else if (newState === "closed") {
11810
+ this.failureCount = 0;
11811
+ this.successCount = 0;
11812
+ this.halfOpenRequests = 0;
11813
+ }
11814
+ }
11815
+ };
11816
+ async function fetchWithTimeoutAndRetry(fetcher, config3) {
11817
+ const maxAttempts = config3.maxAttempts ?? DEFAULT_RETRY_CONFIG2.maxAttempts;
11818
+ const requestTimeoutMs = config3.requestTimeoutMs ?? 8e3;
11819
+ let lastError;
11820
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
11821
+ try {
11822
+ const controller = new AbortController();
11823
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
11824
+ try {
11825
+ const result = await fetcher(controller.signal);
11826
+ return result;
11827
+ } finally {
11828
+ clearTimeout(timeoutId);
11829
+ }
11830
+ } catch (error) {
11831
+ lastError = error;
11832
+ const shouldRetry = shouldRetryError(error, config3.retryOn);
11833
+ if (!shouldRetry) {
11834
+ throw error;
11835
+ }
11836
+ if (attempt < maxAttempts) {
11837
+ const delay = getRetryDelay(error, attempt, config3);
11838
+ await sleep2(delay);
11839
+ }
11840
+ }
11841
+ }
11842
+ throw lastError;
11843
+ }
11844
+ function isAbortError(error) {
11845
+ if (typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError") {
11846
+ return true;
11847
+ }
11848
+ if (error instanceof Error && error.name === "AbortError") {
11849
+ return true;
11850
+ }
11851
+ return false;
11852
+ }
11853
+ function shouldRetryError(error, customRetryOn) {
11854
+ if (isAbortError(error)) {
11855
+ return false;
11856
+ }
11857
+ if (customRetryOn) {
11858
+ const statusCode = error instanceof RiverbankApiError ? error.status : void 0;
11859
+ return customRetryOn(error, statusCode);
11860
+ }
11861
+ return isTransientError(error);
11862
+ }
11863
+ function getRetryDelay(error, attempt, config3) {
11864
+ if (error instanceof RiverbankApiError && error.retryAfterMs) {
11865
+ return error.retryAfterMs;
11866
+ }
11867
+ return calculateBackoff(attempt, config3);
11868
+ }
11869
+ function sleep2(ms) {
11870
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
11871
+ }
11872
+ var CircuitOpenError = class extends Error {
11873
+ constructor(state) {
11874
+ super("Circuit breaker is open");
11875
+ this.name = "CircuitOpenError";
11876
+ this.circuitState = state;
11877
+ }
11878
+ };
11879
+
11880
+ // src/client/index.ts
11881
+ var prebuildModule = null;
11882
+ function getPrebuildModule() {
11883
+ if (prebuildModule !== null) return prebuildModule;
11884
+ if (typeof process === "undefined" || !process.versions?.node) {
11885
+ return null;
11886
+ }
11887
+ try {
11888
+ prebuildModule = (init_loader(), __toCommonJS(loader_exports));
11889
+ return prebuildModule;
11890
+ } catch {
11891
+ return null;
11892
+ }
11893
+ }
11894
+ setSdkVersion(SDK_VERSION);
11895
+ var DEFAULT_BROWSER_TIMEOUT_MS = 5e3;
11896
+ var DEFAULT_SERVER_TIMEOUT_MS = 8e3;
11897
+ function generateRequestId2() {
11898
+ return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
11899
+ }
11900
+ function isAbortError2(error) {
11901
+ if (error instanceof DOMException && error.name === "AbortError") {
11902
+ return true;
11903
+ }
11904
+ if (error instanceof Error && error.name === "AbortError") {
11905
+ return true;
11906
+ }
11907
+ return false;
11908
+ }
11909
+ function getNetworkErrorCode(error) {
11910
+ if (error.name === "TimeoutError" || error.name === "AbortError") {
11911
+ return "network:timeout";
11912
+ }
11913
+ const nodeError = error;
11914
+ if (nodeError.code) {
11915
+ switch (nodeError.code) {
11916
+ case "ETIMEDOUT":
11917
+ case "ESOCKETTIMEDOUT":
11918
+ return "network:timeout";
11919
+ case "ENOTFOUND":
11920
+ case "EAI_AGAIN":
11921
+ return "network:dns_error";
11922
+ case "ECONNREFUSED":
11923
+ case "ECONNRESET":
11924
+ case "EPIPE":
11925
+ return "network:connection_error";
11926
+ }
11927
+ }
11928
+ const message = error.message.toLowerCase();
11929
+ if (message.includes("timeout") || message.includes("timed out")) {
11930
+ return "network:timeout";
11931
+ }
11932
+ if (message.includes("dns") || message.includes("getaddrinfo") || message.includes("enotfound")) {
11933
+ return "network:dns_error";
11934
+ }
11935
+ return "network:connection_error";
11936
+ }
11937
+ function convertToTypedError(error) {
11938
+ if (isAbortError2(error)) {
11939
+ throw error;
11940
+ }
11941
+ if (error instanceof ApiEnvelopeError) {
11942
+ throw new RiverbankApiError({
11943
+ code: error.code,
11944
+ message: error.message,
11945
+ requestId: error.requestId,
11946
+ timestamp: error.timestamp,
11947
+ status: error.status,
11948
+ fieldErrors: error.fieldErrors
11949
+ });
11950
+ }
11951
+ if (error instanceof ApiRequestError && error.body && typeof error.body === "object") {
11952
+ const body = error.body;
11953
+ if (isApiError(body)) {
11954
+ const envelopeError = body.error;
11955
+ throw new RiverbankApiError({
11956
+ ...envelopeError,
11957
+ retryAfterMs: error.retryAfterMs
11958
+ });
11959
+ }
11960
+ }
11961
+ if (error instanceof TypeError || error instanceof Error && !("status" in error)) {
11962
+ const networkError = error;
11963
+ throw new RiverbankApiError({
11964
+ code: getNetworkErrorCode(networkError),
11965
+ message: networkError.message || "Network request failed",
11966
+ requestId: `local-${Date.now()}`,
11967
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11968
+ status: 0,
11969
+ // No HTTP response received
11970
+ cause: networkError
11971
+ // Preserve original error for retry/circuit breaker classification
11972
+ });
11973
+ }
11974
+ throw error;
11975
+ }
11976
+ function detectKeyType(apiKey) {
11977
+ if (apiKey.startsWith("bld_live_sk_") || apiKey.startsWith("bld_test_sk_")) {
11978
+ return "content";
11979
+ }
11980
+ if (apiKey.startsWith("bld_preview_sk_")) {
11981
+ return "preview";
11982
+ }
11983
+ return "unknown";
11984
+ }
11985
+ function createRiverbankClient(config3) {
11986
+ if (!config3.baseUrl) {
11987
+ throw new Error(
11988
+ "baseUrl is required when creating a Riverbank client. Expected format: https://dashboard.example.com/api (must include /api path)"
11989
+ );
11990
+ }
11991
+ if (!config3.baseUrl.endsWith("/api")) {
11992
+ throw new Error(
11993
+ `baseUrl must end with '/api'. Received: ${config3.baseUrl}. Expected format: https://dashboard.example.com/api`
11994
+ );
11995
+ }
11996
+ const cacheEnabled = config3.cache?.enabled ?? true;
11997
+ const cacheTTL = (config3.cache?.ttl ?? 300) * 1e3;
11998
+ const cacheMaxSize = config3.cache?.maxSize ?? 100;
11999
+ const resilienceEnabled = config3.resilience?.enabled ?? true;
12000
+ const staleIfError = config3.resilience?.staleIfError ?? true;
12001
+ const staleTtlMs = (config3.resilience?.staleTtlSec ?? 300) * 1e3;
12002
+ const requestTimeoutMs = config3.resilience?.requestTimeoutMs ?? (typeof window !== "undefined" ? DEFAULT_BROWSER_TIMEOUT_MS : DEFAULT_SERVER_TIMEOUT_MS);
12003
+ const retryConfig = {
12004
+ maxAttempts: config3.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG2.maxAttempts,
12005
+ baseDelayMs: config3.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG2.baseDelayMs,
12006
+ maxDelayMs: config3.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG2.maxDelayMs,
12007
+ jitter: config3.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG2.jitter,
12008
+ retryOn: config3.resilience?.retry?.retryOn
12009
+ };
12010
+ const circuitBreakerConfig = {
12011
+ failureThreshold: config3.resilience?.circuitBreaker?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,
12012
+ resetTimeoutMs: config3.resilience?.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
12013
+ halfOpenMaxRequests: config3.resilience?.circuitBreaker?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
12014
+ };
12015
+ const keyType = detectKeyType(config3.apiKey);
12016
+ const apiClient = createBearerAPIClient(config3.apiKey, config3.baseUrl);
12017
+ const cache = new SimpleCache({
12018
+ maxSize: cacheMaxSize,
12019
+ ttl: cacheTTL,
12020
+ staleTtl: staleTtlMs
12021
+ });
12022
+ const circuitBreaker = new CircuitBreaker(circuitBreakerConfig);
12023
+ const prebuildDir = config3.resilience?.prebuildDir;
12024
+ const prebuildMod = prebuildDir ? getPrebuildModule() : null;
12025
+ const prebuildLoader = prebuildMod?.canUsePrebuild() && prebuildDir ? new prebuildMod.PrebuildLoader({
12026
+ prebuildDir,
12027
+ maxPrebuildAgeSec: config3.resilience?.maxPrebuildAgeSec
12028
+ }) : null;
12029
+ let lastStatus = null;
12030
+ let isDegraded = false;
12031
+ function emitStatus(source, data, details) {
12032
+ const status = {
12033
+ source,
12034
+ isPreview: details.isPreview,
12035
+ cacheKey: details.cacheKey,
12036
+ error: details.error,
12037
+ staleAgeSec: details.staleAgeSec,
12038
+ prebuildAgeSec: details.prebuildAgeSec,
12039
+ circuit: circuitBreaker.getState(),
12040
+ requestId: details.requestId,
12041
+ durationMs: details.durationMs
12042
+ };
12043
+ lastStatus = status;
12044
+ config3.resilience?.onStatusChange?.(status);
12045
+ const nowDegraded = source === "stale" || source === "error";
12046
+ if (nowDegraded !== isDegraded) {
12047
+ isDegraded = nowDegraded;
12048
+ config3.resilience?.onDegradedMode?.(nowDegraded, status);
12049
+ }
12050
+ return data;
12051
+ }
12052
+ async function resilientFetch(cacheKey, fetcher, options) {
12053
+ const requestId = generateRequestId2();
12054
+ const startTime = Date.now();
12055
+ const isPreview = options.preview ?? false;
12056
+ const statusDetails = (extra = {}) => ({
12057
+ requestId,
12058
+ cacheKey,
12059
+ isPreview,
12060
+ durationMs: Date.now() - startTime,
12061
+ ...extra
12062
+ });
12063
+ if (cacheEnabled && !options.force) {
12064
+ const fresh = cache.getFresh(cacheKey);
12065
+ if (fresh !== null) {
12066
+ return emitStatus("cache", fresh, statusDetails());
12067
+ }
12068
+ }
12069
+ if (resilienceEnabled && circuitBreaker.isOpen()) {
12070
+ if (!isPreview && staleIfError) {
12071
+ const stale = cache.getStale(cacheKey);
12072
+ if (stale) {
12073
+ return emitStatus("stale", stale.value, statusDetails({
12074
+ staleAgeSec: stale.staleAgeSec,
12075
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
12076
+ }));
12077
+ }
12078
+ }
12079
+ if (!isPreview && options.prebuildFallback) {
12080
+ const prebuildResult = options.prebuildFallback();
12081
+ if (prebuildResult) {
12082
+ return emitStatus("prebuild", prebuildResult.data, statusDetails({
12083
+ prebuildAgeSec: prebuildResult.prebuildAgeSec,
12084
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
12085
+ }));
12086
+ }
12087
+ }
12088
+ const circuitState = circuitBreaker.getState();
12089
+ emitStatus("error", null, statusDetails({
12090
+ error: { code: "circuit_open", message: "Circuit breaker is open" }
12091
+ }));
12092
+ throw new CircuitOpenError(circuitState);
12093
+ }
12094
+ try {
12095
+ let data;
12096
+ if (resilienceEnabled) {
12097
+ if (circuitBreaker.getState().state === "half-open") {
12098
+ circuitBreaker.incrementHalfOpenRequests();
12099
+ }
12100
+ data = await fetchWithTimeoutAndRetry(
12101
+ async (timeoutSignal) => {
12102
+ const combinedSignal = options.signal ? combineAbortSignals(timeoutSignal, options.signal) : timeoutSignal;
12103
+ try {
12104
+ const response = await fetcher(combinedSignal);
12105
+ return unwrapResponse(response);
12106
+ } catch (error) {
12107
+ convertToTypedError(error);
12108
+ }
12109
+ },
12110
+ {
12111
+ ...retryConfig,
12112
+ requestTimeoutMs
12113
+ }
12114
+ );
12115
+ circuitBreaker.recordSuccess();
12116
+ } else {
12117
+ try {
12118
+ const response = await fetcher(options.signal ?? new AbortController().signal);
12119
+ data = unwrapResponse(response);
12120
+ } catch (error) {
12121
+ convertToTypedError(error);
12122
+ }
12123
+ }
12124
+ if (cacheEnabled) {
12125
+ cache.set(cacheKey, data);
12126
+ }
12127
+ return emitStatus("live", data, statusDetails());
12128
+ } catch (error) {
12129
+ if (resilienceEnabled && error instanceof Error) {
12130
+ circuitBreaker.recordFailure(error);
12131
+ }
12132
+ if (!isPreview && staleIfError && cacheEnabled) {
12133
+ const stale = cache.getStale(cacheKey);
12134
+ if (stale) {
12135
+ const errorInfo2 = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
12136
+ return emitStatus("stale", stale.value, statusDetails({
12137
+ staleAgeSec: stale.staleAgeSec,
12138
+ error: errorInfo2
12139
+ }));
12140
+ }
12141
+ }
12142
+ if (!isPreview && options.prebuildFallback) {
12143
+ const prebuildResult = options.prebuildFallback();
12144
+ if (prebuildResult) {
12145
+ const errorInfo2 = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
12146
+ return emitStatus("prebuild", prebuildResult.data, statusDetails({
12147
+ prebuildAgeSec: prebuildResult.prebuildAgeSec,
12148
+ error: errorInfo2
12149
+ }));
12150
+ }
12151
+ }
12152
+ const errorInfo = error instanceof RiverbankApiError ? { code: error.code, message: error.message } : { message: error.message };
12153
+ emitStatus("error", null, statusDetails({ error: errorInfo }));
12154
+ throw error;
12155
+ }
12156
+ }
12157
+ function combineAbortSignals(...signals) {
12158
+ const controller = new AbortController();
12159
+ for (const signal of signals) {
12160
+ if (signal.aborted) {
12161
+ controller.abort(signal.reason);
12162
+ break;
12163
+ }
12164
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
12165
+ }
12166
+ return controller.signal;
12167
+ }
12168
+ return {
12169
+ async getSite(params) {
12170
+ const { slug, domain, id, signal } = params;
12171
+ if (!slug && !domain && !id) {
12172
+ throw new Error(
12173
+ `getSite() requires at least one identifier: slug, domain, or id. Received: ${JSON.stringify(params)}`
12174
+ );
12175
+ }
12176
+ const cacheKey = `site:${slug || domain || id}`;
12177
+ const siteId = id || slug || domain;
12178
+ return resilientFetch(cacheKey, async (sig) => {
12179
+ const apiParams = {};
12180
+ if (params.slug) apiParams.slug = params.slug;
12181
+ if (params.domain) apiParams.domain = params.domain;
12182
+ if (params.id) apiParams.id = params.id;
12183
+ return await apiClient({ endpoint: "getSite", params: apiParams, options: { signal: sig } });
12184
+ }, {
12185
+ signal,
12186
+ prebuildFallback: prebuildLoader && siteId ? () => prebuildLoader.loadSite(siteId) : void 0
12187
+ });
12188
+ },
12189
+ async getPage(params) {
12190
+ const { siteId, path: path13, preview = false, signal } = params;
12191
+ const cacheKey = `page:${siteId}:${path13}:${preview}`;
12192
+ return resilientFetch(cacheKey, async (sig) => {
12193
+ return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path: path13, preview }, options: { signal: sig } });
12194
+ }, {
12195
+ preview,
12196
+ signal,
12197
+ // Prebuild fallback only for published pages (not preview)
12198
+ prebuildFallback: prebuildLoader && !preview ? () => prebuildLoader.loadPage(siteId, path13) : void 0
12199
+ });
12200
+ },
12201
+ async getEntries(params) {
12202
+ const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta, signal } = params;
12203
+ const entryIdsCacheKey = mode === "manual" && entryIds?.length ? entryIds.join(",") : "";
12204
+ const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ""}:${offset ?? ""}:${order ?? ""}:${preview}:${mode ?? ""}:${entryIdsCacheKey}:${includeMeta ?? ""}`;
12205
+ return resilientFetch(cacheKey, async (sig) => {
12206
+ let orderParam;
12207
+ if (order === "newest") {
12208
+ orderParam = "published_at.desc";
12209
+ } else if (order === "oldest") {
12210
+ orderParam = "published_at.asc";
12211
+ } else if (order === "title") {
12212
+ orderParam = "title.asc";
12213
+ }
12214
+ const apiParams = {
12215
+ siteId,
12216
+ type: contentType,
12217
+ ...typeof limit === "number" && { limit: String(limit) },
12218
+ ...typeof offset === "number" && { offset: String(offset) },
12219
+ ...includeMeta && { meta: "true" },
12220
+ ...orderParam && { order: orderParam },
12221
+ ...preview && { stage: "preview" },
12222
+ ...mode === "manual" && entryIds?.length && {
12223
+ mode: "manual",
12224
+ entryIds: JSON.stringify(entryIds)
12225
+ }
12226
+ };
12227
+ return await apiClient({ endpoint: "listPublishedEntries", params: apiParams, options: { signal: sig } });
12228
+ }, {
12229
+ preview,
12230
+ signal,
12231
+ // Prebuild fallback only for published entries (not preview, not manual mode)
12232
+ prebuildFallback: prebuildLoader && !preview ? () => prebuildLoader.loadEntries(siteId, params) : void 0
12233
+ });
12234
+ },
12235
+ async getEntry(params) {
12236
+ const { siteId, contentType, slug, signal } = params;
12237
+ const cacheKey = `entry:${siteId}:${contentType}:${slug}`;
12238
+ return resilientFetch(cacheKey, async (sig) => {
12239
+ return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug }, options: { signal: sig } });
12240
+ }, { signal });
12241
+ },
12242
+ async getPublicFormById(params) {
12243
+ const { formId, signal } = params;
12244
+ if (!formId) {
12245
+ throw new Error("getPublicFormById() requires formId");
12246
+ }
12247
+ const cacheKey = `public-form:${formId}`;
12248
+ return resilientFetch(cacheKey, async (sig) => {
12249
+ return await apiClient({ endpoint: "getPublicFormById", params: { formId }, options: { signal: sig } });
12250
+ }, { signal });
12251
+ },
12252
+ async getPublicBookingServices(params) {
12253
+ const { siteId, ids, signal } = params;
12254
+ if (!siteId) {
12255
+ throw new Error("getPublicBookingServices() requires siteId");
12256
+ }
12257
+ const cacheKey = `public-booking-services:${siteId}:${ids ?? ""}`;
12258
+ return resilientFetch(cacheKey, async (sig) => {
12259
+ const apiParams = {
12260
+ siteId,
12261
+ ...ids && { ids }
12262
+ };
12263
+ return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams, options: { signal: sig } });
12264
+ }, { signal });
12265
+ },
12266
+ async listPublicEvents(params) {
12267
+ const { siteId, limit, from, to, stage, signal } = params;
12268
+ if (!siteId) {
12269
+ throw new Error("listPublicEvents() requires siteId");
12270
+ }
12271
+ const cacheKey = `public-events:${siteId}:${limit ?? ""}:${from ?? ""}:${to ?? ""}:${stage ?? ""}`;
12272
+ return resilientFetch(cacheKey, async (sig) => {
12273
+ const apiParams = {
12274
+ siteId,
12275
+ ...typeof limit === "number" && { limit: String(limit) },
12276
+ ...from && { from },
12277
+ ...to && { to },
12278
+ ...stage && { stage }
12279
+ };
12280
+ return await apiClient({ endpoint: "listPublicEvents", params: apiParams, options: { signal: sig } });
12281
+ }, { signal });
12282
+ },
12283
+ async resolveEventOccurrence(params) {
12284
+ const { siteId, entryId, segment, signal } = params;
12285
+ if (!siteId || !entryId || !segment) {
12286
+ throw new Error("resolveEventOccurrence() requires siteId, entryId, and segment");
12287
+ }
12288
+ const cacheKey = `event-occurrence:${siteId}:${entryId}:${segment}`;
12289
+ return resilientFetch(cacheKey, async (sig) => {
12290
+ return await apiClient({
12291
+ endpoint: "resolveEventOccurrence",
12292
+ params: { siteId, entryId, segment },
12293
+ options: { signal: sig }
12294
+ });
12295
+ }, { signal });
12296
+ },
12297
+ async checkRedirect(params) {
12298
+ const { siteId, path: path13, signal } = params;
12299
+ if (!siteId || !path13) {
12300
+ throw new Error("checkRedirect() requires siteId and path");
12301
+ }
12302
+ const cacheKey = `redirect:${siteId}:${path13}`;
12303
+ return resilientFetch(cacheKey, async (sig) => {
12304
+ return await apiClient({
12305
+ endpoint: "checkRedirect",
12306
+ params: { site: siteId, path: path13 },
12307
+ options: { signal: sig }
12308
+ });
12309
+ }, { signal });
12310
+ },
12311
+ async getAllPublishedRoutes(params) {
12312
+ const { siteId, signal } = params;
12313
+ if (!siteId) {
12314
+ throw new Error("getAllPublishedRoutes() requires siteId");
12315
+ }
12316
+ const cacheKey = `routable-content:${siteId}:published`;
12317
+ return resilientFetch(cacheKey, async (sig) => {
12318
+ return await apiClient({
12319
+ endpoint: "getPublicRoutableContent",
12320
+ params: { siteId, publishedOnly: "true" },
12321
+ options: { signal: sig }
12322
+ });
12323
+ }, { signal });
12324
+ },
12325
+ clearCache() {
12326
+ cache.clear();
12327
+ },
12328
+ getKeyType() {
12329
+ return keyType;
12330
+ },
12331
+ getLastEmittedStatus() {
12332
+ return lastStatus;
12333
+ },
12334
+ getCircuitState() {
12335
+ return circuitBreaker.getState();
12336
+ }
12337
+ };
12338
+ }
12339
+ init_constants();
12340
+ var MANIFEST_VERSION = "1.0.0";
12341
+ var MANIFEST_FILENAME = "manifest.json";
12342
+ function pathToFilename(routePath) {
12343
+ if (routePath === "/") return "_home.json";
12344
+ return routePath.slice(1).replace(/[^a-zA-Z0-9\-\/]/g, "_").replace(/\//g, "-") + ".json";
12345
+ }
12346
+ function ensureDir3(dirPath) {
12347
+ if (!fs6__namespace.existsSync(dirPath)) {
12348
+ fs6__namespace.mkdirSync(dirPath, { recursive: true });
12349
+ }
12350
+ }
12351
+ function writeJsonFile2(filePath, data) {
12352
+ const content = JSON.stringify(data, null, 2);
12353
+ fs6__namespace.writeFileSync(filePath, content, "utf-8");
12354
+ return Buffer.byteLength(content, "utf-8");
12355
+ }
12356
+ function calculateChecksum(data) {
12357
+ const { checksum: _, ...rest } = data;
12358
+ const content = JSON.stringify(rest);
12359
+ return crypto2__namespace.createHash("sha256").update(content).digest("hex");
12360
+ }
12361
+ async function fetchAllEntries(client, siteId, contentType, onProgress) {
12362
+ const allEntries = [];
12363
+ let offset = 0;
12364
+ let hasMore = true;
12365
+ while (hasMore) {
12366
+ onProgress?.(`${contentType} (batch ${Math.floor(offset / PREBUILD_PAGE_SIZE) + 1})`);
12367
+ const response = await client.getEntries({
12368
+ siteId,
12369
+ contentType,
12370
+ limit: PREBUILD_PAGE_SIZE,
12371
+ offset,
12372
+ preview: false
12373
+ // Published content only
12374
+ });
12375
+ allEntries.push(...response.entries);
12376
+ offset += PREBUILD_PAGE_SIZE;
12377
+ hasMore = response.entries.length === PREBUILD_PAGE_SIZE;
12378
+ }
12379
+ return allEntries;
12380
+ }
12381
+ async function prebuildSite(client, siteId, outputDir) {
12382
+ const site = await client.getSite({ id: siteId });
12383
+ const filename = "site.json";
12384
+ const filePath = path9__namespace.join(outputDir, filename);
12385
+ const cacheFile = {
12386
+ data: site,
12387
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12388
+ };
12389
+ const size = writeJsonFile2(filePath, cacheFile);
12390
+ return { files: [filename], size, siteData: site };
12391
+ }
12392
+ async function prebuildPages(client, siteId, routes, outputDir, onProgress) {
12393
+ const pagesDir = path9__namespace.join(outputDir, "pages");
12394
+ ensureDir3(pagesDir);
12395
+ const files = [];
12396
+ let totalSize = 0;
12397
+ const publishedPaths = Object.entries(routes).filter(([_, route]) => route.status === "published").map(([_, route]) => route.path);
12398
+ const pageIndex = [];
12399
+ const total = publishedPaths.length;
12400
+ let current = 0;
12401
+ for (const pagePath of publishedPaths) {
12402
+ current++;
12403
+ onProgress?.({
12404
+ current,
12405
+ total,
12406
+ item: `Page: ${pagePath}`,
12407
+ contentType: "pages"
12408
+ });
12409
+ try {
12410
+ const pageData = await client.getPage({ siteId, path: pagePath, preview: false });
12411
+ const filename = pathToFilename(pagePath);
12412
+ const filePath = path9__namespace.join(pagesDir, filename);
12413
+ const cacheFile = {
12414
+ data: pageData,
12415
+ path: pagePath,
12416
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12417
+ };
12418
+ totalSize += writeJsonFile2(filePath, cacheFile);
12419
+ files.push(`pages/${filename}`);
12420
+ if ("page" in pageData) {
12421
+ pageIndex.push({
12422
+ path: pagePath,
12423
+ pageId: pageData.page.routeId || "",
12424
+ title: pageData.page.name || "Untitled"
12425
+ });
12426
+ }
12427
+ } catch (error) {
12428
+ console.warn(`[Prebuild] Failed to fetch page ${pagePath}:`, error.message);
12429
+ }
12430
+ }
12431
+ const indexFile = {
12432
+ pages: pageIndex,
12433
+ totalCount: pageIndex.length,
12434
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12435
+ };
12436
+ const indexPath = path9__namespace.join(pagesDir, "_index.json");
12437
+ totalSize += writeJsonFile2(indexPath, indexFile);
12438
+ files.push("pages/_index.json");
12439
+ return { files, size: totalSize };
12440
+ }
12441
+ async function prebuildEntries(client, siteId, contentTypes, outputDir, onProgress) {
12442
+ const entriesDir = path9__namespace.join(outputDir, "entries");
12443
+ ensureDir3(entriesDir);
12444
+ const files = [];
12445
+ let totalSize = 0;
12446
+ const total = contentTypes.length;
12447
+ let current = 0;
12448
+ for (const contentType of contentTypes) {
12449
+ current++;
12450
+ const entries = await fetchAllEntries(
12451
+ client,
12452
+ siteId,
12453
+ contentType,
12454
+ (item) => {
12455
+ onProgress?.({
12456
+ current,
12457
+ total,
12458
+ item: `Entries: ${item}`,
12459
+ contentType: "entries"
12460
+ });
12461
+ }
12462
+ );
12463
+ const typeDir = path9__namespace.join(entriesDir, contentType);
12464
+ ensureDir3(typeDir);
12465
+ const cacheFile = {
12466
+ entries,
12467
+ contentType,
12468
+ totalCount: entries.length,
12469
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12470
+ };
12471
+ const filename = `entries/${contentType}/all.json`;
12472
+ const filePath = path9__namespace.join(entriesDir, contentType, "all.json");
12473
+ totalSize += writeJsonFile2(filePath, cacheFile);
12474
+ files.push(filename);
12475
+ }
12476
+ return { files, size: totalSize };
12477
+ }
12478
+ async function prebuildNavigation(siteData, outputDir, onProgress) {
12479
+ const navDir = path9__namespace.join(outputDir, "navigation");
12480
+ ensureDir3(navDir);
12481
+ const files = [];
12482
+ let totalSize = 0;
12483
+ const menus = siteData.navigation || [];
12484
+ onProgress?.({
12485
+ current: 1,
12486
+ total: 1,
12487
+ item: "Navigation menus",
12488
+ contentType: "navigation"
12489
+ });
12490
+ const cacheFile = {
12491
+ menus: menus.map((menu) => ({
12492
+ identifier: menu.identifier,
12493
+ id: menu.id,
12494
+ name: menu.name,
12495
+ items: menu.items
12496
+ })),
12497
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12498
+ };
12499
+ const filename = "navigation/menus.json";
12500
+ const filePath = path9__namespace.join(navDir, "menus.json");
12501
+ totalSize += writeJsonFile2(filePath, cacheFile);
12502
+ files.push(filename);
12503
+ return { files, size: totalSize };
12504
+ }
12505
+ async function prebuildCache(options) {
12506
+ const {
12507
+ client,
12508
+ siteId,
12509
+ outputDir = DEFAULT_PREBUILD_DIR,
12510
+ include = ["site", "pages", "entries", "navigation"],
12511
+ contentTypes,
12512
+ onProgress
12513
+ } = options;
12514
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
12515
+ const files = [];
12516
+ let totalSize = 0;
12517
+ const errors = [];
12518
+ let siteData = null;
12519
+ ensureDir3(outputDir);
12520
+ if (include.includes("site") || include.includes("pages") || include.includes("navigation")) {
12521
+ try {
12522
+ const result = await prebuildSite(client, siteId, outputDir);
12523
+ siteData = result.siteData;
12524
+ if (include.includes("site")) {
12525
+ files.push(...result.files);
12526
+ totalSize += result.size;
12527
+ }
12528
+ onProgress?.({ current: 1, total: 4, item: "Site data", contentType: "site" });
12529
+ } catch (error) {
12530
+ errors.push(`Site prebuild failed: ${error.message}`);
12531
+ }
12532
+ }
12533
+ if (include.includes("pages") && siteData) {
12534
+ try {
12535
+ const result = await prebuildPages(
12536
+ client,
12537
+ siteId,
12538
+ siteData.routes,
12539
+ outputDir,
12540
+ onProgress
12541
+ );
12542
+ files.push(...result.files);
12543
+ totalSize += result.size;
12544
+ } catch (error) {
12545
+ errors.push(`Pages prebuild failed: ${error.message}`);
12546
+ }
12547
+ }
12548
+ if (include.includes("entries")) {
12549
+ try {
12550
+ const typesToPrebuild = contentTypes || [];
12551
+ if (typesToPrebuild.length > 0) {
12552
+ const result = await prebuildEntries(
12553
+ client,
12554
+ siteId,
12555
+ typesToPrebuild,
12556
+ outputDir,
12557
+ onProgress
12558
+ );
12559
+ files.push(...result.files);
12560
+ totalSize += result.size;
12561
+ } else {
12562
+ console.warn("[Prebuild] No contentTypes provided - skipping entries prebuild");
12563
+ }
12564
+ } catch (error) {
12565
+ errors.push(`Entries prebuild failed: ${error.message}`);
12566
+ }
12567
+ }
12568
+ if (include.includes("navigation") && siteData) {
12569
+ try {
12570
+ const result = await prebuildNavigation(siteData, outputDir, onProgress);
12571
+ files.push(...result.files);
12572
+ totalSize += result.size;
12573
+ } catch (error) {
12574
+ errors.push(`Navigation prebuild failed: ${error.message}`);
12575
+ }
12576
+ }
12577
+ const keyToFile = {};
12578
+ if (files.includes("site.json")) {
12579
+ keyToFile[`site:${siteId}`] = "site.json";
12580
+ }
12581
+ for (const file of files) {
12582
+ if (file.startsWith("pages/") && file !== "pages/_index.json") {
12583
+ const filename = file.replace("pages/", "").replace(".json", "");
12584
+ const pagePath = filename === "_home" ? "/" : "/" + filename.replace(/-/g, "/");
12585
+ keyToFile[`page:${siteId}:${pagePath}:false`] = file;
12586
+ }
12587
+ if (file.startsWith("entries/") && file.endsWith("/all.json")) {
12588
+ const contentType = file.split("/")[1];
12589
+ keyToFile[`entries-all:${siteId}:${contentType}`] = file;
12590
+ }
12591
+ }
12592
+ const manifest = {
12593
+ version: MANIFEST_VERSION,
12594
+ generatedAt,
12595
+ siteId,
12596
+ sdkVersion: SDK_VERSION,
12597
+ keyToFile,
12598
+ includedTypes: include,
12599
+ fileCount: files.length,
12600
+ totalSize
12601
+ };
12602
+ const checksum = calculateChecksum(manifest);
12603
+ manifest.checksum = checksum;
12604
+ const manifestPath = path9__namespace.join(outputDir, MANIFEST_FILENAME);
12605
+ writeJsonFile2(manifestPath, manifest);
12606
+ return {
12607
+ success: errors.length === 0,
12608
+ outputDir,
12609
+ files,
12610
+ totalSize,
12611
+ generatedAt,
12612
+ checksum,
12613
+ errors: errors.length > 0 ? errors : void 0
12614
+ };
12615
+ }
12616
+
12617
+ // src/cli/commands/deploy.ts
12618
+ init_constants();
12619
+ async function loadDeployConfig() {
12620
+ const rawConfig = await loadConfigFile();
12621
+ const config3 = rawConfig;
12622
+ if (!config3.siteId) {
12623
+ throw new Error("siteId is required in riverbank.config.ts");
12624
+ }
12625
+ const contentTypes = config3.content?.contentTypes?.map((ct) => ct.key);
12626
+ return {
12627
+ siteId: config3.siteId,
12628
+ deploy: config3.deploy ?? {},
12629
+ contentTypes
12630
+ };
12631
+ }
12632
+ async function checkWorkingDirectoryClean(git, prebuildOutput) {
12633
+ const status = await git.status();
12634
+ const dirtyFiles = status.files.map((f) => f.path).filter((path13) => !path13.startsWith(prebuildOutput));
12635
+ return {
12636
+ clean: dirtyFiles.length === 0,
12637
+ dirtyFiles
12638
+ };
12639
+ }
12640
+ async function commitCacheIfChanged(git, prebuildOutput) {
12641
+ const status = await git.status();
12642
+ const cacheChanges = status.files.filter((f) => f.path.startsWith(prebuildOutput));
12643
+ if (cacheChanges.length === 0) {
12644
+ return false;
12645
+ }
12646
+ await git.add(prebuildOutput);
12647
+ const log = await git.log({ maxCount: 1 });
12648
+ const lastCommit = log.latest;
12649
+ const isLastCommitCache = lastCommit?.message.startsWith("chore: update prebuild cache");
12650
+ let isUnpushed = false;
12651
+ if (isLastCommitCache && lastCommit) {
12652
+ try {
12653
+ const branches = await git.branch(["-r", "--contains", lastCommit.hash]);
12654
+ isUnpushed = branches.all.length === 0;
12655
+ } catch (error) {
12656
+ if (process.env.DEBUG) {
12657
+ console.debug("[deploy] Could not check remote branches:", error);
12658
+ }
12659
+ isUnpushed = true;
12660
+ }
12661
+ }
12662
+ if (isLastCommitCache && isUnpushed) {
12663
+ await git.commit([], { "--amend": null, "--no-edit": null });
12664
+ } else {
12665
+ await git.commit("chore: update prebuild cache");
12666
+ }
12667
+ return true;
12668
+ }
12669
+ var deployCommand = new commander.Command("deploy").description("Deploy site with prebuild cache generation").option("--preview", "Preview deploy - skip prebuild cache generation").addHelpText("after", `
12670
+ Examples:
12671
+ $ riverbankcms deploy # Full deploy with cache generation
12672
+ $ riverbankcms deploy --preview # Deploy without regenerating cache
12673
+
12674
+ Configuration (riverbank.config.ts):
12675
+ deploy: {
12676
+ verifyCommand: 'pnpm verify', // Command to run before deploy
12677
+ prebuildOutput: '.riverbank-cache', // Prebuild output directory
12678
+ }
12679
+
12680
+ Workflow:
12681
+ 1. Run verifyCommand (if configured)
12682
+ 2. Check working directory is clean
12683
+ 3. Generate prebuild cache (unless --preview)
12684
+ 4. Commit cache changes (squashes consecutive unpushed commits)
12685
+ 5. Push to remote
12686
+
12687
+ Notes:
12688
+ - Requires git repository with remote configured
12689
+ - CMS failure during prebuild continues with existing cache
12690
+ - Uses --remote environment variables for API access
12691
+ `).action(async (options, command) => {
12692
+ const { output } = getOutputContext(command);
12693
+ const git = simpleGit__default.default();
12694
+ try {
12695
+ if (!await git.checkIsRepo()) {
12696
+ return output.error("Not a git repository", {
12697
+ suggestion: "Run this command from your project root directory"
12698
+ });
12699
+ }
12700
+ output.info("Loading configuration...");
12701
+ const config3 = await loadDeployConfig();
12702
+ const prebuildOutput = config3.deploy.prebuildOutput ?? DEFAULT_PREBUILD_DIR;
12703
+ if (config3.deploy.verifyCommand) {
12704
+ output.info(`Running verification: ${config3.deploy.verifyCommand}`);
12705
+ try {
12706
+ child_process.execSync(config3.deploy.verifyCommand, { stdio: "inherit" });
12707
+ output.success("Verification passed");
12708
+ } catch {
12709
+ return output.error("Verification failed", {
12710
+ suggestion: "Fix the issues reported by the verify command and try again"
12711
+ });
12712
+ }
12713
+ } else {
12714
+ output.warn("No verifyCommand configured, skipping verification");
12715
+ }
12716
+ const { clean, dirtyFiles } = await checkWorkingDirectoryClean(git, prebuildOutput);
12717
+ if (!clean) {
12718
+ output.warn("Uncommitted changes detected:");
12719
+ dirtyFiles.slice(0, 5).forEach((f) => output.warn(` - ${f}`));
12720
+ if (dirtyFiles.length > 5) {
12721
+ output.warn(` ... and ${dirtyFiles.length - 5} more files`);
12722
+ }
12723
+ return output.error("Working directory not clean", {
12724
+ suggestion: "Commit or stash your changes before deploying"
12725
+ });
12726
+ }
12727
+ if (!options.preview) {
12728
+ output.info("Generating prebuild cache...");
12729
+ try {
12730
+ const env = loadEnvironment(true);
12731
+ const client = createRiverbankClient({
12732
+ apiKey: env.managementApiKey,
12733
+ baseUrl: env.dashboardUrl
12734
+ });
12735
+ const result = await prebuildCache({
12736
+ client,
12737
+ siteId: config3.siteId,
12738
+ outputDir: prebuildOutput,
12739
+ contentTypes: config3.contentTypes,
12740
+ onProgress: (p) => {
12741
+ output.info(` ${p.item}`);
12742
+ }
12743
+ });
12744
+ if (result.success) {
12745
+ output.success(`Prebuild complete: ${result.files.length} files (${formatBytes(result.totalSize)})`);
12746
+ } else {
12747
+ output.warn("Prebuild completed with errors:");
12748
+ result.errors?.forEach((e) => output.warn(` - ${e}`));
12749
+ }
12750
+ const committed = await commitCacheIfChanged(git, prebuildOutput);
12751
+ if (committed) {
12752
+ output.success("Cache committed");
12753
+ } else {
12754
+ output.info("Cache unchanged, no commit needed");
12755
+ }
12756
+ } catch (error) {
12757
+ const message = error instanceof Error ? error.message : "Unknown error";
12758
+ output.warn(`Prebuild failed: ${message}`);
12759
+ output.warn("Continuing with existing cache...");
12760
+ }
12761
+ } else {
12762
+ output.info("Skipping prebuild cache (--preview mode)");
12763
+ }
12764
+ output.info("Pushing to remote...");
12765
+ try {
12766
+ await git.push();
12767
+ output.success("Deploy triggered successfully!");
12768
+ } catch (error) {
12769
+ const message = error instanceof Error ? error.message : "Unknown error";
12770
+ return output.error(`Push failed: ${message}`, {
12771
+ suggestion: "Check your git remote configuration and permissions"
12772
+ });
12773
+ }
12774
+ } catch (error) {
12775
+ handleCommandError(error, output);
12776
+ }
12777
+ });
12778
+ function formatBytes(bytes) {
12779
+ if (bytes < 1024) return `${bytes} B`;
12780
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
12781
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
12782
+ }
12783
+
8695
12784
  // src/cli/index.ts
8696
12785
  dotenv.config({ path: ".env.local" });
8697
12786
  dotenv.config({ path: ".env" });
@@ -8729,6 +12818,7 @@ program.addCommand(deleteCommand);
8729
12818
  program.addCommand(previewCommand);
8730
12819
  program.addCommand(initDocsCommand);
8731
12820
  program.addCommand(identifiersCommand);
12821
+ program.addCommand(deployCommand);
8732
12822
  program.parse();
8733
12823
  //# sourceMappingURL=index.js.map
8734
12824
  //# sourceMappingURL=index.js.map