@riverbankcms/sdk 0.8.1 → 0.9.0

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 (187) hide show
  1. package/README.md +298 -2
  2. package/dist/cli/index.js +1996 -618
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/init-docs/content/agents-section.md +11 -0
  5. package/dist/cli/init-docs/content/cli-reference.md +15 -1
  6. package/dist/cli/init-docs/content/workflow-add-block.md +7 -0
  7. package/dist/cli/init-docs/content/workflow-block-extensions.md +361 -0
  8. package/dist/cli/init-docs/content/workflow-cmsify-page.md +357 -0
  9. package/dist/cli/init-docs/content/workflow-content-types.md +328 -0
  10. package/dist/cli/init-docs/content/workflow-create-page.md +9 -0
  11. package/dist/cli/init-docs/content/workflow-custom-block.md +446 -0
  12. package/dist/client/client.d.mts +2 -2
  13. package/dist/client/client.d.ts +2 -2
  14. package/dist/client/client.js +262 -17
  15. package/dist/client/client.js.map +1 -1
  16. package/dist/client/client.mjs +262 -17
  17. package/dist/client/client.mjs.map +1 -1
  18. package/dist/client/hooks.d.mts +2 -2
  19. package/dist/client/hooks.d.ts +2 -2
  20. package/dist/client/hooks.js +6 -6
  21. package/dist/client/hooks.js.map +1 -1
  22. package/dist/client/hooks.mjs +6 -6
  23. package/dist/client/hooks.mjs.map +1 -1
  24. package/dist/client/rendering/client.js +29 -6
  25. package/dist/client/rendering/client.js.map +1 -1
  26. package/dist/client/rendering/client.mjs +29 -6
  27. package/dist/client/rendering/client.mjs.map +1 -1
  28. package/dist/client/usePage--LiGLbVz.d.mts +7195 -0
  29. package/dist/client/usePage-BwQJlxpe.d.mts +7218 -0
  30. package/dist/client/usePage-Ds-ow1-d.d.ts +7195 -0
  31. package/dist/client/usePage-Duc2GC-H.d.ts +7218 -0
  32. package/dist/client/usePage-DyzrgxqR.d.mts +7215 -0
  33. package/dist/client/usePage-lTWkuVMZ.d.ts +7215 -0
  34. package/dist/server/{Layout-CZ-kxKfl.d.ts → Layout-BHGokJmV.d.ts} +1 -1
  35. package/dist/server/{Layout-ESG8zvrk.d.mts → Layout-CXkMcTR4.d.mts} +1 -1
  36. package/dist/server/chunk-274Y2CUE.js +341 -0
  37. package/dist/server/chunk-274Y2CUE.js.map +1 -0
  38. package/dist/server/{chunk-IJTJH4J3.js → chunk-2WL52ZOE.js} +8 -8
  39. package/dist/server/{chunk-IJTJH4J3.js.map → chunk-2WL52ZOE.js.map} +1 -1
  40. package/dist/server/{chunk-DAXWU3S3.js → chunk-5HGVBSWA.js} +9 -9
  41. package/dist/server/{chunk-DAXWU3S3.js.map → chunk-5HGVBSWA.js.map} +1 -1
  42. package/dist/server/chunk-7WJGJY3B.js +7 -0
  43. package/dist/server/chunk-7WJGJY3B.js.map +1 -0
  44. package/dist/server/chunk-AGAOKSPY.mjs +22 -0
  45. package/dist/server/chunk-AGAOKSPY.mjs.map +1 -0
  46. package/dist/server/{chunk-A3UZ2LDH.mjs → chunk-BOYBN4KN.mjs} +3 -3
  47. package/dist/server/chunk-BOYBN4KN.mjs.map +1 -0
  48. package/dist/server/{chunk-FUFPKTSI.mjs → chunk-CKZDJBMC.mjs} +33 -9
  49. package/dist/server/chunk-CKZDJBMC.mjs.map +1 -0
  50. package/dist/server/{chunk-PGZJUNCY.mjs → chunk-E4R5ILRE.mjs} +3 -3
  51. package/dist/server/{chunk-MFNWLB5G.js → chunk-EC2AA2IP.js} +275 -297
  52. package/dist/server/chunk-EC2AA2IP.js.map +1 -0
  53. package/dist/server/{chunk-HE3RTUDX.js → chunk-F4U4LC5D.js} +8 -8
  54. package/dist/server/{chunk-HE3RTUDX.js.map → chunk-F4U4LC5D.js.map} +1 -1
  55. package/dist/server/{chunk-T5PAA22U.mjs → chunk-H44G72AB.mjs} +2 -2
  56. package/dist/server/{chunk-KGORQCHF.js → chunk-JVLQDZTZ.js} +6 -6
  57. package/dist/server/{chunk-KGORQCHF.js.map → chunk-JVLQDZTZ.js.map} +1 -1
  58. package/dist/server/{chunk-ADD3O2QO.mjs → chunk-KKUR3PDT.mjs} +4 -4
  59. package/dist/server/chunk-NTG7XP3E.js +264 -0
  60. package/dist/server/chunk-NTG7XP3E.js.map +1 -0
  61. package/dist/server/{chunk-TR7MSLWL.mjs → chunk-OSTUHBFE.mjs} +3 -3
  62. package/dist/server/chunk-PAHSKNY5.mjs +264 -0
  63. package/dist/server/chunk-PAHSKNY5.mjs.map +1 -0
  64. package/dist/server/chunk-PSN6HXUD.js +22 -0
  65. package/dist/server/chunk-PSN6HXUD.js.map +1 -0
  66. package/dist/server/{chunk-GRFFJUCO.mjs → chunk-QS6ZTLLB.mjs} +242 -264
  67. package/dist/server/chunk-QS6ZTLLB.mjs.map +1 -0
  68. package/dist/server/{chunk-K44OPKLA.js → chunk-R6T3Z4W5.js} +3 -3
  69. package/dist/server/{chunk-K44OPKLA.js.map → chunk-R6T3Z4W5.js.map} +1 -1
  70. package/dist/server/{chunk-KDCVCDW6.js → chunk-RIROJYPX.js} +4 -4
  71. package/dist/server/{chunk-KDCVCDW6.js.map → chunk-RIROJYPX.js.map} +1 -1
  72. package/dist/server/chunk-SVEQVEA5.mjs +341 -0
  73. package/dist/server/chunk-SVEQVEA5.mjs.map +1 -0
  74. package/dist/server/{chunk-HDHY4236.mjs → chunk-TBN35TGI.mjs} +6 -6
  75. package/dist/server/{chunk-HDHY4236.mjs.map → chunk-TBN35TGI.mjs.map} +1 -1
  76. package/dist/server/{chunk-5GCSRTIU.mjs → chunk-TBX6CXBM.mjs} +2 -2
  77. package/dist/server/{chunk-6ERSDFTY.js → chunk-U2F4BWKW.js} +3 -3
  78. package/dist/server/chunk-U2F4BWKW.js.map +1 -0
  79. package/dist/server/chunk-WWGVFOLS.mjs +7 -0
  80. package/dist/server/chunk-WWGVFOLS.mjs.map +1 -0
  81. package/dist/server/{chunk-TLZHVGTL.js → chunk-X4REO3S7.js} +4 -4
  82. package/dist/server/{chunk-TLZHVGTL.js.map → chunk-X4REO3S7.js.map} +1 -1
  83. package/dist/server/{chunk-BNHK7YOC.mjs → chunk-YUD7ONZG.mjs} +2 -2
  84. package/dist/server/{chunk-F2NDLDDA.js → chunk-ZJXFRSTC.js} +92 -68
  85. package/dist/server/chunk-ZJXFRSTC.js.map +1 -0
  86. package/dist/server/{components-iEDvl2Yw.d.mts → components-Bqn4xmR6.d.mts} +74 -5
  87. package/dist/server/{components-CE48wJM1.d.ts → components-C7j9yzAt.d.ts} +74 -5
  88. package/dist/server/components.d.mts +5 -5
  89. package/dist/server/components.d.ts +5 -5
  90. package/dist/server/components.js +7 -5
  91. package/dist/server/components.js.map +1 -1
  92. package/dist/server/components.mjs +6 -4
  93. package/dist/server/config-validation.js +5 -5
  94. package/dist/server/config-validation.mjs +4 -4
  95. package/dist/server/config.js +5 -5
  96. package/dist/server/config.mjs +4 -4
  97. package/dist/server/data.d.mts +2 -2
  98. package/dist/server/data.d.ts +2 -2
  99. package/dist/server/data.js +3 -3
  100. package/dist/server/data.mjs +2 -2
  101. package/dist/server/{index-BHLK2mgQ.d.ts → index-Bns_1a4N.d.ts} +1 -1
  102. package/dist/server/{index-DTBg8eXj.d.ts → index-CHp2kyp0.d.ts} +2 -2
  103. package/dist/server/{index-Cgvb5fVQ.d.mts → index-CPDT8kn9.d.mts} +1 -1
  104. package/dist/server/{index-BrH_NIRO.d.mts → index-Cm9nMPkf.d.mts} +2 -2
  105. package/dist/server/index.d.mts +193 -215
  106. package/dist/server/index.d.ts +193 -215
  107. package/dist/server/index.js +9 -288
  108. package/dist/server/index.js.map +1 -1
  109. package/dist/server/index.mjs +10 -289
  110. package/dist/server/index.mjs.map +1 -1
  111. package/dist/server/{loadContent-BUK6IVJf.d.ts → loadContent-DD7J5_WO.d.ts} +3 -3
  112. package/dist/server/{loadContent-au9Weoy0.d.mts → loadContent-DTEgYI-l.d.mts} +3 -3
  113. package/dist/server/{loadPage-DiHEl8BA.d.mts → loadPage-B578Xg2W.d.mts} +2 -2
  114. package/dist/server/{loadPage-JOIbF7ih.d.ts → loadPage-Dkiimbsg.d.ts} +2 -2
  115. package/dist/server/loadPage-IBX7FXGH.mjs +11 -0
  116. package/dist/server/loadPage-KG74OG4V.js +11 -0
  117. package/dist/server/{loadPage-CMHYAW2J.js.map → loadPage-KG74OG4V.js.map} +1 -1
  118. package/dist/server/metadata.d.mts +4 -4
  119. package/dist/server/metadata.d.ts +4 -4
  120. package/dist/server/navigation.d.mts +2 -2
  121. package/dist/server/navigation.d.ts +2 -2
  122. package/dist/server/next.d.mts +5 -5
  123. package/dist/server/next.d.ts +5 -5
  124. package/dist/server/next.js +17 -14
  125. package/dist/server/next.js.map +1 -1
  126. package/dist/server/next.mjs +9 -6
  127. package/dist/server/next.mjs.map +1 -1
  128. package/dist/server/prebuild-loader.d.mts +87 -0
  129. package/dist/server/prebuild-loader.d.ts +87 -0
  130. package/dist/server/prebuild-loader.js +15 -0
  131. package/dist/server/prebuild-loader.js.map +1 -0
  132. package/dist/server/prebuild-loader.mjs +15 -0
  133. package/dist/server/prebuild-loader.mjs.map +1 -0
  134. package/dist/server/prebuild-types.d.mts +201 -0
  135. package/dist/server/prebuild-types.d.ts +201 -0
  136. package/dist/server/prebuild-types.js +1 -0
  137. package/dist/server/prebuild-types.js.map +1 -0
  138. package/dist/server/prebuild-types.mjs +1 -0
  139. package/dist/server/prebuild-types.mjs.map +1 -0
  140. package/dist/server/prebuild.d.mts +46 -0
  141. package/dist/server/prebuild.d.ts +46 -0
  142. package/dist/server/prebuild.js +10 -0
  143. package/dist/server/prebuild.js.map +1 -0
  144. package/dist/server/prebuild.mjs +10 -0
  145. package/dist/server/prebuild.mjs.map +1 -0
  146. package/dist/server/rendering/server.d.mts +4 -4
  147. package/dist/server/rendering/server.d.ts +4 -4
  148. package/dist/server/rendering/server.js +7 -7
  149. package/dist/server/rendering/server.mjs +6 -6
  150. package/dist/server/rendering.d.mts +8 -8
  151. package/dist/server/rendering.d.ts +8 -8
  152. package/dist/server/rendering.js +11 -9
  153. package/dist/server/rendering.js.map +1 -1
  154. package/dist/server/rendering.mjs +10 -8
  155. package/dist/server/routing.d.mts +3 -3
  156. package/dist/server/routing.d.ts +3 -3
  157. package/dist/server/routing.js +1 -1
  158. package/dist/server/routing.mjs +1 -1
  159. package/dist/server/server.d.mts +5 -5
  160. package/dist/server/server.d.ts +5 -5
  161. package/dist/server/server.js +9 -6
  162. package/dist/server/server.js.map +1 -1
  163. package/dist/server/server.mjs +8 -5
  164. package/dist/server/theme-bridge.js +8 -8
  165. package/dist/server/theme-bridge.mjs +2 -2
  166. package/dist/server/{types-BAM1kcGA.d.mts → types-B6P_iaDz.d.mts} +295 -1
  167. package/dist/server/{types-_SNCu2ZZ.d.ts → types-C4jfCjaP.d.ts} +295 -1
  168. package/dist/server/{types-DDNKxQXw.d.mts → types-CSvCkmYi.d.mts} +12 -3
  169. package/dist/server/{types-CmBB0Osp.d.ts → types-gKcrQV09.d.ts} +12 -3
  170. package/dist/styles/index.css +419 -0
  171. package/package.json +17 -3
  172. package/dist/server/chunk-6ERSDFTY.js.map +0 -1
  173. package/dist/server/chunk-A3UZ2LDH.mjs.map +0 -1
  174. package/dist/server/chunk-F2NDLDDA.js.map +0 -1
  175. package/dist/server/chunk-FUFPKTSI.mjs.map +0 -1
  176. package/dist/server/chunk-GRFFJUCO.mjs.map +0 -1
  177. package/dist/server/chunk-MFNWLB5G.js.map +0 -1
  178. package/dist/server/loadPage-AWYZ2QA2.mjs +0 -11
  179. package/dist/server/loadPage-CMHYAW2J.js +0 -11
  180. package/src/styles/index.css +0 -10
  181. /package/dist/server/{chunk-PGZJUNCY.mjs.map → chunk-E4R5ILRE.mjs.map} +0 -0
  182. /package/dist/server/{chunk-T5PAA22U.mjs.map → chunk-H44G72AB.mjs.map} +0 -0
  183. /package/dist/server/{chunk-ADD3O2QO.mjs.map → chunk-KKUR3PDT.mjs.map} +0 -0
  184. /package/dist/server/{chunk-TR7MSLWL.mjs.map → chunk-OSTUHBFE.mjs.map} +0 -0
  185. /package/dist/server/{chunk-5GCSRTIU.mjs.map → chunk-TBX6CXBM.mjs.map} +0 -0
  186. /package/dist/server/{chunk-BNHK7YOC.mjs.map → chunk-YUD7ONZG.mjs.map} +0 -0
  187. /package/dist/server/{loadPage-AWYZ2QA2.mjs.map → loadPage-IBX7FXGH.mjs.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var jiti = require('jiti');
5
- var path9 = require('path');
5
+ var path2 = require('path');
6
6
  var fs6 = require('fs');
7
7
  var dotenv = require('dotenv');
8
8
  var commander = require('commander');
@@ -39,7 +39,7 @@ function _interopNamespace(e) {
39
39
  return Object.freeze(n);
40
40
  }
41
41
 
42
- var path9__namespace = /*#__PURE__*/_interopNamespace(path9);
42
+ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
43
43
  var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
44
44
  var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
45
45
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
@@ -105,11 +105,11 @@ Create a riverbank.config.ts file or specify a path with --config`
105
105
  }
106
106
  function resolveConfigPath(configPath) {
107
107
  if (!configPath) {
108
- return path9.resolve(process.cwd(), DEFAULT_CONFIG_FILENAME);
108
+ return path2.resolve(process.cwd(), DEFAULT_CONFIG_FILENAME);
109
109
  }
110
- const resolved = path9.resolve(configPath);
110
+ const resolved = path2.resolve(configPath);
111
111
  if (fs6.existsSync(resolved) && !resolved.endsWith(".ts") && !resolved.endsWith(".js")) {
112
- return path9.resolve(resolved, DEFAULT_CONFIG_FILENAME);
112
+ return path2.resolve(resolved, DEFAULT_CONFIG_FILENAME);
113
113
  }
114
114
  return resolved;
115
115
  }
@@ -141,7 +141,7 @@ function loadManifest(prebuildDir) {
141
141
  if (cachedManifest?.dir === prebuildDir) {
142
142
  return cachedManifest.manifest;
143
143
  }
144
- const manifestPath = path9__namespace.join(prebuildDir, "manifest.json");
144
+ const manifestPath = path2__namespace.join(prebuildDir, "manifest.json");
145
145
  if (!fs6__namespace.existsSync(manifestPath)) {
146
146
  return null;
147
147
  }
@@ -155,7 +155,7 @@ function loadManifest(prebuildDir) {
155
155
  }
156
156
  }
157
157
  function loadJsonFile(prebuildDir, relativePath) {
158
- const filePath = path9__namespace.join(prebuildDir, relativePath);
158
+ const filePath = path2__namespace.join(prebuildDir, relativePath);
159
159
  if (!fs6__namespace.existsSync(filePath)) {
160
160
  return null;
161
161
  }
@@ -338,6 +338,26 @@ var init_loader = __esm({
338
338
  prebuildAgeSec: getPrebuildAgeSec(manifest)
339
339
  };
340
340
  }
341
+ /**
342
+ * Load forms data from prebuild cache.
343
+ */
344
+ loadForms() {
345
+ const manifest = loadManifest(this.prebuildDir);
346
+ if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {
347
+ return null;
348
+ }
349
+ const cacheFile = loadJsonFile(
350
+ this.prebuildDir,
351
+ "forms/forms.json"
352
+ );
353
+ if (!cacheFile) {
354
+ return null;
355
+ }
356
+ return {
357
+ data: cacheFile.forms,
358
+ prebuildAgeSec: getPrebuildAgeSec(manifest)
359
+ };
360
+ }
341
361
  /**
342
362
  * Get the manifest for inspection.
343
363
  */
@@ -745,6 +765,245 @@ zod.z.object({
745
765
  tags: zod.z.array(zod.z.string()).optional(),
746
766
  icon: zod.z.string().optional()
747
767
  });
768
+ var TipTapNodeSchema = zod.z.lazy(
769
+ () => zod.z.object({
770
+ type: zod.z.string(),
771
+ content: zod.z.array(TipTapNodeSchema).optional(),
772
+ text: zod.z.string().optional(),
773
+ attrs: zod.z.record(zod.z.string(), zod.z.any()).optional(),
774
+ marks: zod.z.array(zod.z.any()).optional()
775
+ })
776
+ );
777
+ var RichTextValueSchema = zod.z.union([
778
+ // New format: TipTap document directly
779
+ TipTapNodeSchema,
780
+ // Legacy format: wrapped in { doc: {...} }
781
+ zod.z.object({
782
+ doc: TipTapNodeSchema
783
+ })
784
+ ]);
785
+ function buildRichTextSchema(options) {
786
+ let schema = RichTextValueSchema;
787
+ return schema;
788
+ }
789
+
790
+ // ../blocks/src/system/manifest/validation.ts
791
+ function createManifestValidator(manifestOrFields, options = {}) {
792
+ const allowNull = options.allowNull ?? true;
793
+ const allowIncomplete = options.allowIncomplete ?? false;
794
+ const shape = {};
795
+ const fields4 = Array.isArray(manifestOrFields) ? manifestOrFields : manifestOrFields.fields ?? [];
796
+ for (const field of fields4) {
797
+ shape[field.id] = buildFieldSchema(field, allowNull, allowIncomplete);
798
+ }
799
+ return zod.z.object(shape).catchall(zod.z.unknown()).passthrough();
800
+ }
801
+ function buildFieldSchema(field, allowNull, allowIncomplete = false) {
802
+ const required = Boolean(field.required);
803
+ let schema;
804
+ switch (field.type) {
805
+ case "text":
806
+ schema = buildTextSchema(field);
807
+ break;
808
+ case "richText":
809
+ schema = buildRichTextSchema2();
810
+ break;
811
+ case "media": {
812
+ const baseMediaSchema = zod.z.record(zod.z.string(), zod.z.unknown());
813
+ const isRequired = Boolean(field.required);
814
+ schema = isRequired ? baseMediaSchema : baseMediaSchema.nullable();
815
+ break;
816
+ }
817
+ case "boolean":
818
+ schema = zod.z.boolean();
819
+ break;
820
+ case "slug":
821
+ schema = buildSlugSchema(field);
822
+ break;
823
+ case "url":
824
+ schema = buildUrlSchema(field);
825
+ break;
826
+ case "link":
827
+ schema = buildLinkSchema();
828
+ break;
829
+ case "select":
830
+ schema = buildSelectSchema(field);
831
+ break;
832
+ case "reference":
833
+ schema = buildReferenceSchema(field);
834
+ break;
835
+ case "repeater":
836
+ schema = buildRepeaterSchema(field, allowNull, allowIncomplete);
837
+ break;
838
+ case "group":
839
+ schema = buildGroupSchema(field, allowNull, allowIncomplete);
840
+ break;
841
+ default:
842
+ schema = zod.z.unknown();
843
+ break;
844
+ }
845
+ return finalizeSchema(schema, required, allowNull, allowIncomplete);
846
+ }
847
+ function buildTextSchema(field) {
848
+ const ui = field?.ui ?? {};
849
+ const inputType = ui?.inputType;
850
+ let schema;
851
+ if (inputType === "number") {
852
+ schema = zod.z.coerce.number();
853
+ if (typeof ui?.min === "number") schema = schema.min(ui.min);
854
+ if (typeof ui?.max === "number") schema = schema.max(ui.max);
855
+ } else {
856
+ schema = zod.z.string();
857
+ if (inputType === "email") {
858
+ schema = schema.email(`${field.label} must be a valid email`);
859
+ }
860
+ if (inputType === "tel") {
861
+ const TEL_RE = /^[+()0-9\s\-]{3,}$/;
862
+ schema = schema.regex(TEL_RE, `${field.label} must be a valid phone number`);
863
+ }
864
+ if (ui?.pattern) {
865
+ try {
866
+ const re = new RegExp(ui.pattern);
867
+ schema = schema.regex(re, `${field.label} is not in the correct format`);
868
+ } catch {
869
+ }
870
+ }
871
+ if (field.maxLength) {
872
+ schema = schema.max(field.maxLength, `${field.label} must have at most ${field.maxLength} characters`);
873
+ }
874
+ if (field.required) {
875
+ schema = schema.min(1, `${field.label} is required`);
876
+ }
877
+ }
878
+ return schema;
879
+ }
880
+ function buildRichTextSchema2() {
881
+ return buildRichTextSchema();
882
+ }
883
+ var SLUG_PATTERN = /^(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:\/(?:[a-z0-9]+(?:-[a-z0-9]+)*))*$/;
884
+ function buildSlugSchema(field) {
885
+ const message = `${field.label} must contain lowercase letters, numbers, and dashes`;
886
+ let schema = zod.z.string().regex(SLUG_PATTERN, message);
887
+ if (field.maxLength) {
888
+ schema = schema.max(field.maxLength, `${field.label} must be at most ${field.maxLength} characters`);
889
+ }
890
+ if (field.required) {
891
+ schema = schema.min(1, `${field.label} is required`);
892
+ }
893
+ return schema;
894
+ }
895
+ var ALLOWED_URL_PROTOCOLS = ["http:", "https:"];
896
+ function buildUrlSchema(field) {
897
+ const message = `${field.label} must be a valid URL`;
898
+ const validate = (value) => {
899
+ if (!value) {
900
+ return !field.required;
901
+ }
902
+ if (field.allowRelative && value.startsWith("/")) {
903
+ return true;
904
+ }
905
+ try {
906
+ const parsed = new URL(value);
907
+ return ALLOWED_URL_PROTOCOLS.includes(parsed.protocol);
908
+ } catch {
909
+ return false;
910
+ }
911
+ };
912
+ let schema = zod.z.string().trim().refine(validate, message);
913
+ if (field.required) {
914
+ schema = schema.min(1, `${field.label} is required`);
915
+ }
916
+ return schema;
917
+ }
918
+ function buildLinkSchema() {
919
+ const nullableString = zod.z.union([zod.z.string().min(1), zod.z.null()]);
920
+ const internal = zod.z.object({
921
+ kind: zod.z.literal("internal"),
922
+ routeId: zod.z.string().min(1),
923
+ entityId: zod.z.string().min(1).optional(),
924
+ entityType: zod.z.enum(["page", "content"]).optional(),
925
+ href: nullableString.optional(),
926
+ title: nullableString.optional(),
927
+ typeLabel: nullableString.optional(),
928
+ contentTypeKey: nullableString.optional(),
929
+ contentTypeName: nullableString.optional(),
930
+ updatedAt: nullableString.optional()
931
+ }).passthrough();
932
+ const external = zod.z.object({
933
+ kind: zod.z.literal("external"),
934
+ href: zod.z.string().min(1)
935
+ }).passthrough();
936
+ const custom = zod.z.object({
937
+ kind: zod.z.literal("url"),
938
+ href: zod.z.string().min(1)
939
+ }).passthrough();
940
+ return zod.z.union([internal, external, custom]);
941
+ }
942
+ function buildSelectSchema(field) {
943
+ const ui = field.ui;
944
+ if (ui?.widget === "sdkSelect") {
945
+ const anyStringSchema = zod.z.string();
946
+ if (field.multiple) {
947
+ const arraySchema = zod.z.array(anyStringSchema);
948
+ return field.required ? arraySchema : arraySchema.nullable();
949
+ }
950
+ return field.required ? anyStringSchema : anyStringSchema.nullable();
951
+ }
952
+ const values = field.options.map((option) => option.value);
953
+ const valueSchema = zod.z.string().refine((value) => values.includes(value), `${field.label} must be one of: ${values.join(", ")}`);
954
+ if (field.multiple) {
955
+ const arraySchema = zod.z.array(valueSchema);
956
+ return field.required ? arraySchema : arraySchema.nullable();
957
+ }
958
+ return field.required ? valueSchema : valueSchema.nullable();
959
+ }
960
+ function buildReferenceSchema(field) {
961
+ let schema = zod.z.string();
962
+ if (field.required) {
963
+ schema = schema.min(1, `${field.label} is required`);
964
+ }
965
+ return schema;
966
+ }
967
+ function buildRepeaterSchema(field, allowNull, allowIncomplete) {
968
+ const parsed = fieldSchema.array().parse(field.schema?.fields ?? []);
969
+ const childShape = {};
970
+ for (const child of parsed) {
971
+ if (child.type === "group" && child.ui?.flattenInRepeater) {
972
+ const groupParsed = fieldSchema.array().parse(child.schema?.fields ?? []);
973
+ for (const gc of groupParsed) {
974
+ childShape[gc.id] = buildFieldSchema(gc, allowNull, allowIncomplete);
975
+ }
976
+ } else {
977
+ childShape[child.id] = buildFieldSchema(child, allowNull, allowIncomplete);
978
+ }
979
+ }
980
+ let schema = zod.z.object(childShape).catchall(zod.z.unknown()).passthrough();
981
+ let arraySchema = zod.z.array(schema);
982
+ if (typeof field.minItems === "number") {
983
+ arraySchema = arraySchema.min(field.minItems);
984
+ }
985
+ if (typeof field.maxItems === "number") {
986
+ arraySchema = arraySchema.max(field.maxItems);
987
+ }
988
+ return arraySchema;
989
+ }
990
+ function buildGroupSchema(field, allowNull, allowIncomplete) {
991
+ const parsed = fieldSchema.array().parse(field.schema?.fields ?? []);
992
+ const childShape = {};
993
+ for (const child of parsed) {
994
+ childShape[child.id] = buildFieldSchema(child, allowNull, allowIncomplete);
995
+ }
996
+ return zod.z.object(childShape).catchall(zod.z.unknown()).passthrough();
997
+ }
998
+ function finalizeSchema(schema, required, allowNull, allowIncomplete = false) {
999
+ if (required) {
1000
+ if (allowIncomplete) {
1001
+ return allowNull ? schema.or(zod.z.null()).optional() : schema.optional();
1002
+ }
1003
+ return allowNull ? schema.or(zod.z.null()) : schema;
1004
+ }
1005
+ return allowNull ? schema.optional().nullable() : schema.optional();
1006
+ }
748
1007
 
749
1008
  // ../blocks/src/utils/env.ts
750
1009
  function isDevEnvironment() {
@@ -1984,7 +2243,7 @@ var formEmbedFragment = defineFragment({
1984
2243
  description: "Embeds a saved form with configurable submit button copy.",
1985
2244
  fields: [
1986
2245
  {
1987
- id: "formId",
2246
+ id: "formSlug",
1988
2247
  type: "reference",
1989
2248
  label: "Form",
1990
2249
  description: "Pick a saved form to render.",
@@ -2020,7 +2279,7 @@ var formEmbedFragment = defineFragment({
2020
2279
  loader: {
2021
2280
  endpoint: "getPublicFormById",
2022
2281
  params: {
2023
- formId: { $bind: { from: "formId" } }
2282
+ formSlug: { $bind: { from: "formSlug" } }
2024
2283
  },
2025
2284
  mode: "server"
2026
2285
  }
@@ -5866,7 +6125,7 @@ function createBlockOperations(http) {
5866
6125
  async reorder(pageIdentifier, blockIdentifiers) {
5867
6126
  await http.post(
5868
6127
  `/pages/${encodeURIComponent(pageIdentifier)}/blocks/reorder`,
5869
- { order: blockIdentifiers }
6128
+ { identifiers: blockIdentifiers }
5870
6129
  );
5871
6130
  },
5872
6131
  async delete(pageIdentifier, blockIdentifier) {
@@ -5916,6 +6175,36 @@ function createSettingsOperations(http) {
5916
6175
  };
5917
6176
  }
5918
6177
 
6178
+ // src/client/management/forms.ts
6179
+ function createFormOperations(http) {
6180
+ return {
6181
+ async list() {
6182
+ const result = await http.get("/forms");
6183
+ return result.forms;
6184
+ },
6185
+ async get(slug) {
6186
+ try {
6187
+ const result = await http.get(
6188
+ `/forms/${encodeURIComponent(slug)}`
6189
+ );
6190
+ return result.form;
6191
+ } catch (error) {
6192
+ if (is404Error(error)) {
6193
+ return null;
6194
+ }
6195
+ throw error;
6196
+ }
6197
+ },
6198
+ async upsert(input) {
6199
+ const result = await http.post("/forms", input);
6200
+ return result.form;
6201
+ },
6202
+ async delete(slug) {
6203
+ await http.delete(`/forms/${encodeURIComponent(slug)}`);
6204
+ }
6205
+ };
6206
+ }
6207
+
5919
6208
  // src/client/management/pull.ts
5920
6209
  function createPullOperations(http) {
5921
6210
  return {
@@ -5940,18 +6229,23 @@ function createPullOperations(http) {
5940
6229
  async settings() {
5941
6230
  return http.get("/pull/settings");
5942
6231
  },
6232
+ async forms() {
6233
+ return http.get("/pull/forms");
6234
+ },
5943
6235
  async all() {
5944
- const [entriesResult, pagesResult, navigationResult, settingsResult] = await Promise.all([
6236
+ const [entriesResult, pagesResult, navigationResult, settingsResult, formsResult] = await Promise.all([
5945
6237
  http.get("/pull/entries/all"),
5946
6238
  http.get("/pull/pages"),
5947
6239
  http.get("/pull/navigation"),
5948
- http.get("/pull/settings")
6240
+ http.get("/pull/settings"),
6241
+ http.get("/pull/forms")
5949
6242
  ]);
5950
6243
  return {
5951
6244
  entries: entriesResult?.entries || {},
5952
6245
  pages: pagesResult?.pages || [],
5953
6246
  navigation: navigationResult?.menus || [],
5954
6247
  settings: settingsResult?.settings || {},
6248
+ forms: formsResult?.forms || [],
5955
6249
  meta: {
5956
6250
  pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
5957
6251
  entries: entriesResult?.meta?.entries,
@@ -6129,6 +6423,7 @@ function createManagementClient(config3) {
6129
6423
  blocks: createBlockOperations(http),
6130
6424
  navigation: createNavigationOperations(http),
6131
6425
  settings: createSettingsOperations(http),
6426
+ forms: createFormOperations(http),
6132
6427
  pull: createPullOperations(http),
6133
6428
  preview: createPreviewOperations(http),
6134
6429
  identifiers: createIdentifiersOperations(http),
@@ -6160,6 +6455,7 @@ function loadEnvironment(remote) {
6160
6455
  const siteId = requireEnv(`${prefix}_SITE_ID`);
6161
6456
  const dashboardUrl = requireEnv(`${prefix}_DASHBOARD_URL`);
6162
6457
  const managementApiKey = requireEnv(`${prefix}_MGMT_API_KEY`);
6458
+ const apiKey = getEnv(`${prefix}_API_KEY`);
6163
6459
  const supabaseUrl = getEnv(`${prefix}_SUPABASE_URL`);
6164
6460
  if (!managementApiKey.startsWith("bld_mgmt_sk_")) {
6165
6461
  throw new Error(
@@ -6186,6 +6482,7 @@ function loadEnvironment(remote) {
6186
6482
  siteId,
6187
6483
  dashboardUrl,
6188
6484
  managementApiKey,
6485
+ apiKey,
6189
6486
  supabaseUrl
6190
6487
  };
6191
6488
  }
@@ -6357,7 +6654,7 @@ function formatDateShort(isoDate) {
6357
6654
  }
6358
6655
  async function parseJsonData(options) {
6359
6656
  if (options.file) {
6360
- const filePath = path9__namespace.resolve(options.file);
6657
+ const filePath = path2__namespace.resolve(options.file);
6361
6658
  const content = await fs3__namespace.readFile(filePath, "utf-8");
6362
6659
  return JSON.parse(content);
6363
6660
  }
@@ -6587,9 +6884,13 @@ async function pushToDashboard(output, dashboardUrl, siteId, apiKey, config3) {
6587
6884
  try {
6588
6885
  const errorBody = await response.json();
6589
6886
  if (errorBody.error) {
6590
- errorMessage = errorBody.error;
6591
- if (errorBody.details) {
6592
- errorMessage += ":\n" + errorBody.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
6887
+ if (typeof errorBody.error === "string") {
6888
+ errorMessage = errorBody.error;
6889
+ } else if (errorBody.error.message) {
6890
+ errorMessage = errorBody.error.message;
6891
+ if (errorBody.error.details?.fieldErrors) {
6892
+ errorMessage += ":\n" + errorBody.error.details.fieldErrors.map((d) => ` - ${d.field}: ${d.message}`).join("\n");
6893
+ }
6593
6894
  }
6594
6895
  }
6595
6896
  } catch {
@@ -6599,6 +6900,7 @@ async function pushToDashboard(output, dashboardUrl, siteId, apiKey, config3) {
6599
6900
  }
6600
6901
  async function pushConfigAction(output, options) {
6601
6902
  try {
6903
+ process.env.RIVERBANK_ENV = options.isRemote ? "remote" : "local";
6602
6904
  const rawConfig = await loadConfigFile(options.config);
6603
6905
  output.info("Validating config...");
6604
6906
  const parseResult = riverbankSiteConfigSchema.safeParse(rawConfig);
@@ -6707,8 +7009,8 @@ async function writeJsonFile(filePath, data) {
6707
7009
  await fs3__namespace.writeFile(filePath, content, "utf-8");
6708
7010
  }
6709
7011
  async function writeEntries(contentDir, pulledEntries) {
6710
- const entriesDir = path9__namespace.join(contentDir, "entries");
6711
- const metaDir = path9__namespace.join(contentDir, ".meta");
7012
+ const entriesDir = path2__namespace.join(contentDir, "entries");
7013
+ const metaDir = path2__namespace.join(contentDir, ".meta");
6712
7014
  await ensureDir(entriesDir);
6713
7015
  await ensureDir(metaDir);
6714
7016
  const { contentType, entries, meta } = pulledEntries;
@@ -6716,21 +7018,21 @@ async function writeEntries(contentDir, pulledEntries) {
6716
7018
  contentType,
6717
7019
  entries: entries.map(mapEntryForOutput)
6718
7020
  };
6719
- const filePath = path9__namespace.join(entriesDir, `${contentType}.json`);
7021
+ const filePath = path2__namespace.join(entriesDir, `${contentType}.json`);
6720
7022
  await writeJsonFile(filePath, entriesFile);
6721
- const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
7023
+ const metaPath = path2__namespace.join(metaDir, `${contentType}.json`);
6722
7024
  await writeJsonFile(metaPath, meta);
6723
7025
  return { filePath, metaPath };
6724
7026
  }
6725
7027
  async function writePages(contentDir, pulledPages) {
6726
- const pagesDir = path9__namespace.join(contentDir, "pages");
6727
- const metaDir = path9__namespace.join(contentDir, ".meta");
7028
+ const pagesDir = path2__namespace.join(contentDir, "pages");
7029
+ const metaDir = path2__namespace.join(contentDir, ".meta");
6728
7030
  await ensureDir(pagesDir);
6729
7031
  await ensureDir(metaDir);
6730
7032
  const filePaths = [];
6731
7033
  const pagesMeta = {};
6732
7034
  for (const page of pulledPages.pages) {
6733
- const filePath = path9__namespace.join(pagesDir, `${page.identifier}.json`);
7035
+ const filePath = path2__namespace.join(pagesDir, `${page.identifier}.json`);
6734
7036
  await writeJsonFile(filePath, page);
6735
7037
  filePaths.push(filePath);
6736
7038
  pagesMeta[page.identifier] = {
@@ -6738,7 +7040,7 @@ async function writePages(contentDir, pulledPages) {
6738
7040
  updatedAt: page.updatedAt
6739
7041
  };
6740
7042
  }
6741
- const metaPath = path9__namespace.join(metaDir, "pages.json");
7043
+ const metaPath = path2__namespace.join(metaDir, "pages.json");
6742
7044
  await writeJsonFile(metaPath, {
6743
7045
  pulledAt: pulledPages.meta.pulledAt,
6744
7046
  pages: pagesMeta
@@ -6746,10 +7048,10 @@ async function writePages(contentDir, pulledPages) {
6746
7048
  return { filePaths, metaPath };
6747
7049
  }
6748
7050
  async function writeNavigation(contentDir, pulledNavigation) {
6749
- const metaDir = path9__namespace.join(contentDir, ".meta");
7051
+ const metaDir = path2__namespace.join(contentDir, ".meta");
6750
7052
  await ensureDir(contentDir);
6751
7053
  await ensureDir(metaDir);
6752
- const filePath = path9__namespace.join(contentDir, "navigation.json");
7054
+ const filePath = path2__namespace.join(contentDir, "navigation.json");
6753
7055
  const localNavigation = convertPulledNavigationToLocal(pulledNavigation.menus);
6754
7056
  await writeJsonFile(filePath, localNavigation);
6755
7057
  const menusMeta = {};
@@ -6759,7 +7061,7 @@ async function writeNavigation(contentDir, pulledNavigation) {
6759
7061
  updatedAt: menu.updatedAt
6760
7062
  };
6761
7063
  }
6762
- const metaPath = path9__namespace.join(metaDir, "navigation.json");
7064
+ const metaPath = path2__namespace.join(metaDir, "navigation.json");
6763
7065
  await writeJsonFile(metaPath, {
6764
7066
  pulledAt: pulledNavigation.meta.pulledAt,
6765
7067
  menus: menusMeta
@@ -6768,16 +7070,44 @@ async function writeNavigation(contentDir, pulledNavigation) {
6768
7070
  }
6769
7071
  async function writeSettings(contentDir, pulledSettings) {
6770
7072
  await ensureDir(contentDir);
6771
- const filePath = path9__namespace.join(contentDir, "settings.json");
7073
+ const filePath = path2__namespace.join(contentDir, "settings.json");
6772
7074
  await writeJsonFile(filePath, pulledSettings.settings);
6773
7075
  return filePath;
6774
7076
  }
7077
+ async function writeForms(contentDir, pulledForms) {
7078
+ const formsDir = path2__namespace.join(contentDir, "forms");
7079
+ const metaDir = path2__namespace.join(contentDir, ".meta");
7080
+ await ensureDir(formsDir);
7081
+ await ensureDir(metaDir);
7082
+ const filePaths = [];
7083
+ const formsMeta = {};
7084
+ for (const form2 of pulledForms.forms) {
7085
+ const filePath = path2__namespace.join(formsDir, `${form2.slug}.json`);
7086
+ await writeJsonFile(filePath, {
7087
+ slug: form2.slug,
7088
+ name: form2.name,
7089
+ schema: form2.schema,
7090
+ settings: form2.settings ?? {}
7091
+ });
7092
+ filePaths.push(filePath);
7093
+ formsMeta[form2.slug] = {
7094
+ createdAt: form2.createdAt,
7095
+ updatedAt: form2.updatedAt
7096
+ };
7097
+ }
7098
+ const metaPath = path2__namespace.join(metaDir, "forms.json");
7099
+ await writeJsonFile(metaPath, {
7100
+ pulledAt: pulledForms.meta.pulledAt,
7101
+ forms: formsMeta
7102
+ });
7103
+ return { filePaths, metaPath };
7104
+ }
6775
7105
  async function updateMetadataAfterPush(contentDir, remoteContent) {
6776
- const metaDir = path9__namespace.join(contentDir, ".meta");
7106
+ const metaDir = path2__namespace.join(contentDir, ".meta");
6777
7107
  await ensureDir(metaDir);
6778
7108
  const now = (/* @__PURE__ */ new Date()).toISOString();
6779
7109
  for (const [contentType, entries] of Object.entries(remoteContent.entries)) {
6780
- const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
7110
+ const metaPath = path2__namespace.join(metaDir, `${contentType}.json`);
6781
7111
  const entriesMeta = {};
6782
7112
  for (const entry of entries) {
6783
7113
  entriesMeta[entry.identifier] = {
@@ -6791,7 +7121,7 @@ async function updateMetadataAfterPush(contentDir, remoteContent) {
6791
7121
  });
6792
7122
  }
6793
7123
  if (remoteContent.pages.length > 0) {
6794
- const metaPath = path9__namespace.join(metaDir, "pages.json");
7124
+ const metaPath = path2__namespace.join(metaDir, "pages.json");
6795
7125
  const pagesMeta = {};
6796
7126
  for (const page of remoteContent.pages) {
6797
7127
  pagesMeta[page.identifier] = {
@@ -6805,7 +7135,7 @@ async function updateMetadataAfterPush(contentDir, remoteContent) {
6805
7135
  });
6806
7136
  }
6807
7137
  if (remoteContent.navigation.length > 0) {
6808
- const metaPath = path9__namespace.join(metaDir, "navigation.json");
7138
+ const metaPath = path2__namespace.join(metaDir, "navigation.json");
6809
7139
  const menusMeta = {};
6810
7140
  for (const menu of remoteContent.navigation) {
6811
7141
  menusMeta[menu.name] = {
@@ -6818,6 +7148,20 @@ async function updateMetadataAfterPush(contentDir, remoteContent) {
6818
7148
  menus: menusMeta
6819
7149
  });
6820
7150
  }
7151
+ if (remoteContent.forms && remoteContent.forms.length > 0) {
7152
+ const metaPath = path2__namespace.join(metaDir, "forms.json");
7153
+ const formsMeta = {};
7154
+ for (const form2 of remoteContent.forms) {
7155
+ formsMeta[form2.slug] = {
7156
+ createdAt: form2.createdAt,
7157
+ updatedAt: form2.updatedAt
7158
+ };
7159
+ }
7160
+ await writeJsonFile(metaPath, {
7161
+ pulledAt: now,
7162
+ forms: formsMeta
7163
+ });
7164
+ }
6821
7165
  }
6822
7166
  async function fileExists(filePath) {
6823
7167
  try {
@@ -6834,13 +7178,13 @@ async function readJsonFile(filePath) {
6834
7178
  async function listFiles(dirPath, extension) {
6835
7179
  try {
6836
7180
  const files = await fs3__namespace.readdir(dirPath);
6837
- return files.filter((f) => f.endsWith(extension)).map((f) => path9__namespace.join(dirPath, f));
7181
+ return files.filter((f) => f.endsWith(extension)).map((f) => path2__namespace.join(dirPath, f));
6838
7182
  } catch {
6839
7183
  return [];
6840
7184
  }
6841
7185
  }
6842
7186
  async function readEntries(contentDir, contentType) {
6843
- const entriesDir = path9__namespace.join(contentDir, "entries");
7187
+ const entriesDir = path2__namespace.join(contentDir, "entries");
6844
7188
  const result = /* @__PURE__ */ new Map();
6845
7189
  {
6846
7190
  const files = await listFiles(entriesDir, ".json");
@@ -6856,7 +7200,7 @@ async function readEntries(contentDir, contentType) {
6856
7200
  return result;
6857
7201
  }
6858
7202
  async function readPages(contentDir) {
6859
- const pagesDir = path9__namespace.join(contentDir, "pages");
7203
+ const pagesDir = path2__namespace.join(contentDir, "pages");
6860
7204
  const pages = [];
6861
7205
  const files = await listFiles(pagesDir, ".json");
6862
7206
  for (const filePath of files) {
@@ -6870,27 +7214,42 @@ async function readPages(contentDir) {
6870
7214
  return pages;
6871
7215
  }
6872
7216
  async function readNavigation(contentDir) {
6873
- const filePath = path9__namespace.join(contentDir, "navigation.json");
7217
+ const filePath = path2__namespace.join(contentDir, "navigation.json");
6874
7218
  if (!await fileExists(filePath)) {
6875
7219
  return null;
6876
7220
  }
6877
7221
  return readJsonFile(filePath);
6878
7222
  }
6879
7223
  async function readSettings(contentDir) {
6880
- const filePath = path9__namespace.join(contentDir, "settings.json");
7224
+ const filePath = path2__namespace.join(contentDir, "settings.json");
6881
7225
  if (!await fileExists(filePath)) {
6882
7226
  return null;
6883
7227
  }
6884
7228
  return readJsonFile(filePath);
6885
7229
  }
7230
+ async function readForms(contentDir) {
7231
+ const formsDir = path2__namespace.join(contentDir, "forms");
7232
+ const forms = [];
7233
+ const files = await listFiles(formsDir, ".json");
7234
+ for (const filePath of files) {
7235
+ try {
7236
+ const form2 = await readJsonFile(filePath);
7237
+ forms.push(form2);
7238
+ } catch (error) {
7239
+ console.warn(`Warning: Could not parse ${filePath}:`, error);
7240
+ }
7241
+ }
7242
+ return forms;
7243
+ }
6886
7244
  async function readAllContent(contentDir) {
6887
- const [entries, pages, navigation, settings] = await Promise.all([
7245
+ const [entries, pages, navigation, settings, forms] = await Promise.all([
6888
7246
  readEntries(contentDir),
6889
7247
  readPages(contentDir),
6890
7248
  readNavigation(contentDir),
6891
- readSettings(contentDir)
7249
+ readSettings(contentDir),
7250
+ readForms(contentDir)
6892
7251
  ]);
6893
- return { entries, pages, navigation, settings };
7252
+ return { entries, pages, navigation, settings, forms };
6894
7253
  }
6895
7254
  async function contentDirExists(contentDir) {
6896
7255
  return fileExists(contentDir);
@@ -6910,11 +7269,13 @@ async function getContentSummary(contentDir) {
6910
7269
  pageCount: content.pages.length,
6911
7270
  hasNavigation: content.navigation !== null && content.navigation.menus.length > 0,
6912
7271
  menuCount: content.navigation?.menus.length ?? 0,
6913
- hasSettings: content.settings !== null
7272
+ hasSettings: content.settings !== null,
7273
+ hasForms: content.forms.length > 0,
7274
+ formCount: content.forms.length
6914
7275
  };
6915
7276
  }
6916
7277
  async function readEntriesMeta(contentDir, contentType) {
6917
- const metaPath = path9__namespace.join(contentDir, ".meta", `${contentType}.json`);
7278
+ const metaPath = path2__namespace.join(contentDir, ".meta", `${contentType}.json`);
6918
7279
  try {
6919
7280
  const content = await fs3__namespace.readFile(metaPath, "utf-8");
6920
7281
  return JSON.parse(content);
@@ -6926,12 +7287,12 @@ async function readEntriesMeta(contentDir, contentType) {
6926
7287
  }
6927
7288
  }
6928
7289
  async function readAllMeta(contentDir) {
6929
- const metaDir = path9__namespace.join(contentDir, ".meta");
7290
+ const metaDir = path2__namespace.join(contentDir, ".meta");
6930
7291
  const metaMap = /* @__PURE__ */ new Map();
6931
7292
  try {
6932
7293
  const files = await fs3__namespace.readdir(metaDir);
6933
7294
  for (const file of files) {
6934
- if (file.endsWith(".json") && file !== "pages.json" && file !== "navigation.json") {
7295
+ if (file.endsWith(".json") && file !== "pages.json" && file !== "navigation.json" && file !== "forms.json") {
6935
7296
  const contentType = file.replace(".json", "");
6936
7297
  const meta = await readEntriesMeta(contentDir, contentType);
6937
7298
  if (meta) metaMap.set(contentType, meta);
@@ -6943,7 +7304,7 @@ async function readAllMeta(contentDir) {
6943
7304
  return metaMap;
6944
7305
  }
6945
7306
  async function readPagesMeta(contentDir) {
6946
- const metaPath = path9__namespace.join(contentDir, ".meta", "pages.json");
7307
+ const metaPath = path2__namespace.join(contentDir, ".meta", "pages.json");
6947
7308
  try {
6948
7309
  const content = await fs3__namespace.readFile(metaPath, "utf-8");
6949
7310
  return JSON.parse(content);
@@ -6955,7 +7316,19 @@ async function readPagesMeta(contentDir) {
6955
7316
  }
6956
7317
  }
6957
7318
  async function readNavigationMeta(contentDir) {
6958
- const metaPath = path9__namespace.join(contentDir, ".meta", "navigation.json");
7319
+ const metaPath = path2__namespace.join(contentDir, ".meta", "navigation.json");
7320
+ try {
7321
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
7322
+ return JSON.parse(content);
7323
+ } catch (error) {
7324
+ if (error.code === "ENOENT") {
7325
+ return null;
7326
+ }
7327
+ throw error;
7328
+ }
7329
+ }
7330
+ async function readFormsMeta(contentDir) {
7331
+ const metaPath = path2__namespace.join(contentDir, ".meta", "forms.json");
6959
7332
  try {
6960
7333
  const content = await fs3__namespace.readFile(metaPath, "utf-8");
6961
7334
  return JSON.parse(content);
@@ -7067,397 +7440,71 @@ var DownloadError = class extends Error {
7067
7440
  this.name = "DownloadError";
7068
7441
  }
7069
7442
  };
7070
-
7071
- // src/cli/commands/pull.ts
7072
- var DEFAULT_PAGE_LIMIT = 500;
7073
- async function pullEntriesWithPagination(client, contentType, output) {
7074
- const allEntries = [];
7075
- const aggregatedMeta = {};
7076
- let page = 1;
7077
- let hasMore = true;
7078
- let pulledAt = (/* @__PURE__ */ new Date()).toISOString();
7079
- while (hasMore) {
7080
- const result = await client.pull.entries(contentType, {
7081
- page,
7082
- limit: DEFAULT_PAGE_LIMIT
7083
- });
7084
- allEntries.push(...result.entries);
7085
- if (result.meta.entries) {
7086
- Object.assign(aggregatedMeta, result.meta.entries);
7087
- }
7088
- pulledAt = result.meta.pulledAt;
7089
- hasMore = result.pagination.hasMore;
7090
- if (hasMore) {
7091
- output.info(`Fetched ${allEntries.length} entries (page ${page})...`);
7443
+ function findChangedFields(local, remote, prefix = "") {
7444
+ const changes = [];
7445
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(local), ...Object.keys(remote)]);
7446
+ for (const key of allKeys) {
7447
+ const path13 = prefix ? `${prefix}.${key}` : key;
7448
+ const localVal = local[key];
7449
+ const remoteVal = remote[key];
7450
+ if (!equal__default.default(localVal, remoteVal)) {
7451
+ changes.push(path13);
7092
7452
  }
7093
- page++;
7094
7453
  }
7095
- return {
7096
- contentType,
7097
- entries: allEntries,
7098
- meta: { pulledAt, entries: aggregatedMeta },
7099
- pagination: {
7100
- page: 1,
7101
- limit: allEntries.length,
7102
- total: allEntries.length,
7103
- hasMore: false
7104
- }
7105
- };
7454
+ return changes;
7106
7455
  }
7107
- async function writeAllEntries(contentDir, entriesByType, pulledAt, entriesMeta) {
7108
- let totalCount = 0;
7109
- const files = [];
7110
- for (const [contentType, entries] of Object.entries(entriesByType)) {
7111
- const ctMeta = {};
7112
- for (const entry of entries) {
7113
- const key = `${contentType}:${entry.identifier}`;
7114
- if (entriesMeta?.[key]) {
7115
- ctMeta[entry.identifier] = entriesMeta[key];
7456
+ function buildJsonDiff(diff, local, remote, mode) {
7457
+ const summary = { creates: 0, updates: 0, deletes: 0 };
7458
+ const changes = [];
7459
+ const localPages = new Map(local.pages.map((page) => [page.identifier, page]));
7460
+ const remotePages = new Map(remote.pages.map((page) => [page.identifier, page]));
7461
+ const localEntries = local.entries;
7462
+ const remoteEntries = remote.entries;
7463
+ const localMenus = new Map((local.navigation?.menus ?? []).map((menu) => [menu.name, menu]));
7464
+ const remoteMenus = new Map(remote.navigation.map((menu) => [menu.name, menu]));
7465
+ const localForms = new Map(local.forms.map((form2) => [form2.slug, form2]));
7466
+ const remoteForms = new Map(remote.forms.map((form2) => [form2.slug, form2]));
7467
+ const addChange = (change) => {
7468
+ if (change.operation === "create") summary.creates += 1;
7469
+ if (change.operation === "update") summary.updates += 1;
7470
+ if (change.operation === "delete") summary.deletes += 1;
7471
+ changes.push(change);
7472
+ };
7473
+ for (const [contentType, entryDiffs] of diff.entries) {
7474
+ for (const entryDiff of entryDiffs) {
7475
+ if (entryDiff.type === "unchanged") continue;
7476
+ if (entryDiff.type !== "create" && entryDiff.type !== "update") continue;
7477
+ const identifier = `${contentType}/${entryDiff.identifier}`;
7478
+ const localEntry = localEntries.get(contentType)?.find((entry) => entry.identifier === entryDiff.identifier);
7479
+ const remoteEntry = remoteEntries[contentType]?.find((entry) => entry.identifier === entryDiff.identifier);
7480
+ const change = {
7481
+ type: "entry",
7482
+ identifier,
7483
+ operation: entryDiff.type
7484
+ };
7485
+ if (mode === "full") {
7486
+ change.before = remoteEntry ?? null;
7487
+ change.after = localEntry ?? null;
7488
+ if (entryDiff.changes) change.diff = { changes: entryDiff.changes };
7116
7489
  }
7490
+ addChange(change);
7117
7491
  }
7118
- const { filePath } = await writeEntries(contentDir, {
7119
- contentType,
7120
- entries,
7121
- meta: { pulledAt, entries: ctMeta },
7122
- pagination: { limit: entries.length, total: entries.length}
7123
- });
7124
- totalCount += entries.length;
7125
- files.push(filePath);
7126
7492
  }
7127
- return { totalCount, files };
7128
- }
7129
- async function fetchAllContentPaginated(client, contentTypes, output) {
7130
- const allEntries = {};
7131
- const allMeta = {};
7132
- for (const contentType of contentTypes) {
7133
- output.info(`Fetching ${contentType} entries...`);
7134
- const result = await pullEntriesWithPagination(client, contentType, output);
7135
- allEntries[contentType] = result.entries.map(mapEntryForOutput);
7136
- for (const [id, meta] of Object.entries(result.meta.entries || {})) {
7137
- allMeta[`${contentType}:${id}`] = meta;
7138
- }
7139
- output.info(` ${result.entries.length} entries`);
7140
- }
7141
- output.info("Fetching pages...");
7142
- const pagesResult = await client.pull.pages();
7143
- output.info("Fetching navigation...");
7144
- const navigationResult = await client.pull.navigation();
7145
- output.info("Fetching settings...");
7146
- const settingsResult = await client.pull.settings();
7147
- return {
7148
- entries: allEntries,
7149
- pages: pagesResult.pages,
7150
- navigation: navigationResult.menus,
7151
- settings: settingsResult.settings,
7152
- meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: allMeta }
7153
- };
7154
- }
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", `
7203
- Examples:
7204
- $ riverbankcms pull # Pull all content
7205
- $ riverbankcms pull --remote # Pull from production
7206
- $ riverbankcms pull --remote --sync-media # Pull from production and sync media to local
7207
- $ riverbankcms pull entries # Pull all entries
7208
- $ riverbankcms pull entries blog-post # Pull specific content type
7209
- $ riverbankcms pull pages # Pull pages with blocks
7210
- $ riverbankcms pull navigation # Pull navigation menus
7211
- $ riverbankcms pull settings # Pull site settings
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.
7222
- `).action(
7223
- withErrorHandling(
7224
- async (scope, type, options, command) => {
7225
- const { output, client, isRemote } = createCommandContext(command);
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
- }
7254
- if (await contentDirExists(contentDir) && !options.force && !options.yes) {
7255
- if (!process.stdin.isTTY) {
7256
- output.error("Content directory already exists and --yes not specified", {
7257
- suggestion: "Use --yes or --force to overwrite in non-interactive mode"
7258
- });
7259
- return;
7260
- }
7261
- const response = await prompts__default.default({
7262
- type: "confirm",
7263
- name: "overwrite",
7264
- message: `Content directory '${path9__namespace.basename(contentDir)}' already exists. Overwrite?`,
7265
- initial: false
7266
- });
7267
- if (!response.overwrite) {
7268
- output.info("Aborted.");
7269
- return;
7270
- }
7271
- }
7272
- const pullScope = scope?.toLowerCase() ?? "all";
7273
- switch (pullScope) {
7274
- case "entries": {
7275
- if (type) {
7276
- output.info(`Pulling entries for content type: ${type}`);
7277
- const result = await pullEntriesWithPagination(client, type, output);
7278
- const { filePath, metaPath } = await writeEntries(contentDir, result);
7279
- output.success(`Pulled ${result.entries.length} entries`, {
7280
- contentType: type,
7281
- file: filePath,
7282
- meta: metaPath
7283
- });
7284
- } else {
7285
- output.info("Pulling all entries");
7286
- let result = await client.pull.all();
7287
- if (result.meta.truncated) {
7288
- output.warn("Content was truncated due to size limits.");
7289
- output.info("Fetching complete data via pagination...");
7290
- const contentTypes = Object.keys(result.entries);
7291
- result = await fetchAllContentPaginated(client, contentTypes, output);
7292
- }
7293
- const { totalCount, files } = await writeAllEntries(
7294
- contentDir,
7295
- result.entries,
7296
- result.meta.pulledAt,
7297
- result.meta.entries
7298
- );
7299
- output.success(`Pulled ${totalCount} entries across ${Object.keys(result.entries).length} content types`, {
7300
- files
7301
- });
7302
- }
7303
- break;
7304
- }
7305
- case "pages": {
7306
- output.info("Pulling pages");
7307
- const result = await client.pull.pages();
7308
- const { filePaths, metaPath } = await writePages(contentDir, result);
7309
- output.success(`Pulled ${result.pages.length} pages`, { files: filePaths, meta: metaPath });
7310
- break;
7311
- }
7312
- case "navigation": {
7313
- output.info("Pulling navigation menus");
7314
- const result = await client.pull.navigation();
7315
- const { filePath, metaPath } = await writeNavigation(contentDir, result);
7316
- output.success(`Pulled ${result.menus.length} navigation menus`, { file: filePath, meta: metaPath });
7317
- break;
7318
- }
7319
- case "settings": {
7320
- output.info("Pulling site settings");
7321
- const result = await client.pull.settings();
7322
- const filePath = await writeSettings(contentDir, result);
7323
- output.success("Pulled site settings", { file: filePath });
7324
- break;
7325
- }
7326
- case "all":
7327
- default: {
7328
- output.info("Pulling all content");
7329
- let result = await client.pull.all();
7330
- if (result.meta.truncated) {
7331
- output.warn("Content was truncated due to size limits.");
7332
- output.info("Fetching complete data via pagination...");
7333
- const contentTypes = Object.keys(result.entries);
7334
- result = await fetchAllContentPaginated(client, contentTypes, output);
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
- }
7347
- const { totalCount: totalEntries } = await writeAllEntries(
7348
- contentDir,
7349
- result.entries,
7350
- result.meta.pulledAt,
7351
- result.meta.entries
7352
- );
7353
- await writePages(contentDir, { pages: result.pages, meta: result.meta });
7354
- await writeNavigation(contentDir, { menus: result.navigation, meta: result.meta });
7355
- await writeSettings(contentDir, { settings: result.settings, meta: result.meta });
7356
- output.success("Pull complete", {
7357
- entries: { count: totalEntries, types: Object.keys(result.entries).length },
7358
- pages: { count: result.pages.length },
7359
- navigation: { count: result.navigation.length },
7360
- directory: contentDir
7361
- });
7362
- break;
7363
- }
7364
- }
7365
- }
7366
- )
7367
- );
7368
-
7369
- // src/cli/config-loader.ts
7370
- init_load_config();
7371
- var DEFAULT_SYNC_CONFIG = {
7372
- existingEntries: "skip",
7373
- contentTarget: "draft"
7374
- };
7375
- async function loadCliConfig(configPath) {
7376
- try {
7377
- const rawConfig = await loadConfigFile(configPath);
7378
- const configObj = rawConfig;
7379
- const contentDir = typeof configObj.contentDir === "string" ? configObj.contentDir : "./content";
7380
- const syncConfig = configObj.sync;
7381
- return {
7382
- contentDir,
7383
- sync: {
7384
- existingEntries: syncConfig?.existingEntries ?? DEFAULT_SYNC_CONFIG.existingEntries,
7385
- contentTarget: syncConfig?.contentTarget ?? DEFAULT_SYNC_CONFIG.contentTarget
7386
- }
7387
- };
7388
- } catch (error) {
7389
- if (error instanceof Error && error.message.includes("Config file not found")) {
7390
- return {
7391
- contentDir: "./content",
7392
- sync: { ...DEFAULT_SYNC_CONFIG }
7393
- };
7394
- }
7395
- throw error;
7396
- }
7397
- }
7398
- function findChangedFields(local, remote, prefix = "") {
7399
- const changes = [];
7400
- const allKeys = /* @__PURE__ */ new Set([...Object.keys(local), ...Object.keys(remote)]);
7401
- for (const key of allKeys) {
7402
- const path13 = prefix ? `${prefix}.${key}` : key;
7403
- const localVal = local[key];
7404
- const remoteVal = remote[key];
7405
- if (!equal__default.default(localVal, remoteVal)) {
7406
- changes.push(path13);
7407
- }
7408
- }
7409
- return changes;
7410
- }
7411
- function buildJsonDiff(diff, local, remote, mode) {
7412
- const summary = { creates: 0, updates: 0, deletes: 0 };
7413
- const changes = [];
7414
- const localPages = new Map(local.pages.map((page) => [page.identifier, page]));
7415
- const remotePages = new Map(remote.pages.map((page) => [page.identifier, page]));
7416
- const localEntries = local.entries;
7417
- const remoteEntries = remote.entries;
7418
- const localMenus = new Map((local.navigation?.menus ?? []).map((menu) => [menu.name, menu]));
7419
- const remoteMenus = new Map(remote.navigation.map((menu) => [menu.name, menu]));
7420
- const addChange = (change) => {
7421
- if (change.operation === "create") summary.creates += 1;
7422
- if (change.operation === "update") summary.updates += 1;
7423
- if (change.operation === "delete") summary.deletes += 1;
7424
- changes.push(change);
7425
- };
7426
- for (const [contentType, entryDiffs] of diff.entries) {
7427
- for (const entryDiff of entryDiffs) {
7428
- if (entryDiff.type === "unchanged") continue;
7429
- if (entryDiff.type !== "create" && entryDiff.type !== "update") continue;
7430
- const identifier = `${contentType}/${entryDiff.identifier}`;
7431
- const localEntry = localEntries.get(contentType)?.find((entry) => entry.identifier === entryDiff.identifier);
7432
- const remoteEntry = remoteEntries[contentType]?.find((entry) => entry.identifier === entryDiff.identifier);
7433
- const change = {
7434
- type: "entry",
7435
- identifier,
7436
- operation: entryDiff.type
7437
- };
7438
- if (mode === "full") {
7439
- change.before = remoteEntry ?? null;
7440
- change.after = localEntry ?? null;
7441
- if (entryDiff.changes) change.diff = { changes: entryDiff.changes };
7442
- }
7443
- addChange(change);
7444
- }
7445
- }
7446
- for (const pageDiff of diff.pages) {
7447
- if (pageDiff.type === "unchanged") continue;
7448
- if (pageDiff.type !== "create" && pageDiff.type !== "update") continue;
7449
- const identifier = pageDiff.identifier;
7450
- const localPage = localPages.get(identifier);
7451
- const remotePage = remotePages.get(identifier);
7452
- const change = {
7453
- type: "page",
7454
- identifier,
7455
- operation: pageDiff.type
7456
- };
7457
- if (mode === "full") {
7458
- change.before = remotePage ?? null;
7459
- change.after = localPage ?? null;
7460
- if (pageDiff.changes) change.diff = { changes: pageDiff.changes };
7493
+ for (const pageDiff of diff.pages) {
7494
+ if (pageDiff.type === "unchanged") continue;
7495
+ if (pageDiff.type !== "create" && pageDiff.type !== "update") continue;
7496
+ const identifier = pageDiff.identifier;
7497
+ const localPage = localPages.get(identifier);
7498
+ const remotePage = remotePages.get(identifier);
7499
+ const change = {
7500
+ type: "page",
7501
+ identifier,
7502
+ operation: pageDiff.type
7503
+ };
7504
+ if (mode === "full") {
7505
+ change.before = remotePage ?? null;
7506
+ change.after = localPage ?? null;
7507
+ if (pageDiff.changes) change.diff = { changes: pageDiff.changes };
7461
7508
  }
7462
7509
  addChange(change);
7463
7510
  if (pageDiff.blocks) {
@@ -7523,6 +7570,24 @@ function buildJsonDiff(diff, local, remote, mode) {
7523
7570
  }
7524
7571
  addChange(change);
7525
7572
  }
7573
+ for (const formDiff of diff.forms) {
7574
+ if (formDiff.type === "unchanged") continue;
7575
+ if (formDiff.type !== "create" && formDiff.type !== "update") continue;
7576
+ const identifier = formDiff.slug;
7577
+ const localForm = localForms.get(identifier);
7578
+ const remoteForm = remoteForms.get(identifier);
7579
+ const change = {
7580
+ type: "form",
7581
+ identifier,
7582
+ operation: formDiff.type
7583
+ };
7584
+ if (mode === "full") {
7585
+ change.before = remoteForm ?? null;
7586
+ change.after = localForm ?? null;
7587
+ if (formDiff.changes) change.diff = { changes: formDiff.changes };
7588
+ }
7589
+ addChange(change);
7590
+ }
7526
7591
  return { summary, changes };
7527
7592
  }
7528
7593
  function calculateEntryDiffs(localEntries, remoteEntries, options) {
@@ -7694,14 +7759,24 @@ function calculateBlockDiffs(localBlocks, remoteBlocks, pageIdentifier, summary)
7694
7759
  summary.delete++;
7695
7760
  }
7696
7761
  }
7697
- const localOrder = localBlocks.filter((b) => remoteLookup.has(b.identifier)).map((b) => b.identifier);
7698
- const remoteOrder = remoteBlocks.filter((b) => seenRemoteIds.has(b.identifier)).sort((a, b) => a.position - b.position).map((b) => b.identifier);
7699
- if (localOrder.length > 1 && !equal__default.default(localOrder, remoteOrder)) {
7762
+ const hasCreates = diffs.some((d) => d.type === "create");
7763
+ const existingBlockCount = remoteBlocks.length;
7764
+ if (hasCreates && existingBlockCount > 0) {
7700
7765
  diffs.push({
7701
7766
  type: "reorder",
7702
7767
  identifier: pageIdentifier,
7703
7768
  pageIdentifier
7704
7769
  });
7770
+ } else {
7771
+ const localOrder = localBlocks.filter((b) => remoteLookup.has(b.identifier)).map((b) => b.identifier);
7772
+ const remoteOrder = remoteBlocks.filter((b) => seenRemoteIds.has(b.identifier)).sort((a, b) => a.position - b.position).map((b) => b.identifier);
7773
+ if (localOrder.length > 1 && !equal__default.default(localOrder, remoteOrder)) {
7774
+ diffs.push({
7775
+ type: "reorder",
7776
+ identifier: pageIdentifier,
7777
+ pageIdentifier
7778
+ });
7779
+ }
7705
7780
  }
7706
7781
  return diffs;
7707
7782
  }
@@ -7762,6 +7837,53 @@ function calculateSettingsDiff(localSettings, remoteSettings) {
7762
7837
  summary.unchanged = 1;
7763
7838
  return { diff: null, summary };
7764
7839
  }
7840
+ function calculateFormDiffs(localForms, remoteForms, options) {
7841
+ const diffs = [];
7842
+ const summary = { create: 0, update: 0, unchanged: 0 };
7843
+ const remoteLookup = /* @__PURE__ */ new Map();
7844
+ for (const form2 of remoteForms) {
7845
+ remoteLookup.set(form2.slug, form2);
7846
+ }
7847
+ for (const localForm of localForms) {
7848
+ const remoteForm = remoteLookup.get(localForm.slug);
7849
+ if (!remoteForm) {
7850
+ diffs.push({
7851
+ type: "create",
7852
+ slug: localForm.slug
7853
+ });
7854
+ summary.create++;
7855
+ } else if (options.existingEntries === "skip") {
7856
+ diffs.push({
7857
+ type: "unchanged",
7858
+ slug: localForm.slug
7859
+ });
7860
+ summary.unchanged++;
7861
+ } else {
7862
+ const nameChanged = localForm.name !== remoteForm.name;
7863
+ const schemaChanged = !equal__default.default(localForm.schema, remoteForm.schema);
7864
+ const settingsChanged = !equal__default.default(localForm.settings, remoteForm.settings);
7865
+ if (nameChanged || schemaChanged || settingsChanged) {
7866
+ const changes = [];
7867
+ if (nameChanged) changes.push("name");
7868
+ if (schemaChanged) changes.push("schema");
7869
+ if (settingsChanged) changes.push("settings");
7870
+ diffs.push({
7871
+ type: "update",
7872
+ slug: localForm.slug,
7873
+ changes
7874
+ });
7875
+ summary.update++;
7876
+ } else {
7877
+ diffs.push({
7878
+ type: "unchanged",
7879
+ slug: localForm.slug
7880
+ });
7881
+ summary.unchanged++;
7882
+ }
7883
+ }
7884
+ }
7885
+ return { diffs, summary };
7886
+ }
7765
7887
  function calculateDiff(local, remote, options) {
7766
7888
  const { diffs: entryDiffs, summary: entrySummary } = calculateEntryDiffs(
7767
7889
  local.entries,
@@ -7781,23 +7903,30 @@ function calculateDiff(local, remote, options) {
7781
7903
  local.settings,
7782
7904
  remote.settings
7783
7905
  );
7906
+ const { diffs: formDiffs, summary: formsSummary } = calculateFormDiffs(
7907
+ local.forms,
7908
+ remote.forms,
7909
+ options
7910
+ );
7784
7911
  return {
7785
7912
  entries: entryDiffs,
7786
7913
  pages: pageDiffs,
7787
7914
  navigation: navDiffs,
7788
7915
  settings: settingsDiff,
7916
+ forms: formDiffs,
7789
7917
  summary: {
7790
7918
  entries: entrySummary,
7791
7919
  pages: pageSummary,
7792
7920
  blocks: blockSummary,
7793
7921
  navigation: navSummary,
7794
- settings: settingsSummary
7922
+ settings: settingsSummary,
7923
+ forms: formsSummary
7795
7924
  }
7796
7925
  };
7797
7926
  }
7798
7927
  function hasPendingChanges(diff) {
7799
7928
  const { summary } = diff;
7800
- return summary.entries.create > 0 || summary.entries.update > 0 || summary.pages.create > 0 || summary.pages.update > 0 || summary.blocks.create > 0 || summary.blocks.update > 0 || summary.blocks.delete > 0 || summary.navigation.create > 0 || summary.navigation.update > 0 || summary.settings.update > 0;
7929
+ return summary.entries.create > 0 || summary.entries.update > 0 || summary.pages.create > 0 || summary.pages.update > 0 || summary.blocks.create > 0 || summary.blocks.update > 0 || summary.blocks.delete > 0 || summary.navigation.create > 0 || summary.navigation.update > 0 || summary.settings.update > 0 || summary.forms.create > 0 || summary.forms.update > 0;
7801
7930
  }
7802
7931
  function formatDiffSummary(diff) {
7803
7932
  const lines = [];
@@ -7817,6 +7946,9 @@ function formatDiffSummary(diff) {
7817
7946
  if (summary.settings.update > 0) {
7818
7947
  lines.push(`Settings: ~${summary.settings.update}`);
7819
7948
  }
7949
+ if (summary.forms.create > 0 || summary.forms.update > 0) {
7950
+ lines.push(`Forms: +${summary.forms.create} ~${summary.forms.update}`);
7951
+ }
7820
7952
  if (lines.length === 0) {
7821
7953
  return "No changes detected";
7822
7954
  }
@@ -7874,8 +8006,374 @@ ${contentType} (updated):`);
7874
8006
  lines.push("\nSettings (updated):");
7875
8007
  lines.push(` ~ [${diff.settings.changes.join(", ")}]`);
7876
8008
  }
8009
+ const formCreates = diff.forms.filter((d) => d.type === "create");
8010
+ const formUpdates = diff.forms.filter((d) => d.type === "update");
8011
+ if (formCreates.length > 0) {
8012
+ lines.push("\nForms (new):");
8013
+ for (const d of formCreates) {
8014
+ lines.push(` + ${d.slug}`);
8015
+ }
8016
+ }
8017
+ if (formUpdates.length > 0) {
8018
+ lines.push("\nForms (updated):");
8019
+ for (const d of formUpdates) {
8020
+ lines.push(` ~ ${d.slug}${d.changes ? ` [${d.changes.join(", ")}]` : ""}`);
8021
+ }
8022
+ }
7877
8023
  return lines.join("\n");
7878
8024
  }
8025
+ function calculatePullDiff(remote, local) {
8026
+ const entryDiffs = /* @__PURE__ */ new Map();
8027
+ const entrySummary = { create: 0, update: 0, unchanged: 0 };
8028
+ const localEntriesLookup = /* @__PURE__ */ new Map();
8029
+ if (local) {
8030
+ for (const [contentType, entries] of local.entries) {
8031
+ const typeMap = /* @__PURE__ */ new Map();
8032
+ for (const entry of entries) {
8033
+ typeMap.set(entry.identifier, entry.data);
8034
+ }
8035
+ localEntriesLookup.set(contentType, typeMap);
8036
+ }
8037
+ }
8038
+ for (const [contentType, remoteEntries] of Object.entries(remote.entries)) {
8039
+ const typeDiffs = [];
8040
+ const localTypeMap = localEntriesLookup.get(contentType) ?? /* @__PURE__ */ new Map();
8041
+ for (const remoteEntry of remoteEntries) {
8042
+ const localData = localTypeMap.get(remoteEntry.identifier);
8043
+ if (!localData) {
8044
+ typeDiffs.push({
8045
+ type: "create",
8046
+ identifier: remoteEntry.identifier,
8047
+ contentType
8048
+ });
8049
+ entrySummary.create++;
8050
+ } else if (!equal__default.default(remoteEntry.data, localData)) {
8051
+ const changes = findChangedFields(remoteEntry.data, localData);
8052
+ typeDiffs.push({
8053
+ type: "update",
8054
+ identifier: remoteEntry.identifier,
8055
+ contentType,
8056
+ changes
8057
+ });
8058
+ entrySummary.update++;
8059
+ } else {
8060
+ typeDiffs.push({
8061
+ type: "unchanged",
8062
+ identifier: remoteEntry.identifier,
8063
+ contentType
8064
+ });
8065
+ entrySummary.unchanged++;
8066
+ }
8067
+ }
8068
+ if (typeDiffs.length > 0) {
8069
+ entryDiffs.set(contentType, typeDiffs);
8070
+ }
8071
+ }
8072
+ const pageDiffs = [];
8073
+ const pageSummary = { create: 0, update: 0, unchanged: 0 };
8074
+ const blockSummary = { create: 0, update: 0, delete: 0, unchanged: 0 };
8075
+ const localPagesLookup = /* @__PURE__ */ new Map();
8076
+ if (local) {
8077
+ for (const page of local.pages) {
8078
+ localPagesLookup.set(page.identifier, page);
8079
+ }
8080
+ }
8081
+ for (const remotePage of remote.pages) {
8082
+ const localPage = localPagesLookup.get(remotePage.identifier);
8083
+ if (!localPage) {
8084
+ const blockDiffs = (remotePage.blocks ?? []).map((block) => {
8085
+ blockSummary.create++;
8086
+ return {
8087
+ type: "create",
8088
+ identifier: block.identifier,
8089
+ pageIdentifier: remotePage.identifier
8090
+ };
8091
+ });
8092
+ pageDiffs.push({
8093
+ type: "create",
8094
+ identifier: remotePage.identifier,
8095
+ blocks: blockDiffs
8096
+ });
8097
+ pageSummary.create++;
8098
+ } else {
8099
+ const pageChanged = remotePage.title !== localPage.title || remotePage.path !== localPage.path;
8100
+ const blockDiffs = calculatePullBlockDiffs(
8101
+ remotePage.blocks ?? [],
8102
+ localPage.blocks ?? [],
8103
+ remotePage.identifier,
8104
+ blockSummary
8105
+ );
8106
+ if (pageChanged || blockDiffs.some((b) => b.type !== "unchanged")) {
8107
+ const changes = [];
8108
+ if (remotePage.title !== localPage.title) changes.push("title");
8109
+ if (remotePage.path !== localPage.path) changes.push("path");
8110
+ pageDiffs.push({
8111
+ type: "update",
8112
+ identifier: remotePage.identifier,
8113
+ changes: changes.length > 0 ? changes : void 0,
8114
+ blocks: blockDiffs
8115
+ });
8116
+ pageSummary.update++;
8117
+ } else {
8118
+ pageDiffs.push({
8119
+ type: "unchanged",
8120
+ identifier: remotePage.identifier
8121
+ });
8122
+ pageSummary.unchanged++;
8123
+ }
8124
+ }
8125
+ }
8126
+ const navDiffs = [];
8127
+ const navSummary = { create: 0, update: 0, unchanged: 0 };
8128
+ const localNavLookup = /* @__PURE__ */ new Map();
8129
+ if (local?.navigation?.menus) {
8130
+ for (const menu of local.navigation.menus) {
8131
+ localNavLookup.set(menu.name, menu);
8132
+ }
8133
+ }
8134
+ for (const remoteMenu of remote.navigation) {
8135
+ const localMenu = localNavLookup.get(remoteMenu.name);
8136
+ if (!localMenu) {
8137
+ navDiffs.push({ type: "create", name: remoteMenu.name });
8138
+ navSummary.create++;
8139
+ } else if (!equal__default.default(stripNavigationItemIds(remoteMenu.items), localMenu.items)) {
8140
+ navDiffs.push({ type: "update", name: remoteMenu.name, changes: ["items"] });
8141
+ navSummary.update++;
8142
+ } else {
8143
+ navDiffs.push({ type: "unchanged", name: remoteMenu.name });
8144
+ navSummary.unchanged++;
8145
+ }
8146
+ }
8147
+ let settingsDiff = null;
8148
+ const settingsSummary = { update: 0, unchanged: 0 };
8149
+ if (remote.settings && local?.settings) {
8150
+ const changes = [];
8151
+ if (remote.settings.homepageId !== local.settings.homepageId) {
8152
+ changes.push("homepageId");
8153
+ }
8154
+ if (!equal__default.default(remote.settings.seoDefaults, local.settings.seoDefaults)) {
8155
+ changes.push("seoDefaults");
8156
+ }
8157
+ if (changes.length > 0) {
8158
+ settingsDiff = { type: "update", changes };
8159
+ settingsSummary.update = 1;
8160
+ } else {
8161
+ settingsSummary.unchanged = 1;
8162
+ }
8163
+ } else if (remote.settings && !local?.settings) {
8164
+ settingsDiff = { type: "update", changes: ["homepageId", "seoDefaults"] };
8165
+ settingsSummary.update = 1;
8166
+ }
8167
+ const formDiffs = [];
8168
+ const formsSummary = { create: 0, update: 0, unchanged: 0 };
8169
+ const localFormsLookup = /* @__PURE__ */ new Map();
8170
+ if (local?.forms) {
8171
+ for (const form2 of local.forms) {
8172
+ localFormsLookup.set(form2.slug, form2);
8173
+ }
8174
+ }
8175
+ for (const remoteForm of remote.forms) {
8176
+ const localForm = localFormsLookup.get(remoteForm.slug);
8177
+ if (!localForm) {
8178
+ formDiffs.push({ type: "create", slug: remoteForm.slug });
8179
+ formsSummary.create++;
8180
+ } else {
8181
+ const changes = [];
8182
+ if (remoteForm.name !== localForm.name) changes.push("name");
8183
+ if (!equal__default.default(remoteForm.schema, localForm.schema)) changes.push("schema");
8184
+ if (!equal__default.default(remoteForm.settings, localForm.settings)) changes.push("settings");
8185
+ if (changes.length > 0) {
8186
+ formDiffs.push({ type: "update", slug: remoteForm.slug, changes });
8187
+ formsSummary.update++;
8188
+ } else {
8189
+ formDiffs.push({ type: "unchanged", slug: remoteForm.slug });
8190
+ formsSummary.unchanged++;
8191
+ }
8192
+ }
8193
+ }
8194
+ return {
8195
+ entries: entryDiffs,
8196
+ pages: pageDiffs,
8197
+ navigation: navDiffs,
8198
+ settings: settingsDiff,
8199
+ forms: formDiffs,
8200
+ summary: {
8201
+ entries: entrySummary,
8202
+ pages: pageSummary,
8203
+ blocks: blockSummary,
8204
+ navigation: navSummary,
8205
+ settings: settingsSummary,
8206
+ forms: formsSummary
8207
+ }
8208
+ };
8209
+ }
8210
+ function calculatePullBlockDiffs(remoteBlocks, localBlocks, pageIdentifier, summary) {
8211
+ const diffs = [];
8212
+ const localLookup = /* @__PURE__ */ new Map();
8213
+ for (const block of localBlocks) {
8214
+ localLookup.set(block.identifier, block);
8215
+ }
8216
+ for (const remoteBlock of remoteBlocks) {
8217
+ const localBlock = localLookup.get(remoteBlock.identifier);
8218
+ if (!localBlock) {
8219
+ diffs.push({
8220
+ type: "create",
8221
+ identifier: remoteBlock.identifier,
8222
+ pageIdentifier
8223
+ });
8224
+ summary.create++;
8225
+ } else if (!equal__default.default(remoteBlock.data, localBlock.data) || remoteBlock.kind !== localBlock.kind) {
8226
+ const changes = findChangedFields(remoteBlock.data, localBlock.data);
8227
+ if (remoteBlock.kind !== localBlock.kind) {
8228
+ changes.push("kind");
8229
+ }
8230
+ diffs.push({
8231
+ type: "update",
8232
+ identifier: remoteBlock.identifier,
8233
+ pageIdentifier,
8234
+ changes
8235
+ });
8236
+ summary.update++;
8237
+ } else {
8238
+ diffs.push({
8239
+ type: "unchanged",
8240
+ identifier: remoteBlock.identifier,
8241
+ pageIdentifier
8242
+ });
8243
+ summary.unchanged++;
8244
+ }
8245
+ }
8246
+ return diffs;
8247
+ }
8248
+ function extractFieldIds(fields4) {
8249
+ const ids = /* @__PURE__ */ new Set();
8250
+ function processField(field) {
8251
+ ids.add(field.id);
8252
+ switch (field.type) {
8253
+ case "group":
8254
+ case "modal":
8255
+ if (field.schema?.fields) {
8256
+ for (const nestedField of field.schema.fields) {
8257
+ processField(nestedField);
8258
+ }
8259
+ }
8260
+ break;
8261
+ case "repeater":
8262
+ if (field.schema?.fields) {
8263
+ for (const nestedField of field.schema.fields) {
8264
+ processField(nestedField);
8265
+ }
8266
+ }
8267
+ if ("itemTypes" in field && field.itemTypes) {
8268
+ for (const itemType of Object.values(field.itemTypes)) {
8269
+ if (itemType.fields) {
8270
+ for (const nestedField of itemType.fields) {
8271
+ processField(nestedField);
8272
+ }
8273
+ }
8274
+ }
8275
+ }
8276
+ break;
8277
+ case "tabGroup":
8278
+ if ("tabs" in field && field.tabs) {
8279
+ for (const tab of field.tabs) {
8280
+ if (tab.fields) {
8281
+ for (const nestedField of tab.fields) {
8282
+ processField(nestedField);
8283
+ }
8284
+ }
8285
+ }
8286
+ }
8287
+ break;
8288
+ }
8289
+ }
8290
+ for (const field of fields4) {
8291
+ processField(field);
8292
+ }
8293
+ return ids;
8294
+ }
8295
+ function extractRequiredFieldIds(fields4) {
8296
+ const required = /* @__PURE__ */ new Set();
8297
+ for (const field of fields4) {
8298
+ if (field.required) {
8299
+ required.add(field.id);
8300
+ }
8301
+ }
8302
+ return required;
8303
+ }
8304
+ function getDataKeys(data) {
8305
+ return new Set(Object.keys(data));
8306
+ }
8307
+ function validateBlockContent(data, manifest, customFields) {
8308
+ const warnings = [];
8309
+ try {
8310
+ const manifestWithCustomFields = customFields ? { ...manifest, fields: [...manifest.fields, ...customFields] } : manifest;
8311
+ const validator = createManifestValidator(manifestWithCustomFields, {
8312
+ allowNull: true,
8313
+ allowIncomplete: true
8314
+ });
8315
+ validator.parse(data);
8316
+ } catch (error) {
8317
+ if (error instanceof zod.ZodError) {
8318
+ for (const issue of error.issues) {
8319
+ const fieldPath = issue.path.join(".");
8320
+ if (issue.code === "invalid_type" && issue.received === "undefined") {
8321
+ continue;
8322
+ }
8323
+ warnings.push({
8324
+ type: "invalid_content",
8325
+ field: fieldPath || "(root)",
8326
+ message: `Invalid content${fieldPath ? ` in "${fieldPath}"` : ""}: ${issue.message}`
8327
+ });
8328
+ }
8329
+ }
8330
+ }
8331
+ return warnings;
8332
+ }
8333
+ function validateBlockData(data, manifest, customFields) {
8334
+ const warnings = [];
8335
+ const errors = [];
8336
+ const allFields = customFields ? [...manifest.fields, ...customFields] : manifest.fields;
8337
+ const validFieldIds = extractFieldIds(allFields);
8338
+ const requiredFieldIds = extractRequiredFieldIds(allFields);
8339
+ const dataKeys = getDataKeys(data);
8340
+ for (const key of dataKeys) {
8341
+ if (!validFieldIds.has(key)) {
8342
+ warnings.push({
8343
+ type: "unknown_field",
8344
+ field: key,
8345
+ message: `Unknown field "${key}" - will be ignored. Valid fields: ${Array.from(validFieldIds).join(", ")}`
8346
+ });
8347
+ }
8348
+ }
8349
+ for (const requiredId of requiredFieldIds) {
8350
+ if (!(requiredId in data) || data[requiredId] === void 0) {
8351
+ errors.push({
8352
+ type: "missing_required",
8353
+ field: requiredId,
8354
+ message: `Required field "${requiredId}" is missing`
8355
+ });
8356
+ }
8357
+ }
8358
+ const contentWarnings = validateBlockContent(data, manifest, customFields);
8359
+ warnings.push(...contentWarnings);
8360
+ return {
8361
+ valid: errors.length === 0,
8362
+ warnings,
8363
+ errors
8364
+ };
8365
+ }
8366
+ function formatValidationResult(pageIdentifier, blockIdentifier, blockKind, result) {
8367
+ const lines = [];
8368
+ const prefix = `${pageIdentifier}/${blockIdentifier} (${blockKind})`;
8369
+ for (const warning of result.warnings) {
8370
+ lines.push(`\u26A0 ${prefix}: ${warning.message}`);
8371
+ }
8372
+ for (const error of result.errors) {
8373
+ lines.push(`\u2717 ${prefix}: ${error.message}`);
8374
+ }
8375
+ return lines;
8376
+ }
7879
8377
 
7880
8378
  // src/cli/sync/executor.ts
7881
8379
  function hasFieldErrors(details) {
@@ -7902,7 +8400,9 @@ function createEmptyResult() {
7902
8400
  blocks: { created: 0, updated: 0, deleted: 0, failed: 0 },
7903
8401
  navigation: { created: 0, updated: 0, failed: 0 },
7904
8402
  settings: { updated: 0, failed: 0 },
7905
- errors: []
8403
+ forms: { created: 0, updated: 0, failed: 0 },
8404
+ errors: [],
8405
+ warnings: []
7906
8406
  };
7907
8407
  }
7908
8408
  async function syncEntries(client, diff, local, options, result) {
@@ -7947,6 +8447,16 @@ async function syncPages(client, diff, local, options, result) {
7947
8447
  const localPage = pageLookup.get(pageDiff.identifier);
7948
8448
  if (!localPage) continue;
7949
8449
  try {
8450
+ let failedBlocks = /* @__PURE__ */ new Set();
8451
+ if (pageDiff.blocks && localPage.blocks) {
8452
+ failedBlocks = validateBlocksForPage(
8453
+ pageDiff.identifier,
8454
+ pageDiff.blocks,
8455
+ localPage.blocks,
8456
+ options,
8457
+ result
8458
+ );
8459
+ }
7950
8460
  if (options.dryRun) ;
7951
8461
  await client.pages.upsert({
7952
8462
  identifier: localPage.identifier,
@@ -7961,8 +8471,8 @@ async function syncPages(client, diff, local, options, result) {
7961
8471
  pageDiff.identifier,
7962
8472
  pageDiff.blocks,
7963
8473
  localPage.blocks,
7964
- options,
7965
- result
8474
+ result,
8475
+ failedBlocks
7966
8476
  );
7967
8477
  }
7968
8478
  if (options.contentTarget === "publish") {
@@ -7983,10 +8493,45 @@ async function syncPages(client, diff, local, options, result) {
7983
8493
  }
7984
8494
  }
7985
8495
  }
7986
- async function syncBlocks(client, pageIdentifier, blockDiffs, localBlocks, options, result) {
8496
+ function validateBlocksForPage(pageIdentifier, blockDiffs, localBlocks, options, result) {
8497
+ const failedBlocks = /* @__PURE__ */ new Set();
8498
+ const getCustomFields = (kind) => options.blockFieldExtensions?.[kind]?.fields;
8499
+ const blockLookup = new Map(localBlocks.map((b) => [b.identifier, b]));
8500
+ for (const blockDiff of blockDiffs) {
8501
+ if (blockDiff.type === "unchanged" || blockDiff.type === "delete" || blockDiff.type === "reorder") {
8502
+ continue;
8503
+ }
8504
+ const localBlock = blockLookup.get(blockDiff.identifier);
8505
+ if (!localBlock) continue;
8506
+ const blockDefinition = getBlockDefinition(localBlock.kind);
8507
+ if (blockDefinition) {
8508
+ const customFields = getCustomFields(localBlock.kind);
8509
+ const validation = validateBlockData(localBlock.data, blockDefinition.manifest, customFields);
8510
+ const formattedMessages = formatValidationResult(
8511
+ pageIdentifier,
8512
+ localBlock.identifier,
8513
+ localBlock.kind,
8514
+ validation
8515
+ );
8516
+ result.warnings.push(...formattedMessages);
8517
+ if (!validation.valid) {
8518
+ failedBlocks.add(blockDiff.identifier);
8519
+ result.blocks.failed++;
8520
+ result.errors.push({
8521
+ resource: "block",
8522
+ identifier: `${pageIdentifier}/${blockDiff.identifier}`,
8523
+ error: validation.errors.map((e) => e.message).join("; ")
8524
+ });
8525
+ }
8526
+ }
8527
+ }
8528
+ return failedBlocks;
8529
+ }
8530
+ async function syncBlocks(client, pageIdentifier, blockDiffs, localBlocks, result, failedBlocks) {
7987
8531
  const blockLookup = new Map(localBlocks.map((b) => [b.identifier, b]));
7988
8532
  for (const blockDiff of blockDiffs) {
7989
8533
  if (blockDiff.type === "unchanged") continue;
8534
+ if (failedBlocks.has(blockDiff.identifier)) continue;
7990
8535
  try {
7991
8536
  if (blockDiff.type === "delete") {
7992
8537
  await client.blocks.delete(pageIdentifier, blockDiff.identifier);
@@ -8014,116 +8559,618 @@ async function syncBlocks(client, pageIdentifier, blockDiffs, localBlocks, optio
8014
8559
  });
8015
8560
  }
8016
8561
  }
8017
- const hasReorderDiff = blockDiffs.some((d) => d.type === "reorder");
8018
- if (hasReorderDiff) {
8019
- const localIdentifiers = localBlocks.map((b) => b.identifier);
8020
- if (localIdentifiers.length > 1) {
8021
- try {
8022
- await client.blocks.reorder(pageIdentifier, localIdentifiers);
8023
- } catch {
8562
+ const hasReorderDiff = blockDiffs.some((d) => d.type === "reorder");
8563
+ if (hasReorderDiff) {
8564
+ const localIdentifiers = localBlocks.map((b) => b.identifier);
8565
+ if (localIdentifiers.length > 1) {
8566
+ try {
8567
+ await client.blocks.reorder(pageIdentifier, localIdentifiers);
8568
+ } catch (error) {
8569
+ console.warn("Block reorder failed:", error);
8570
+ }
8571
+ }
8572
+ }
8573
+ }
8574
+ async function syncNavigation(client, diff, local, options, result) {
8575
+ if (!local.navigation?.menus) return;
8576
+ const menuLookup = new Map(local.navigation.menus.map((m) => [m.name, m]));
8577
+ for (const navDiff of diff.navigation) {
8578
+ if (navDiff.type === "unchanged") continue;
8579
+ const localMenu = menuLookup.get(navDiff.name);
8580
+ if (!localMenu) continue;
8581
+ try {
8582
+ if (options.dryRun) ;
8583
+ await client.navigation.upsert({
8584
+ name: localMenu.name,
8585
+ items: localMenu.items
8586
+ });
8587
+ if (navDiff.type === "create") {
8588
+ result.navigation.created++;
8589
+ } else {
8590
+ result.navigation.updated++;
8591
+ }
8592
+ } catch (error) {
8593
+ result.navigation.failed++;
8594
+ result.errors.push({
8595
+ resource: "navigation",
8596
+ identifier: navDiff.name,
8597
+ error: formatApiError(error)
8598
+ });
8599
+ }
8600
+ }
8601
+ }
8602
+ async function syncSettings(client, diff, local, options, result) {
8603
+ if (!diff.settings || !local.settings) return;
8604
+ try {
8605
+ if (options.dryRun) ;
8606
+ await client.settings.update({
8607
+ homepageId: local.settings.homepageId,
8608
+ seoDefaults: local.settings.seoDefaults
8609
+ });
8610
+ result.settings.updated++;
8611
+ } catch (error) {
8612
+ result.settings.failed++;
8613
+ result.errors.push({
8614
+ resource: "settings",
8615
+ identifier: "site",
8616
+ error: formatApiError(error)
8617
+ });
8618
+ }
8619
+ }
8620
+ async function syncForms(client, diff, local, options, result) {
8621
+ const formLookup = new Map(local.forms.map((f) => [f.slug, f]));
8622
+ for (const formDiff of diff.forms) {
8623
+ if (formDiff.type === "unchanged") continue;
8624
+ const localForm = formLookup.get(formDiff.slug);
8625
+ if (!localForm) continue;
8626
+ try {
8627
+ if (options.dryRun) ;
8628
+ await client.forms.upsert({
8629
+ slug: localForm.slug,
8630
+ name: localForm.name,
8631
+ schema: localForm.schema,
8632
+ settings: localForm.settings
8633
+ });
8634
+ if (formDiff.type === "create") {
8635
+ result.forms.created++;
8636
+ } else {
8637
+ result.forms.updated++;
8638
+ }
8639
+ } catch (error) {
8640
+ result.forms.failed++;
8641
+ result.errors.push({
8642
+ resource: "form",
8643
+ identifier: formDiff.slug,
8644
+ error: formatApiError(error)
8645
+ });
8646
+ }
8647
+ }
8648
+ }
8649
+ async function executeSyncPlan(client, diff, local, options, output) {
8650
+ const result = createEmptyResult();
8651
+ const totalOps = diff.summary.entries.create + diff.summary.entries.update + diff.summary.pages.create + diff.summary.pages.update + diff.summary.blocks.create + diff.summary.blocks.update + diff.summary.blocks.delete + diff.summary.navigation.create + diff.summary.navigation.update + diff.summary.settings.update + diff.summary.forms.create + diff.summary.forms.update;
8652
+ if (totalOps === 0) {
8653
+ output.info("No changes to sync");
8654
+ return result;
8655
+ }
8656
+ const prefix = "Syncing";
8657
+ output.info(`${prefix} ${totalOps} operations...`);
8658
+ await syncEntries(client, diff, local, options, result);
8659
+ await syncPages(client, diff, local, options, result);
8660
+ await syncNavigation(client, diff, local, options, result);
8661
+ await syncSettings(client, diff, local, options, result);
8662
+ await syncForms(client, diff, local, options, result);
8663
+ return result;
8664
+ }
8665
+ function formatSyncResult(result, dryRun) {
8666
+ const lines = [];
8667
+ const verb = "";
8668
+ if (result.entries.created > 0 || result.entries.updated > 0) {
8669
+ lines.push(
8670
+ `Entries: ${verb} created ${result.entries.created}, updated ${result.entries.updated}` + (result.entries.failed > 0 ? `, failed ${result.entries.failed}` : "")
8671
+ );
8672
+ }
8673
+ if (result.pages.created > 0 || result.pages.updated > 0) {
8674
+ lines.push(
8675
+ `Pages: ${verb} created ${result.pages.created}, updated ${result.pages.updated}` + (result.pages.failed > 0 ? `, failed ${result.pages.failed}` : "")
8676
+ );
8677
+ }
8678
+ if (result.blocks.created > 0 || result.blocks.updated > 0 || result.blocks.deleted > 0) {
8679
+ lines.push(
8680
+ `Blocks: ${verb} created ${result.blocks.created}, updated ${result.blocks.updated}, deleted ${result.blocks.deleted}` + (result.blocks.failed > 0 ? `, failed ${result.blocks.failed}` : "")
8681
+ );
8682
+ }
8683
+ if (result.navigation.created > 0 || result.navigation.updated > 0) {
8684
+ lines.push(
8685
+ `Navigation: ${verb} created ${result.navigation.created}, updated ${result.navigation.updated}` + (result.navigation.failed > 0 ? `, failed ${result.navigation.failed}` : "")
8686
+ );
8687
+ }
8688
+ if (result.settings.updated > 0) {
8689
+ lines.push(
8690
+ `Settings: ${verb} updated ${result.settings.updated}` + (result.settings.failed > 0 ? `, failed ${result.settings.failed}` : "")
8691
+ );
8692
+ }
8693
+ if (result.forms.created > 0 || result.forms.updated > 0) {
8694
+ lines.push(
8695
+ `Forms: ${verb} created ${result.forms.created}, updated ${result.forms.updated}` + (result.forms.failed > 0 ? `, failed ${result.forms.failed}` : "")
8696
+ );
8697
+ }
8698
+ if (result.warnings.length > 0) {
8699
+ lines.push("\nWarnings:");
8700
+ for (const warning of result.warnings) {
8701
+ lines.push(` ${warning}`);
8702
+ }
8703
+ }
8704
+ if (result.errors.length > 0) {
8705
+ lines.push("\nErrors:");
8706
+ for (const error of result.errors) {
8707
+ lines.push(` ${error.resource}/${error.identifier}: ${error.error}`);
8708
+ }
8709
+ }
8710
+ if (lines.length === 0) {
8711
+ return "Everything is in sync - no changes needed";
8712
+ }
8713
+ return lines.join("\n");
8714
+ }
8715
+
8716
+ // src/cli/commands/pull.ts
8717
+ var DEFAULT_PAGE_LIMIT = 500;
8718
+ async function pullEntriesWithPagination(client, contentType, output) {
8719
+ const allEntries = [];
8720
+ const aggregatedMeta = {};
8721
+ let page = 1;
8722
+ let hasMore = true;
8723
+ let pulledAt = (/* @__PURE__ */ new Date()).toISOString();
8724
+ while (hasMore) {
8725
+ const result = await client.pull.entries(contentType, {
8726
+ page,
8727
+ limit: DEFAULT_PAGE_LIMIT
8728
+ });
8729
+ allEntries.push(...result.entries);
8730
+ if (result.meta.entries) {
8731
+ Object.assign(aggregatedMeta, result.meta.entries);
8732
+ }
8733
+ pulledAt = result.meta.pulledAt;
8734
+ hasMore = result.pagination.hasMore;
8735
+ if (hasMore) {
8736
+ output.info(`Fetched ${allEntries.length} entries (page ${page})...`);
8737
+ }
8738
+ page++;
8739
+ }
8740
+ return {
8741
+ contentType,
8742
+ entries: allEntries,
8743
+ meta: { pulledAt, entries: aggregatedMeta },
8744
+ pagination: {
8745
+ page: 1,
8746
+ limit: allEntries.length,
8747
+ total: allEntries.length,
8748
+ hasMore: false
8749
+ }
8750
+ };
8751
+ }
8752
+ async function writeAllEntries(contentDir, entriesByType, pulledAt, entriesMeta) {
8753
+ let totalCount = 0;
8754
+ const files = [];
8755
+ for (const [contentType, entries] of Object.entries(entriesByType)) {
8756
+ const ctMeta = {};
8757
+ for (const entry of entries) {
8758
+ const key = `${contentType}:${entry.identifier}`;
8759
+ if (entriesMeta?.[key]) {
8760
+ ctMeta[entry.identifier] = entriesMeta[key];
8761
+ }
8762
+ }
8763
+ const { filePath } = await writeEntries(contentDir, {
8764
+ contentType,
8765
+ entries,
8766
+ meta: { pulledAt, entries: ctMeta },
8767
+ pagination: { limit: entries.length, total: entries.length}
8768
+ });
8769
+ totalCount += entries.length;
8770
+ files.push(filePath);
8771
+ }
8772
+ return { totalCount, files };
8773
+ }
8774
+ async function fetchAllContentPaginated(client, contentTypes, output) {
8775
+ const allEntries = {};
8776
+ const allMeta = {};
8777
+ for (const contentType of contentTypes) {
8778
+ output.info(`Fetching ${contentType} entries...`);
8779
+ const result = await pullEntriesWithPagination(client, contentType, output);
8780
+ allEntries[contentType] = result.entries.map(mapEntryForOutput);
8781
+ for (const [id, meta] of Object.entries(result.meta.entries || {})) {
8782
+ allMeta[`${contentType}:${id}`] = meta;
8783
+ }
8784
+ output.info(` ${result.entries.length} entries`);
8785
+ }
8786
+ output.info("Fetching pages...");
8787
+ const pagesResult = await client.pull.pages();
8788
+ output.info("Fetching navigation...");
8789
+ const navigationResult = await client.pull.navigation();
8790
+ output.info("Fetching settings...");
8791
+ const settingsResult = await client.pull.settings();
8792
+ output.info("Fetching forms...");
8793
+ const formsResult = await client.pull.forms();
8794
+ return {
8795
+ entries: allEntries,
8796
+ pages: pagesResult.pages,
8797
+ navigation: navigationResult.menus,
8798
+ settings: settingsResult.settings,
8799
+ forms: formsResult.forms,
8800
+ meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: allMeta }
8801
+ };
8802
+ }
8803
+ async function syncMediaFiles(content, sourceSupabaseUrl, sourceSiteId, targetSiteId, targetClient, output) {
8804
+ const mediaPaths = extractMediaPaths(content);
8805
+ if (mediaPaths.size === 0) {
8806
+ output.info("No media files found in content");
8807
+ return;
8808
+ }
8809
+ output.info(`Found ${mediaPaths.size} media files to sync`);
8810
+ let synced = 0;
8811
+ let skipped = 0;
8812
+ let failed = 0;
8813
+ for (const relativePath of mediaPaths) {
8814
+ const filename = relativePath.split("/").pop() ?? "file";
8815
+ const targetPath = `sites/${targetSiteId}/${relativePath}`;
8816
+ try {
8817
+ const exists = await targetClient.media.exists(targetPath);
8818
+ if (exists) {
8819
+ skipped++;
8820
+ continue;
8821
+ }
8822
+ } catch (error) {
8823
+ if (process.env.DEBUG) {
8824
+ const message = error instanceof Error ? error.message : "Unknown error";
8825
+ console.warn(`[media-sync] Could not check if ${filename} exists: ${message}`);
8826
+ }
8827
+ }
8828
+ const url = buildStorageUrl(sourceSupabaseUrl, relativePath, sourceSiteId);
8829
+ const downloaded = await downloadMedia(url);
8830
+ if (!downloaded) {
8831
+ output.warn(` Failed to download: ${filename}`);
8832
+ failed++;
8833
+ continue;
8834
+ }
8835
+ try {
8836
+ await targetClient.media.upload({
8837
+ data: downloaded.data,
8838
+ filename,
8839
+ contentType: downloaded.contentType,
8840
+ storagePath: targetPath
8841
+ });
8842
+ synced++;
8843
+ } catch (error) {
8844
+ output.warn(` Failed to upload: ${filename}`);
8845
+ failed++;
8846
+ }
8847
+ }
8848
+ output.info(`Media sync: ${synced} synced, ${skipped} skipped (already exist), ${failed} failed`);
8849
+ }
8850
+ function buildPullJsonDiff(diff, remoteContent, localContent, mode) {
8851
+ const result = {
8852
+ mode,
8853
+ direction: "pull",
8854
+ summary: diff.summary
8855
+ };
8856
+ if (mode === "full") {
8857
+ const changes = [];
8858
+ for (const [contentType, entryDiffs] of diff.entries) {
8859
+ const remoteEntries = remoteContent.entries[contentType] ?? [];
8860
+ const localEntries = localContent?.entries.get(contentType) ?? [];
8861
+ const localLookup = new Map(localEntries.map((e) => [e.identifier, e.data]));
8862
+ for (const entryDiff of entryDiffs) {
8863
+ if (entryDiff.type === "unchanged") continue;
8864
+ const remoteEntry = remoteEntries.find((e) => e.identifier === entryDiff.identifier);
8865
+ const localData = localLookup.get(entryDiff.identifier);
8866
+ changes.push({
8867
+ type: "entry",
8868
+ operation: entryDiff.type,
8869
+ identifier: entryDiff.identifier,
8870
+ contentType,
8871
+ before: localData ?? null,
8872
+ after: remoteEntry?.data ?? null
8873
+ });
8024
8874
  }
8025
8875
  }
8026
- }
8027
- }
8028
- async function syncNavigation(client, diff, local, options, result) {
8029
- if (!local.navigation?.menus) return;
8030
- const menuLookup = new Map(local.navigation.menus.map((m) => [m.name, m]));
8031
- for (const navDiff of diff.navigation) {
8032
- if (navDiff.type === "unchanged") continue;
8033
- const localMenu = menuLookup.get(navDiff.name);
8034
- if (!localMenu) continue;
8035
- try {
8036
- if (options.dryRun) ;
8037
- await client.navigation.upsert({
8038
- name: localMenu.name,
8039
- items: localMenu.items
8876
+ for (const pageDiff of diff.pages) {
8877
+ if (pageDiff.type === "unchanged") continue;
8878
+ const remotePage = remoteContent.pages.find((p) => p.identifier === pageDiff.identifier);
8879
+ const localPage = localContent?.pages.find((p) => p.identifier === pageDiff.identifier);
8880
+ changes.push({
8881
+ type: "page",
8882
+ operation: pageDiff.type,
8883
+ identifier: pageDiff.identifier,
8884
+ before: localPage ?? null,
8885
+ after: remotePage ?? null
8040
8886
  });
8041
- if (navDiff.type === "create") {
8042
- result.navigation.created++;
8043
- } else {
8044
- result.navigation.updated++;
8045
- }
8046
- } catch (error) {
8047
- result.navigation.failed++;
8048
- result.errors.push({
8049
- resource: "navigation",
8887
+ }
8888
+ for (const navDiff of diff.navigation) {
8889
+ if (navDiff.type === "unchanged") continue;
8890
+ const remoteMenu = remoteContent.navigation.find((m) => m.name === navDiff.name);
8891
+ const localMenu = localContent?.navigation?.menus.find((m) => m.name === navDiff.name);
8892
+ changes.push({
8893
+ type: "navigation",
8894
+ operation: navDiff.type,
8050
8895
  identifier: navDiff.name,
8051
- error: formatApiError(error)
8896
+ before: localMenu ?? null,
8897
+ after: remoteMenu ?? null
8052
8898
  });
8053
8899
  }
8900
+ if (diff.settings?.type === "update") {
8901
+ changes.push({
8902
+ type: "settings",
8903
+ operation: "update",
8904
+ identifier: "site-settings",
8905
+ before: localContent?.settings ?? null,
8906
+ after: remoteContent.settings ?? null
8907
+ });
8908
+ }
8909
+ result.changes = changes;
8054
8910
  }
8911
+ return result;
8055
8912
  }
8056
- async function syncSettings(client, diff, local, options, result) {
8057
- if (!diff.settings || !local.settings) return;
8058
- try {
8059
- if (options.dryRun) ;
8060
- await client.settings.update({
8061
- homepageId: local.settings.homepageId,
8062
- seoDefaults: local.settings.seoDefaults
8063
- });
8064
- result.settings.updated++;
8913
+ var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, forms, 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").option("--dry-run", "Show what would be pulled without writing files").option("--json-diff [mode]", "Output diff as JSON (summary or full)", "summary").addHelpText("after", `
8914
+ Examples:
8915
+ $ riverbankcms pull # Pull all content
8916
+ $ riverbankcms pull --dry-run # Preview what would be pulled
8917
+ $ riverbankcms pull --remote # Pull from production
8918
+ $ riverbankcms pull --remote --sync-media # Pull from production and sync media to local
8919
+ $ riverbankcms pull entries # Pull all entries
8920
+ $ riverbankcms pull entries blog-post # Pull specific content type
8921
+ $ riverbankcms pull pages # Pull pages with blocks
8922
+ $ riverbankcms pull navigation # Pull navigation menus
8923
+ $ riverbankcms pull settings # Pull site settings
8924
+ $ riverbankcms pull forms # Pull forms
8925
+ $ riverbankcms pull --output ./src/content # Custom output directory
8926
+ $ riverbankcms pull --dry-run --json-diff=summary # JSON summary for agents
8927
+ $ riverbankcms pull --dry-run --json-diff=full # Full JSON with before/after
8928
+
8929
+ Dry Run:
8930
+ When using --dry-run, no files are written. Instead, the command shows:
8931
+ - What entries, pages, navigation, and settings would be created or updated
8932
+ - A summary of changes (or full JSON with --json-diff=full)
8933
+ Exit code: 0 if no changes, 1 if changes would be made
8934
+
8935
+ Media Sync:
8936
+ When using --sync-media, media files are:
8937
+ 1. Downloaded from the source environment's Supabase storage
8938
+ 2. Uploaded to the target environment via the management API
8939
+
8940
+ The storage URL is automatically fetched from the source CMS API.
8941
+ No additional environment variables are required beyond the standard
8942
+ RIVERBANK_*_DASHBOARD_URL, RIVERBANK_*_SITE_ID, and RIVERBANK_*_MGMT_API_KEY.
8943
+ `).action(
8944
+ withErrorHandling(
8945
+ async (scope, type, options, command) => {
8946
+ const { output, client, isRemote } = createCommandContext(command);
8947
+ const contentDir = path2__namespace.resolve(options.output ?? "./content");
8948
+ const envLabel = isRemote ? "REMOTE" : "LOCAL";
8949
+ const jsonDiffMode = options.jsonDiff ? options.jsonDiff === "summary" || options.jsonDiff === "full" ? options.jsonDiff : null : void 0;
8950
+ if (options.jsonDiff && !jsonDiffMode && options.jsonDiff !== true) {
8951
+ output.error('Invalid value for --json-diff. Use "summary" or "full".');
8952
+ return;
8953
+ }
8954
+ if (options.dryRun) {
8955
+ const pullScope2 = scope?.toLowerCase() ?? "all";
8956
+ if (pullScope2 !== "all") {
8957
+ output.error("--dry-run currently only supports pulling all content.", {
8958
+ suggestion: 'Use "riverbankcms pull --dry-run" without scope arguments.'
8959
+ });
8960
+ return;
8961
+ }
8962
+ output.info(`[DRY RUN] Fetching content from ${envLabel}...`);
8963
+ let remoteContent = await client.pull.all();
8964
+ if (remoteContent.meta.truncated) {
8965
+ output.warn("Content was truncated due to size limits.");
8966
+ output.info("Fetching complete data via pagination...");
8967
+ const contentTypes = Object.keys(remoteContent.entries);
8968
+ remoteContent = await fetchAllContentPaginated(client, contentTypes, output);
8969
+ }
8970
+ let localContent = null;
8971
+ if (await contentDirExists(contentDir)) {
8972
+ output.info("Reading local content for comparison...");
8973
+ localContent = await readAllContent(contentDir);
8974
+ }
8975
+ const diff = calculatePullDiff(remoteContent, localContent);
8976
+ if (!hasPendingChanges(diff)) {
8977
+ output.success("No changes detected - local content is already in sync");
8978
+ process.exitCode = 0;
8979
+ return;
8980
+ }
8981
+ if (jsonDiffMode) {
8982
+ const jsonDiff = buildPullJsonDiff(diff, remoteContent, localContent, jsonDiffMode);
8983
+ output.json(jsonDiff);
8984
+ } else {
8985
+ output.info(`[DRY RUN] Changes that would be pulled from ${envLabel}:`);
8986
+ output.info(formatDiffSummary(diff));
8987
+ output.info(formatDiffDetail(diff));
8988
+ output.info('\nUse "riverbankcms pull" (without --dry-run) to apply changes.');
8989
+ }
8990
+ process.exitCode = 1;
8991
+ return;
8992
+ }
8993
+ let targetClient = null;
8994
+ let sourceSupabaseUrl = null;
8995
+ let sourceSiteId = null;
8996
+ let targetSiteId = null;
8997
+ if (options.syncMedia) {
8998
+ const sourceEnv = loadEnvironment(isRemote);
8999
+ const targetEnv = loadEnvironment(!isRemote);
9000
+ output.info("Fetching storage configuration from source...");
9001
+ try {
9002
+ const siteInfo = await client.pull.siteInfo();
9003
+ sourceSupabaseUrl = siteInfo.supabaseUrl;
9004
+ } catch (error) {
9005
+ const message = error instanceof Error ? error.message : "Unknown error";
9006
+ output.error(`Failed to get storage configuration from source: ${message}`, {
9007
+ suggestion: "Ensure the source CMS is running and accessible"
9008
+ });
9009
+ return;
9010
+ }
9011
+ sourceSiteId = sourceEnv.siteId;
9012
+ targetSiteId = targetEnv.siteId;
9013
+ targetClient = createManagementClient({
9014
+ dashboardUrl: targetEnv.dashboardUrl,
9015
+ managementApiKey: targetEnv.managementApiKey,
9016
+ siteId: targetEnv.siteId
9017
+ });
9018
+ output.info(`Media sync enabled: ${isRemote ? "remote" : "local"} -> ${isRemote ? "local" : "remote"}`);
9019
+ }
9020
+ if (await contentDirExists(contentDir) && !options.force && !options.yes) {
9021
+ if (!process.stdin.isTTY) {
9022
+ output.error("Content directory already exists and --yes not specified", {
9023
+ suggestion: "Use --yes or --force to overwrite in non-interactive mode"
9024
+ });
9025
+ return;
9026
+ }
9027
+ const response = await prompts__default.default({
9028
+ type: "confirm",
9029
+ name: "overwrite",
9030
+ message: `Content directory '${path2__namespace.basename(contentDir)}' already exists. Overwrite?`,
9031
+ initial: false
9032
+ });
9033
+ if (!response.overwrite) {
9034
+ output.info("Aborted.");
9035
+ return;
9036
+ }
9037
+ }
9038
+ const pullScope = scope?.toLowerCase() ?? "all";
9039
+ switch (pullScope) {
9040
+ case "entries": {
9041
+ if (type) {
9042
+ output.info(`Pulling entries for content type: ${type}`);
9043
+ const result = await pullEntriesWithPagination(client, type, output);
9044
+ const { filePath, metaPath } = await writeEntries(contentDir, result);
9045
+ output.success(`Pulled ${result.entries.length} entries`, {
9046
+ contentType: type,
9047
+ file: filePath,
9048
+ meta: metaPath
9049
+ });
9050
+ } else {
9051
+ output.info("Pulling all entries");
9052
+ let result = await client.pull.all();
9053
+ if (result.meta.truncated) {
9054
+ output.warn("Content was truncated due to size limits.");
9055
+ output.info("Fetching complete data via pagination...");
9056
+ const contentTypes = Object.keys(result.entries);
9057
+ result = await fetchAllContentPaginated(client, contentTypes, output);
9058
+ }
9059
+ const { totalCount, files } = await writeAllEntries(
9060
+ contentDir,
9061
+ result.entries,
9062
+ result.meta.pulledAt,
9063
+ result.meta.entries
9064
+ );
9065
+ output.success(`Pulled ${totalCount} entries across ${Object.keys(result.entries).length} content types`, {
9066
+ files
9067
+ });
9068
+ }
9069
+ break;
9070
+ }
9071
+ case "pages": {
9072
+ output.info("Pulling pages");
9073
+ const result = await client.pull.pages();
9074
+ const { filePaths, metaPath } = await writePages(contentDir, result);
9075
+ output.success(`Pulled ${result.pages.length} pages`, { files: filePaths, meta: metaPath });
9076
+ break;
9077
+ }
9078
+ case "navigation": {
9079
+ output.info("Pulling navigation menus");
9080
+ const result = await client.pull.navigation();
9081
+ const { filePath, metaPath } = await writeNavigation(contentDir, result);
9082
+ output.success(`Pulled ${result.menus.length} navigation menus`, { file: filePath, meta: metaPath });
9083
+ break;
9084
+ }
9085
+ case "settings": {
9086
+ output.info("Pulling site settings");
9087
+ const result = await client.pull.settings();
9088
+ const filePath = await writeSettings(contentDir, result);
9089
+ output.success("Pulled site settings", { file: filePath });
9090
+ break;
9091
+ }
9092
+ case "forms": {
9093
+ output.info("Pulling forms");
9094
+ const result = await client.pull.forms();
9095
+ const { filePaths, metaPath } = await writeForms(contentDir, result);
9096
+ output.success(`Pulled ${result.forms.length} forms`, { files: filePaths, meta: metaPath });
9097
+ break;
9098
+ }
9099
+ case "all":
9100
+ default: {
9101
+ output.info("Pulling all content");
9102
+ let result = await client.pull.all();
9103
+ if (result.meta.truncated) {
9104
+ output.warn("Content was truncated due to size limits.");
9105
+ output.info("Fetching complete data via pagination...");
9106
+ const contentTypes = Object.keys(result.entries);
9107
+ result = await fetchAllContentPaginated(client, contentTypes, output);
9108
+ }
9109
+ if (targetClient && sourceSupabaseUrl && sourceSiteId && targetSiteId) {
9110
+ output.info("Syncing media files...");
9111
+ await syncMediaFiles(
9112
+ result,
9113
+ sourceSupabaseUrl,
9114
+ sourceSiteId,
9115
+ targetSiteId,
9116
+ targetClient,
9117
+ output
9118
+ );
9119
+ }
9120
+ const { totalCount: totalEntries } = await writeAllEntries(
9121
+ contentDir,
9122
+ result.entries,
9123
+ result.meta.pulledAt,
9124
+ result.meta.entries
9125
+ );
9126
+ await writePages(contentDir, { pages: result.pages, meta: result.meta });
9127
+ await writeNavigation(contentDir, { menus: result.navigation, meta: result.meta });
9128
+ await writeSettings(contentDir, { settings: result.settings, meta: result.meta });
9129
+ await writeForms(contentDir, { forms: result.forms, meta: result.meta });
9130
+ output.success("Pull complete", {
9131
+ entries: { count: totalEntries, types: Object.keys(result.entries).length },
9132
+ pages: { count: result.pages.length },
9133
+ navigation: { count: result.navigation.length },
9134
+ forms: { count: result.forms.length },
9135
+ directory: contentDir
9136
+ });
9137
+ break;
9138
+ }
9139
+ }
9140
+ }
9141
+ )
9142
+ );
9143
+
9144
+ // src/cli/config-loader.ts
9145
+ init_load_config();
9146
+ var DEFAULT_SYNC_CONFIG = {
9147
+ existingEntries: "skip",
9148
+ contentTarget: "draft"
9149
+ };
9150
+ async function loadCliConfig(configPath) {
9151
+ try {
9152
+ const rawConfig = await loadConfigFile(configPath);
9153
+ const configObj = rawConfig;
9154
+ const contentDir = typeof configObj.contentDir === "string" ? configObj.contentDir : "./content";
9155
+ const syncConfig = configObj.sync;
9156
+ const blockFieldExtensions = configObj.blockFieldExtensions;
9157
+ return {
9158
+ contentDir,
9159
+ sync: {
9160
+ existingEntries: syncConfig?.existingEntries ?? DEFAULT_SYNC_CONFIG.existingEntries,
9161
+ contentTarget: syncConfig?.contentTarget ?? DEFAULT_SYNC_CONFIG.contentTarget
9162
+ },
9163
+ blockFieldExtensions
9164
+ };
8065
9165
  } catch (error) {
8066
- result.settings.failed++;
8067
- result.errors.push({
8068
- resource: "settings",
8069
- identifier: "site",
8070
- error: formatApiError(error)
8071
- });
8072
- }
8073
- }
8074
- async function executeSyncPlan(client, diff, local, options, output) {
8075
- const result = createEmptyResult();
8076
- const totalOps = diff.summary.entries.create + diff.summary.entries.update + diff.summary.pages.create + diff.summary.pages.update + diff.summary.blocks.create + diff.summary.blocks.update + diff.summary.blocks.delete + diff.summary.navigation.create + diff.summary.navigation.update + diff.summary.settings.update;
8077
- if (totalOps === 0) {
8078
- output.info("No changes to sync");
8079
- return result;
8080
- }
8081
- const prefix = "Syncing";
8082
- output.info(`${prefix} ${totalOps} operations...`);
8083
- await syncEntries(client, diff, local, options, result);
8084
- await syncPages(client, diff, local, options, result);
8085
- await syncNavigation(client, diff, local, options, result);
8086
- await syncSettings(client, diff, local, options, result);
8087
- return result;
8088
- }
8089
- function formatSyncResult(result, dryRun) {
8090
- const lines = [];
8091
- const verb = "";
8092
- if (result.entries.created > 0 || result.entries.updated > 0) {
8093
- lines.push(
8094
- `Entries: ${verb} created ${result.entries.created}, updated ${result.entries.updated}` + (result.entries.failed > 0 ? `, failed ${result.entries.failed}` : "")
8095
- );
8096
- }
8097
- if (result.pages.created > 0 || result.pages.updated > 0) {
8098
- lines.push(
8099
- `Pages: ${verb} created ${result.pages.created}, updated ${result.pages.updated}` + (result.pages.failed > 0 ? `, failed ${result.pages.failed}` : "")
8100
- );
8101
- }
8102
- if (result.blocks.created > 0 || result.blocks.updated > 0 || result.blocks.deleted > 0) {
8103
- lines.push(
8104
- `Blocks: ${verb} created ${result.blocks.created}, updated ${result.blocks.updated}, deleted ${result.blocks.deleted}` + (result.blocks.failed > 0 ? `, failed ${result.blocks.failed}` : "")
8105
- );
8106
- }
8107
- if (result.navigation.created > 0 || result.navigation.updated > 0) {
8108
- lines.push(
8109
- `Navigation: ${verb} created ${result.navigation.created}, updated ${result.navigation.updated}` + (result.navigation.failed > 0 ? `, failed ${result.navigation.failed}` : "")
8110
- );
8111
- }
8112
- if (result.settings.updated > 0) {
8113
- lines.push(
8114
- `Settings: ${verb} updated ${result.settings.updated}` + (result.settings.failed > 0 ? `, failed ${result.settings.failed}` : "")
8115
- );
8116
- }
8117
- if (result.errors.length > 0) {
8118
- lines.push("\nErrors:");
8119
- for (const error of result.errors) {
8120
- lines.push(` ${error.resource}/${error.identifier}: ${error.error}`);
9166
+ if (error instanceof Error && error.message.includes("Config file not found")) {
9167
+ return {
9168
+ contentDir: "./content",
9169
+ sync: { ...DEFAULT_SYNC_CONFIG }
9170
+ };
8121
9171
  }
9172
+ throw error;
8122
9173
  }
8123
- if (lines.length === 0) {
8124
- return "Everything is in sync - no changes needed";
8125
- }
8126
- return lines.join("\n");
8127
9174
  }
8128
9175
 
8129
9176
  // src/cli/commands/push.ts
@@ -8135,15 +9182,18 @@ function filterLocalContent(localContent, scope, contentType) {
8135
9182
  if (entries) {
8136
9183
  filtered.set(contentType, entries);
8137
9184
  }
8138
- return { ...localContent, entries: filtered, pages: [], navigation: null };
9185
+ return { ...localContent, entries: filtered, pages: [], navigation: null, forms: [] };
8139
9186
  }
8140
- return { ...localContent, pages: [], navigation: null };
9187
+ return { ...localContent, pages: [], navigation: null, forms: [] };
8141
9188
  }
8142
9189
  if (scope === "pages") {
8143
- return { ...localContent, entries: /* @__PURE__ */ new Map(), navigation: null };
9190
+ return { ...localContent, entries: /* @__PURE__ */ new Map(), navigation: null, forms: [] };
8144
9191
  }
8145
9192
  if (scope === "navigation") {
8146
- return { ...localContent, entries: /* @__PURE__ */ new Map(), pages: [] };
9193
+ return { ...localContent, entries: /* @__PURE__ */ new Map(), pages: [], forms: [] };
9194
+ }
9195
+ if (scope === "forms") {
9196
+ return { ...localContent, entries: /* @__PURE__ */ new Map(), pages: [], navigation: null };
8147
9197
  }
8148
9198
  return localContent;
8149
9199
  }
@@ -8178,7 +9228,7 @@ function reportSyncResults(output, result, hasErrors) {
8178
9228
  }
8179
9229
  output.info(formatSyncResult(result));
8180
9230
  }
8181
- function checkForStaleContent(localContent, localMeta, pagesMeta, navigationMeta, remoteContent) {
9231
+ function checkForStaleContent(localContent, localMeta, pagesMeta, navigationMeta, formsMeta, remoteContent) {
8182
9232
  const staleItems = [];
8183
9233
  for (const [contentType, localEntries] of localContent.entries) {
8184
9234
  const meta = localMeta.get(contentType);
@@ -8217,17 +9267,30 @@ function checkForStaleContent(localContent, localMeta, pagesMeta, navigationMeta
8217
9267
  }
8218
9268
  });
8219
9269
  }
9270
+ if (formsMeta) {
9271
+ localContent.forms.forEach((localForm) => {
9272
+ const remoteForm = remoteContent.forms.find((f) => f.slug === localForm.slug);
9273
+ const localBaseTime = formsMeta.forms[localForm.slug]?.updatedAt;
9274
+ if (remoteForm && localBaseTime) {
9275
+ if (new Date(remoteForm.updatedAt) > new Date(localBaseTime)) {
9276
+ staleItems.push(`Form: ${localForm.slug} (Remote updated: ${remoteForm.updatedAt})`);
9277
+ }
9278
+ }
9279
+ });
9280
+ }
8220
9281
  return staleItems;
8221
9282
  }
8222
- var pushCommand = new commander.Command("push").description("Push content to CMS").argument("[scope]", "What to push: entries, pages, navigation, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--content-dir <dir>", "Content directory (overrides config)").option("--dry-run", "Show what would be pushed without making changes").option("--yes", "Skip confirmation prompt (required for --remote)").option("--force", "Push even if remote content is newer (skip stale check)").option("--allow-truncated", "Push even if remote content was truncated (may cause incomplete sync)").option("--json-diff [mode]", "Output JSON diff (summary or full)", "summary").addHelpText("after", `
9283
+ var pushCommand = new commander.Command("push").description("Push content to CMS").argument("[scope]", "What to push: entries, pages, navigation, forms, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--content-dir <dir>", "Content directory (overrides config)").option("--dry-run", "Show what would be pushed without making changes").option("--yes", "Skip confirmation prompt (required for --remote)").option("--force", "Push even if remote content is newer (skip stale check)").option("--allow-truncated", "Push even if remote content was truncated (may cause incomplete sync)").option("--json-diff [mode]", "Output JSON diff (summary or full)", "summary").option("--with-config", "Also push SDK config after content").addHelpText("after", `
8223
9284
  Examples:
8224
9285
  $ riverbankcms push # Push all content
8225
9286
  $ riverbankcms push --dry-run # Preview changes
8226
9287
  $ riverbankcms push --remote --yes # Push to production
9288
+ $ riverbankcms push --with-config # Push content and config together
8227
9289
  $ riverbankcms push entries # Push all entries
8228
9290
  $ riverbankcms push entries blog-post # Push specific content type
8229
9291
  $ riverbankcms push pages # Push pages with blocks
8230
9292
  $ riverbankcms push navigation # Push navigation menus
9293
+ $ riverbankcms push forms # Push forms
8231
9294
  $ riverbankcms push --content-dir ./src/content # Custom content directory
8232
9295
  $ riverbankcms push --dry-run --json-diff=summary # JSON summary for agents
8233
9296
  $ riverbankcms push --dry-run --json-diff=full # Full JSON before/after payloads
@@ -8255,14 +9318,14 @@ Safety:
8255
9318
  }
8256
9319
  try {
8257
9320
  const cliConfig = await loadCliConfig();
8258
- const contentDir = path9__namespace.resolve(options.contentDir ?? cliConfig.contentDir);
9321
+ const contentDir = path2__namespace.resolve(options.contentDir ?? cliConfig.contentDir);
8259
9322
  if (!await contentDirExists(contentDir)) {
8260
9323
  return output.error(`Content directory not found: ${contentDir}`, {
8261
9324
  suggestion: 'Run "riverbankcms pull" first to download content, or specify a different directory with --content-dir'
8262
9325
  });
8263
9326
  }
8264
9327
  const summary = await getContentSummary(contentDir);
8265
- if (summary.totalEntries === 0 && summary.pageCount === 0 && !summary.hasNavigation) {
9328
+ if (summary.totalEntries === 0 && summary.pageCount === 0 && !summary.hasNavigation && !summary.hasForms) {
8266
9329
  output.warn("No content found to push");
8267
9330
  return;
8268
9331
  }
@@ -8276,6 +9339,20 @@ Safety:
8276
9339
  const localContent = await readAllContent(contentDir);
8277
9340
  const pushScope = scope?.toLowerCase() ?? "all";
8278
9341
  const filteredLocal = filterLocalContent(localContent, pushScope, type);
9342
+ const pathToIdentifiers = /* @__PURE__ */ new Map();
9343
+ for (const page of filteredLocal.pages) {
9344
+ const ids = pathToIdentifiers.get(page.path) ?? [];
9345
+ ids.push(page.identifier);
9346
+ pathToIdentifiers.set(page.path, ids);
9347
+ }
9348
+ const duplicatePaths = Array.from(pathToIdentifiers.entries()).filter(([_, ids]) => ids.length > 1);
9349
+ if (duplicatePaths.length > 0) {
9350
+ output.error("Duplicate page paths detected", {
9351
+ details: duplicatePaths.map(([pagePath, ids]) => ` ${pagePath}: ${ids.join(", ")}`).join("\n"),
9352
+ suggestion: "Each page must have a unique path. Delete or rename one of the files."
9353
+ });
9354
+ return;
9355
+ }
8279
9356
  output.info("Fetching remote content for comparison...");
8280
9357
  const remoteContent = await client.pull.all();
8281
9358
  if (remoteContent.meta.truncated) {
@@ -8294,12 +9371,13 @@ Safety:
8294
9371
  }
8295
9372
  if (!options.force) {
8296
9373
  output.info("Checking for stale content...");
8297
- const [localMeta, pagesMeta, navigationMeta] = await Promise.all([
9374
+ const [localMeta, pagesMeta, navigationMeta, formsMeta] = await Promise.all([
8298
9375
  readAllMeta(contentDir),
8299
9376
  readPagesMeta(contentDir),
8300
- readNavigationMeta(contentDir)
9377
+ readNavigationMeta(contentDir),
9378
+ readFormsMeta(contentDir)
8301
9379
  ]);
8302
- const staleItems = checkForStaleContent(filteredLocal, localMeta, pagesMeta, navigationMeta, remoteContent);
9380
+ const staleItems = checkForStaleContent(filteredLocal, localMeta, pagesMeta, navigationMeta, formsMeta, remoteContent);
8303
9381
  if (staleItems.length > 0) {
8304
9382
  output.warn("WARNING: The following remote content has changed since you last pulled:");
8305
9383
  staleItems.forEach((item) => output.warn(` - ${item}`));
@@ -8338,7 +9416,11 @@ Safety:
8338
9416
  client,
8339
9417
  diff,
8340
9418
  filteredLocal,
8341
- { dryRun: false, contentTarget: cliConfig.sync.contentTarget },
9419
+ {
9420
+ dryRun: false,
9421
+ contentTarget: cliConfig.sync.contentTarget,
9422
+ blockFieldExtensions: cliConfig.blockFieldExtensions
9423
+ },
8342
9424
  output
8343
9425
  );
8344
9426
  reportSyncResults(output, result, result.errors.length > 0);
@@ -8373,11 +9455,31 @@ Safety:
8373
9455
  name: n.name,
8374
9456
  updatedAt: n.updatedAt,
8375
9457
  createdAt: n.createdAt
9458
+ })),
9459
+ forms: freshRemote.forms.map((f) => ({
9460
+ slug: f.slug,
9461
+ updatedAt: f.updatedAt,
9462
+ createdAt: f.createdAt
8376
9463
  }))
8377
9464
  });
8378
9465
  } catch (metaError) {
8379
9466
  output.warn('Push succeeded but metadata update failed. Run "pull" to sync metadata.');
8380
9467
  }
9468
+ if (options.withConfig) {
9469
+ output.info("\nPushing config...");
9470
+ try {
9471
+ await pushConfigAction(output, {
9472
+ dashboard: env.dashboardUrl,
9473
+ isRemote
9474
+ });
9475
+ } catch (configError) {
9476
+ output.warn("Content was pushed successfully, but config push failed:");
9477
+ const message = configError instanceof Error ? configError.message : String(configError);
9478
+ output.error(message);
9479
+ }
9480
+ }
9481
+ } else if (options.withConfig) {
9482
+ output.warn("Skipping config push due to content push errors.");
8381
9483
  }
8382
9484
  } catch (error) {
8383
9485
  handleCommandError(error, output);
@@ -8746,7 +9848,7 @@ async function captureScreenshot(options) {
8746
9848
  colorScheme: playwright_config_default.use.colorScheme
8747
9849
  });
8748
9850
  await page.setContent(html, { waitUntil: "load" });
8749
- await page.screenshot({ path: path9__namespace.resolve(outputPath), fullPage: true });
9851
+ await page.screenshot({ path: path2__namespace.resolve(outputPath), fullPage: true });
8750
9852
  await browser.close();
8751
9853
  }
8752
9854
  function resolveOutputMode(options) {
@@ -8759,8 +9861,8 @@ function resolveOutputMode(options) {
8759
9861
  return "terminal";
8760
9862
  }
8761
9863
  async function writePreviewHtmlFile(html) {
8762
- const dir = await fs3__namespace.mkdtemp(path9__namespace.join(os__namespace.tmpdir(), "riverbank-preview-"));
8763
- const filePath = path9__namespace.join(dir, "index.html");
9864
+ const dir = await fs3__namespace.mkdtemp(path2__namespace.join(os__namespace.tmpdir(), "riverbank-preview-"));
9865
+ const filePath = path2__namespace.join(dir, "index.html");
8764
9866
  await fs3__namespace.writeFile(filePath, html, "utf-8");
8765
9867
  return filePath;
8766
9868
  }
@@ -8789,7 +9891,7 @@ function runCommand(command, args) {
8789
9891
  async function readCssFiles(paths) {
8790
9892
  if (!paths || paths.length === 0) return "";
8791
9893
  const chunks = await Promise.all(paths.map(async (filePath) => {
8792
- const resolved = path9__namespace.resolve(filePath);
9894
+ const resolved = path2__namespace.resolve(filePath);
8793
9895
  const { readFile: readFile4 } = await import('fs/promises');
8794
9896
  return readFile4(resolved, "utf-8");
8795
9897
  }));
@@ -8833,7 +9935,7 @@ ${extraCss}` : response.cssText;
8833
9935
  return;
8834
9936
  }
8835
9937
  if (mode === "screenshot") {
8836
- const outputPath = options.output ?? path9__namespace.resolve(process.cwd(), "block-preview.png");
9938
+ const outputPath = options.output ?? path2__namespace.resolve(process.cwd(), "block-preview.png");
8837
9939
  await captureScreenshot({ html: htmlDocument, outputPath });
8838
9940
  output.success("Screenshot saved", { outputPath });
8839
9941
  return;
@@ -9186,60 +10288,62 @@ var AGENTS_END = "<!-- RIVERBANK-CONTEXT-END -->";
9186
10288
  function getContentDir() {
9187
10289
  const isBundled = __dirname.includes("/dist/cli") || __dirname.endsWith("/dist/cli");
9188
10290
  if (isBundled) {
9189
- return path9__namespace.join(__dirname, "init-docs", "content");
10291
+ return path2__namespace.join(__dirname, "init-docs", "content");
9190
10292
  }
9191
- return path9__namespace.join(__dirname, "content");
10293
+ return path2__namespace.join(__dirname, "content");
9192
10294
  }
9193
10295
  function loadTemplate(name) {
9194
10296
  const contentDir = getContentDir();
9195
- const filePath = path9__namespace.join(contentDir, `${name}.md`);
10297
+ const filePath = path2__namespace.join(contentDir, `${name}.md`);
9196
10298
  return fs6.readFileSync(filePath, "utf-8");
9197
10299
  }
9198
10300
  async function initDocs(options) {
9199
10301
  const { rootDir, configPath, agentsPath } = options;
9200
- const docsDir = path9__namespace.join(rootDir, "docs");
9201
- const contextDir = path9__namespace.join(rootDir, "context");
9202
- const workflowsDir = path9__namespace.join(docsDir, "workflows");
9203
- const siteWorkflowsDir = path9__namespace.join(docsDir, "site-workflows");
9204
- const knowledgeDir = path9__namespace.join(contextDir, "knowledge");
9205
- const brandDir = path9__namespace.join(contextDir, "brand");
10302
+ const docsDir = path2__namespace.join(rootDir, "docs");
10303
+ const contextDir = path2__namespace.join(rootDir, "context");
10304
+ const workflowsDir = path2__namespace.join(docsDir, "workflows");
10305
+ const siteWorkflowsDir = path2__namespace.join(docsDir, "site-workflows");
10306
+ const knowledgeDir = path2__namespace.join(contextDir, "knowledge");
10307
+ const brandDir = path2__namespace.join(contextDir, "brand");
9206
10308
  await ensureDir2(workflowsDir);
9207
10309
  await ensureDir2(siteWorkflowsDir);
9208
10310
  await ensureDir2(knowledgeDir);
9209
10311
  await ensureDir2(brandDir);
9210
- await writeFileIfMissing(path9__namespace.join(docsDir, "getting-started.md"), loadTemplate("getting-started"));
9211
- await writeFileIfMissing(path9__namespace.join(docsDir, "cli-reference.md"), loadTemplate("cli-reference"));
10312
+ await writeFileIfMissing(path2__namespace.join(docsDir, "getting-started.md"), loadTemplate("getting-started"));
10313
+ await writeFileIfMissing(path2__namespace.join(docsDir, "cli-reference.md"), loadTemplate("cli-reference"));
9212
10314
  await writeFileIfMissing(
9213
- path9__namespace.join(docsDir, "content-management.md"),
10315
+ path2__namespace.join(docsDir, "content-management.md"),
9214
10316
  loadTemplate("content-management")
9215
10317
  );
9216
- await writeFileIfMissing(
9217
- path9__namespace.join(workflowsDir, "create-page.md"),
9218
- loadTemplate("workflow-create-page")
9219
- );
9220
- await writeFileIfMissing(
9221
- path9__namespace.join(workflowsDir, "add-block.md"),
9222
- loadTemplate("workflow-add-block")
9223
- );
9224
- await writeFileIfMissing(
9225
- path9__namespace.join(workflowsDir, "publish-workflow.md"),
9226
- loadTemplate("workflow-publish")
10318
+ const coreWorkflows = [
10319
+ { filename: "create-page.md", template: "workflow-create-page" },
10320
+ { filename: "add-block.md", template: "workflow-add-block" },
10321
+ { filename: "publish-workflow.md", template: "workflow-publish" },
10322
+ { filename: "block-extensions.md", template: "workflow-block-extensions" },
10323
+ { filename: "custom-block.md", template: "workflow-custom-block" },
10324
+ { filename: "content-types.md", template: "workflow-content-types" },
10325
+ { filename: "cmsify-page.md", template: "workflow-cmsify-page" }
10326
+ ];
10327
+ await Promise.all(
10328
+ coreWorkflows.map(
10329
+ ({ filename, template }) => fs3__namespace.writeFile(path2__namespace.join(workflowsDir, filename), loadTemplate(template), "utf-8")
10330
+ )
9227
10331
  );
9228
10332
  await writeFileIfMissing(
9229
- path9__namespace.join(siteWorkflowsDir, "README.md"),
10333
+ path2__namespace.join(siteWorkflowsDir, "README.md"),
9230
10334
  loadTemplate("site-workflows-readme")
9231
10335
  );
9232
- await writeFileIfMissing(path9__namespace.join(contextDir, "brief.md"), loadTemplate("context-brief"));
9233
- await writeFileIfMissing(path9__namespace.join(knowledgeDir, "README.md"), loadTemplate("context-knowledge"));
9234
- await writeFileIfMissing(path9__namespace.join(brandDir, "README.md"), loadTemplate("context-brand"));
10336
+ await writeFileIfMissing(path2__namespace.join(contextDir, "brief.md"), loadTemplate("context-brief"));
10337
+ await writeFileIfMissing(path2__namespace.join(knowledgeDir, "README.md"), loadTemplate("context-knowledge"));
10338
+ await writeFileIfMissing(path2__namespace.join(brandDir, "README.md"), loadTemplate("context-brand"));
9235
10339
  const { config: config3, errorMessage } = await loadConfig(configPath, options.output);
9236
10340
  await upsertGeneratedDoc(
9237
- path9__namespace.join(docsDir, "schema.md"),
10341
+ path2__namespace.join(docsDir, "schema.md"),
9238
10342
  buildSchemaTemplate({ config: config3, errorMessage }),
9239
10343
  buildSchemaGeneratedSection({ config: config3, errorMessage })
9240
10344
  );
9241
10345
  await upsertGeneratedDoc(
9242
- path9__namespace.join(docsDir, "block-types.md"),
10346
+ path2__namespace.join(docsDir, "block-types.md"),
9243
10347
  buildBlockTypesTemplate(),
9244
10348
  buildBlockTypesGeneratedSection()
9245
10349
  );
@@ -9314,9 +10418,9 @@ async function upsertAgentsSection(filePath) {
9314
10418
  // src/cli/commands/init-docs.ts
9315
10419
  async function runInitDocs(options, command) {
9316
10420
  const { output, isJsonOutput } = createCommandContext(command);
9317
- const rootDir = path9__namespace.resolve(options.path ?? ".riverbank");
9318
- const configPath = path9__namespace.resolve(options.config ?? path9__namespace.join(process.cwd(), "riverbank.config.ts"));
9319
- const agentsPath = path9__namespace.resolve(process.cwd(), "AGENTS.md");
10421
+ const rootDir = path2__namespace.resolve(options.path ?? ".riverbank");
10422
+ const configPath = path2__namespace.resolve(options.config ?? path2__namespace.join(process.cwd(), "riverbank.config.ts"));
10423
+ const agentsPath = path2__namespace.resolve(process.cwd(), "AGENTS.md");
9320
10424
  await initDocs({
9321
10425
  rootDir,
9322
10426
  configPath,
@@ -10826,6 +11930,15 @@ var ENDPOINT_DEFINITIONS = {
10826
11930
  auth: "public",
10827
11931
  responseKind: "json"
10828
11932
  },
11933
+ // List all forms for a site (for prebuild cache)
11934
+ listPublicForms: {
11935
+ path: "/public/sites/{siteId}/forms",
11936
+ method: "GET",
11937
+ revalidate: 60,
11938
+ tags: ["site-forms-{siteId}"],
11939
+ auth: "public",
11940
+ responseKind: "json"
11941
+ },
10829
11942
  // Public booking services
10830
11943
  getPublicBookingServices: {
10831
11944
  path: "/public/bookings/services",
@@ -11012,6 +12125,194 @@ var ENDPOINT_DEFINITIONS = {
11012
12125
  tags: ["admin", "billing", "price-override"],
11013
12126
  auth: "admin",
11014
12127
  responseKind: "json"
12128
+ },
12129
+ // Bookings - Event Series
12130
+ listEventSeries: {
12131
+ path: "/sites/{siteId}/bookings/event-series",
12132
+ method: "GET",
12133
+ tags: ["site-{siteId}", "event-series-{siteId}"],
12134
+ auth: "user",
12135
+ responseKind: "json"
12136
+ },
12137
+ createEventSeries: {
12138
+ path: "/sites/{siteId}/bookings/event-series",
12139
+ method: "POST",
12140
+ tags: ["site-{siteId}", "event-series-{siteId}"],
12141
+ auth: "user",
12142
+ responseKind: "json"
12143
+ },
12144
+ getEventSeries: {
12145
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}",
12146
+ method: "GET",
12147
+ tags: ["site-{siteId}", "event-series-{seriesId}"],
12148
+ auth: "user",
12149
+ responseKind: "json"
12150
+ },
12151
+ updateEventSeries: {
12152
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}",
12153
+ method: "PATCH",
12154
+ tags: ["site-{siteId}", "event-series-{seriesId}", "event-series-{siteId}"],
12155
+ auth: "user",
12156
+ responseKind: "json"
12157
+ },
12158
+ deleteEventSeries: {
12159
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}",
12160
+ method: "DELETE",
12161
+ tags: ["site-{siteId}", "event-series-{seriesId}", "event-series-{siteId}"],
12162
+ auth: "user",
12163
+ responseKind: "json"
12164
+ },
12165
+ listEventSeriesAttendees: {
12166
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}/attendees",
12167
+ method: "GET",
12168
+ tags: ["site-{siteId}", "event-series-{seriesId}"],
12169
+ auth: "user",
12170
+ responseKind: "json"
12171
+ },
12172
+ listEventSeriesOccurrences: {
12173
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}/occurrences",
12174
+ method: "GET",
12175
+ tags: ["site-{siteId}", "event-series-{seriesId}", "occurrences-{seriesId}"],
12176
+ auth: "user",
12177
+ responseKind: "json"
12178
+ },
12179
+ generateEventOccurrences: {
12180
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}/occurrences/generate",
12181
+ method: "POST",
12182
+ tags: ["site-{siteId}", "event-series-{seriesId}", "occurrences-{seriesId}"],
12183
+ auth: "user",
12184
+ responseKind: "json"
12185
+ },
12186
+ updateEventOccurrence: {
12187
+ path: "/sites/{siteId}/bookings/event-series/{seriesId}/occurrences/{occurrenceId}",
12188
+ method: "PATCH",
12189
+ tags: ["site-{siteId}", "event-series-{seriesId}", "occurrences-{seriesId}"],
12190
+ auth: "user",
12191
+ responseKind: "json"
12192
+ },
12193
+ // Bookings - Venues
12194
+ listVenues: {
12195
+ path: "/sites/{siteId}/bookings/venues",
12196
+ method: "GET",
12197
+ tags: ["site-{siteId}", "venues-{siteId}"],
12198
+ auth: "user",
12199
+ responseKind: "json"
12200
+ },
12201
+ createVenue: {
12202
+ path: "/sites/{siteId}/bookings/venues",
12203
+ method: "POST",
12204
+ tags: ["site-{siteId}", "venues-{siteId}"],
12205
+ auth: "user",
12206
+ responseKind: "json"
12207
+ },
12208
+ // AI Playground
12209
+ aiPlaygroundApply: {
12210
+ path: "/sites/{siteId}/ai/playground/apply",
12211
+ method: "POST",
12212
+ tags: ["site-{siteId}"],
12213
+ auth: "user",
12214
+ responseKind: "json"
12215
+ },
12216
+ // Billing status endpoints
12217
+ billingStatus: {
12218
+ path: "/billing/status",
12219
+ method: "GET",
12220
+ auth: "user",
12221
+ responseKind: "json"
12222
+ },
12223
+ billingSummary: {
12224
+ path: "/billing/summary",
12225
+ method: "GET",
12226
+ auth: "user",
12227
+ responseKind: "json"
12228
+ },
12229
+ siteBillingCost: {
12230
+ path: "/sites/{siteId}/billing/cost",
12231
+ method: "GET",
12232
+ tags: ["site-{siteId}", "billing"],
12233
+ auth: "user",
12234
+ responseKind: "json"
12235
+ },
12236
+ // Account
12237
+ deleteAccount: {
12238
+ path: "/account/delete",
12239
+ method: "POST",
12240
+ auth: "user",
12241
+ responseKind: "json"
12242
+ },
12243
+ // Fonts
12244
+ listFonts: {
12245
+ path: "/fonts",
12246
+ method: "GET",
12247
+ revalidate: 3600,
12248
+ // 1 hour - fonts rarely change
12249
+ auth: "public",
12250
+ responseKind: "json"
12251
+ },
12252
+ // Binary download endpoints
12253
+ exportSiteBackup: {
12254
+ path: "/sites/{siteId}/backup/export",
12255
+ method: "POST",
12256
+ tags: ["site-{siteId}", "backup"],
12257
+ auth: "user",
12258
+ responseKind: "blob"
12259
+ },
12260
+ downloadMediaAsset: {
12261
+ path: "/media/{mediaId}/download",
12262
+ method: "GET",
12263
+ auth: "user",
12264
+ responseKind: "blob"
12265
+ },
12266
+ exportSeoCsv: {
12267
+ path: "/sites/{siteId}/seo/export",
12268
+ method: "GET",
12269
+ tags: ["site-{siteId}", "seo"],
12270
+ auth: "user",
12271
+ responseKind: "blob"
12272
+ },
12273
+ // Admin site cost (for admin panel)
12274
+ adminSiteCost: {
12275
+ path: "/admin/sites/{siteId}/cost",
12276
+ method: "GET",
12277
+ tags: ["admin", "site-{siteId}", "billing"],
12278
+ auth: "admin",
12279
+ responseKind: "json"
12280
+ },
12281
+ adminUpdateSiteCost: {
12282
+ path: "/admin/sites/{siteId}/cost",
12283
+ method: "POST",
12284
+ tags: ["admin", "site-{siteId}", "billing"],
12285
+ auth: "admin",
12286
+ responseKind: "json"
12287
+ },
12288
+ // Admin GSC/SEO endpoints
12289
+ adminGscVerifyStart: {
12290
+ path: "/admin/seo/gsc/properties/verify/start",
12291
+ method: "POST",
12292
+ tags: ["admin", "seo"],
12293
+ auth: "admin",
12294
+ responseKind: "json"
12295
+ },
12296
+ adminGscVerifyConfirm: {
12297
+ path: "/admin/seo/gsc/properties/verify/confirm",
12298
+ method: "POST",
12299
+ tags: ["admin", "seo"],
12300
+ auth: "admin",
12301
+ responseKind: "json"
12302
+ },
12303
+ adminGscMetaPersist: {
12304
+ path: "/admin/seo/gsc/meta/persist",
12305
+ method: "POST",
12306
+ tags: ["admin", "seo"],
12307
+ auth: "admin",
12308
+ responseKind: "json"
12309
+ },
12310
+ adminSeoIngestRun: {
12311
+ path: "/admin/seo/ingest/run",
12312
+ method: "POST",
12313
+ tags: ["admin", "seo"],
12314
+ auth: "admin",
12315
+ responseKind: "json"
11015
12316
  }
11016
12317
  };
11017
12318
  var API_ENDPOINTS = ENDPOINT_DEFINITIONS;
@@ -11203,6 +12504,10 @@ async function parseSuccessResponse(endpoint, response, config3) {
11203
12504
  const stream = body;
11204
12505
  return buildSuccessEnvelope(stream, requestId);
11205
12506
  }
12507
+ case "blob": {
12508
+ const blob = await response.blob();
12509
+ return buildSuccessEnvelope(blob, requestId);
12510
+ }
11206
12511
  case "void": {
11207
12512
  return buildSuccessEnvelope(void 0, requestId);
11208
12513
  }
@@ -11478,7 +12783,7 @@ var SimpleCache = class {
11478
12783
  };
11479
12784
 
11480
12785
  // src/version.ts
11481
- var SDK_VERSION = "0.8.1";
12786
+ var SDK_VERSION = "0.9.0";
11482
12787
 
11483
12788
  // src/client/error.ts
11484
12789
  var RiverbankApiError = class _RiverbankApiError extends Error {
@@ -12022,7 +13327,8 @@ function createRiverbankClient(config3) {
12022
13327
  const circuitBreaker = new CircuitBreaker(circuitBreakerConfig);
12023
13328
  const prebuildDir = config3.resilience?.prebuildDir;
12024
13329
  const prebuildMod = prebuildDir ? getPrebuildModule() : null;
12025
- const prebuildLoader = prebuildMod?.canUsePrebuild() && prebuildDir ? new prebuildMod.PrebuildLoader({
13330
+ const canUsePrebuild2 = prebuildMod?.canUsePrebuild() ?? false;
13331
+ const prebuildLoader = canUsePrebuild2 && prebuildMod && prebuildDir ? new prebuildMod.PrebuildLoader({
12026
13332
  prebuildDir,
12027
13333
  maxPrebuildAgeSec: config3.resilience?.maxPrebuildAgeSec
12028
13334
  }) : null;
@@ -12240,15 +13546,35 @@ function createRiverbankClient(config3) {
12240
13546
  }, { signal });
12241
13547
  },
12242
13548
  async getPublicFormById(params) {
12243
- const { formId, signal } = params;
13549
+ const { formId, siteId, signal } = params;
12244
13550
  if (!formId) {
12245
13551
  throw new Error("getPublicFormById() requires formId");
12246
13552
  }
12247
- const cacheKey = `public-form:${formId}`;
13553
+ const cacheKey = siteId ? `public-form:${siteId}:${formId}` : `public-form:${formId}`;
12248
13554
  return resilientFetch(cacheKey, async (sig) => {
12249
- return await apiClient({ endpoint: "getPublicFormById", params: { formId }, options: { signal: sig } });
13555
+ const apiParams = siteId ? { formId, siteId } : { formId };
13556
+ return await apiClient({ endpoint: "getPublicFormById", params: apiParams, options: { signal: sig } });
12250
13557
  }, { signal });
12251
13558
  },
13559
+ async getForms(params) {
13560
+ const { siteId, signal } = params;
13561
+ if (!siteId) {
13562
+ throw new Error("getForms() requires siteId");
13563
+ }
13564
+ const cacheKey = `forms:${siteId}`;
13565
+ return resilientFetch(cacheKey, async (sig) => {
13566
+ return await apiClient({ endpoint: "listPublicForms", params: { siteId }, options: { signal: sig } });
13567
+ }, {
13568
+ signal,
13569
+ prebuildFallback: prebuildLoader ? () => {
13570
+ const result = prebuildLoader.loadForms();
13571
+ if (result) {
13572
+ return { data: { forms: result.data }, prebuildAgeSec: result.prebuildAgeSec };
13573
+ }
13574
+ return null;
13575
+ } : void 0
13576
+ });
13577
+ },
12252
13578
  async getPublicBookingServices(params) {
12253
13579
  const { siteId, ids, signal } = params;
12254
13580
  if (!siteId) {
@@ -12381,7 +13707,7 @@ async function fetchAllEntries(client, siteId, contentType, onProgress) {
12381
13707
  async function prebuildSite(client, siteId, outputDir) {
12382
13708
  const site = await client.getSite({ id: siteId });
12383
13709
  const filename = "site.json";
12384
- const filePath = path9__namespace.join(outputDir, filename);
13710
+ const filePath = path2__namespace.join(outputDir, filename);
12385
13711
  const cacheFile = {
12386
13712
  data: site,
12387
13713
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -12390,9 +13716,10 @@ async function prebuildSite(client, siteId, outputDir) {
12390
13716
  return { files: [filename], size, siteData: site };
12391
13717
  }
12392
13718
  async function prebuildPages(client, siteId, routes, outputDir, onProgress) {
12393
- const pagesDir = path9__namespace.join(outputDir, "pages");
13719
+ const pagesDir = path2__namespace.join(outputDir, "pages");
12394
13720
  ensureDir3(pagesDir);
12395
13721
  const files = [];
13722
+ const pathMappings = {};
12396
13723
  let totalSize = 0;
12397
13724
  const publishedPaths = Object.entries(routes).filter(([_, route]) => route.status === "published").map(([_, route]) => route.path);
12398
13725
  const pageIndex = [];
@@ -12409,7 +13736,7 @@ async function prebuildPages(client, siteId, routes, outputDir, onProgress) {
12409
13736
  try {
12410
13737
  const pageData = await client.getPage({ siteId, path: pagePath, preview: false });
12411
13738
  const filename = pathToFilename(pagePath);
12412
- const filePath = path9__namespace.join(pagesDir, filename);
13739
+ const filePath = path2__namespace.join(pagesDir, filename);
12413
13740
  const cacheFile = {
12414
13741
  data: pageData,
12415
13742
  path: pagePath,
@@ -12417,6 +13744,7 @@ async function prebuildPages(client, siteId, routes, outputDir, onProgress) {
12417
13744
  };
12418
13745
  totalSize += writeJsonFile2(filePath, cacheFile);
12419
13746
  files.push(`pages/${filename}`);
13747
+ pathMappings[pagePath] = `pages/${filename}`;
12420
13748
  if ("page" in pageData) {
12421
13749
  pageIndex.push({
12422
13750
  path: pagePath,
@@ -12433,13 +13761,13 @@ async function prebuildPages(client, siteId, routes, outputDir, onProgress) {
12433
13761
  totalCount: pageIndex.length,
12434
13762
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12435
13763
  };
12436
- const indexPath = path9__namespace.join(pagesDir, "_index.json");
13764
+ const indexPath = path2__namespace.join(pagesDir, "_index.json");
12437
13765
  totalSize += writeJsonFile2(indexPath, indexFile);
12438
13766
  files.push("pages/_index.json");
12439
- return { files, size: totalSize };
13767
+ return { files, size: totalSize, pathMappings };
12440
13768
  }
12441
13769
  async function prebuildEntries(client, siteId, contentTypes, outputDir, onProgress) {
12442
- const entriesDir = path9__namespace.join(outputDir, "entries");
13770
+ const entriesDir = path2__namespace.join(outputDir, "entries");
12443
13771
  ensureDir3(entriesDir);
12444
13772
  const files = [];
12445
13773
  let totalSize = 0;
@@ -12460,7 +13788,7 @@ async function prebuildEntries(client, siteId, contentTypes, outputDir, onProgre
12460
13788
  });
12461
13789
  }
12462
13790
  );
12463
- const typeDir = path9__namespace.join(entriesDir, contentType);
13791
+ const typeDir = path2__namespace.join(entriesDir, contentType);
12464
13792
  ensureDir3(typeDir);
12465
13793
  const cacheFile = {
12466
13794
  entries,
@@ -12469,14 +13797,14 @@ async function prebuildEntries(client, siteId, contentTypes, outputDir, onProgre
12469
13797
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12470
13798
  };
12471
13799
  const filename = `entries/${contentType}/all.json`;
12472
- const filePath = path9__namespace.join(entriesDir, contentType, "all.json");
13800
+ const filePath = path2__namespace.join(entriesDir, contentType, "all.json");
12473
13801
  totalSize += writeJsonFile2(filePath, cacheFile);
12474
13802
  files.push(filename);
12475
13803
  }
12476
13804
  return { files, size: totalSize };
12477
13805
  }
12478
13806
  async function prebuildNavigation(siteData, outputDir, onProgress) {
12479
- const navDir = path9__namespace.join(outputDir, "navigation");
13807
+ const navDir = path2__namespace.join(outputDir, "navigation");
12480
13808
  ensureDir3(navDir);
12481
13809
  const files = [];
12482
13810
  let totalSize = 0;
@@ -12497,17 +13825,51 @@ async function prebuildNavigation(siteData, outputDir, onProgress) {
12497
13825
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
12498
13826
  };
12499
13827
  const filename = "navigation/menus.json";
12500
- const filePath = path9__namespace.join(navDir, "menus.json");
13828
+ const filePath = path2__namespace.join(navDir, "menus.json");
12501
13829
  totalSize += writeJsonFile2(filePath, cacheFile);
12502
13830
  files.push(filename);
12503
13831
  return { files, size: totalSize };
12504
13832
  }
13833
+ async function prebuildForms(client, siteId, outputDir, onProgress) {
13834
+ const formsDir = path2__namespace.join(outputDir, "forms");
13835
+ ensureDir3(formsDir);
13836
+ const files = [];
13837
+ let totalSize = 0;
13838
+ onProgress?.({
13839
+ current: 1,
13840
+ total: 1,
13841
+ item: "Forms",
13842
+ contentType: "forms"
13843
+ });
13844
+ try {
13845
+ const response = await client.getForms({ siteId });
13846
+ const forms = response.forms || [];
13847
+ const cacheFile = {
13848
+ forms: forms.map((form2) => ({
13849
+ id: form2.id,
13850
+ slug: form2.slug,
13851
+ name: form2.name,
13852
+ schema: form2.schema,
13853
+ settings: form2.settings
13854
+ })),
13855
+ totalCount: forms.length,
13856
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
13857
+ };
13858
+ const filename = "forms/forms.json";
13859
+ const filePath = path2__namespace.join(formsDir, "forms.json");
13860
+ totalSize += writeJsonFile2(filePath, cacheFile);
13861
+ files.push(filename);
13862
+ } catch (error) {
13863
+ console.warn("[Prebuild] Failed to fetch forms:", error.message);
13864
+ }
13865
+ return { files, size: totalSize };
13866
+ }
12505
13867
  async function prebuildCache(options) {
12506
13868
  const {
12507
13869
  client,
12508
13870
  siteId,
12509
13871
  outputDir = DEFAULT_PREBUILD_DIR,
12510
- include = ["site", "pages", "entries", "navigation"],
13872
+ include = ["site", "pages", "entries", "navigation", "forms"],
12511
13873
  contentTypes,
12512
13874
  onProgress
12513
13875
  } = options;
@@ -12516,6 +13878,7 @@ async function prebuildCache(options) {
12516
13878
  let totalSize = 0;
12517
13879
  const errors = [];
12518
13880
  let siteData = null;
13881
+ let pagePathMappings = {};
12519
13882
  ensureDir3(outputDir);
12520
13883
  if (include.includes("site") || include.includes("pages") || include.includes("navigation")) {
12521
13884
  try {
@@ -12541,6 +13904,7 @@ async function prebuildCache(options) {
12541
13904
  );
12542
13905
  files.push(...result.files);
12543
13906
  totalSize += result.size;
13907
+ pagePathMappings = result.pathMappings;
12544
13908
  } catch (error) {
12545
13909
  errors.push(`Pages prebuild failed: ${error.message}`);
12546
13910
  }
@@ -12574,20 +13938,28 @@ async function prebuildCache(options) {
12574
13938
  errors.push(`Navigation prebuild failed: ${error.message}`);
12575
13939
  }
12576
13940
  }
13941
+ if (include.includes("forms")) {
13942
+ try {
13943
+ const result = await prebuildForms(client, siteId, outputDir, onProgress);
13944
+ files.push(...result.files);
13945
+ totalSize += result.size;
13946
+ } catch (error) {
13947
+ errors.push(`Forms prebuild failed: ${error.message}`);
13948
+ }
13949
+ }
12577
13950
  const keyToFile = {};
12578
13951
  if (files.includes("site.json")) {
12579
13952
  keyToFile[`site:${siteId}`] = "site.json";
12580
13953
  }
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
- }
13954
+ for (const [pagePath, file] of Object.entries(pagePathMappings)) {
13955
+ keyToFile[`page:${siteId}:${pagePath}:false`] = file;
13956
+ }
13957
+ for (const file of files.filter((f) => f.startsWith("entries/") && f.endsWith("/all.json"))) {
13958
+ const contentType = file.split("/")[1];
13959
+ keyToFile[`entries-all:${siteId}:${contentType}`] = file;
13960
+ }
13961
+ if (files.includes("forms/forms.json")) {
13962
+ keyToFile[`forms:${siteId}`] = "forms/forms.json";
12591
13963
  }
12592
13964
  const manifest = {
12593
13965
  version: MANIFEST_VERSION,
@@ -12601,7 +13973,7 @@ async function prebuildCache(options) {
12601
13973
  };
12602
13974
  const checksum = calculateChecksum(manifest);
12603
13975
  manifest.checksum = checksum;
12604
- const manifestPath = path9__namespace.join(outputDir, MANIFEST_FILENAME);
13976
+ const manifestPath = path2__namespace.join(outputDir, MANIFEST_FILENAME);
12605
13977
  writeJsonFile2(manifestPath, manifest);
12606
13978
  return {
12607
13979
  success: errors.length === 0,
@@ -12728,13 +14100,19 @@ Notes:
12728
14100
  output.info("Generating prebuild cache...");
12729
14101
  try {
12730
14102
  const env = loadEnvironment(true);
14103
+ if (!env.apiKey) {
14104
+ return output.error("Missing RIVERBANK_REMOTE_API_KEY", {
14105
+ suggestion: "Set RIVERBANK_REMOTE_API_KEY in .env.local (your bld_live_sk_... key)"
14106
+ });
14107
+ }
12731
14108
  const client = createRiverbankClient({
12732
- apiKey: env.managementApiKey,
12733
- baseUrl: env.dashboardUrl
14109
+ apiKey: env.apiKey,
14110
+ baseUrl: `${env.dashboardUrl}/api`
12734
14111
  });
12735
14112
  const result = await prebuildCache({
12736
14113
  client,
12737
- siteId: config3.siteId,
14114
+ siteId: env.siteId,
14115
+ // Use remote site ID, not config (which may be local)
12738
14116
  outputDir: prebuildOutput,
12739
14117
  contentTypes: config3.contentTypes,
12740
14118
  onProgress: (p) => {