@riverbankcms/sdk 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +84 -0
  2. package/dist/cli/index.js +3463 -120
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/client/analytics.js +1 -1
  5. package/dist/client/analytics.js.map +1 -1
  6. package/dist/client/analytics.mjs +1 -1
  7. package/dist/client/analytics.mjs.map +1 -1
  8. package/dist/client/bookings.js +6 -6
  9. package/dist/client/bookings.js.map +1 -1
  10. package/dist/client/bookings.mjs +6 -6
  11. package/dist/client/bookings.mjs.map +1 -1
  12. package/dist/client/client.d.mts +2 -2
  13. package/dist/client/client.d.ts +2 -2
  14. package/dist/client/client.js +1379 -519
  15. package/dist/client/client.js.map +1 -1
  16. package/dist/client/client.mjs +1379 -519
  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 +26 -11
  21. package/dist/client/hooks.js.map +1 -1
  22. package/dist/client/hooks.mjs +26 -11
  23. package/dist/client/hooks.mjs.map +1 -1
  24. package/dist/client/rendering/client.js +20 -14
  25. package/dist/client/rendering/client.js.map +1 -1
  26. package/dist/client/rendering/client.mjs +20 -14
  27. package/dist/client/rendering/client.mjs.map +1 -1
  28. package/dist/client/usePage-BTPnCuWC.d.mts +6511 -0
  29. package/dist/client/usePage-BXjk8BhD.d.mts +6704 -0
  30. package/dist/client/usePage-BafOS9UT.d.mts +6512 -0
  31. package/dist/client/usePage-BiOReg0_.d.ts +6704 -0
  32. package/dist/client/usePage-Bnx-kA6x.d.mts +6670 -0
  33. package/dist/client/usePage-DoPI6b8V.d.ts +6511 -0
  34. package/dist/client/usePage-QNWArrVO.d.ts +6670 -0
  35. package/dist/client/usePage-fBgPB6Oq.d.ts +6512 -0
  36. package/dist/server/{Layout-CXI_VkhN.d.ts → Layout-BClXUTsd.d.mts} +4 -4
  37. package/dist/server/{Layout-p6f3TLw9.d.mts → Layout-UXGjXv8M.d.ts} +4 -4
  38. package/dist/server/{chunk-6JBKKV3G.js → chunk-2KCF2DNK.js} +30 -10
  39. package/dist/server/chunk-2KCF2DNK.js.map +1 -0
  40. package/dist/server/chunk-5STV4MWD.js +189 -0
  41. package/dist/server/chunk-5STV4MWD.js.map +1 -0
  42. package/dist/server/{chunk-VHDDXCK6.js → chunk-7UPVCT3K.js} +1206 -496
  43. package/dist/server/chunk-7UPVCT3K.js.map +1 -0
  44. package/dist/server/{chunk-7DS4Q3GA.mjs → chunk-AEFWG657.mjs} +3 -3
  45. package/dist/server/chunk-AEFWG657.mjs.map +1 -0
  46. package/dist/server/{chunk-USQF2XTU.mjs → chunk-BYBJA6SP.mjs} +26 -11
  47. package/dist/server/chunk-BYBJA6SP.mjs.map +1 -0
  48. package/dist/server/{chunk-ES6QDZUX.mjs → chunk-C6FIJC7T.mjs} +2 -2
  49. package/dist/server/{chunk-N3PX76AP.mjs → chunk-CMABGYGI.mjs} +269 -135
  50. package/dist/server/chunk-CMABGYGI.mjs.map +1 -0
  51. package/dist/server/{chunk-TO7FD6TQ.js → chunk-I2D7KOEA.js} +4 -4
  52. package/dist/server/{chunk-TO7FD6TQ.js.map → chunk-I2D7KOEA.js.map} +1 -1
  53. package/dist/server/{chunk-R5B6IOFQ.js → chunk-KA74YRK6.js} +269 -135
  54. package/dist/server/chunk-KA74YRK6.js.map +1 -0
  55. package/dist/server/chunk-KFLZGNPO.mjs +189 -0
  56. package/dist/server/chunk-KFLZGNPO.mjs.map +1 -0
  57. package/dist/server/chunk-L5EA4FXU.mjs +134 -0
  58. package/dist/server/chunk-L5EA4FXU.mjs.map +1 -0
  59. package/dist/server/{chunk-U2NI3TS3.mjs → chunk-LNOUXALA.mjs} +1135 -425
  60. package/dist/server/chunk-LNOUXALA.mjs.map +1 -0
  61. package/dist/server/{chunk-24F6FTCI.mjs → chunk-OSF34JTQ.mjs} +4 -4
  62. package/dist/server/{chunk-G35R7N7B.js → chunk-P3NNN73G.js} +3 -3
  63. package/dist/server/{chunk-G35R7N7B.js.map → chunk-P3NNN73G.js.map} +1 -1
  64. package/dist/server/{chunk-I6K5REFT.mjs → chunk-P4K63SBZ.mjs} +24 -4
  65. package/dist/server/chunk-P4K63SBZ.mjs.map +1 -0
  66. package/dist/server/{chunk-HOY77YBF.js → chunk-RVDS7VSP.js} +5 -5
  67. package/dist/server/chunk-RVDS7VSP.js.map +1 -0
  68. package/dist/server/{chunk-2SSEBAHC.js → chunk-TT5JWA4X.js} +9 -9
  69. package/dist/server/{chunk-2SSEBAHC.js.map → chunk-TT5JWA4X.js.map} +1 -1
  70. package/dist/server/chunk-VSFQRHYZ.js +134 -0
  71. package/dist/server/chunk-VSFQRHYZ.js.map +1 -0
  72. package/dist/server/{chunk-EGTDJ4PL.js → chunk-YYO3RIFO.js} +26 -11
  73. package/dist/server/chunk-YYO3RIFO.js.map +1 -0
  74. package/dist/server/{chunk-OP2GHK27.mjs → chunk-Z5ZA6Q4D.mjs} +2 -2
  75. package/dist/server/{components-Dhiemsjd.d.ts → components-BmaJxgCV.d.mts} +20 -75
  76. package/dist/server/{components-C75e4poV.d.mts → components-DppHY5oD.d.ts} +20 -75
  77. package/dist/server/components.d.mts +11 -8
  78. package/dist/server/components.d.ts +11 -8
  79. package/dist/server/components.js +5 -4
  80. package/dist/server/components.js.map +1 -1
  81. package/dist/server/components.mjs +4 -3
  82. package/dist/server/config-validation.d.mts +3 -3
  83. package/dist/server/config-validation.d.ts +3 -3
  84. package/dist/server/config-validation.js +9 -5
  85. package/dist/server/config-validation.js.map +1 -1
  86. package/dist/server/config-validation.mjs +8 -4
  87. package/dist/server/config.d.mts +243 -5
  88. package/dist/server/config.d.ts +243 -5
  89. package/dist/server/config.js +72 -5
  90. package/dist/server/config.js.map +1 -1
  91. package/dist/server/config.mjs +72 -5
  92. package/dist/server/config.mjs.map +1 -1
  93. package/dist/server/core-DsNWrl3o.d.mts +44 -0
  94. package/dist/server/core-DsNWrl3o.d.ts +44 -0
  95. package/dist/server/data.d.mts +4 -3
  96. package/dist/server/data.d.ts +4 -3
  97. package/dist/server/data.js +3 -3
  98. package/dist/server/data.mjs +2 -2
  99. package/dist/server/{index-CAwBj3-A.d.ts → index-Bucs6UqG.d.mts} +2 -1
  100. package/dist/server/{index-C6o9LPvq.d.mts → index-Cp7tJuRt.d.ts} +2 -1
  101. package/dist/server/index.d.mts +84 -6
  102. package/dist/server/index.d.ts +84 -6
  103. package/dist/server/index.js +91 -2
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/index.mjs +90 -1
  106. package/dist/server/index.mjs.map +1 -1
  107. package/dist/server/link-DjxLyC82.d.mts +23 -0
  108. package/dist/server/link-DjxLyC82.d.ts +23 -0
  109. package/dist/server/{loadContent-CdXfuCuE.d.mts → loadContent-BS-3wesN.d.mts} +4 -4
  110. package/dist/server/{loadContent-CsvQRoxb.d.ts → loadContent-Buvmudee.d.ts} +4 -4
  111. package/dist/server/{loadPage-p3AWwwrd.d.mts → loadPage-B8mQUUSo.d.mts} +5 -46
  112. package/dist/server/loadPage-DNQTTRHL.mjs +11 -0
  113. package/dist/server/{loadPage-BA0HiT-6.d.ts → loadPage-DP3nrHBi.d.ts} +5 -46
  114. package/dist/server/loadPage-IDGVDFBB.js +11 -0
  115. package/dist/server/{loadPage-DLC7DJZP.js.map → loadPage-IDGVDFBB.js.map} +1 -1
  116. package/dist/server/metadata.d.mts +6 -4
  117. package/dist/server/metadata.d.ts +6 -4
  118. package/dist/server/navigation.d.mts +199 -29
  119. package/dist/server/navigation.d.ts +199 -29
  120. package/dist/server/navigation.js +27 -43
  121. package/dist/server/navigation.js.map +1 -1
  122. package/dist/server/navigation.mjs +20 -36
  123. package/dist/server/navigation.mjs.map +1 -1
  124. package/dist/server/rendering/server.d.mts +8 -6
  125. package/dist/server/rendering/server.d.ts +8 -6
  126. package/dist/server/rendering/server.js +7 -6
  127. package/dist/server/rendering/server.js.map +1 -1
  128. package/dist/server/rendering/server.mjs +6 -5
  129. package/dist/server/rendering.d.mts +14 -10
  130. package/dist/server/rendering.d.ts +14 -10
  131. package/dist/server/rendering.js +9 -8
  132. package/dist/server/rendering.js.map +1 -1
  133. package/dist/server/rendering.mjs +8 -7
  134. package/dist/server/richTextSchema-DURiozvD.d.mts +62 -0
  135. package/dist/server/richTextSchema-DURiozvD.d.ts +62 -0
  136. package/dist/server/routing.d.mts +178 -11
  137. package/dist/server/routing.d.ts +178 -11
  138. package/dist/server/routing.js +95 -2
  139. package/dist/server/routing.js.map +1 -1
  140. package/dist/server/routing.mjs +94 -1
  141. package/dist/server/routing.mjs.map +1 -1
  142. package/dist/server/{schema-Bpy9N5ZI.d.mts → schema-Z6-afHJG.d.mts} +1 -1
  143. package/dist/server/{schema-Bpy9N5ZI.d.ts → schema-Z6-afHJG.d.ts} +1 -1
  144. package/dist/server/server.d.mts +9 -7
  145. package/dist/server/server.d.ts +9 -7
  146. package/dist/server/server.js +6 -6
  147. package/dist/server/server.mjs +5 -5
  148. package/dist/server/theme-bridge.js +8 -8
  149. package/dist/server/theme-bridge.mjs +2 -2
  150. package/dist/server/{types-Dj8B3QRb.d.ts → types-1cLz0vnq.d.mts} +55 -2
  151. package/dist/server/{types-txWsSxN7.d.mts → types-BjgZt8xJ.d.mts} +63 -2
  152. package/dist/server/{types-CdhKJrB0.d.mts → types-BvcJU7zk.d.ts} +55 -2
  153. package/dist/server/{types-BWQ-TohG.d.ts → types-CVykEqXN.d.ts} +289 -83
  154. package/dist/server/{types-CL916r6x.d.ts → types-DLBhEPSt.d.ts} +63 -2
  155. package/dist/server/{types-BLf-hE50.d.mts → types-Dsu9wsUh.d.mts} +289 -83
  156. package/dist/server/{validation-DzvDwwRo.d.mts → validation-BGuRo8P1.d.mts} +18 -5
  157. package/dist/server/{validation-CoU8uAiu.d.ts → validation-DU2YE7u5.d.ts} +18 -5
  158. package/package.json +5 -1
  159. package/dist/server/chunk-6JBKKV3G.js.map +0 -1
  160. package/dist/server/chunk-7DS4Q3GA.mjs.map +0 -1
  161. package/dist/server/chunk-EGTDJ4PL.js.map +0 -1
  162. package/dist/server/chunk-HOY77YBF.js.map +0 -1
  163. package/dist/server/chunk-I6K5REFT.mjs.map +0 -1
  164. package/dist/server/chunk-LCYGQDAB.mjs +0 -835
  165. package/dist/server/chunk-LCYGQDAB.mjs.map +0 -1
  166. package/dist/server/chunk-N3PX76AP.mjs.map +0 -1
  167. package/dist/server/chunk-R5B6IOFQ.js.map +0 -1
  168. package/dist/server/chunk-TNYU5EIO.js +0 -835
  169. package/dist/server/chunk-TNYU5EIO.js.map +0 -1
  170. package/dist/server/chunk-U2NI3TS3.mjs.map +0 -1
  171. package/dist/server/chunk-USQF2XTU.mjs.map +0 -1
  172. package/dist/server/chunk-VHDDXCK6.js.map +0 -1
  173. package/dist/server/loadPage-DLC7DJZP.js +0 -11
  174. package/dist/server/loadPage-GEGN4UAL.mjs +0 -11
  175. /package/dist/server/{chunk-ES6QDZUX.mjs.map → chunk-C6FIJC7T.mjs.map} +0 -0
  176. /package/dist/server/{chunk-24F6FTCI.mjs.map → chunk-OSF34JTQ.mjs.map} +0 -0
  177. /package/dist/server/{chunk-OP2GHK27.mjs.map → chunk-Z5ZA6Q4D.mjs.map} +0 -0
  178. /package/dist/server/{loadPage-GEGN4UAL.mjs.map → loadPage-DNQTTRHL.mjs.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -1,14 +1,107 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ var jiti = require('jiti');
5
+ var path9 = require('path');
6
+ var fs = require('fs');
4
7
  var dotenv = require('dotenv');
5
8
  var commander = require('commander');
6
9
  var zod = require('zod');
7
- var jiti = require('jiti');
8
- var path = require('path');
9
- var fs = require('fs');
10
+ var prompts = require('prompts');
11
+ var fs3 = require('fs/promises');
12
+ var readline = require('readline');
13
+ var equal = require('fast-deep-equal');
14
+ var os = require('os');
15
+ var child_process = require('child_process');
10
16
 
11
17
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
18
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
19
+
20
+ function _interopNamespace(e) {
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
30
+ });
31
+ }
32
+ });
33
+ }
34
+ n.default = e;
35
+ return Object.freeze(n);
36
+ }
37
+
38
+ var path9__namespace = /*#__PURE__*/_interopNamespace(path9);
39
+ var prompts__default = /*#__PURE__*/_interopDefault(prompts);
40
+ var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
41
+ var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
42
+ var equal__default = /*#__PURE__*/_interopDefault(equal);
43
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
44
+
45
+ var __defProp = Object.defineProperty;
46
+ var __getOwnPropNames = Object.getOwnPropertyNames;
47
+ var __esm = (fn, res) => function __init() {
48
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
49
+ };
50
+ var __export = (target, all) => {
51
+ for (var name in all)
52
+ __defProp(target, name, { get: all[name], enumerable: true });
53
+ };
54
+
55
+ // src/cli/load-config.ts
56
+ var load_config_exports = {};
57
+ __export(load_config_exports, {
58
+ loadConfigFile: () => loadConfigFile
59
+ });
60
+ async function loadConfigFile(configPath) {
61
+ const resolvedPath = resolveConfigPath(configPath);
62
+ if (!fs.existsSync(resolvedPath)) {
63
+ throw new Error(
64
+ `Config file not found: ${resolvedPath}
65
+ Create a riverbank.config.ts file or specify a path with --config`
66
+ );
67
+ }
68
+ console.log(`Loading config from ${resolvedPath}...`);
69
+ const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)), {
70
+ // Disable caching for CLI tool
71
+ fsCache: false,
72
+ moduleCache: false
73
+ });
74
+ try {
75
+ const configModule = await jiti$1.import(resolvedPath);
76
+ const config3 = configModule.default ?? configModule.config ?? configModule;
77
+ if (!config3 || typeof config3 !== "object") {
78
+ throw new Error("Config file must export a configuration object");
79
+ }
80
+ return config3;
81
+ } catch (error) {
82
+ if (error instanceof Error) {
83
+ throw new Error(`Failed to load config: ${error.message}`);
84
+ }
85
+ throw error;
86
+ }
87
+ }
88
+ function resolveConfigPath(configPath) {
89
+ if (!configPath) {
90
+ return path9.resolve(process.cwd(), DEFAULT_CONFIG_FILENAME);
91
+ }
92
+ const resolved = path9.resolve(configPath);
93
+ if (fs.existsSync(resolved) && !resolved.endsWith(".ts") && !resolved.endsWith(".js")) {
94
+ return path9.resolve(resolved, DEFAULT_CONFIG_FILENAME);
95
+ }
96
+ return resolved;
97
+ }
98
+ var DEFAULT_CONFIG_FILENAME;
99
+ var init_load_config = __esm({
100
+ "src/cli/load-config.ts"() {
101
+ DEFAULT_CONFIG_FILENAME = "riverbank.config.ts";
102
+ }
103
+ });
104
+
12
105
  // ../blocks/src/system/manifest/augmentManifest.ts
13
106
  function augmentManifest(manifest) {
14
107
  let augmentedFields = manifest.fields ?? [];
@@ -481,11 +574,11 @@ function bind(from, options) {
481
574
  }
482
575
  });
483
576
  }
484
- function when(path, options) {
577
+ function when(path11, options) {
485
578
  return (node) => ({
486
579
  ...node,
487
580
  $when: {
488
- when: { from: path },
581
+ when: { from: path11 },
489
582
  ...options?.equals !== void 0 ? { equals: options.equals } : {},
490
583
  ...options?.not ? { not: true } : {}
491
584
  }
@@ -558,25 +651,25 @@ function devValidate(node) {
558
651
  }
559
652
 
560
653
  // ../blocks/src/system/node/fragments/backgroundLayer.ts
561
- function backgroundLayer(path, options = {}) {
654
+ function backgroundLayer(path11, options = {}) {
562
655
  const {
563
656
  styleClassName = "absolute inset-0 -z-10 h-full w-full pointer-events-none",
564
657
  imageClassName
565
658
  } = options;
566
659
  const styleLayer = el("div", {
567
- className: { $bind: { from: path, transforms: [{ id: "background.resolveClass", options: { baseClass: styleClassName } }] } },
568
- style: { $bind: { from: path, transforms: [{ id: "background.resolveStyle" }] } }
660
+ className: { $bind: { from: path11, transforms: [{ id: "background.resolveClass", options: { baseClass: styleClassName } }] } },
661
+ style: { $bind: { from: path11, transforms: [{ id: "background.resolveStyle" }] } }
569
662
  });
570
- const imageLayer = createBackgroundImageNode(path, imageClassName);
663
+ const imageLayer = createBackgroundImageNode(path11, imageClassName);
571
664
  return [styleLayer, imageLayer];
572
665
  }
573
- function createBackgroundImageNode(path, baseClassName = "absolute -z-10") {
574
- const imagePath = `${path}.image`;
666
+ function createBackgroundImageNode(path11, baseClassName = "absolute -z-10") {
667
+ const imagePath = `${path11}.image`;
575
668
  return media(
576
669
  {
577
670
  className: {
578
671
  $bind: {
579
- from: path,
672
+ from: path11,
580
673
  transforms: [{
581
674
  id: "background.resolveImageClassName",
582
675
  options: { baseClass: `background-image ${baseClassName}` }
@@ -585,7 +678,7 @@ function createBackgroundImageNode(path, baseClassName = "absolute -z-10") {
585
678
  },
586
679
  style: {
587
680
  $bind: {
588
- from: path,
681
+ from: path11,
589
682
  transforms: [{ id: "background.resolveImageStyle" }]
590
683
  }
591
684
  }
@@ -604,7 +697,7 @@ function parseToken(source) {
604
697
  if (source.includes("/")) {
605
698
  const [token, opacity] = source.split("/");
606
699
  const alpha = Number(opacity) / 100;
607
- if (!Number.isNaN(alpha)) {
700
+ if (!Number.isNaN(alpha) && token) {
608
701
  return { token, alpha };
609
702
  }
610
703
  return { token: source };
@@ -644,7 +737,8 @@ function headingGroup(opts) {
644
737
  containerClass = "text-center",
645
738
  className,
646
739
  eyebrowClass = "heading-eyebrow text-sm font-semibold tracking-wide",
647
- titleClass = "heading-title text-3xl font-semibold sm:text-4xl",
740
+ // h2 now gets size/weight from theme typography CSS
741
+ titleClass = "heading-title",
648
742
  eyebrowStyle = textColorStyle("neutral-500"),
649
743
  titleStyle = textColorStyle("neutral-900")
650
744
  } = opts;
@@ -686,7 +780,7 @@ function sectionContainer(children, opts) {
686
780
  var DEFAULT_SECTION_SPACING = "medium";
687
781
 
688
782
  // ../blocks/src/system/node/fragments/styledSection.ts
689
- function styledSection(config2) {
783
+ function styledSection(config3) {
690
784
  const {
691
785
  children,
692
786
  baseClass = "px-6",
@@ -694,7 +788,7 @@ function styledSection(config2) {
694
788
  background = "background/base",
695
789
  bindFrom = "_sectionStyles",
696
790
  imageClassName = "absolute -z-10"
697
- } = config2;
791
+ } = config3;
698
792
  const backgroundNodes = backgroundLayer(`${bindFrom}.background`, {
699
793
  imageClassName
700
794
  });
@@ -835,8 +929,8 @@ var FragmentConfigError = class extends Error {
835
929
  this.name = "FragmentConfigError";
836
930
  }
837
931
  };
838
- function defineFragment(config2) {
839
- const parsed = fragmentConfigSchema.parse(config2);
932
+ function defineFragment(config3) {
933
+ const parsed = fragmentConfigSchema.parse(config3);
840
934
  validateFieldDefinitions(parsed.fields, parsed.id);
841
935
  return {
842
936
  ...parsed,
@@ -1012,27 +1106,27 @@ function scopePropValue(value, scope) {
1012
1106
  }
1013
1107
  return value;
1014
1108
  }
1015
- function scopeContentPath(path, scope) {
1109
+ function scopeContentPath(path11, scope) {
1016
1110
  if (!scope || scope.length === 0) {
1017
- return path;
1111
+ return path11;
1018
1112
  }
1019
- if (path === "content") {
1113
+ if (path11 === "content") {
1020
1114
  return `content.${scope}`;
1021
1115
  }
1022
- if (path.startsWith("content.")) {
1023
- const remainder = path.slice("content.".length);
1116
+ if (path11.startsWith("content.")) {
1117
+ const remainder = path11.slice("content.".length);
1024
1118
  return remainder.length > 0 ? `content.${scope}.${remainder}` : `content.${scope}`;
1025
1119
  }
1026
- if (path.startsWith("content[")) {
1027
- return path.replace(/^content/, `content.${scope}`);
1120
+ if (path11.startsWith("content[")) {
1121
+ return path11.replace(/^content/, `content.${scope}`);
1028
1122
  }
1029
- if (path.startsWith("$root.")) {
1030
- return path;
1123
+ if (path11.startsWith("$root.")) {
1124
+ return path11;
1031
1125
  }
1032
- if (path.includes(".")) {
1033
- return path;
1126
+ if (path11.includes(".")) {
1127
+ return path11;
1034
1128
  }
1035
- return `content.${scope}.${path}`;
1129
+ return `content.${scope}.${path11}`;
1036
1130
  }
1037
1131
 
1038
1132
  // ../blocks/src/system/fragments/builder.ts
@@ -1043,26 +1137,26 @@ var FragmentCompositionError = class extends Error {
1043
1137
  }
1044
1138
  };
1045
1139
  var DOT_SCOPE_REGEX = /^[A-Za-z0-9_.-]*$/;
1046
- function materializeFragment(config2) {
1047
- const scope = normalizeScope(config2.scope);
1048
- const fields4 = scopeFragmentFields(config2.fragment, scope);
1049
- const layout = scopeFragmentLayout(config2.fragment, scope);
1050
- const fieldPriority = config2.fieldPriority ?? 0;
1140
+ function materializeFragment(config3) {
1141
+ const scope = normalizeScope(config3.scope);
1142
+ const fields4 = scopeFragmentFields(config3.fragment, scope);
1143
+ const layout = scopeFragmentLayout(config3.fragment, scope);
1144
+ const fieldPriority = config3.fieldPriority ?? 0;
1051
1145
  let data;
1052
- if (config2.fragment.data) {
1053
- const key = config2.dataKey ?? config2.fragment.data.key;
1146
+ if (config3.fragment.data) {
1147
+ const key = config3.dataKey ?? config3.fragment.data.key;
1054
1148
  if (!key || typeof key !== "string") {
1055
1149
  throw new FragmentCompositionError(
1056
- `Fragment "${config2.fragment.id}" requires a data key to compose.`
1150
+ `Fragment "${config3.fragment.id}" requires a data key to compose.`
1057
1151
  );
1058
1152
  }
1059
1153
  data = {
1060
1154
  key,
1061
- loader: config2.fragment.data.loader ? { ...config2.fragment.data.loader } : void 0
1155
+ loader: config3.fragment.data.loader ? { ...config3.fragment.data.loader } : void 0
1062
1156
  };
1063
1157
  }
1064
1158
  return {
1065
- fragment: config2.fragment,
1159
+ fragment: config3.fragment,
1066
1160
  scope,
1067
1161
  fields: fields4,
1068
1162
  layout,
@@ -1185,6 +1279,8 @@ var bodyCopyFragment = defineFragment({
1185
1279
  text(
1186
1280
  {
1187
1281
  as: "h2",
1282
+ // h2 now gets size/weight from theme typography CSS
1283
+ // Only dynamic class here is for text alignment
1188
1284
  className: {
1189
1285
  $bind: {
1190
1286
  from: "content.alignment",
@@ -1192,11 +1288,11 @@ var bodyCopyFragment = defineFragment({
1192
1288
  {
1193
1289
  id: "ui.headingClassFromAlignment",
1194
1290
  options: {
1195
- base: "text-3xl font-semibold sm:text-4xl"
1291
+ base: ""
1196
1292
  }
1197
1293
  }
1198
1294
  ],
1199
- fallback: "text-3xl font-semibold sm:text-4xl"
1295
+ fallback: ""
1200
1296
  }
1201
1297
  },
1202
1298
  style: textColorStyle("neutral-900")
@@ -1269,7 +1365,9 @@ var heroCopyFragment = defineFragment({
1269
1365
  text(
1270
1366
  {
1271
1367
  as: "h1",
1272
- className: "hero-headline text-4xl font-semibold sm:text-5xl md:text-6xl",
1368
+ // heading-display: uses fluid typography from theme (--fs-h1-display-fluid)
1369
+ // Removes hardcoded text-4xl/5xl/6xl - size now controlled by theme
1370
+ className: "hero-headline heading-display",
1273
1371
  style: textColorStyle("neutral-900")
1274
1372
  },
1275
1373
  bind("content.headline")
@@ -1448,7 +1546,8 @@ var testimonialsHeadingFragment = defineFragment({
1448
1546
  { gap: "md", className: "mx-auto max-w-2xl text-center" },
1449
1547
  [
1450
1548
  text(
1451
- { as: "h2", className: "text-3xl font-semibold sm:text-4xl", style: textColorStyle("neutral-900") },
1549
+ // h2 now gets size/weight from theme typography CSS
1550
+ { as: "h2", style: textColorStyle("neutral-900") },
1452
1551
  when("content.heading"),
1453
1552
  bind("content.heading")
1454
1553
  ),
@@ -1820,7 +1919,7 @@ var blogFeaturedPostFragment = defineFragment({
1820
1919
  text(
1821
1920
  {
1822
1921
  as: "h2",
1823
- className: "text-2xl font-semibold md:text-3xl",
1922
+ // h2 now gets size/weight from theme typography CSS
1824
1923
  style: textColorStyle("neutral-900")
1825
1924
  },
1826
1925
  bind("title", { fallback: "Latest post" })
@@ -2217,7 +2316,8 @@ var faqHeadingFragment = defineFragment({
2217
2316
  text(
2218
2317
  {
2219
2318
  as: "h2",
2220
- className: "faq-title text-3xl font-semibold sm:text-4xl",
2319
+ // h2 now gets size/weight from theme typography CSS
2320
+ className: "faq-title",
2221
2321
  style: textColorStyle("neutral-900")
2222
2322
  },
2223
2323
  when("content.title"),
@@ -2846,13 +2946,13 @@ function sectionStylesField(options = {}) {
2846
2946
  }
2847
2947
 
2848
2948
  // ../blocks/src/system/defineBlock.ts
2849
- function createBlockManifest(config2) {
2850
- const composition2 = config2.fragments ? composeFragments(config2.fragments) : { fields: []};
2949
+ function createBlockManifest(config3) {
2950
+ const composition2 = config3.fragments ? composeFragments(config3.fragments) : { fields: []};
2851
2951
  const allFields = [
2852
2952
  ...composition2.fields,
2853
- ...config2.additionalFields ?? []
2953
+ ...config3.additionalFields ?? []
2854
2954
  ];
2855
- if (!config2.skipSectionStyles) {
2955
+ if (!config3.skipSectionStyles) {
2856
2956
  allFields.push(
2857
2957
  sectionStylesField({
2858
2958
  id: "_sectionStyles",
@@ -2861,36 +2961,36 @@ function createBlockManifest(config2) {
2861
2961
  );
2862
2962
  }
2863
2963
  const fields4 = fieldSchema.array().parse(allFields);
2864
- const layout = config2.layout;
2865
- const variants = config2.variants;
2866
- let behaviours = config2.behaviours;
2867
- if (!behaviours && config2.paletteHidden !== void 0) {
2964
+ const layout = config3.layout;
2965
+ const variants = config3.variants;
2966
+ let behaviours = config3.behaviours;
2967
+ if (!behaviours && config3.paletteHidden !== void 0) {
2868
2968
  behaviours = {
2869
2969
  supportsThemeSwitching: true,
2870
2970
  inlineEditing: true,
2871
2971
  animation: true,
2872
- paletteHidden: config2.paletteHidden
2972
+ paletteHidden: config3.paletteHidden
2873
2973
  };
2874
2974
  }
2875
2975
  const manifest = {
2876
- name: config2.id,
2976
+ name: config3.id,
2877
2977
  version: "0.1.0",
2878
- title: config2.title,
2879
- titleSource: config2.titleSource,
2880
- description: config2.description ?? "",
2881
- component: config2.component ?? deriveComponentName(config2.id),
2978
+ title: config3.title,
2979
+ titleSource: config3.titleSource,
2980
+ description: config3.description ?? "",
2981
+ component: config3.component ?? deriveComponentName(config3.id),
2882
2982
  fields: fields4,
2883
2983
  slots: [],
2884
2984
  // Always empty
2885
- styleTokens: config2.styleTokens,
2985
+ styleTokens: config3.styleTokens,
2886
2986
  behaviours,
2887
- category: config2.category,
2888
- contentTypes: config2.contentTypes,
2889
- tags: config2.tags ?? [],
2890
- icon: config2.icon ?? "Box",
2987
+ category: config3.category,
2988
+ contentTypes: config3.contentTypes,
2989
+ tags: config3.tags ?? [],
2990
+ icon: config3.icon ?? "Box",
2891
2991
  layout,
2892
2992
  variants,
2893
- defaultVariant: config2.defaultVariant
2993
+ defaultVariant: config3.defaultVariant
2894
2994
  };
2895
2995
  return augmentManifest(manifest);
2896
2996
  }
@@ -3591,7 +3691,11 @@ var simpleFooterLayout = stack(
3591
3691
  }
3592
3692
  },
3593
3693
  [
3594
- navRow({ align: "center", className: "flex flex-wrap justify-center gap-x-6 gap-y-3" }),
3694
+ navRow({
3695
+ align: "center",
3696
+ className: "flex flex-wrap justify-center gap-x-6 gap-y-3",
3697
+ linkClassName: "footer-nav-link inline-flex items-center px-4 py-2 text-sm font-medium transition-theme-standard"
3698
+ }),
3595
3699
  ...bottomTextLayout()
3596
3700
  ],
3597
3701
  when("$root.theme.footer.variant", { equals: "simple" })
@@ -3619,7 +3723,11 @@ var columnsFooterLayout = stack(
3619
3723
  { className: "flex w-full flex-wrap items-center justify-between gap-4" },
3620
3724
  [
3621
3725
  text({ as: "span", className: "text-sm font-semibold", style: textColorStyle("text") }, bind("site.title", { fallback: "Your Site" })),
3622
- navRow({ className: "flex flex-wrap justify-end gap-x-6 gap-y-3", align: "end" })
3726
+ navRow({
3727
+ className: "flex flex-wrap justify-end gap-x-6 gap-y-3",
3728
+ align: "end",
3729
+ linkClassName: "footer-nav-link inline-flex items-center px-4 py-2 text-sm font-medium transition-theme-standard"
3730
+ })
3623
3731
  ]
3624
3732
  ),
3625
3733
  ...bottomTextLayout()
@@ -4840,6 +4948,10 @@ function getBlockDefinition(name) {
4840
4948
  ensureDefaults();
4841
4949
  return blockStore.get(name);
4842
4950
  }
4951
+ function listBlockDefinitions() {
4952
+ ensureDefaults();
4953
+ return Array.from(blockStore.values());
4954
+ }
4843
4955
 
4844
4956
  // ../blocks/src/system/constants/blockKinds.ts
4845
4957
  var SYSTEM_BLOCK_KINDS = [
@@ -5021,7 +5133,7 @@ var contentConfigSchema = contentConfigBaseSchema.superRefine((data, ctx) => {
5021
5133
  });
5022
5134
  }
5023
5135
  const paths = data.pages.map((p) => p.path);
5024
- const duplicatePaths = paths.filter((path, i) => paths.indexOf(path) !== i);
5136
+ const duplicatePaths = paths.filter((path11, i) => paths.indexOf(path11) !== i);
5025
5137
  if (duplicatePaths.length > 0) {
5026
5138
  ctx.addIssue({
5027
5139
  code: zod.z.ZodIssueCode.custom,
@@ -5083,6 +5195,15 @@ var sdkThemePaletteSchema = zod.z.record(zod.z.string(), zod.z.string());
5083
5195
  var sdkThemeConfigSchema = zod.z.object({
5084
5196
  palette: sdkThemePaletteSchema
5085
5197
  });
5198
+ var sdkSiteUrlSchema = zod.z.string().url("Must be a valid URL").refine((url) => {
5199
+ try {
5200
+ const parsed = new URL(url);
5201
+ const isLocal = ["localhost", "lvh.me"].includes(parsed.hostname) || parsed.hostname.endsWith(".local") || parsed.hostname.endsWith(".test") || parsed.hostname.endsWith(".lvh.me");
5202
+ return parsed.protocol === "https:" || isLocal;
5203
+ } catch {
5204
+ return false;
5205
+ }
5206
+ }, { message: "Production URLs must use HTTPS" }).transform((url) => url.replace(/\/$/, ""));
5086
5207
  var sectionBackgroundSchema = zod.z.object({
5087
5208
  id: zod.z.string(),
5088
5209
  label: zod.z.string(),
@@ -5216,6 +5337,10 @@ function validateFieldIdConflicts(blockFieldExtensions) {
5216
5337
  }
5217
5338
  return conflicts;
5218
5339
  }
5340
+ var syncConfigSchema = zod.z.object({
5341
+ existingEntries: zod.z.enum(["skip", "update"]).optional(),
5342
+ contentTarget: zod.z.enum(["draft", "publish"]).optional()
5343
+ }).optional();
5219
5344
  var sdkCustomBlockSchema = zod.z.object({
5220
5345
  // Block ID must start with 'custom.'
5221
5346
  id: zod.z.string().min(8).regex(/^custom\.[a-z][a-z0-9-]*$/, {
@@ -5244,6 +5369,8 @@ var sdkCustomBlockSchema = zod.z.object({
5244
5369
  );
5245
5370
  var riverbankSiteConfigSchema = zod.z.object({
5246
5371
  siteId: zod.z.string().uuid(),
5372
+ previewUrl: sdkSiteUrlSchema.optional(),
5373
+ liveUrl: sdkSiteUrlSchema.optional(),
5247
5374
  theme: sdkThemeConfigSchema.optional(),
5248
5375
  styles: siteStyleConfigSchema,
5249
5376
  customBlocks: zod.z.array(sdkCustomBlockSchema).max(20, "Maximum 20 custom blocks per site").refine(
@@ -5256,50 +5383,15 @@ var riverbankSiteConfigSchema = zod.z.object({
5256
5383
  ).optional(),
5257
5384
  blockFieldOptions: blockFieldOptionsSchema,
5258
5385
  blockFieldExtensions: blockFieldExtensionsSchema,
5259
- content: contentConfigSchema.optional()
5386
+ content: contentConfigSchema.optional(),
5387
+ // CLI-related configuration (Phase 4)
5388
+ contentDir: zod.z.string().optional(),
5389
+ sync: syncConfigSchema
5260
5390
  }).strict();
5261
- var DEFAULT_CONFIG_FILENAME = "riverbank.config.ts";
5262
- async function loadConfigFile(configPath) {
5263
- const resolvedPath = resolveConfigPath(configPath);
5264
- if (!fs.existsSync(resolvedPath)) {
5265
- throw new Error(
5266
- `Config file not found: ${resolvedPath}
5267
- Create a riverbank.config.ts file or specify a path with --config`
5268
- );
5269
- }
5270
- console.log(`Loading config from ${resolvedPath}...`);
5271
- const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)), {
5272
- // Disable caching for CLI tool
5273
- fsCache: false,
5274
- moduleCache: false
5275
- });
5276
- try {
5277
- const configModule = await jiti$1.import(resolvedPath);
5278
- const config2 = configModule.default ?? configModule.config ?? configModule;
5279
- if (!config2 || typeof config2 !== "object") {
5280
- throw new Error("Config file must export a configuration object");
5281
- }
5282
- return config2;
5283
- } catch (error) {
5284
- if (error instanceof Error) {
5285
- throw new Error(`Failed to load config: ${error.message}`);
5286
- }
5287
- throw error;
5288
- }
5289
- }
5290
- function resolveConfigPath(configPath) {
5291
- if (!configPath) {
5292
- return path.resolve(process.cwd(), DEFAULT_CONFIG_FILENAME);
5293
- }
5294
- const resolved = path.resolve(configPath);
5295
- if (fs.existsSync(resolved) && !resolved.endsWith(".ts") && !resolved.endsWith(".js")) {
5296
- return path.resolve(resolved, DEFAULT_CONFIG_FILENAME);
5297
- }
5298
- return resolved;
5299
- }
5300
5391
 
5301
5392
  // src/cli/push-config.ts
5302
- async function pushToDashboard(dashboardUrl, siteId, apiKey, config2) {
5393
+ init_load_config();
5394
+ async function pushToDashboard(dashboardUrl, siteId, apiKey, config3) {
5303
5395
  const pushUrl = `${dashboardUrl}/api/sites/${siteId}/sdk-config`;
5304
5396
  console.log(`Pushing config to ${pushUrl}...`);
5305
5397
  let response;
@@ -5310,7 +5402,7 @@ async function pushToDashboard(dashboardUrl, siteId, apiKey, config2) {
5310
5402
  "Content-Type": "application/json",
5311
5403
  "X-API-Key": apiKey
5312
5404
  },
5313
- body: JSON.stringify({ config: config2 }),
5405
+ body: JSON.stringify({ config: config3 }),
5314
5406
  signal: AbortSignal.timeout(3e4)
5315
5407
  });
5316
5408
  } catch (error) {
@@ -5391,18 +5483,3269 @@ Examples:
5391
5483
  return pushConfigAction({ ...options, dashboard });
5392
5484
  });
5393
5485
 
5394
- // src/cli/index.ts
5395
- dotenv.config({ path: ".env.local" });
5396
- dotenv.config({ path: ".env" });
5397
- var program = new commander.Command();
5398
- program.name("riverbankcms").description("Riverbank CMS SDK CLI - sync config to dashboard").version("0.1.0").addHelpText("after", `
5399
- Examples:
5400
- $ npx riverbankcms push-config --api-key $RIVERBANK_API_KEY
5401
- $ npx riverbankcms push-config --api-key abc123 --dashboard https://www.riverbankcms.com
5486
+ // src/client/management/http.ts
5487
+ var ManagementApiError = class extends Error {
5488
+ constructor(message, code, details, statusCode) {
5489
+ super(message);
5490
+ this.name = "ManagementApiError";
5491
+ this.code = code;
5492
+ this.details = details;
5493
+ this.statusCode = statusCode;
5494
+ }
5495
+ };
5496
+ function createHttpClient(config3) {
5497
+ const baseUrl = `${config3.dashboardUrl}/api/sdk/${config3.siteId}`;
5498
+ const timeout = config3.timeout ?? 3e4;
5499
+ async function request(method, path11, options) {
5500
+ let url = `${baseUrl}${path11}`;
5501
+ if (options?.params && Object.keys(options.params).length > 0) {
5502
+ const searchParams = new URLSearchParams(options.params);
5503
+ url = `${url}?${searchParams.toString()}`;
5504
+ }
5505
+ const headers = {
5506
+ "Authorization": `Bearer ${config3.managementApiKey}`,
5507
+ "Content-Type": "application/json"
5508
+ };
5509
+ const fetchOptions = {
5510
+ method,
5511
+ headers,
5512
+ signal: AbortSignal.timeout(timeout)
5513
+ };
5514
+ if (options?.body && method !== "GET") {
5515
+ fetchOptions.body = JSON.stringify(options.body);
5516
+ }
5517
+ let response;
5518
+ try {
5519
+ response = await fetch(url, fetchOptions);
5520
+ } catch (error) {
5521
+ if (error instanceof Error && error.name === "TimeoutError") {
5522
+ throw new ManagementApiError(
5523
+ `Request timed out after ${timeout}ms`,
5524
+ "sdk:timeout",
5525
+ void 0,
5526
+ void 0
5527
+ );
5528
+ }
5529
+ throw new ManagementApiError(
5530
+ error instanceof Error ? error.message : "Network request failed",
5531
+ "sdk:network-error",
5532
+ void 0,
5533
+ void 0
5534
+ );
5535
+ }
5536
+ const contentType = response.headers.get("content-type");
5537
+ if (!contentType?.includes("application/json")) {
5538
+ if (!response.ok) {
5539
+ throw new ManagementApiError(
5540
+ `Request failed with status ${response.status}`,
5541
+ "sdk:http-error",
5542
+ void 0,
5543
+ response.status
5544
+ );
5545
+ }
5546
+ return void 0;
5547
+ }
5548
+ const json = await response.json();
5549
+ if (!response.ok || !json.success) {
5550
+ if ("error" in json && json.error) {
5551
+ throw new ManagementApiError(
5552
+ json.error.message,
5553
+ json.error.code,
5554
+ json.error.details,
5555
+ response.status
5556
+ );
5557
+ }
5558
+ throw new ManagementApiError(
5559
+ `Request failed with status ${response.status}`,
5560
+ "sdk:http-error",
5561
+ void 0,
5562
+ response.status
5563
+ );
5564
+ }
5565
+ return json.data;
5566
+ }
5567
+ return {
5568
+ async get(path11, params) {
5569
+ return request("GET", path11, { params });
5570
+ },
5571
+ async post(path11, body) {
5572
+ return request("POST", path11, { body });
5573
+ },
5574
+ async patch(path11, body) {
5575
+ return request("PATCH", path11, { body });
5576
+ },
5577
+ async delete(path11, body) {
5578
+ await request("DELETE", path11, { body });
5579
+ }
5580
+ };
5581
+ }
5582
+
5583
+ // src/client/management/entries.ts
5584
+ function createEntryOperations(http) {
5585
+ return {
5586
+ async list(contentType, options) {
5587
+ const params = {};
5588
+ if (options?.page) params.page = String(options.page);
5589
+ if (options?.limit) params.limit = String(options.limit);
5590
+ return http.get(
5591
+ `/entries/${encodeURIComponent(contentType)}`,
5592
+ params
5593
+ );
5594
+ },
5595
+ async get(contentType, identifier) {
5596
+ try {
5597
+ return await http.get(
5598
+ `/entries/${encodeURIComponent(contentType)}/${encodeURIComponent(identifier)}`
5599
+ );
5600
+ } catch (error) {
5601
+ if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5602
+ return null;
5603
+ }
5604
+ throw error;
5605
+ }
5606
+ },
5607
+ async upsert(input) {
5608
+ return http.post("/entries", input);
5609
+ },
5610
+ async publish(contentType, identifier) {
5611
+ await http.post(
5612
+ `/entries/${encodeURIComponent(contentType)}/${encodeURIComponent(identifier)}/publish`
5613
+ );
5614
+ },
5615
+ async unpublish(contentType, identifier) {
5616
+ await http.post(
5617
+ `/entries/${encodeURIComponent(contentType)}/${encodeURIComponent(identifier)}/unpublish`
5618
+ );
5619
+ },
5620
+ async delete(contentType, identifier) {
5621
+ await http.delete(
5622
+ `/entries/${encodeURIComponent(contentType)}/${encodeURIComponent(identifier)}`
5623
+ );
5624
+ }
5625
+ };
5626
+ }
5627
+
5628
+ // src/client/management/pages.ts
5629
+ function createPageOperations(http) {
5630
+ return {
5631
+ async list(options) {
5632
+ const params = {};
5633
+ if (options?.page) params.page = String(options.page);
5634
+ if (options?.limit) params.limit = String(options.limit);
5635
+ return http.get("/pages", params);
5636
+ },
5637
+ async get(identifier) {
5638
+ try {
5639
+ return await http.get(
5640
+ `/pages/${encodeURIComponent(identifier)}`
5641
+ );
5642
+ } catch (error) {
5643
+ if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5644
+ return null;
5645
+ }
5646
+ throw error;
5647
+ }
5648
+ },
5649
+ async upsert(input) {
5650
+ return http.post("/pages", input);
5651
+ },
5652
+ async publish(identifier) {
5653
+ await http.post(`/pages/${encodeURIComponent(identifier)}/publish`);
5654
+ },
5655
+ async unpublish(identifier) {
5656
+ await http.post(`/pages/${encodeURIComponent(identifier)}/unpublish`);
5657
+ }
5658
+ };
5659
+ }
5660
+
5661
+ // src/client/management/blocks.ts
5662
+ function createBlockOperations(http) {
5663
+ return {
5664
+ async list(pageIdentifier) {
5665
+ const result = await http.get(
5666
+ `/pages/${encodeURIComponent(pageIdentifier)}/blocks`
5667
+ );
5668
+ return result.blocks;
5669
+ },
5670
+ async get(pageIdentifier, blockIdentifier) {
5671
+ try {
5672
+ return await http.get(
5673
+ `/pages/${encodeURIComponent(pageIdentifier)}/blocks/${encodeURIComponent(blockIdentifier)}`
5674
+ );
5675
+ } catch (error) {
5676
+ if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5677
+ return null;
5678
+ }
5679
+ throw error;
5680
+ }
5681
+ },
5682
+ async upsert(pageIdentifier, input) {
5683
+ return http.post(
5684
+ `/pages/${encodeURIComponent(pageIdentifier)}/blocks`,
5685
+ input
5686
+ );
5687
+ },
5688
+ async reorder(pageIdentifier, blockIdentifiers) {
5689
+ await http.post(
5690
+ `/pages/${encodeURIComponent(pageIdentifier)}/blocks/reorder`,
5691
+ { order: blockIdentifiers }
5692
+ );
5693
+ },
5694
+ async delete(pageIdentifier, blockIdentifier) {
5695
+ await http.delete(
5696
+ `/pages/${encodeURIComponent(pageIdentifier)}/blocks/${encodeURIComponent(blockIdentifier)}`
5697
+ );
5698
+ }
5699
+ };
5700
+ }
5701
+
5702
+ // src/client/management/navigation.ts
5703
+ function createNavigationOperations(http) {
5704
+ return {
5705
+ async list() {
5706
+ const result = await http.get("/navigation");
5707
+ return result.menus;
5708
+ },
5709
+ async get(name) {
5710
+ try {
5711
+ return await http.get(
5712
+ `/navigation/${encodeURIComponent(name)}`
5713
+ );
5714
+ } catch (error) {
5715
+ if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5716
+ return null;
5717
+ }
5718
+ throw error;
5719
+ }
5720
+ },
5721
+ async upsert(input) {
5722
+ return http.post("/navigation", input);
5723
+ }
5724
+ };
5725
+ }
5726
+
5727
+ // src/client/management/settings.ts
5728
+ function createSettingsOperations(http) {
5729
+ return {
5730
+ async get() {
5731
+ const result = await http.get("/pull/settings");
5732
+ return result.settings;
5733
+ },
5734
+ async update(input) {
5735
+ const result = await http.patch("/settings", input);
5736
+ return result.settings;
5737
+ }
5738
+ };
5739
+ }
5740
+
5741
+ // src/client/management/pull.ts
5742
+ function createPullOperations(http) {
5743
+ return {
5744
+ async entries(contentType, options) {
5745
+ const params = {};
5746
+ if (options?.page) params.page = String(options.page);
5747
+ if (options?.limit) params.limit = String(options.limit);
5748
+ if (contentType) {
5749
+ return http.get(
5750
+ `/pull/entries/${encodeURIComponent(contentType)}`,
5751
+ params
5752
+ );
5753
+ }
5754
+ return http.get("/pull/entries", params);
5755
+ },
5756
+ async pages() {
5757
+ return http.get("/pull/pages");
5758
+ },
5759
+ async navigation() {
5760
+ return http.get("/pull/navigation");
5761
+ },
5762
+ async settings() {
5763
+ return http.get("/pull/settings");
5764
+ },
5765
+ async all() {
5766
+ const [entriesResult, pagesResult, navigationResult, settingsResult] = await Promise.all([
5767
+ http.get("/pull/entries/all"),
5768
+ http.get("/pull/pages"),
5769
+ http.get("/pull/navigation"),
5770
+ http.get("/pull/settings")
5771
+ ]);
5772
+ return {
5773
+ entries: entriesResult?.entries || {},
5774
+ pages: pagesResult?.pages || [],
5775
+ navigation: navigationResult?.menus || [],
5776
+ settings: settingsResult?.settings || {},
5777
+ meta: {
5778
+ pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
5779
+ entries: entriesResult?.meta?.entries,
5780
+ truncated: entriesResult?.meta?.truncated,
5781
+ truncationMessage: entriesResult?.meta?.truncationMessage
5782
+ }
5783
+ };
5784
+ }
5785
+ };
5786
+ }
5787
+
5788
+ // src/client/management/preview.ts
5789
+ function createPreviewOperations(http) {
5790
+ return {
5791
+ async block(input) {
5792
+ return http.post("/preview/block", input);
5793
+ }
5794
+ };
5795
+ }
5796
+
5797
+ // src/client/management/identifiers.ts
5798
+ function createIdentifiersOperations(http) {
5799
+ return {
5800
+ async backfill() {
5801
+ const response = await http.post("/identifiers/backfill");
5802
+ return response.data;
5803
+ }
5804
+ };
5805
+ }
5806
+
5807
+ // src/client/management/index.ts
5808
+ function createManagementClient(config3) {
5809
+ if (!config3.dashboardUrl) {
5810
+ throw new Error(
5811
+ "dashboardUrl is required when creating a management client. Example: http://localhost:4000 or https://dashboard.riverbankcms.com"
5812
+ );
5813
+ }
5814
+ if (!config3.managementApiKey) {
5815
+ throw new Error(
5816
+ "managementApiKey is required when creating a management client. A management API key starts with bld_mgmt_sk_"
5817
+ );
5818
+ }
5819
+ if (!config3.managementApiKey.startsWith("bld_mgmt_sk_")) {
5820
+ throw new Error(
5821
+ "Invalid management API key format. A management API key must start with bld_mgmt_sk_"
5822
+ );
5823
+ }
5824
+ if (!config3.siteId) {
5825
+ throw new Error(
5826
+ "siteId is required when creating a management client."
5827
+ );
5828
+ }
5829
+ const http = createHttpClient(config3);
5830
+ return {
5831
+ entries: createEntryOperations(http),
5832
+ pages: createPageOperations(http),
5833
+ blocks: createBlockOperations(http),
5834
+ navigation: createNavigationOperations(http),
5835
+ settings: createSettingsOperations(http),
5836
+ pull: createPullOperations(http),
5837
+ preview: createPreviewOperations(http),
5838
+ identifiers: createIdentifiersOperations(http)
5839
+ };
5840
+ }
5841
+
5842
+ // src/cli/env.ts
5843
+ function getEnvPrefix(target) {
5844
+ return target === "remote" ? "RIVERBANK_REMOTE" : "RIVERBANK_LOCAL";
5845
+ }
5846
+ function requireEnv(name) {
5847
+ const value = process.env[name];
5848
+ if (!value) {
5849
+ throw new Error(`Missing required environment variable: ${name}`);
5850
+ }
5851
+ return value;
5852
+ }
5853
+ function loadEnvironment(remote) {
5854
+ const target = remote ? "remote" : "local";
5855
+ const prefix = getEnvPrefix(target);
5856
+ const siteId = requireEnv(`${prefix}_SITE_ID`);
5857
+ const dashboardUrl = requireEnv(`${prefix}_DASHBOARD_URL`);
5858
+ const managementApiKey = requireEnv(`${prefix}_MGMT_API_KEY`);
5859
+ if (!managementApiKey.startsWith("bld_mgmt_sk_")) {
5860
+ throw new Error(
5861
+ `Invalid management API key format for ${prefix}_MGMT_API_KEY. Expected key starting with bld_mgmt_sk_`
5862
+ );
5863
+ }
5864
+ try {
5865
+ new URL(dashboardUrl);
5866
+ } catch {
5867
+ throw new Error(
5868
+ `Invalid dashboard URL in ${prefix}_DASHBOARD_URL: ${dashboardUrl}. Expected format: http://localhost:4000 or https://dashboard.example.com`
5869
+ );
5870
+ }
5871
+ return {
5872
+ siteId,
5873
+ dashboardUrl,
5874
+ managementApiKey
5875
+ };
5876
+ }
5877
+
5878
+ // src/cli/output.ts
5879
+ function createOutput(options) {
5880
+ const { json: jsonMode = false, quiet = false } = options;
5881
+ return {
5882
+ success(message, data) {
5883
+ if (jsonMode) {
5884
+ console.log(JSON.stringify({ success: true, message, data }, null, 2));
5885
+ } else if (!quiet) {
5886
+ console.log(`\u2713 ${message}`);
5887
+ if (data !== void 0) {
5888
+ console.log(JSON.stringify(data, null, 2));
5889
+ }
5890
+ }
5891
+ },
5892
+ error(message, details) {
5893
+ if (jsonMode) {
5894
+ console.error(JSON.stringify({ success: false, error: message, details }, null, 2));
5895
+ } else {
5896
+ console.error(`\u2717 ${message}`);
5897
+ if (details !== void 0) {
5898
+ if (typeof details === "object" && details !== null) {
5899
+ console.error(JSON.stringify(details, null, 2));
5900
+ } else {
5901
+ console.error(String(details));
5902
+ }
5903
+ }
5904
+ }
5905
+ process.exit(1);
5906
+ },
5907
+ warn(message, data) {
5908
+ if (jsonMode) {
5909
+ console.error(JSON.stringify({ warning: message, data }, null, 2));
5910
+ } else if (!quiet) {
5911
+ console.warn(`\u26A0 ${message}`);
5912
+ if (data !== void 0) {
5913
+ console.warn(JSON.stringify(data, null, 2));
5914
+ }
5915
+ }
5916
+ },
5917
+ info(message, data) {
5918
+ if (!jsonMode && !quiet) {
5919
+ console.log(`\u2139 ${message}`);
5920
+ if (data !== void 0) {
5921
+ console.log(JSON.stringify(data, null, 2));
5922
+ }
5923
+ }
5924
+ },
5925
+ table(headers, rows) {
5926
+ if (jsonMode) {
5927
+ const objects = rows.map((row) => {
5928
+ const obj = {};
5929
+ headers.forEach((header, i) => {
5930
+ obj[header] = row[i] ?? "";
5931
+ });
5932
+ return obj;
5933
+ });
5934
+ console.log(JSON.stringify(objects, null, 2));
5935
+ } else if (!quiet) {
5936
+ const widths = headers.map((h, i) => {
5937
+ const maxRowWidth = Math.max(...rows.map((r) => (r[i] ?? "").length));
5938
+ return Math.max(h.length, maxRowWidth);
5939
+ });
5940
+ const headerRow = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
5941
+ const separator = widths.map((w) => "-".repeat(w)).join(" ");
5942
+ console.log(headerRow);
5943
+ console.log(separator);
5944
+ for (const row of rows) {
5945
+ const formattedRow = row.map((cell, i) => (cell ?? "").padEnd(widths[i])).join(" ");
5946
+ console.log(formattedRow);
5947
+ }
5948
+ }
5949
+ },
5950
+ json(data) {
5951
+ console.log(JSON.stringify(data, null, 2));
5952
+ },
5953
+ progress(current, total, message) {
5954
+ if (!jsonMode && !quiet) {
5955
+ const percent = Math.round(current / total * 100);
5956
+ const bar = "\u2588".repeat(Math.floor(percent / 5)) + "\u2591".repeat(20 - Math.floor(percent / 5));
5957
+ process.stdout.write(`\r${bar} ${percent}% ${message}`);
5958
+ if (current === total) {
5959
+ console.log();
5960
+ }
5961
+ }
5962
+ }
5963
+ };
5964
+ }
5965
+
5966
+ // src/cli/errors.ts
5967
+ var ErrorCodes = {
5968
+ // Network errors
5969
+ NETWORK_ERROR: "sdk:network-error",
5970
+ // General errors
5971
+ UNKNOWN: "sdk:unknown"
5972
+ };
5973
+ var errorSuggestions = {
5974
+ "sdk:missing-env": "Check your .env.local file and ensure all RIVERBANK_* variables are set.",
5975
+ "sdk:unauthorized": "Verify your management API key is correct and not expired.",
5976
+ "sdk:forbidden": "Ensure your API key has the correct permissions for this operation.",
5977
+ "sdk:missing-content-type": "Run `riverbankcms push-config` to sync your content type definitions.",
5978
+ "sdk:unknown-block-kind": "Run `riverbankcms push-config` to sync your custom block definitions.",
5979
+ "sdk:network-error": "Check your network connection and ensure the dashboard is accessible.",
5980
+ "sdk:timeout": "The request timed out. Try again or check if the dashboard is responding.",
5981
+ "auth:forbidden": "Your API key is not authorized for this site or operation.",
5982
+ "auth:invalid": "Invalid API key. Generate a new management key from the dashboard."
5983
+ };
5984
+ function formatError(error) {
5985
+ if (error instanceof ManagementApiError) {
5986
+ return {
5987
+ code: error.code,
5988
+ message: error.message,
5989
+ details: error.details,
5990
+ suggestion: errorSuggestions[error.code]
5991
+ };
5992
+ }
5993
+ if (error instanceof Error) {
5994
+ if (error.message.includes("ECONNREFUSED")) {
5995
+ return {
5996
+ code: ErrorCodes.NETWORK_ERROR,
5997
+ message: "Could not connect to the dashboard",
5998
+ details: error.message,
5999
+ suggestion: errorSuggestions[ErrorCodes.NETWORK_ERROR]
6000
+ };
6001
+ }
6002
+ if (error.message.includes("fetch failed")) {
6003
+ return {
6004
+ code: ErrorCodes.NETWORK_ERROR,
6005
+ message: "Network request failed",
6006
+ details: error.message,
6007
+ suggestion: errorSuggestions[ErrorCodes.NETWORK_ERROR]
6008
+ };
6009
+ }
6010
+ return {
6011
+ code: ErrorCodes.UNKNOWN,
6012
+ message: error.message
6013
+ };
6014
+ }
6015
+ return {
6016
+ code: ErrorCodes.UNKNOWN,
6017
+ message: String(error)
6018
+ };
6019
+ }
6020
+
6021
+ // src/cli/helpers.ts
6022
+ function mapEntryForOutput(entry) {
6023
+ const result = {
6024
+ identifier: entry.identifier,
6025
+ data: entry.data,
6026
+ status: entry.status,
6027
+ hasUnpublishedChanges: entry.hasUnpublishedChanges
6028
+ };
6029
+ if (entry.slug !== void 0) {
6030
+ result.slug = entry.slug;
6031
+ }
6032
+ return result;
6033
+ }
6034
+ function parsePaginationOptions(options) {
6035
+ return {
6036
+ limit: parseInt(options.limit ?? "20", 10),
6037
+ page: parseInt(options.page ?? "1", 10)
6038
+ };
6039
+ }
6040
+ function formatDateShort(isoDate) {
6041
+ return isoDate.split("T")[0] ?? "";
6042
+ }
6043
+ async function parseJsonData(options) {
6044
+ if (options.file) {
6045
+ const filePath = path9__namespace.resolve(options.file);
6046
+ const content = await fs3__namespace.readFile(filePath, "utf-8");
6047
+ return JSON.parse(content);
6048
+ }
6049
+ if (options.data) {
6050
+ return JSON.parse(options.data);
6051
+ }
6052
+ throw new Error("Either --data or --file is required");
6053
+ }
6054
+ async function parseJsonArray(options) {
6055
+ const parsed = await parseJsonData(options);
6056
+ if ("items" in parsed && Array.isArray(parsed.items)) {
6057
+ return parsed.items;
6058
+ }
6059
+ if (Array.isArray(parsed)) {
6060
+ return parsed;
6061
+ }
6062
+ throw new Error('Expected an array or an object with an "items" array');
6063
+ }
6064
+ async function confirmAction(message, options, isRemote) {
6065
+ if (isRemote && !options.yes) {
6066
+ console.error("Remote operations require --yes flag for safety");
6067
+ return false;
6068
+ }
6069
+ if (options.yes) {
6070
+ return true;
6071
+ }
6072
+ const rl = readline__namespace.createInterface({
6073
+ input: process.stdin,
6074
+ output: process.stdout
6075
+ });
6076
+ return new Promise((resolve8) => {
6077
+ rl.question(`${message} (y/N): `, (answer) => {
6078
+ rl.close();
6079
+ resolve8(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
6080
+ });
6081
+ });
6082
+ }
6083
+ function createCommandContext(command) {
6084
+ const globalOpts = command.optsWithGlobals();
6085
+ const isRemote = globalOpts.remote ?? false;
6086
+ const isJsonOutput = globalOpts.json ?? false;
6087
+ const output = createOutput({
6088
+ json: globalOpts.json,
6089
+ quiet: globalOpts.quiet
6090
+ });
6091
+ const env = loadEnvironment(isRemote);
6092
+ const client = createManagementClient({
6093
+ dashboardUrl: env.dashboardUrl,
6094
+ managementApiKey: env.managementApiKey,
6095
+ siteId: env.siteId
6096
+ });
6097
+ return { output, client, isRemote, isJsonOutput };
6098
+ }
6099
+ function getOutputContext(command) {
6100
+ const globalOpts = command.optsWithGlobals();
6101
+ const isRemote = globalOpts.remote ?? false;
6102
+ const output = createOutput({
6103
+ json: globalOpts.json,
6104
+ quiet: globalOpts.quiet
6105
+ });
6106
+ return { output, isRemote, globalOpts };
6107
+ }
6108
+ function handleCommandError(error, output) {
6109
+ if (error instanceof SyntaxError) {
6110
+ output.error("Invalid JSON data", { suggestion: "Check your JSON syntax" });
6111
+ return;
6112
+ }
6113
+ const formatted = formatError(error);
6114
+ output.error(formatted.message, {
6115
+ code: formatted.code,
6116
+ details: formatted.details,
6117
+ suggestion: formatted.suggestion
6118
+ });
6119
+ }
6120
+ function withErrorHandling(action) {
6121
+ return async (...args) => {
6122
+ const command = args[args.length - 1];
6123
+ const { output } = getOutputContext(command);
6124
+ try {
6125
+ await action(...args);
6126
+ } catch (error) {
6127
+ handleCommandError(error, output);
6128
+ }
6129
+ };
6130
+ }
6131
+ function withConfirmation(getMessage, action) {
6132
+ return async (...allArgs) => {
6133
+ const command = allArgs[allArgs.length - 1];
6134
+ const options = allArgs[allArgs.length - 2];
6135
+ const args = allArgs.slice(0, -2);
6136
+ const ctx = createCommandContext(command);
6137
+ const { output, isRemote } = ctx;
6138
+ const confirmed = await confirmAction(getMessage(...args), options, isRemote);
6139
+ if (!confirmed) {
6140
+ output.info("Operation cancelled");
6141
+ process.exit(0);
6142
+ }
6143
+ try {
6144
+ await action(ctx, ...args);
6145
+ } catch (error) {
6146
+ handleCommandError(error, output);
6147
+ }
6148
+ };
6149
+ }
6150
+ function capitalize(str) {
6151
+ return str.charAt(0).toUpperCase() + str.slice(1);
6152
+ }
6153
+ function createPublishCommand(config3) {
6154
+ const cmd = new commander.Command("publish").description(`Publish a ${config3.resourceName}`);
6155
+ for (const arg of config3.args) {
6156
+ cmd.argument(`<${arg}>`, `${capitalize(arg.replace("-", " "))}`);
6157
+ }
6158
+ return cmd.action(
6159
+ withErrorHandling(async (...actionArgs) => {
6160
+ const args = actionArgs.slice(0, config3.args.length);
6161
+ const command = actionArgs[actionArgs.length - 1];
6162
+ const { output, client } = createCommandContext(command);
6163
+ const label = args.join("/");
6164
+ output.info(`Publishing ${config3.resourceName}: ${label}`);
6165
+ await config3.action(client, ...args);
6166
+ output.success(`${capitalize(config3.resourceName)} published: ${label}`);
6167
+ })
6168
+ );
6169
+ }
6170
+ function createUnpublishCommand(config3) {
6171
+ const cmd = new commander.Command("unpublish").description(`Unpublish a ${config3.resourceName}`);
6172
+ for (const arg of config3.args) {
6173
+ cmd.argument(`<${arg}>`, `${capitalize(arg.replace("-", " "))}`);
6174
+ }
6175
+ return cmd.action(
6176
+ withErrorHandling(async (...actionArgs) => {
6177
+ const args = actionArgs.slice(0, config3.args.length);
6178
+ const command = actionArgs[actionArgs.length - 1];
6179
+ const { output, client } = createCommandContext(command);
6180
+ const label = args.join("/");
6181
+ output.info(`Unpublishing ${config3.resourceName}: ${label}`);
6182
+ await config3.action(client, ...args);
6183
+ output.success(`${capitalize(config3.resourceName)} unpublished: ${label}`);
6184
+ })
6185
+ );
6186
+ }
6187
+ function createGetCommand(config3) {
6188
+ const cmd = new commander.Command("get").description(`Get a single ${config3.resourceName}`);
6189
+ for (const arg of config3.args) {
6190
+ cmd.argument(`<${arg}>`, `${capitalize(arg.replace("-", " "))}`);
6191
+ }
6192
+ return cmd.action(
6193
+ withErrorHandling(async (...actionArgs) => {
6194
+ const args = actionArgs.slice(0, config3.args.length);
6195
+ const command = actionArgs[actionArgs.length - 1];
6196
+ const { output, client, isJsonOutput } = createCommandContext(command);
6197
+ const label = args.join("/");
6198
+ const item = await config3.get(client, ...args);
6199
+ if (!item) {
6200
+ return output.error(`${capitalize(config3.resourceName)} not found: ${label}`, {
6201
+ suggestion: `Check that the ${config3.args.join(" and ")} ${config3.args.length > 1 ? "are" : "is"} correct`
6202
+ });
6203
+ }
6204
+ if (isJsonOutput) {
6205
+ output.json(item);
6206
+ } else {
6207
+ output.success(`${capitalize(config3.resourceName)}: ${label}`, config3.formatOutput(item));
6208
+ }
6209
+ })
6210
+ );
6211
+ }
6212
+ function createListCommand(config3) {
6213
+ const cmd = new commander.Command("list").description(`List ${config3.resourceNamePlural}`);
6214
+ for (const arg of config3.args) {
6215
+ cmd.argument(`<${arg}>`, `${capitalize(arg.replace("-", " "))}`);
6216
+ }
6217
+ if (config3.hasPagination) {
6218
+ cmd.option("--limit <n>", "Number of items per page", "20");
6219
+ cmd.option("--page <n>", "Page number", "1");
6220
+ }
6221
+ return cmd.action(
6222
+ withErrorHandling(async (...actionArgs) => {
6223
+ const args = actionArgs.slice(0, config3.args.length);
6224
+ const options = actionArgs[config3.args.length];
6225
+ const command = actionArgs[actionArgs.length - 1];
6226
+ const { output, client, isJsonOutput } = createCommandContext(command);
6227
+ const pagination = config3.hasPagination ? parsePaginationOptions(options) : void 0;
6228
+ const result = await config3.list(client, args, pagination);
6229
+ if (isJsonOutput) {
6230
+ output.json({
6231
+ [config3.resourceNamePlural]: result.items,
6232
+ ...result.pagination && { pagination: result.pagination }
6233
+ });
6234
+ } else if (result.items.length === 0) {
6235
+ const context = args.length > 0 ? ` for ${args.join("/")}` : "";
6236
+ output.info(`No ${config3.resourceNamePlural} found${context}`);
6237
+ } else {
6238
+ output.table(config3.tableHeaders, result.items.map(config3.formatRow));
6239
+ if (result.pagination) {
6240
+ output.info(
6241
+ `Showing ${result.items.length} of ${result.pagination.total} ${config3.resourceNamePlural} (page ${result.pagination.page})`
6242
+ );
6243
+ } else {
6244
+ output.info(`${result.items.length} ${config3.resourceNamePlural}`);
6245
+ }
6246
+ }
6247
+ })
6248
+ );
6249
+ }
6250
+
6251
+ // src/cli/content/writer.ts
6252
+ async function ensureDir(dirPath) {
6253
+ try {
6254
+ await fs3__namespace.mkdir(dirPath, { recursive: true });
6255
+ } catch (error) {
6256
+ if (error.code !== "EEXIST") {
6257
+ throw error;
6258
+ }
6259
+ }
6260
+ }
6261
+ async function writeJsonFile(filePath, data) {
6262
+ const content = JSON.stringify(data, null, 2) + "\n";
6263
+ await fs3__namespace.writeFile(filePath, content, "utf-8");
6264
+ }
6265
+ async function writeEntries(contentDir, pulledEntries) {
6266
+ const entriesDir = path9__namespace.join(contentDir, "entries");
6267
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6268
+ await ensureDir(entriesDir);
6269
+ await ensureDir(metaDir);
6270
+ const { contentType, entries, meta } = pulledEntries;
6271
+ const entriesFile = {
6272
+ contentType,
6273
+ entries: entries.map(mapEntryForOutput)
6274
+ };
6275
+ const filePath = path9__namespace.join(entriesDir, `${contentType}.json`);
6276
+ await writeJsonFile(filePath, entriesFile);
6277
+ const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
6278
+ await writeJsonFile(metaPath, meta);
6279
+ return { filePath, metaPath };
6280
+ }
6281
+ async function writePages(contentDir, pulledPages) {
6282
+ const pagesDir = path9__namespace.join(contentDir, "pages");
6283
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6284
+ await ensureDir(pagesDir);
6285
+ await ensureDir(metaDir);
6286
+ const filePaths = [];
6287
+ const pagesMeta = {};
6288
+ for (const page of pulledPages.pages) {
6289
+ const filePath = path9__namespace.join(pagesDir, `${page.identifier}.json`);
6290
+ await writeJsonFile(filePath, page);
6291
+ filePaths.push(filePath);
6292
+ pagesMeta[page.identifier] = {
6293
+ createdAt: page.createdAt,
6294
+ updatedAt: page.updatedAt
6295
+ };
6296
+ }
6297
+ const metaPath = path9__namespace.join(metaDir, "pages.json");
6298
+ await writeJsonFile(metaPath, {
6299
+ pulledAt: pulledPages.meta.pulledAt,
6300
+ pages: pagesMeta
6301
+ });
6302
+ return { filePaths, metaPath };
6303
+ }
6304
+ async function writeNavigation(contentDir, pulledNavigation) {
6305
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6306
+ await ensureDir(contentDir);
6307
+ await ensureDir(metaDir);
6308
+ const filePath = path9__namespace.join(contentDir, "navigation.json");
6309
+ await writeJsonFile(filePath, {
6310
+ menus: pulledNavigation.menus
6311
+ });
6312
+ const menusMeta = {};
6313
+ for (const menu of pulledNavigation.menus) {
6314
+ menusMeta[menu.name] = {
6315
+ createdAt: menu.createdAt,
6316
+ updatedAt: menu.updatedAt
6317
+ };
6318
+ }
6319
+ const metaPath = path9__namespace.join(metaDir, "navigation.json");
6320
+ await writeJsonFile(metaPath, {
6321
+ pulledAt: pulledNavigation.meta.pulledAt,
6322
+ menus: menusMeta
6323
+ });
6324
+ return { filePath, metaPath };
6325
+ }
6326
+ async function writeSettings(contentDir, pulledSettings) {
6327
+ await ensureDir(contentDir);
6328
+ const filePath = path9__namespace.join(contentDir, "settings.json");
6329
+ await writeJsonFile(filePath, pulledSettings.settings);
6330
+ return filePath;
6331
+ }
6332
+ async function fileExists(filePath) {
6333
+ try {
6334
+ await fs3__namespace.access(filePath);
6335
+ return true;
6336
+ } catch {
6337
+ return false;
6338
+ }
6339
+ }
6340
+ async function readJsonFile(filePath) {
6341
+ const content = await fs3__namespace.readFile(filePath, "utf-8");
6342
+ return JSON.parse(content);
6343
+ }
6344
+ async function listFiles(dirPath, extension) {
6345
+ try {
6346
+ const files = await fs3__namespace.readdir(dirPath);
6347
+ return files.filter((f) => f.endsWith(extension)).map((f) => path9__namespace.join(dirPath, f));
6348
+ } catch {
6349
+ return [];
6350
+ }
6351
+ }
6352
+ async function readEntries(contentDir, contentType) {
6353
+ const entriesDir = path9__namespace.join(contentDir, "entries");
6354
+ const result = /* @__PURE__ */ new Map();
6355
+ {
6356
+ const files = await listFiles(entriesDir, ".json");
6357
+ for (const filePath of files) {
6358
+ try {
6359
+ const file = await readJsonFile(filePath);
6360
+ result.set(file.contentType, file.entries);
6361
+ } catch (error) {
6362
+ console.warn(`Warning: Could not parse ${filePath}:`, error);
6363
+ }
6364
+ }
6365
+ }
6366
+ return result;
6367
+ }
6368
+ async function readPages(contentDir) {
6369
+ const pagesDir = path9__namespace.join(contentDir, "pages");
6370
+ const pages = [];
6371
+ const files = await listFiles(pagesDir, ".json");
6372
+ for (const filePath of files) {
6373
+ try {
6374
+ const page = await readJsonFile(filePath);
6375
+ pages.push(page);
6376
+ } catch (error) {
6377
+ console.warn(`Warning: Could not parse ${filePath}:`, error);
6378
+ }
6379
+ }
6380
+ return pages;
6381
+ }
6382
+ async function readNavigation(contentDir) {
6383
+ const filePath = path9__namespace.join(contentDir, "navigation.json");
6384
+ if (!await fileExists(filePath)) {
6385
+ return null;
6386
+ }
6387
+ return readJsonFile(filePath);
6388
+ }
6389
+ async function readSettings(contentDir) {
6390
+ const filePath = path9__namespace.join(contentDir, "settings.json");
6391
+ if (!await fileExists(filePath)) {
6392
+ return null;
6393
+ }
6394
+ return readJsonFile(filePath);
6395
+ }
6396
+ async function readAllContent(contentDir) {
6397
+ const [entries, pages, navigation, settings] = await Promise.all([
6398
+ readEntries(contentDir),
6399
+ readPages(contentDir),
6400
+ readNavigation(contentDir),
6401
+ readSettings(contentDir)
6402
+ ]);
6403
+ return { entries, pages, navigation, settings };
6404
+ }
6405
+ async function contentDirExists(contentDir) {
6406
+ return fileExists(contentDir);
6407
+ }
6408
+ async function getContentSummary(contentDir) {
6409
+ const content = await readAllContent(contentDir);
6410
+ const entryTypes = Array.from(content.entries.keys());
6411
+ let totalEntries = 0;
6412
+ for (const entries of content.entries.values()) {
6413
+ totalEntries += entries.length;
6414
+ }
6415
+ return {
6416
+ hasEntries: content.entries.size > 0,
6417
+ entryTypes,
6418
+ totalEntries,
6419
+ hasPages: content.pages.length > 0,
6420
+ pageCount: content.pages.length,
6421
+ hasNavigation: content.navigation !== null && content.navigation.menus.length > 0,
6422
+ menuCount: content.navigation?.menus.length ?? 0,
6423
+ hasSettings: content.settings !== null
6424
+ };
6425
+ }
6426
+ async function readEntriesMeta(contentDir, contentType) {
6427
+ const metaPath = path9__namespace.join(contentDir, ".meta", `${contentType}.json`);
6428
+ try {
6429
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
6430
+ return JSON.parse(content);
6431
+ } catch (error) {
6432
+ if (error.code === "ENOENT") {
6433
+ return null;
6434
+ }
6435
+ throw error;
6436
+ }
6437
+ }
6438
+ async function readAllMeta(contentDir) {
6439
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6440
+ const metaMap = /* @__PURE__ */ new Map();
6441
+ try {
6442
+ const files = await fs3__namespace.readdir(metaDir);
6443
+ for (const file of files) {
6444
+ if (file.endsWith(".json") && file !== "pages.json" && file !== "navigation.json") {
6445
+ const contentType = file.replace(".json", "");
6446
+ const meta = await readEntriesMeta(contentDir, contentType);
6447
+ if (meta) metaMap.set(contentType, meta);
6448
+ }
6449
+ }
6450
+ } catch (error) {
6451
+ if (error.code !== "ENOENT") throw error;
6452
+ }
6453
+ return metaMap;
6454
+ }
6455
+ async function readPagesMeta(contentDir) {
6456
+ const metaPath = path9__namespace.join(contentDir, ".meta", "pages.json");
6457
+ try {
6458
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
6459
+ return JSON.parse(content);
6460
+ } catch (error) {
6461
+ if (error.code === "ENOENT") {
6462
+ return null;
6463
+ }
6464
+ throw error;
6465
+ }
6466
+ }
6467
+ async function readNavigationMeta(contentDir) {
6468
+ const metaPath = path9__namespace.join(contentDir, ".meta", "navigation.json");
6469
+ try {
6470
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
6471
+ return JSON.parse(content);
6472
+ } catch (error) {
6473
+ if (error.code === "ENOENT") {
6474
+ return null;
6475
+ }
6476
+ throw error;
6477
+ }
6478
+ }
6479
+
6480
+ // src/cli/commands/pull.ts
6481
+ var DEFAULT_PAGE_LIMIT = 500;
6482
+ async function pullEntriesWithPagination(client, contentType, output) {
6483
+ const allEntries = [];
6484
+ const aggregatedMeta = {};
6485
+ let page = 1;
6486
+ let hasMore = true;
6487
+ let pulledAt = (/* @__PURE__ */ new Date()).toISOString();
6488
+ while (hasMore) {
6489
+ const result = await client.pull.entries(contentType, {
6490
+ page,
6491
+ limit: DEFAULT_PAGE_LIMIT
6492
+ });
6493
+ allEntries.push(...result.entries);
6494
+ if (result.meta.entries) {
6495
+ Object.assign(aggregatedMeta, result.meta.entries);
6496
+ }
6497
+ pulledAt = result.meta.pulledAt;
6498
+ hasMore = result.pagination.hasMore;
6499
+ if (hasMore) {
6500
+ output.info(`Fetched ${allEntries.length} entries (page ${page})...`);
6501
+ }
6502
+ page++;
6503
+ }
6504
+ return {
6505
+ contentType,
6506
+ entries: allEntries,
6507
+ meta: { pulledAt, entries: aggregatedMeta },
6508
+ pagination: {
6509
+ page: 1,
6510
+ limit: allEntries.length,
6511
+ total: allEntries.length,
6512
+ hasMore: false
6513
+ }
6514
+ };
6515
+ }
6516
+ async function writeAllEntries(contentDir, entriesByType, pulledAt, entriesMeta) {
6517
+ let totalCount = 0;
6518
+ const files = [];
6519
+ for (const [contentType, entries] of Object.entries(entriesByType)) {
6520
+ const ctMeta = {};
6521
+ for (const entry of entries) {
6522
+ const key = `${contentType}:${entry.identifier}`;
6523
+ if (entriesMeta?.[key]) {
6524
+ ctMeta[entry.identifier] = entriesMeta[key];
6525
+ }
6526
+ }
6527
+ const { filePath } = await writeEntries(contentDir, {
6528
+ contentType,
6529
+ entries,
6530
+ meta: { pulledAt, entries: ctMeta },
6531
+ pagination: { limit: entries.length, total: entries.length}
6532
+ });
6533
+ totalCount += entries.length;
6534
+ files.push(filePath);
6535
+ }
6536
+ return { totalCount, files };
6537
+ }
6538
+ async function fetchAllContentPaginated(client, contentTypes, output) {
6539
+ const allEntries = {};
6540
+ const allMeta = {};
6541
+ for (const contentType of contentTypes) {
6542
+ output.info(`Fetching ${contentType} entries...`);
6543
+ const result = await pullEntriesWithPagination(client, contentType, output);
6544
+ allEntries[contentType] = result.entries.map(mapEntryForOutput);
6545
+ for (const [id, meta] of Object.entries(result.meta.entries || {})) {
6546
+ allMeta[`${contentType}:${id}`] = meta;
6547
+ }
6548
+ output.info(` ${result.entries.length} entries`);
6549
+ }
6550
+ output.info("Fetching pages...");
6551
+ const pagesResult = await client.pull.pages();
6552
+ output.info("Fetching navigation...");
6553
+ const navigationResult = await client.pull.navigation();
6554
+ output.info("Fetching settings...");
6555
+ const settingsResult = await client.pull.settings();
6556
+ return {
6557
+ entries: allEntries,
6558
+ pages: pagesResult.pages,
6559
+ navigation: navigationResult.menus,
6560
+ settings: settingsResult.settings,
6561
+ meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: allMeta }
6562
+ };
6563
+ }
6564
+ var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").option("--force", "Overwrite existing files without prompting").option("--yes", "Skip confirmation prompt (same as --force)").addHelpText("after", `
6565
+ Examples:
6566
+ $ riverbankcms pull # Pull all content
6567
+ $ riverbankcms pull --remote # Pull from production
6568
+ $ riverbankcms pull entries # Pull all entries
6569
+ $ riverbankcms pull entries blog-post # Pull specific content type
6570
+ $ riverbankcms pull pages # Pull pages with blocks
6571
+ $ riverbankcms pull navigation # Pull navigation menus
6572
+ $ riverbankcms pull settings # Pull site settings
6573
+ $ riverbankcms pull --output ./src/content # Custom output directory
6574
+ `).action(
6575
+ withErrorHandling(
6576
+ async (scope, type, options, command) => {
6577
+ const { output, client } = createCommandContext(command);
6578
+ const contentDir = path9__namespace.resolve(options.output ?? "./content");
6579
+ if (await contentDirExists(contentDir) && !options.force && !options.yes) {
6580
+ if (!process.stdin.isTTY) {
6581
+ output.error("Content directory already exists and --yes not specified", {
6582
+ suggestion: "Use --yes or --force to overwrite in non-interactive mode"
6583
+ });
6584
+ return;
6585
+ }
6586
+ const response = await prompts__default.default({
6587
+ type: "confirm",
6588
+ name: "overwrite",
6589
+ message: `Content directory '${path9__namespace.basename(contentDir)}' already exists. Overwrite?`,
6590
+ initial: false
6591
+ });
6592
+ if (!response.overwrite) {
6593
+ output.info("Aborted.");
6594
+ return;
6595
+ }
6596
+ }
6597
+ const pullScope = scope?.toLowerCase() ?? "all";
6598
+ switch (pullScope) {
6599
+ case "entries": {
6600
+ if (type) {
6601
+ output.info(`Pulling entries for content type: ${type}`);
6602
+ const result = await pullEntriesWithPagination(client, type, output);
6603
+ const { filePath, metaPath } = await writeEntries(contentDir, result);
6604
+ output.success(`Pulled ${result.entries.length} entries`, {
6605
+ contentType: type,
6606
+ file: filePath,
6607
+ meta: metaPath
6608
+ });
6609
+ } else {
6610
+ output.info("Pulling all entries");
6611
+ let result = await client.pull.all();
6612
+ if (result.meta.truncated) {
6613
+ output.warn("Content was truncated due to size limits.");
6614
+ output.info("Fetching complete data via pagination...");
6615
+ const contentTypes = Object.keys(result.entries);
6616
+ result = await fetchAllContentPaginated(client, contentTypes, output);
6617
+ }
6618
+ const { totalCount, files } = await writeAllEntries(
6619
+ contentDir,
6620
+ result.entries,
6621
+ result.meta.pulledAt,
6622
+ result.meta.entries
6623
+ );
6624
+ output.success(`Pulled ${totalCount} entries across ${Object.keys(result.entries).length} content types`, {
6625
+ files
6626
+ });
6627
+ }
6628
+ break;
6629
+ }
6630
+ case "pages": {
6631
+ output.info("Pulling pages");
6632
+ const result = await client.pull.pages();
6633
+ const { filePaths, metaPath } = await writePages(contentDir, result);
6634
+ output.success(`Pulled ${result.pages.length} pages`, { files: filePaths, meta: metaPath });
6635
+ break;
6636
+ }
6637
+ case "navigation": {
6638
+ output.info("Pulling navigation menus");
6639
+ const result = await client.pull.navigation();
6640
+ const { filePath, metaPath } = await writeNavigation(contentDir, result);
6641
+ output.success(`Pulled ${result.menus.length} navigation menus`, { file: filePath, meta: metaPath });
6642
+ break;
6643
+ }
6644
+ case "settings": {
6645
+ output.info("Pulling site settings");
6646
+ const result = await client.pull.settings();
6647
+ const filePath = await writeSettings(contentDir, result);
6648
+ output.success("Pulled site settings", { file: filePath });
6649
+ break;
6650
+ }
6651
+ case "all":
6652
+ default: {
6653
+ output.info("Pulling all content");
6654
+ let result = await client.pull.all();
6655
+ if (result.meta.truncated) {
6656
+ output.warn("Content was truncated due to size limits.");
6657
+ output.info("Fetching complete data via pagination...");
6658
+ const contentTypes = Object.keys(result.entries);
6659
+ result = await fetchAllContentPaginated(client, contentTypes, output);
6660
+ }
6661
+ const { totalCount: totalEntries } = await writeAllEntries(
6662
+ contentDir,
6663
+ result.entries,
6664
+ result.meta.pulledAt,
6665
+ result.meta.entries
6666
+ );
6667
+ await writePages(contentDir, { pages: result.pages, meta: result.meta });
6668
+ await writeNavigation(contentDir, { menus: result.navigation, meta: result.meta });
6669
+ await writeSettings(contentDir, { settings: result.settings, meta: result.meta });
6670
+ output.success("Pull complete", {
6671
+ entries: { count: totalEntries, types: Object.keys(result.entries).length },
6672
+ pages: { count: result.pages.length },
6673
+ navigation: { count: result.navigation.length },
6674
+ directory: contentDir
6675
+ });
6676
+ break;
6677
+ }
6678
+ }
6679
+ }
6680
+ )
6681
+ );
6682
+
6683
+ // src/cli/config-loader.ts
6684
+ init_load_config();
6685
+ var DEFAULT_SYNC_CONFIG = {
6686
+ existingEntries: "skip",
6687
+ contentTarget: "draft"
6688
+ };
6689
+ async function loadCliConfig(configPath) {
6690
+ try {
6691
+ const rawConfig = await loadConfigFile(configPath);
6692
+ const configObj = rawConfig;
6693
+ const contentDir = typeof configObj.contentDir === "string" ? configObj.contentDir : "./content";
6694
+ const syncConfig = configObj.sync;
6695
+ return {
6696
+ contentDir,
6697
+ sync: {
6698
+ existingEntries: syncConfig?.existingEntries ?? DEFAULT_SYNC_CONFIG.existingEntries,
6699
+ contentTarget: syncConfig?.contentTarget ?? DEFAULT_SYNC_CONFIG.contentTarget
6700
+ }
6701
+ };
6702
+ } catch (error) {
6703
+ if (error instanceof Error && error.message.includes("Config file not found")) {
6704
+ return {
6705
+ contentDir: "./content",
6706
+ sync: { ...DEFAULT_SYNC_CONFIG }
6707
+ };
6708
+ }
6709
+ throw error;
6710
+ }
6711
+ }
6712
+
6713
+ // src/cli/sync/mapper.ts
6714
+ function stripNavigationItemIds(items) {
6715
+ return items.map((item) => {
6716
+ const result = {
6717
+ label: item.label,
6718
+ url: item.url
6719
+ };
6720
+ if (item.children && item.children.length > 0) {
6721
+ result.children = stripNavigationItemIds(item.children);
6722
+ }
6723
+ return result;
6724
+ });
6725
+ }
6726
+
6727
+ // src/cli/sync/diff.ts
6728
+ function findChangedFields(local, remote, prefix = "") {
6729
+ const changes = [];
6730
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(local), ...Object.keys(remote)]);
6731
+ for (const key of allKeys) {
6732
+ const path11 = prefix ? `${prefix}.${key}` : key;
6733
+ const localVal = local[key];
6734
+ const remoteVal = remote[key];
6735
+ if (!equal__default.default(localVal, remoteVal)) {
6736
+ changes.push(path11);
6737
+ }
6738
+ }
6739
+ return changes;
6740
+ }
6741
+ function buildJsonDiff(diff, local, remote, mode) {
6742
+ const summary = { creates: 0, updates: 0, deletes: 0 };
6743
+ const changes = [];
6744
+ const localPages = new Map(local.pages.map((page) => [page.identifier, page]));
6745
+ const remotePages = new Map(remote.pages.map((page) => [page.identifier, page]));
6746
+ const localEntries = local.entries;
6747
+ const remoteEntries = remote.entries;
6748
+ const localMenus = new Map((local.navigation?.menus ?? []).map((menu) => [menu.name, menu]));
6749
+ const remoteMenus = new Map(remote.navigation.map((menu) => [menu.name, menu]));
6750
+ const addChange = (change) => {
6751
+ if (change.operation === "create") summary.creates += 1;
6752
+ if (change.operation === "update") summary.updates += 1;
6753
+ if (change.operation === "delete") summary.deletes += 1;
6754
+ changes.push(change);
6755
+ };
6756
+ for (const [contentType, entryDiffs] of diff.entries) {
6757
+ for (const entryDiff of entryDiffs) {
6758
+ if (entryDiff.type === "unchanged") continue;
6759
+ if (entryDiff.type !== "create" && entryDiff.type !== "update") continue;
6760
+ const identifier = `${contentType}/${entryDiff.identifier}`;
6761
+ const localEntry = localEntries.get(contentType)?.find((entry) => entry.identifier === entryDiff.identifier);
6762
+ const remoteEntry = remoteEntries[contentType]?.find((entry) => entry.identifier === entryDiff.identifier);
6763
+ const change = {
6764
+ type: "entry",
6765
+ identifier,
6766
+ operation: entryDiff.type
6767
+ };
6768
+ if (mode === "full") {
6769
+ change.before = remoteEntry ?? null;
6770
+ change.after = localEntry ?? null;
6771
+ if (entryDiff.changes) change.diff = { changes: entryDiff.changes };
6772
+ }
6773
+ addChange(change);
6774
+ }
6775
+ }
6776
+ for (const pageDiff of diff.pages) {
6777
+ if (pageDiff.type === "unchanged") continue;
6778
+ if (pageDiff.type !== "create" && pageDiff.type !== "update") continue;
6779
+ const identifier = pageDiff.identifier;
6780
+ const localPage = localPages.get(identifier);
6781
+ const remotePage = remotePages.get(identifier);
6782
+ const change = {
6783
+ type: "page",
6784
+ identifier,
6785
+ operation: pageDiff.type
6786
+ };
6787
+ if (mode === "full") {
6788
+ change.before = remotePage ?? null;
6789
+ change.after = localPage ?? null;
6790
+ if (pageDiff.changes) change.diff = { changes: pageDiff.changes };
6791
+ }
6792
+ addChange(change);
6793
+ if (pageDiff.blocks) {
6794
+ for (const blockDiff of pageDiff.blocks) {
6795
+ if (blockDiff.type === "unchanged") continue;
6796
+ const pageId = blockDiff.pageIdentifier;
6797
+ const localBlock = localPages.get(pageId)?.blocks?.find((block) => block.identifier === blockDiff.identifier);
6798
+ const remoteBlock = remotePages.get(pageId)?.blocks?.find((block) => block.identifier === blockDiff.identifier);
6799
+ if (blockDiff.type === "reorder") {
6800
+ const localOrder = localPages.get(pageId)?.blocks?.map((block) => block.identifier) ?? [];
6801
+ addChange({
6802
+ type: "block",
6803
+ identifier: `${pageId}::reorder`,
6804
+ operation: "update",
6805
+ ...mode === "full" ? { diff: { reorder: true, order: localOrder } } : {}
6806
+ });
6807
+ continue;
6808
+ }
6809
+ const operation = blockDiff.type === "delete" ? "delete" : blockDiff.type;
6810
+ const identifier2 = `${pageId}/${blockDiff.identifier}`;
6811
+ const change2 = {
6812
+ type: "block",
6813
+ identifier: identifier2,
6814
+ operation
6815
+ };
6816
+ if (mode === "full") {
6817
+ change2.before = remoteBlock ?? null;
6818
+ change2.after = localBlock ?? null;
6819
+ if (blockDiff.changes) change2.diff = { changes: blockDiff.changes };
6820
+ }
6821
+ addChange(change2);
6822
+ }
6823
+ }
6824
+ }
6825
+ for (const navDiff of diff.navigation) {
6826
+ if (navDiff.type === "unchanged") continue;
6827
+ if (navDiff.type !== "create" && navDiff.type !== "update") continue;
6828
+ const identifier = navDiff.name;
6829
+ const localMenu = localMenus.get(navDiff.name);
6830
+ const remoteMenu = remoteMenus.get(navDiff.name);
6831
+ const change = {
6832
+ type: "navigation",
6833
+ identifier,
6834
+ operation: navDiff.type
6835
+ };
6836
+ if (mode === "full") {
6837
+ change.before = remoteMenu ? { ...remoteMenu, items: stripNavigationItemIds(remoteMenu.items) } : null;
6838
+ change.after = localMenu ?? null;
6839
+ if (navDiff.changes) change.diff = { changes: navDiff.changes };
6840
+ }
6841
+ addChange(change);
6842
+ }
6843
+ if (diff.settings) {
6844
+ const change = {
6845
+ type: "settings",
6846
+ identifier: "settings",
6847
+ operation: "update"
6848
+ };
6849
+ if (mode === "full") {
6850
+ change.before = remote.settings ?? null;
6851
+ change.after = local.settings ?? null;
6852
+ if (diff.settings.changes) change.diff = { changes: diff.settings.changes };
6853
+ }
6854
+ addChange(change);
6855
+ }
6856
+ return { summary, changes };
6857
+ }
6858
+ function calculateEntryDiffs(localEntries, remoteEntries, options) {
6859
+ const diffs = /* @__PURE__ */ new Map();
6860
+ const summary = { create: 0, update: 0, unchanged: 0 };
6861
+ const remoteLookup = /* @__PURE__ */ new Map();
6862
+ for (const [contentType, entries] of Object.entries(remoteEntries)) {
6863
+ const typeMap = /* @__PURE__ */ new Map();
6864
+ for (const entry of entries) {
6865
+ typeMap.set(entry.identifier, entry.data);
6866
+ }
6867
+ remoteLookup.set(contentType, typeMap);
6868
+ }
6869
+ for (const [contentType, localList] of localEntries) {
6870
+ const typeDiffs = [];
6871
+ const remoteTypeMap = remoteLookup.get(contentType) ?? /* @__PURE__ */ new Map();
6872
+ for (const localEntry of localList) {
6873
+ const remoteData = remoteTypeMap.get(localEntry.identifier);
6874
+ if (!remoteData) {
6875
+ typeDiffs.push({
6876
+ type: "create",
6877
+ identifier: localEntry.identifier,
6878
+ contentType
6879
+ });
6880
+ summary.create++;
6881
+ } else if (options.existingEntries === "skip") {
6882
+ typeDiffs.push({
6883
+ type: "unchanged",
6884
+ identifier: localEntry.identifier,
6885
+ contentType
6886
+ });
6887
+ summary.unchanged++;
6888
+ } else if (!equal__default.default(localEntry.data, remoteData)) {
6889
+ const changes = findChangedFields(
6890
+ localEntry.data,
6891
+ remoteData
6892
+ );
6893
+ typeDiffs.push({
6894
+ type: "update",
6895
+ identifier: localEntry.identifier,
6896
+ contentType,
6897
+ changes
6898
+ });
6899
+ summary.update++;
6900
+ } else {
6901
+ typeDiffs.push({
6902
+ type: "unchanged",
6903
+ identifier: localEntry.identifier,
6904
+ contentType
6905
+ });
6906
+ summary.unchanged++;
6907
+ }
6908
+ }
6909
+ if (typeDiffs.length > 0) {
6910
+ diffs.set(contentType, typeDiffs);
6911
+ }
6912
+ }
6913
+ return { diffs, summary };
6914
+ }
6915
+ function calculatePageDiffs(localPages, remotePages, options) {
6916
+ const diffs = [];
6917
+ const summary = { create: 0, update: 0, unchanged: 0 };
6918
+ const blockSummary = { create: 0, update: 0, delete: 0, unchanged: 0 };
6919
+ const remoteLookup = /* @__PURE__ */ new Map();
6920
+ for (const page of remotePages) {
6921
+ remoteLookup.set(page.identifier, page);
6922
+ }
6923
+ for (const localPage of localPages) {
6924
+ const remotePage = remoteLookup.get(localPage.identifier);
6925
+ if (!remotePage) {
6926
+ const blockDiffs = (localPage.blocks ?? []).map((block) => {
6927
+ blockSummary.create++;
6928
+ return {
6929
+ type: "create",
6930
+ identifier: block.identifier,
6931
+ pageIdentifier: localPage.identifier
6932
+ };
6933
+ });
6934
+ diffs.push({
6935
+ type: "create",
6936
+ identifier: localPage.identifier,
6937
+ blocks: blockDiffs
6938
+ });
6939
+ summary.create++;
6940
+ } else if (options.existingEntries === "skip") {
6941
+ diffs.push({
6942
+ type: "unchanged",
6943
+ identifier: localPage.identifier
6944
+ });
6945
+ summary.unchanged++;
6946
+ } else {
6947
+ const pageChanged = localPage.title !== remotePage.title || localPage.path !== remotePage.path;
6948
+ const blockDiffs = calculateBlockDiffs(
6949
+ localPage.blocks ?? [],
6950
+ remotePage.blocks ?? [],
6951
+ localPage.identifier,
6952
+ blockSummary
6953
+ );
6954
+ if (pageChanged || blockDiffs.some((b) => b.type !== "unchanged")) {
6955
+ const changes = [];
6956
+ if (localPage.title !== remotePage.title) changes.push("title");
6957
+ if (localPage.path !== remotePage.path) changes.push("path");
6958
+ diffs.push({
6959
+ type: "update",
6960
+ identifier: localPage.identifier,
6961
+ changes: changes.length > 0 ? changes : void 0,
6962
+ blocks: blockDiffs
6963
+ });
6964
+ summary.update++;
6965
+ } else {
6966
+ diffs.push({
6967
+ type: "unchanged",
6968
+ identifier: localPage.identifier
6969
+ });
6970
+ summary.unchanged++;
6971
+ }
6972
+ }
6973
+ }
6974
+ return { diffs, summary, blockSummary };
6975
+ }
6976
+ function calculateBlockDiffs(localBlocks, remoteBlocks, pageIdentifier, summary) {
6977
+ const diffs = [];
6978
+ const remoteLookup = /* @__PURE__ */ new Map();
6979
+ for (const block of remoteBlocks) {
6980
+ remoteLookup.set(block.identifier, block);
6981
+ }
6982
+ const seenRemoteIds = /* @__PURE__ */ new Set();
6983
+ for (const localBlock of localBlocks) {
6984
+ const remoteBlock = remoteLookup.get(localBlock.identifier);
6985
+ seenRemoteIds.add(localBlock.identifier);
6986
+ if (!remoteBlock) {
6987
+ diffs.push({
6988
+ type: "create",
6989
+ identifier: localBlock.identifier,
6990
+ pageIdentifier
6991
+ });
6992
+ summary.create++;
6993
+ } else if (!equal__default.default(localBlock.data, remoteBlock.data) || localBlock.kind !== remoteBlock.kind) {
6994
+ const changes = findChangedFields(
6995
+ localBlock.data,
6996
+ remoteBlock.data
6997
+ );
6998
+ if (localBlock.kind !== remoteBlock.kind) {
6999
+ changes.push("kind");
7000
+ }
7001
+ diffs.push({
7002
+ type: "update",
7003
+ identifier: localBlock.identifier,
7004
+ pageIdentifier,
7005
+ changes
7006
+ });
7007
+ summary.update++;
7008
+ } else {
7009
+ diffs.push({
7010
+ type: "unchanged",
7011
+ identifier: localBlock.identifier,
7012
+ pageIdentifier
7013
+ });
7014
+ summary.unchanged++;
7015
+ }
7016
+ }
7017
+ for (const remoteBlock of remoteBlocks) {
7018
+ if (!seenRemoteIds.has(remoteBlock.identifier)) {
7019
+ diffs.push({
7020
+ type: "delete",
7021
+ identifier: remoteBlock.identifier,
7022
+ pageIdentifier
7023
+ });
7024
+ summary.delete++;
7025
+ }
7026
+ }
7027
+ const localOrder = localBlocks.filter((b) => remoteLookup.has(b.identifier)).map((b) => b.identifier);
7028
+ const remoteOrder = remoteBlocks.filter((b) => seenRemoteIds.has(b.identifier)).sort((a, b) => a.position - b.position).map((b) => b.identifier);
7029
+ if (localOrder.length > 1 && !equal__default.default(localOrder, remoteOrder)) {
7030
+ diffs.push({
7031
+ type: "reorder",
7032
+ identifier: pageIdentifier,
7033
+ pageIdentifier
7034
+ });
7035
+ }
7036
+ return diffs;
7037
+ }
7038
+ function calculateNavigationDiffs(localNavigation, remoteNavigation) {
7039
+ const diffs = [];
7040
+ const summary = { create: 0, update: 0, unchanged: 0 };
7041
+ if (!localNavigation || !localNavigation.menus) {
7042
+ return { diffs, summary };
7043
+ }
7044
+ const remoteLookup = /* @__PURE__ */ new Map();
7045
+ for (const menu of remoteNavigation) {
7046
+ remoteLookup.set(menu.name, menu);
7047
+ }
7048
+ for (const localMenu of localNavigation.menus) {
7049
+ const remoteMenu = remoteLookup.get(localMenu.name);
7050
+ if (!remoteMenu) {
7051
+ diffs.push({
7052
+ type: "create",
7053
+ name: localMenu.name
7054
+ });
7055
+ summary.create++;
7056
+ } else if (!equal__default.default(localMenu.items, stripNavigationItemIds(remoteMenu.items))) {
7057
+ diffs.push({
7058
+ type: "update",
7059
+ name: localMenu.name,
7060
+ changes: ["items"]
7061
+ });
7062
+ summary.update++;
7063
+ } else {
7064
+ diffs.push({
7065
+ type: "unchanged",
7066
+ name: localMenu.name
7067
+ });
7068
+ summary.unchanged++;
7069
+ }
7070
+ }
7071
+ return { diffs, summary };
7072
+ }
7073
+ function calculateSettingsDiff(localSettings, remoteSettings) {
7074
+ const summary = { update: 0, unchanged: 0 };
7075
+ if (!localSettings) {
7076
+ return { diff: null, summary };
7077
+ }
7078
+ const changes = [];
7079
+ if (localSettings.homepageId !== remoteSettings.homepageId) {
7080
+ changes.push("homepageId");
7081
+ }
7082
+ if (!equal__default.default(localSettings.seoDefaults, remoteSettings.seoDefaults)) {
7083
+ changes.push("seoDefaults");
7084
+ }
7085
+ if (changes.length > 0) {
7086
+ summary.update = 1;
7087
+ return {
7088
+ diff: { type: "update", changes },
7089
+ summary
7090
+ };
7091
+ }
7092
+ summary.unchanged = 1;
7093
+ return { diff: null, summary };
7094
+ }
7095
+ function calculateDiff(local, remote, options) {
7096
+ const { diffs: entryDiffs, summary: entrySummary } = calculateEntryDiffs(
7097
+ local.entries,
7098
+ remote.entries,
7099
+ options
7100
+ );
7101
+ const { diffs: pageDiffs, summary: pageSummary, blockSummary } = calculatePageDiffs(
7102
+ local.pages,
7103
+ remote.pages,
7104
+ options
7105
+ );
7106
+ const { diffs: navDiffs, summary: navSummary } = calculateNavigationDiffs(
7107
+ local.navigation,
7108
+ remote.navigation
7109
+ );
7110
+ const { diff: settingsDiff, summary: settingsSummary } = calculateSettingsDiff(
7111
+ local.settings,
7112
+ remote.settings
7113
+ );
7114
+ return {
7115
+ entries: entryDiffs,
7116
+ pages: pageDiffs,
7117
+ navigation: navDiffs,
7118
+ settings: settingsDiff,
7119
+ summary: {
7120
+ entries: entrySummary,
7121
+ pages: pageSummary,
7122
+ blocks: blockSummary,
7123
+ navigation: navSummary,
7124
+ settings: settingsSummary
7125
+ }
7126
+ };
7127
+ }
7128
+ function hasPendingChanges(diff) {
7129
+ const { summary } = diff;
7130
+ 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;
7131
+ }
7132
+ function formatDiffSummary(diff) {
7133
+ const lines = [];
7134
+ const { summary } = diff;
7135
+ if (summary.entries.create > 0 || summary.entries.update > 0) {
7136
+ lines.push(`Entries: +${summary.entries.create} ~${summary.entries.update}`);
7137
+ }
7138
+ if (summary.pages.create > 0 || summary.pages.update > 0) {
7139
+ lines.push(`Pages: +${summary.pages.create} ~${summary.pages.update}`);
7140
+ }
7141
+ if (summary.blocks.create > 0 || summary.blocks.update > 0 || summary.blocks.delete > 0) {
7142
+ lines.push(`Blocks: +${summary.blocks.create} ~${summary.blocks.update} -${summary.blocks.delete}`);
7143
+ }
7144
+ if (summary.navigation.create > 0 || summary.navigation.update > 0) {
7145
+ lines.push(`Navigation: +${summary.navigation.create} ~${summary.navigation.update}`);
7146
+ }
7147
+ if (summary.settings.update > 0) {
7148
+ lines.push(`Settings: ~${summary.settings.update}`);
7149
+ }
7150
+ if (lines.length === 0) {
7151
+ return "No changes detected";
7152
+ }
7153
+ return lines.join("\n");
7154
+ }
7155
+ function formatDiffDetail(diff) {
7156
+ const lines = [];
7157
+ for (const [contentType, entryDiffs] of diff.entries) {
7158
+ const creates = entryDiffs.filter((d) => d.type === "create");
7159
+ const updates = entryDiffs.filter((d) => d.type === "update");
7160
+ if (creates.length > 0) {
7161
+ lines.push(`
7162
+ ${contentType} (new):`);
7163
+ for (const d of creates) {
7164
+ lines.push(` + ${d.identifier}`);
7165
+ }
7166
+ }
7167
+ if (updates.length > 0) {
7168
+ lines.push(`
7169
+ ${contentType} (updated):`);
7170
+ for (const d of updates) {
7171
+ lines.push(` ~ ${d.identifier}${d.changes ? ` [${d.changes.join(", ")}]` : ""}`);
7172
+ }
7173
+ }
7174
+ }
7175
+ const pageCreates = diff.pages.filter((d) => d.type === "create");
7176
+ const pageUpdates = diff.pages.filter((d) => d.type === "update");
7177
+ if (pageCreates.length > 0) {
7178
+ lines.push("\nPages (new):");
7179
+ for (const d of pageCreates) {
7180
+ lines.push(` + ${d.identifier}`);
7181
+ }
7182
+ }
7183
+ if (pageUpdates.length > 0) {
7184
+ lines.push("\nPages (updated):");
7185
+ for (const d of pageUpdates) {
7186
+ lines.push(` ~ ${d.identifier}${d.changes ? ` [${d.changes.join(", ")}]` : ""}`);
7187
+ }
7188
+ }
7189
+ const navCreates = diff.navigation.filter((d) => d.type === "create");
7190
+ const navUpdates = diff.navigation.filter((d) => d.type === "update");
7191
+ if (navCreates.length > 0) {
7192
+ lines.push("\nNavigation (new):");
7193
+ for (const d of navCreates) {
7194
+ lines.push(` + ${d.name}`);
7195
+ }
7196
+ }
7197
+ if (navUpdates.length > 0) {
7198
+ lines.push("\nNavigation (updated):");
7199
+ for (const d of navUpdates) {
7200
+ lines.push(` ~ ${d.name}`);
7201
+ }
7202
+ }
7203
+ if (diff.settings) {
7204
+ lines.push("\nSettings (updated):");
7205
+ lines.push(` ~ [${diff.settings.changes.join(", ")}]`);
7206
+ }
7207
+ return lines.join("\n");
7208
+ }
7209
+
7210
+ // src/cli/sync/executor.ts
7211
+ function createEmptyResult() {
7212
+ return {
7213
+ entries: { created: 0, updated: 0, failed: 0 },
7214
+ pages: { created: 0, updated: 0, failed: 0 },
7215
+ blocks: { created: 0, updated: 0, deleted: 0, failed: 0 },
7216
+ navigation: { created: 0, updated: 0, failed: 0 },
7217
+ settings: { updated: 0, failed: 0 },
7218
+ errors: []
7219
+ };
7220
+ }
7221
+ async function syncEntries(client, diff, local, options, result) {
7222
+ for (const [contentType, entryDiffs] of diff.entries) {
7223
+ const localEntries = local.entries.get(contentType) ?? [];
7224
+ const entryLookup = new Map(localEntries.map((e) => [e.identifier, e]));
7225
+ for (const entryDiff of entryDiffs) {
7226
+ if (entryDiff.type === "unchanged") continue;
7227
+ const localEntry = entryLookup.get(entryDiff.identifier);
7228
+ if (!localEntry) continue;
7229
+ try {
7230
+ if (options.dryRun) ;
7231
+ await client.entries.upsert({
7232
+ contentType,
7233
+ identifier: localEntry.identifier,
7234
+ data: localEntry.data,
7235
+ slug: localEntry.slug
7236
+ });
7237
+ if (options.contentTarget === "publish") {
7238
+ await client.entries.publish(contentType, localEntry.identifier);
7239
+ }
7240
+ if (entryDiff.type === "create") {
7241
+ result.entries.created++;
7242
+ } else {
7243
+ result.entries.updated++;
7244
+ }
7245
+ } catch (error) {
7246
+ result.entries.failed++;
7247
+ result.errors.push({
7248
+ resource: "entry",
7249
+ identifier: `${contentType}/${entryDiff.identifier}`,
7250
+ error: error instanceof Error ? error.message : String(error)
7251
+ });
7252
+ }
7253
+ }
7254
+ }
7255
+ }
7256
+ async function syncPages(client, diff, local, options, result) {
7257
+ const pageLookup = new Map(local.pages.map((p) => [p.identifier, p]));
7258
+ for (const pageDiff of diff.pages) {
7259
+ if (pageDiff.type === "unchanged") continue;
7260
+ const localPage = pageLookup.get(pageDiff.identifier);
7261
+ if (!localPage) continue;
7262
+ try {
7263
+ if (options.dryRun) ;
7264
+ await client.pages.upsert({
7265
+ identifier: localPage.identifier,
7266
+ title: localPage.title,
7267
+ path: localPage.path,
7268
+ seoTitle: localPage.seoTitle,
7269
+ seoDescription: localPage.seoDescription
7270
+ });
7271
+ if (pageDiff.blocks && localPage.blocks) {
7272
+ await syncBlocks(
7273
+ client,
7274
+ pageDiff.identifier,
7275
+ pageDiff.blocks,
7276
+ localPage.blocks,
7277
+ options,
7278
+ result
7279
+ );
7280
+ }
7281
+ if (options.contentTarget === "publish") {
7282
+ await client.pages.publish(localPage.identifier);
7283
+ }
7284
+ if (pageDiff.type === "create") {
7285
+ result.pages.created++;
7286
+ } else {
7287
+ result.pages.updated++;
7288
+ }
7289
+ } catch (error) {
7290
+ result.pages.failed++;
7291
+ result.errors.push({
7292
+ resource: "page",
7293
+ identifier: pageDiff.identifier,
7294
+ error: error instanceof Error ? error.message : String(error)
7295
+ });
7296
+ }
7297
+ }
7298
+ }
7299
+ async function syncBlocks(client, pageIdentifier, blockDiffs, localBlocks, options, result) {
7300
+ const blockLookup = new Map(localBlocks.map((b) => [b.identifier, b]));
7301
+ for (const blockDiff of blockDiffs) {
7302
+ if (blockDiff.type === "unchanged") continue;
7303
+ try {
7304
+ if (blockDiff.type === "delete") {
7305
+ await client.blocks.delete(pageIdentifier, blockDiff.identifier);
7306
+ result.blocks.deleted++;
7307
+ } else {
7308
+ const localBlock = blockLookup.get(blockDiff.identifier);
7309
+ if (!localBlock) continue;
7310
+ await client.blocks.upsert(pageIdentifier, {
7311
+ identifier: localBlock.identifier,
7312
+ kind: localBlock.kind,
7313
+ data: localBlock.data
7314
+ });
7315
+ if (blockDiff.type === "create") {
7316
+ result.blocks.created++;
7317
+ } else {
7318
+ result.blocks.updated++;
7319
+ }
7320
+ }
7321
+ } catch (error) {
7322
+ result.blocks.failed++;
7323
+ result.errors.push({
7324
+ resource: "block",
7325
+ identifier: `${pageIdentifier}/${blockDiff.identifier}`,
7326
+ error: error instanceof Error ? error.message : String(error)
7327
+ });
7328
+ }
7329
+ }
7330
+ const hasReorderDiff = blockDiffs.some((d) => d.type === "reorder");
7331
+ if (hasReorderDiff) {
7332
+ const localIdentifiers = localBlocks.map((b) => b.identifier);
7333
+ if (localIdentifiers.length > 1) {
7334
+ try {
7335
+ await client.blocks.reorder(pageIdentifier, localIdentifiers);
7336
+ } catch {
7337
+ }
7338
+ }
7339
+ }
7340
+ }
7341
+ async function syncNavigation(client, diff, local, options, result) {
7342
+ if (!local.navigation?.menus) return;
7343
+ const menuLookup = new Map(local.navigation.menus.map((m) => [m.name, m]));
7344
+ for (const navDiff of diff.navigation) {
7345
+ if (navDiff.type === "unchanged") continue;
7346
+ const localMenu = menuLookup.get(navDiff.name);
7347
+ if (!localMenu) continue;
7348
+ try {
7349
+ if (options.dryRun) ;
7350
+ await client.navigation.upsert({
7351
+ name: localMenu.name,
7352
+ items: localMenu.items
7353
+ });
7354
+ if (navDiff.type === "create") {
7355
+ result.navigation.created++;
7356
+ } else {
7357
+ result.navigation.updated++;
7358
+ }
7359
+ } catch (error) {
7360
+ result.navigation.failed++;
7361
+ result.errors.push({
7362
+ resource: "navigation",
7363
+ identifier: navDiff.name,
7364
+ error: error instanceof Error ? error.message : String(error)
7365
+ });
7366
+ }
7367
+ }
7368
+ }
7369
+ async function syncSettings(client, diff, local, options, result) {
7370
+ if (!diff.settings || !local.settings) return;
7371
+ try {
7372
+ if (options.dryRun) ;
7373
+ await client.settings.update({
7374
+ homepageId: local.settings.homepageId,
7375
+ seoDefaults: local.settings.seoDefaults
7376
+ });
7377
+ result.settings.updated++;
7378
+ } catch (error) {
7379
+ result.settings.failed++;
7380
+ result.errors.push({
7381
+ resource: "settings",
7382
+ identifier: "site",
7383
+ error: error instanceof Error ? error.message : String(error)
7384
+ });
7385
+ }
7386
+ }
7387
+ async function executeSyncPlan(client, diff, local, options, output) {
7388
+ const result = createEmptyResult();
7389
+ 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;
7390
+ if (totalOps === 0) {
7391
+ output.info("No changes to sync");
7392
+ return result;
7393
+ }
7394
+ const prefix = "Syncing";
7395
+ output.info(`${prefix} ${totalOps} operations...`);
7396
+ await syncEntries(client, diff, local, options, result);
7397
+ await syncPages(client, diff, local, options, result);
7398
+ await syncNavigation(client, diff, local, options, result);
7399
+ await syncSettings(client, diff, local, options, result);
7400
+ return result;
7401
+ }
7402
+ function formatSyncResult(result, dryRun) {
7403
+ const lines = [];
7404
+ const verb = "";
7405
+ if (result.entries.created > 0 || result.entries.updated > 0) {
7406
+ lines.push(
7407
+ `Entries: ${verb} created ${result.entries.created}, updated ${result.entries.updated}` + (result.entries.failed > 0 ? `, failed ${result.entries.failed}` : "")
7408
+ );
7409
+ }
7410
+ if (result.pages.created > 0 || result.pages.updated > 0) {
7411
+ lines.push(
7412
+ `Pages: ${verb} created ${result.pages.created}, updated ${result.pages.updated}` + (result.pages.failed > 0 ? `, failed ${result.pages.failed}` : "")
7413
+ );
7414
+ }
7415
+ if (result.blocks.created > 0 || result.blocks.updated > 0 || result.blocks.deleted > 0) {
7416
+ lines.push(
7417
+ `Blocks: ${verb} created ${result.blocks.created}, updated ${result.blocks.updated}, deleted ${result.blocks.deleted}` + (result.blocks.failed > 0 ? `, failed ${result.blocks.failed}` : "")
7418
+ );
7419
+ }
7420
+ if (result.navigation.created > 0 || result.navigation.updated > 0) {
7421
+ lines.push(
7422
+ `Navigation: ${verb} created ${result.navigation.created}, updated ${result.navigation.updated}` + (result.navigation.failed > 0 ? `, failed ${result.navigation.failed}` : "")
7423
+ );
7424
+ }
7425
+ if (result.settings.updated > 0) {
7426
+ lines.push(
7427
+ `Settings: ${verb} updated ${result.settings.updated}` + (result.settings.failed > 0 ? `, failed ${result.settings.failed}` : "")
7428
+ );
7429
+ }
7430
+ if (result.errors.length > 0) {
7431
+ lines.push("\nErrors:");
7432
+ for (const error of result.errors) {
7433
+ lines.push(` ${error.resource}/${error.identifier}: ${error.error}`);
7434
+ }
7435
+ }
7436
+ if (lines.length === 0) {
7437
+ return "Everything is in sync - no changes needed";
7438
+ }
7439
+ return lines.join("\n");
7440
+ }
7441
+
7442
+ // src/cli/commands/push.ts
7443
+ function filterLocalContent(localContent, scope, contentType) {
7444
+ if (scope === "entries") {
7445
+ if (contentType) {
7446
+ const filtered = /* @__PURE__ */ new Map();
7447
+ const entries = localContent.entries.get(contentType);
7448
+ if (entries) {
7449
+ filtered.set(contentType, entries);
7450
+ }
7451
+ return { ...localContent, entries: filtered, pages: [], navigation: null };
7452
+ }
7453
+ return { ...localContent, pages: [], navigation: null };
7454
+ }
7455
+ if (scope === "pages") {
7456
+ return { ...localContent, entries: /* @__PURE__ */ new Map(), navigation: null };
7457
+ }
7458
+ if (scope === "navigation") {
7459
+ return { ...localContent, entries: /* @__PURE__ */ new Map(), pages: [] };
7460
+ }
7461
+ return localContent;
7462
+ }
7463
+ function displayDiff(output, diff, config3, dryRun, isRemote, jsonOutput, jsonDiffMode, localContent, remoteContent) {
7464
+ const envLabel = isRemote ? "REMOTE" : "LOCAL";
7465
+ if (jsonDiffMode) {
7466
+ const jsonDiff = buildJsonDiff(diff, localContent, remoteContent, jsonDiffMode);
7467
+ output.json(jsonDiff);
7468
+ return;
7469
+ }
7470
+ if (jsonOutput) {
7471
+ output.json({
7472
+ summary: diff.summary,
7473
+ syncConfig: config3.sync,
7474
+ dryRun
7475
+ });
7476
+ } else {
7477
+ if (dryRun) {
7478
+ output.info(`[DRY RUN] Changes that would be pushed to ${envLabel}:`);
7479
+ } else {
7480
+ output.info(`Changes to push to ${envLabel}:`);
7481
+ }
7482
+ output.info(formatDiffSummary(diff));
7483
+ output.info(formatDiffDetail(diff));
7484
+ }
7485
+ }
7486
+ function reportSyncResults(output, result, hasErrors) {
7487
+ if (hasErrors) {
7488
+ output.warn("Push completed with errors");
7489
+ } else {
7490
+ output.success("Push complete");
7491
+ }
7492
+ output.info(formatSyncResult(result));
7493
+ }
7494
+ function checkForStaleContent(localContent, localMeta, pagesMeta, navigationMeta, remoteContent) {
7495
+ const staleItems = [];
7496
+ for (const [contentType, localEntries] of localContent.entries) {
7497
+ const meta = localMeta.get(contentType);
7498
+ if (!meta) continue;
7499
+ remoteContent.entries[contentType]?.forEach((remoteEntry) => {
7500
+ const localEntry = localEntries.find((e) => e.identifier === remoteEntry.identifier);
7501
+ if (localEntry) {
7502
+ const entryMeta = meta.entries[remoteEntry.identifier];
7503
+ const localBaseTime = entryMeta?.updatedAt;
7504
+ const remoteTime = remoteContent.meta.entries?.[`${contentType}:${remoteEntry.identifier}`]?.updatedAt;
7505
+ if (localBaseTime && remoteTime && new Date(remoteTime) > new Date(localBaseTime)) {
7506
+ staleItems.push(`Entry: ${contentType}/${remoteEntry.identifier} (Remote updated: ${remoteTime})`);
7507
+ }
7508
+ }
7509
+ });
7510
+ }
7511
+ if (pagesMeta) {
7512
+ localContent.pages.forEach((localPage) => {
7513
+ const remotePage = remoteContent.pages.find((p) => p.identifier === localPage.identifier);
7514
+ const localBaseTime = pagesMeta.pages[localPage.identifier]?.updatedAt;
7515
+ if (remotePage && localBaseTime) {
7516
+ if (new Date(remotePage.updatedAt) > new Date(localBaseTime)) {
7517
+ staleItems.push(`Page: ${localPage.identifier} (Remote updated: ${remotePage.updatedAt})`);
7518
+ }
7519
+ }
7520
+ });
7521
+ }
7522
+ if (navigationMeta) {
7523
+ localContent.navigation?.menus.forEach((localMenu) => {
7524
+ const remoteMenu = remoteContent.navigation.find((m) => m.name === localMenu.name);
7525
+ const localBaseTime = navigationMeta.menus[localMenu.name]?.updatedAt;
7526
+ if (remoteMenu && localBaseTime) {
7527
+ if (new Date(remoteMenu.updatedAt) > new Date(localBaseTime)) {
7528
+ staleItems.push(`Navigation: ${localMenu.name} (Remote updated: ${remoteMenu.updatedAt})`);
7529
+ }
7530
+ }
7531
+ });
7532
+ }
7533
+ return staleItems;
7534
+ }
7535
+ 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", `
7536
+ Examples:
7537
+ $ riverbankcms push # Push all content
7538
+ $ riverbankcms push --dry-run # Preview changes
7539
+ $ riverbankcms push --remote --yes # Push to production
7540
+ $ riverbankcms push entries # Push all entries
7541
+ $ riverbankcms push entries blog-post # Push specific content type
7542
+ $ riverbankcms push pages # Push pages with blocks
7543
+ $ riverbankcms push navigation # Push navigation menus
7544
+ $ riverbankcms push --content-dir ./src/content # Custom content directory
7545
+ $ riverbankcms push --dry-run --json-diff=summary # JSON summary for agents
7546
+ $ riverbankcms push --dry-run --json-diff=full # Full JSON before/after payloads
7547
+
7548
+ Sync Behavior:
7549
+ Content sync is controlled by your riverbank.config.ts:
7550
+ - sync.existingEntries: 'skip' (default) or 'update'
7551
+ - sync.contentTarget: 'draft' (default) or 'publish'
7552
+
7553
+ Safety:
7554
+ - Stale detection: aborts if remote content is newer than last pull (use --force to override)
7555
+ - Truncation: aborts if remote has >100 entries per type (use --allow-truncated to override)
7556
+ - Remote environment (--remote): defaults to dry-run, requires --yes to execute
7557
+ `).action(async (scope, type, options, command) => {
7558
+ const { output, isRemote, globalOpts } = getOutputContext(command);
7559
+ let dryRun = options.dryRun ?? false;
7560
+ const jsonDiffMode = options.jsonDiff ? options.jsonDiff === "summary" || options.jsonDiff === "full" ? options.jsonDiff : null : void 0;
7561
+ if (options.jsonDiff && !jsonDiffMode) {
7562
+ output.error("Invalid value for --json-diff. Use summary or full.");
7563
+ return;
7564
+ }
7565
+ if (isRemote && !options.yes) {
7566
+ dryRun = true;
7567
+ output.info("Remote push defaults to dry-run mode. Use --yes to apply changes.");
7568
+ }
7569
+ try {
7570
+ const cliConfig = await loadCliConfig();
7571
+ const contentDir = path9__namespace.resolve(options.contentDir ?? cliConfig.contentDir);
7572
+ if (!await contentDirExists(contentDir)) {
7573
+ return output.error(`Content directory not found: ${contentDir}`, {
7574
+ suggestion: 'Run "riverbankcms pull" first to download content, or specify a different directory with --content-dir'
7575
+ });
7576
+ }
7577
+ const summary = await getContentSummary(contentDir);
7578
+ if (summary.totalEntries === 0 && summary.pageCount === 0 && !summary.hasNavigation) {
7579
+ output.warn("No content found to push");
7580
+ return;
7581
+ }
7582
+ const env = loadEnvironment(isRemote);
7583
+ const client = createManagementClient({
7584
+ dashboardUrl: env.dashboardUrl,
7585
+ managementApiKey: env.managementApiKey,
7586
+ siteId: env.siteId
7587
+ });
7588
+ output.info("Reading local content...");
7589
+ const localContent = await readAllContent(contentDir);
7590
+ const pushScope = scope?.toLowerCase() ?? "all";
7591
+ const filteredLocal = filterLocalContent(localContent, pushScope, type);
7592
+ output.info("Fetching remote content for comparison...");
7593
+ const remoteContent = await client.pull.all();
7594
+ if (remoteContent.meta.truncated) {
7595
+ output.warn("Warning: Remote content was truncated due to size limits.");
7596
+ output.warn(
7597
+ remoteContent.meta.truncationMessage ?? "Some content types have more than 100 entries. Results may be incomplete."
7598
+ );
7599
+ if (!options.allowTruncated) {
7600
+ output.error(
7601
+ "Push aborted due to truncated remote content.",
7602
+ { suggestion: "Use --allow-truncated to push anyway, or push specific content types: riverbankcms push entries blog-post" }
7603
+ );
7604
+ return;
7605
+ }
7606
+ output.warn("Proceeding with push despite truncation (--allow-truncated flag used)");
7607
+ }
7608
+ if (!options.force) {
7609
+ output.info("Checking for stale content...");
7610
+ const [localMeta, pagesMeta, navigationMeta] = await Promise.all([
7611
+ readAllMeta(contentDir),
7612
+ readPagesMeta(contentDir),
7613
+ readNavigationMeta(contentDir)
7614
+ ]);
7615
+ const staleItems = checkForStaleContent(filteredLocal, localMeta, pagesMeta, navigationMeta, remoteContent);
7616
+ if (staleItems.length > 0) {
7617
+ output.warn("WARNING: The following remote content has changed since you last pulled:");
7618
+ staleItems.forEach((item) => output.warn(` - ${item}`));
7619
+ output.error(
7620
+ "Push aborted to prevent overwriting newer content.",
7621
+ { suggestion: 'Run "riverbankcms pull" to update your local content, or use --force to overwrite.' }
7622
+ );
7623
+ return;
7624
+ }
7625
+ }
7626
+ output.info("Calculating changes...");
7627
+ const diff = calculateDiff(filteredLocal, remoteContent, {
7628
+ existingEntries: cliConfig.sync.existingEntries
7629
+ });
7630
+ if (!hasPendingChanges(diff)) {
7631
+ output.success("No changes detected - content is already in sync");
7632
+ return;
7633
+ }
7634
+ displayDiff(
7635
+ output,
7636
+ diff,
7637
+ cliConfig,
7638
+ dryRun,
7639
+ isRemote,
7640
+ globalOpts.json ?? false,
7641
+ jsonDiffMode ?? void 0,
7642
+ filteredLocal,
7643
+ remoteContent
7644
+ );
7645
+ if (dryRun) {
7646
+ output.info("\nUse --yes (for remote) or remove --dry-run (for local) to apply changes.");
7647
+ return;
7648
+ }
7649
+ output.info("\nApplying changes...");
7650
+ const result = await executeSyncPlan(
7651
+ client,
7652
+ diff,
7653
+ filteredLocal,
7654
+ { dryRun: false, contentTarget: cliConfig.sync.contentTarget },
7655
+ output
7656
+ );
7657
+ reportSyncResults(output, result, result.errors.length > 0);
7658
+ } catch (error) {
7659
+ handleCommandError(error, output);
7660
+ }
7661
+ });
7662
+ var formatEntryRow = (entry) => [
7663
+ entry.identifier,
7664
+ entry.status,
7665
+ entry.hasUnpublishedChanges ? "Yes" : "No",
7666
+ entry.slug ?? "-",
7667
+ formatDateShort(entry.updatedAt)
7668
+ ];
7669
+ var upsertCommand = new commander.Command("upsert").description("Create or update an entry").argument("<type>", "Content type").argument("<identifier>", "Entry identifier").option("--data <json>", "Entry data as JSON string").option("--file <path>", "Path to JSON file with entry data").option("--slug <slug>", "Entry slug (defaults to identifier)").option("--title <title>", "Entry title").action(
7670
+ withErrorHandling(async (type, identifier, options, command) => {
7671
+ const { output, client } = createCommandContext(command);
7672
+ const data = await parseJsonData(options);
7673
+ output.info(`Upserting entry: ${type}/${identifier}`);
7674
+ const entry = await client.entries.upsert({
7675
+ contentType: type,
7676
+ identifier,
7677
+ data,
7678
+ slug: options.slug,
7679
+ title: options.title
7680
+ });
7681
+ output.success(`Entry upserted: ${entry.identifier}`, {
7682
+ id: entry.id,
7683
+ identifier: entry.identifier,
7684
+ contentType: entry.contentType,
7685
+ status: entry.status
7686
+ });
7687
+ })
7688
+ );
7689
+ var publishCommand = createPublishCommand({
7690
+ resourceName: "entry",
7691
+ args: ["type", "identifier"],
7692
+ action: (client, type, identifier) => client.entries.publish(type, identifier)
7693
+ });
7694
+ var unpublishCommand = createUnpublishCommand({
7695
+ resourceName: "entry",
7696
+ args: ["type", "identifier"],
7697
+ action: (client, type, identifier) => client.entries.unpublish(type, identifier)
7698
+ });
7699
+ var getCommand = createGetCommand({
7700
+ resourceName: "entry",
7701
+ args: ["type", "identifier"],
7702
+ get: (client, type, identifier) => client.entries.get(type, identifier),
7703
+ formatOutput: (entry) => ({
7704
+ id: entry.id,
7705
+ contentType: entry.contentType,
7706
+ status: entry.status,
7707
+ hasUnpublishedChanges: entry.hasUnpublishedChanges,
7708
+ slug: entry.slug,
7709
+ data: entry.data,
7710
+ createdAt: entry.createdAt,
7711
+ updatedAt: entry.updatedAt,
7712
+ publishedAt: entry.publishedAt
7713
+ })
7714
+ });
7715
+ var listCommand = createListCommand({
7716
+ resourceName: "entry",
7717
+ resourceNamePlural: "entries",
7718
+ args: ["type"],
7719
+ hasPagination: true,
7720
+ list: (client, args, pagination) => client.entries.list(args[0], pagination),
7721
+ tableHeaders: ["Identifier", "Status", "Unpublished Changes", "Slug", "Updated"],
7722
+ formatRow: formatEntryRow
7723
+ });
7724
+ var entryCommand = new commander.Command("entry").description("Manage content entries").addHelpText("after", `
7725
+ Examples:
7726
+ $ riverbankcms entry upsert blog-post my-post --data '{"title": "Hello"}'
7727
+ $ riverbankcms entry upsert blog-post my-post --file ./post-data.json
7728
+ $ riverbankcms entry publish blog-post my-post
7729
+ $ riverbankcms entry unpublish blog-post my-post
7730
+ $ riverbankcms entry get blog-post my-post
7731
+ $ riverbankcms entry list blog-post --limit 10 --page 1
7732
+ `).addCommand(upsertCommand).addCommand(publishCommand).addCommand(unpublishCommand).addCommand(getCommand).addCommand(listCommand);
7733
+ var formatPageRow = (page) => [
7734
+ page.identifier ?? "(no identifier)",
7735
+ page.title,
7736
+ page.path,
7737
+ page.status,
7738
+ page.hasUnpublishedChanges ? "Yes" : "No",
7739
+ formatDateShort(page.updatedAt)
7740
+ ];
7741
+ var upsertCommand2 = new commander.Command("upsert").description("Create or update a page").argument("<identifier>", "Page identifier").requiredOption("--title <title>", "Page title").requiredOption("--path <path>", "Page path (e.g., /about)").option("--seo-title <title>", "SEO title").option("--seo-description <description>", "SEO description").action(
7742
+ withErrorHandling(async (identifier, options, command) => {
7743
+ const { output, client } = createCommandContext(command);
7744
+ output.info(`Upserting page: ${identifier}`);
7745
+ const page = await client.pages.upsert({
7746
+ identifier,
7747
+ title: options.title,
7748
+ path: options.path,
7749
+ seoTitle: options.seoTitle,
7750
+ seoDescription: options.seoDescription
7751
+ });
7752
+ output.success(`Page upserted: ${page.identifier}`, {
7753
+ id: page.id,
7754
+ identifier: page.identifier,
7755
+ path: page.path,
7756
+ status: page.status
7757
+ });
7758
+ })
7759
+ );
7760
+ var publishCommand2 = createPublishCommand({
7761
+ resourceName: "page",
7762
+ args: ["identifier"],
7763
+ action: (client, identifier) => client.pages.publish(identifier)
7764
+ });
7765
+ var unpublishCommand2 = createUnpublishCommand({
7766
+ resourceName: "page",
7767
+ args: ["identifier"],
7768
+ action: (client, identifier) => client.pages.unpublish(identifier)
7769
+ });
7770
+ var getCommand2 = createGetCommand({
7771
+ resourceName: "page",
7772
+ args: ["identifier"],
7773
+ get: (client, identifier) => client.pages.get(identifier),
7774
+ formatOutput: (page) => ({
7775
+ id: page.id,
7776
+ title: page.title,
7777
+ path: page.path,
7778
+ status: page.status,
7779
+ hasUnpublishedChanges: page.hasUnpublishedChanges,
7780
+ createdAt: page.createdAt,
7781
+ updatedAt: page.updatedAt,
7782
+ publishedAt: page.publishedAt
7783
+ })
7784
+ });
7785
+ var listCommand2 = createListCommand({
7786
+ resourceName: "page",
7787
+ resourceNamePlural: "pages",
7788
+ args: [],
7789
+ hasPagination: true,
7790
+ list: (client, _args, pagination) => client.pages.list(pagination),
7791
+ tableHeaders: ["Identifier", "Title", "Path", "Status", "Unpublished Changes", "Updated"],
7792
+ formatRow: formatPageRow
7793
+ });
7794
+ var pageCommand = new commander.Command("page").description("Manage pages").addHelpText("after", `
7795
+ Examples:
7796
+ $ riverbankcms page upsert about --title "About Us" --path /about
7797
+ $ riverbankcms page upsert about --title "About Us" --path /about --seo-title "About Our Company"
7798
+ $ riverbankcms page publish about
7799
+ $ riverbankcms page unpublish about
7800
+ $ riverbankcms page get about
7801
+ $ riverbankcms page list --limit 10 --page 1
7802
+ `).addCommand(upsertCommand2).addCommand(publishCommand2).addCommand(unpublishCommand2).addCommand(getCommand2).addCommand(listCommand2);
7803
+ var formatBlockRow = (block) => [
7804
+ block.identifier ?? "(no identifier)",
7805
+ block.kind,
7806
+ String(block.position),
7807
+ formatDateShort(block.updatedAt)
7808
+ ];
7809
+ var upsertCommand3 = new commander.Command("upsert").description("Create or update a block on a page").argument("<page-identifier>", "Page identifier").argument("<block-identifier>", "Block identifier").requiredOption("--kind <kind>", "Block kind (e.g., block.hero, block.bodyText)").option("--data <json>", "Block data as JSON string").option("--file <path>", "Path to JSON file with block data").option("--position <n>", "Block position (0-indexed)").action(
7810
+ withErrorHandling(
7811
+ async (pageIdentifier, blockIdentifier, options, command) => {
7812
+ const { output, client } = createCommandContext(command);
7813
+ const data = await parseJsonData(options);
7814
+ output.info(`Upserting block: ${pageIdentifier}/${blockIdentifier}`);
7815
+ const block = await client.blocks.upsert(pageIdentifier, {
7816
+ identifier: blockIdentifier,
7817
+ kind: options.kind,
7818
+ data,
7819
+ position: options.position ? parseInt(options.position, 10) : void 0
7820
+ });
7821
+ output.success(`Block upserted: ${block.identifier}`, {
7822
+ id: block.id,
7823
+ identifier: block.identifier,
7824
+ kind: block.kind,
7825
+ position: block.position
7826
+ });
7827
+ }
7828
+ )
7829
+ );
7830
+ var reorderCommand = new commander.Command("reorder").description("Reorder blocks on a page").argument("<page-identifier>", "Page identifier").argument("<identifiers...>", "Block identifiers in desired order").action(
7831
+ withErrorHandling(
7832
+ async (pageIdentifier, identifiers, _options, command) => {
7833
+ const { output, client } = createCommandContext(command);
7834
+ if (identifiers.length < 2) {
7835
+ return output.error("At least 2 block identifiers are required for reordering");
7836
+ }
7837
+ output.info(`Reordering blocks on page: ${pageIdentifier}`);
7838
+ await client.blocks.reorder(pageIdentifier, identifiers);
7839
+ output.success(`Blocks reordered`, {
7840
+ page: pageIdentifier,
7841
+ order: identifiers
7842
+ });
7843
+ }
7844
+ )
7845
+ );
7846
+ var listCommand3 = createListCommand({
7847
+ resourceName: "block",
7848
+ resourceNamePlural: "blocks",
7849
+ args: ["page-identifier"],
7850
+ hasPagination: false,
7851
+ list: async (client, args) => ({ items: await client.blocks.list(args[0]) }),
7852
+ tableHeaders: ["Identifier", "Kind", "Position", "Updated"],
7853
+ formatRow: formatBlockRow
7854
+ });
7855
+ var getCommand3 = createGetCommand({
7856
+ resourceName: "block",
7857
+ args: ["page-identifier", "block-identifier"],
7858
+ get: (client, pageIdentifier, blockIdentifier) => client.blocks.get(pageIdentifier, blockIdentifier),
7859
+ formatOutput: (block) => ({
7860
+ id: block.id,
7861
+ kind: block.kind,
7862
+ position: block.position,
7863
+ data: block.data,
7864
+ createdAt: block.createdAt,
7865
+ updatedAt: block.updatedAt
7866
+ })
7867
+ });
7868
+ var blockCommand = new commander.Command("block").description("Manage page blocks").addHelpText("after", `
7869
+ Examples:
7870
+ $ riverbankcms block upsert home hero-main --kind block.hero --data '{"heading": "Welcome"}'
7871
+ $ riverbankcms block upsert home hero-main --kind block.hero --file ./hero-data.json
7872
+ $ riverbankcms block reorder home hero-main content-section cta-bottom
7873
+ $ riverbankcms block list home
7874
+ $ riverbankcms block get home hero-main
7875
+
7876
+ To delete a block, use: riverbankcms delete block <page-id> <block-id>
7877
+ `).addCommand(upsertCommand3).addCommand(reorderCommand).addCommand(listCommand3).addCommand(getCommand3);
7878
+ var formatMenuRow = (menu) => {
7879
+ const itemCount = countItems(menu.items);
7880
+ return [
7881
+ menu.name,
7882
+ String(itemCount),
7883
+ formatDateShort(menu.updatedAt)
7884
+ ];
7885
+ };
7886
+ function countItems(items) {
7887
+ let count = items.length;
7888
+ for (const item of items) {
7889
+ if (item.children) {
7890
+ count += countItems(item.children);
7891
+ }
7892
+ }
7893
+ return count;
7894
+ }
7895
+ var upsertCommand4 = new commander.Command("upsert").description("Create or update a navigation menu").argument("<menu-name>", "Navigation menu name").option("--file <path>", "Path to JSON file with menu items").option("--data <json>", "Menu items as JSON string").action(
7896
+ withErrorHandling(async (menuName, options, command) => {
7897
+ const { output, client } = createCommandContext(command);
7898
+ const items = await parseJsonArray(options);
7899
+ output.info(`Upserting navigation menu: ${menuName}`);
7900
+ const menu = await client.navigation.upsert({
7901
+ name: menuName,
7902
+ items
7903
+ });
7904
+ output.success(`Navigation menu upserted: ${menu.name}`, {
7905
+ id: menu.id,
7906
+ name: menu.name,
7907
+ itemCount: countItems(menu.items)
7908
+ });
7909
+ })
7910
+ );
7911
+ var getCommand4 = createGetCommand({
7912
+ resourceName: "navigation menu",
7913
+ args: ["menu-name"],
7914
+ get: (client, menuName) => client.navigation.get(menuName),
7915
+ formatOutput: (menu) => ({
7916
+ id: menu.id,
7917
+ name: menu.name,
7918
+ items: menu.items,
7919
+ createdAt: menu.createdAt,
7920
+ updatedAt: menu.updatedAt
7921
+ })
7922
+ });
7923
+ var listCommand4 = createListCommand({
7924
+ resourceName: "navigation menu",
7925
+ resourceNamePlural: "navigation menus",
7926
+ args: [],
7927
+ hasPagination: false,
7928
+ list: async (client) => ({ items: await client.navigation.list() }),
7929
+ tableHeaders: ["Name", "Items", "Updated"],
7930
+ formatRow: formatMenuRow
7931
+ });
7932
+ var navigationCommand = new commander.Command("navigation").description("Manage navigation menus").addHelpText("after", `
7933
+ Examples:
7934
+ $ riverbankcms navigation upsert main --file ./main-nav.json
7935
+ $ riverbankcms navigation upsert footer --data '[{"label": "Home", "url": "/"}]'
7936
+ $ riverbankcms navigation get main
7937
+ $ riverbankcms navigation list
7938
+
7939
+ JSON file format:
7940
+ {
7941
+ "items": [
7942
+ { "label": "Home", "url": "/" },
7943
+ { "label": "About", "url": "/about", "children": [
7944
+ { "label": "Team", "url": "/about/team" }
7945
+ ]}
7946
+ ]
7947
+ }
7948
+ `).addCommand(upsertCommand4).addCommand(getCommand4).addCommand(listCommand4);
7949
+ var deleteEntryCommand = new commander.Command("entry").description("Delete a content entry").argument("<type>", "Content type").argument("<identifier>", "Entry identifier").option("--yes", "Skip confirmation prompt (required for --remote)").action(
7950
+ withConfirmation(
7951
+ (type, identifier) => `Delete entry "${type}/${identifier}"?`,
7952
+ async (ctx, type, identifier) => {
7953
+ ctx.output.info(`Deleting entry: ${type}/${identifier}`);
7954
+ await ctx.client.entries.delete(type, identifier);
7955
+ ctx.output.success(`Entry deleted: ${identifier}`);
7956
+ }
7957
+ )
7958
+ );
7959
+ var deleteBlockCommand = new commander.Command("block").description("Delete a block from a page").argument("<page-identifier>", "Page identifier").argument("<block-identifier>", "Block identifier").option("--yes", "Skip confirmation prompt (required for --remote)").action(
7960
+ withConfirmation(
7961
+ (pageIdentifier, blockIdentifier) => `Delete block "${blockIdentifier}" from page "${pageIdentifier}"?`,
7962
+ async (ctx, pageIdentifier, blockIdentifier) => {
7963
+ ctx.output.info(`Deleting block: ${pageIdentifier}/${blockIdentifier}`);
7964
+ await ctx.client.blocks.delete(pageIdentifier, blockIdentifier);
7965
+ ctx.output.success(`Block deleted: ${blockIdentifier}`);
7966
+ }
7967
+ )
7968
+ );
7969
+ var deleteCommand = new commander.Command("delete").description("Delete content from CMS").addHelpText("after", `
7970
+ Examples:
7971
+ $ riverbankcms delete entry blog-post my-post
7972
+ $ riverbankcms delete entry blog-post my-post --yes
7973
+ $ riverbankcms delete block home hero-main --yes
7974
+ $ riverbankcms delete entry blog-post my-post --remote --yes
7975
+
7976
+ Note: Remote delete operations require --yes flag for safety.
7977
+ `).addCommand(deleteEntryCommand).addCommand(deleteBlockCommand);
7978
+
7979
+ // src/cli/preview/template.ts
7980
+ function buildPreviewHtml({ html, cssText, title }) {
7981
+ const safeTitle = title ?? "Block Preview";
7982
+ return `<!doctype html>
7983
+ <html lang="en">
7984
+ <head>
7985
+ <meta charset="utf-8" />
7986
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7987
+ <title>${safeTitle}</title>
7988
+ <style>
7989
+ ${cssText}
7990
+ </style>
7991
+ </head>
7992
+ <body>
7993
+ ${html}
7994
+ </body>
7995
+ </html>`;
7996
+ }
7997
+
7998
+ // src/cli/preview/playwright.config.ts
7999
+ var config = {
8000
+ use: {
8001
+ viewport: { width: 1280, height: 720 },
8002
+ deviceScaleFactor: 2,
8003
+ colorScheme: "light"
8004
+ }
8005
+ };
8006
+ var playwright_config_default = config;
8007
+
8008
+ // src/cli/preview/screenshot.ts
8009
+ async function captureScreenshot(options) {
8010
+ const { html, outputPath, viewport } = options;
8011
+ let playwright;
8012
+ try {
8013
+ playwright = await import('playwright');
8014
+ } catch (error) {
8015
+ const message = error instanceof Error ? error.message : String(error);
8016
+ throw new Error(`Playwright is not installed. Install it to use --screenshot. (${message})`);
8017
+ }
8018
+ const browser = await playwright.chromium.launch();
8019
+ const page = await browser.newPage({
8020
+ viewport: viewport ?? playwright_config_default.use.viewport,
8021
+ deviceScaleFactor: playwright_config_default.use.deviceScaleFactor,
8022
+ colorScheme: playwright_config_default.use.colorScheme
8023
+ });
8024
+ await page.setContent(html, { waitUntil: "load" });
8025
+ await page.screenshot({ path: path9__namespace.resolve(outputPath), fullPage: true });
8026
+ await browser.close();
8027
+ }
8028
+ function resolveOutputMode(options) {
8029
+ const modes = [options.terminal, options.open, options.screenshot].filter(Boolean).length;
8030
+ if (modes > 1) {
8031
+ throw new Error("Choose only one output mode: --terminal, --open, or --screenshot");
8032
+ }
8033
+ if (options.open) return "open";
8034
+ if (options.screenshot) return "screenshot";
8035
+ return "terminal";
8036
+ }
8037
+ async function writePreviewHtmlFile(html) {
8038
+ const dir = await fs3__namespace.mkdtemp(path9__namespace.join(os__namespace.tmpdir(), "riverbank-preview-"));
8039
+ const filePath = path9__namespace.join(dir, "index.html");
8040
+ await fs3__namespace.writeFile(filePath, html, "utf-8");
8041
+ return filePath;
8042
+ }
8043
+ async function openPreviewFile(filePath) {
8044
+ const platform = process.platform;
8045
+ if (platform === "darwin") {
8046
+ await runCommand("open", [filePath]);
8047
+ return;
8048
+ }
8049
+ if (platform === "win32") {
8050
+ await runCommand("cmd", ["/c", "start", '""', filePath]);
8051
+ return;
8052
+ }
8053
+ await runCommand("xdg-open", [filePath]);
8054
+ }
8055
+ function runCommand(command, args) {
8056
+ return new Promise((resolve8, reject) => {
8057
+ const child = child_process.spawn(command, args, { stdio: "ignore", detached: true });
8058
+ child.on("error", reject);
8059
+ child.unref();
8060
+ resolve8();
8061
+ });
8062
+ }
8063
+
8064
+ // src/cli/commands/preview.ts
8065
+ async function readCssFiles(paths) {
8066
+ if (!paths || paths.length === 0) return "";
8067
+ const chunks = await Promise.all(paths.map(async (filePath) => {
8068
+ const resolved = path9__namespace.resolve(filePath);
8069
+ const { readFile: readFile4 } = await import('fs/promises');
8070
+ return readFile4(resolved, "utf-8");
8071
+ }));
8072
+ return chunks.join("\n");
8073
+ }
8074
+ function formatValidationIssues(response) {
8075
+ return response.validation.issues.map((issue) => {
8076
+ const pathText = issue.path.length > 0 ? issue.path.join(".") : "(root)";
8077
+ return `${pathText}: ${issue.message}`;
8078
+ });
8079
+ }
8080
+ var previewCommand = new commander.Command("preview").description("Render a block preview").argument("<kind>", "Block kind (e.g., block.hero)").option("--data <json>", "Inline JSON string for block content").option("--file <path>", "Path to JSON file with block content").option("--validation <mode>", "Validation mode (strict or lenient)", "strict").option("--preview-stage <stage>", "Content stage (published or preview)", "preview").option("--theme-id <id>", "Theme ID override").option("--page-id <id>", "Page ID context for loaders").option("--content-entry-id <id>", "Content entry ID context for loaders").option("--css <paths...>", "Append compiled CSS files for site-specific utilities").option("--terminal", "Print HTML output to the terminal (default)").option("--open", "Open the preview in a browser").option("--screenshot", "Capture a screenshot using Playwright").option("--output <path>", "Screenshot output path (default: ./block-preview.png)").action(
8081
+ withErrorHandling(async (kind, options, command) => {
8082
+ const { output, client, isJsonOutput } = createCommandContext(command);
8083
+ const mode = resolveOutputMode(options);
8084
+ const data = await parseJsonData({ data: options.data, file: options.file });
8085
+ const response = await client.preview.block({
8086
+ kind,
8087
+ data,
8088
+ themeId: options.themeId,
8089
+ pageId: options.pageId,
8090
+ contentEntryId: options.contentEntryId,
8091
+ previewStage: options.previewStage,
8092
+ validationMode: options.validation ?? "strict"
8093
+ });
8094
+ const extraCss = await readCssFiles(options.css);
8095
+ const cssText = extraCss ? `${response.cssText}
8096
+ ${extraCss}` : response.cssText;
8097
+ const htmlDocument = buildPreviewHtml({ html: response.html, cssText });
8098
+ if (!response.validation.valid) {
8099
+ const issues = formatValidationIssues(response);
8100
+ output.warn("Block validation failed (lenient mode)", { issues });
8101
+ }
8102
+ if (isJsonOutput) {
8103
+ return output.json({ success: true, data: { ...response, cssText } });
8104
+ }
8105
+ if (mode === "open") {
8106
+ const filePath = await writePreviewHtmlFile(htmlDocument);
8107
+ await openPreviewFile(filePath);
8108
+ output.success("Opened preview in browser", { filePath });
8109
+ return;
8110
+ }
8111
+ if (mode === "screenshot") {
8112
+ const outputPath = options.output ?? path9__namespace.resolve(process.cwd(), "block-preview.png");
8113
+ await captureScreenshot({ html: htmlDocument, outputPath });
8114
+ output.success("Screenshot saved", { outputPath });
8115
+ return;
8116
+ }
8117
+ output.info("Block preview HTML:");
8118
+ console.log(htmlDocument);
8119
+ })
8120
+ );
8121
+
8122
+ // src/cli/init-docs/templates.ts
8123
+ var GENERATED_START = "<!-- RIVERBANK-GENERATED-START -->";
8124
+ var GENERATED_END = "<!-- RIVERBANK-GENERATED-END -->";
8125
+ function getGeneratedMarkers() {
8126
+ return { start: GENERATED_START, end: GENERATED_END };
8127
+ }
8128
+ function buildSchemaTemplate(state) {
8129
+ const header = [
8130
+ "# Site Schema",
8131
+ "",
8132
+ "This document captures the content model and SDK configuration for this site.",
8133
+ "The generated section is updated by `riverbankcms init-docs`.",
8134
+ "",
8135
+ renderConfigGuideSection(),
8136
+ ""
8137
+ ].join("\n");
8138
+ const generated = buildSchemaGeneratedSection(state);
8139
+ return `${header}${GENERATED_START}
8140
+ ${generated}
8141
+ ${GENERATED_END}
8142
+ `;
8143
+ }
8144
+ function buildBlockTypesTemplate() {
8145
+ const header = [
8146
+ "# System Block Types",
8147
+ "",
8148
+ "Reference list of all built-in CMS block types.",
8149
+ "The generated section is updated by `riverbankcms init-docs`.",
8150
+ ""
8151
+ ].join("\n");
8152
+ const generated = buildBlockTypesGeneratedSection();
8153
+ return `${header}${GENERATED_START}
8154
+ ${generated}
8155
+ ${GENERATED_END}
8156
+ `;
8157
+ }
8158
+ function buildSchemaGeneratedSection(state) {
8159
+ if (!state.config) {
8160
+ return [
8161
+ "## Status",
8162
+ "",
8163
+ state.errorMessage ?? "No SDK config available.",
8164
+ ""
8165
+ ].join("\n");
8166
+ }
8167
+ const config3 = state.config;
8168
+ const sections = [];
8169
+ sections.push(renderContentTypesSection(config3));
8170
+ sections.push(renderCustomBlocksSection(config3.customBlocks ?? []));
8171
+ sections.push(renderBlockFieldExtensionsSection(config3.blockFieldExtensions));
8172
+ sections.push(renderBlockFieldOptionsSection(config3.blockFieldOptions));
8173
+ return sections.filter(Boolean).join("\n");
8174
+ }
8175
+ function buildBlockTypesGeneratedSection() {
8176
+ const definitions = listBlockDefinitions();
8177
+ const sections = ["## System Blocks", ""];
8178
+ for (const definition of definitions) {
8179
+ const manifest = definition.manifest;
8180
+ sections.push(`### ${manifest.name}`);
8181
+ sections.push("");
8182
+ sections.push(`- Title: ${manifest.title}`);
8183
+ if (manifest.description) {
8184
+ sections.push(`- Description: ${manifest.description}`);
8185
+ }
8186
+ if (manifest.category) {
8187
+ sections.push(`- Category: ${manifest.category}`);
8188
+ }
8189
+ if (manifest.tags && manifest.tags.length > 0) {
8190
+ sections.push(`- Tags: ${manifest.tags.join(", ")}`);
8191
+ }
8192
+ if (manifest.contentTypes && manifest.contentTypes.length > 0) {
8193
+ sections.push(`- Content Types: ${manifest.contentTypes.join(", ")}`);
8194
+ }
8195
+ if (manifest.behaviours?.paletteHidden) {
8196
+ sections.push("- Palette Hidden: yes");
8197
+ }
8198
+ sections.push("");
8199
+ sections.push("Fields:");
8200
+ if (manifest.fields.length === 0) {
8201
+ sections.push("- (none)");
8202
+ } else {
8203
+ sections.push(...renderFieldList(manifest.fields, 0));
8204
+ }
8205
+ sections.push("");
8206
+ }
8207
+ return sections.join("\n");
8208
+ }
8209
+ function renderContentTypesSection(config3) {
8210
+ const contentTypes = config3.content?.contentTypes ?? [];
8211
+ const lines = ["## Content Types", ""];
8212
+ if (contentTypes.length === 0) {
8213
+ lines.push("No content types defined.");
8214
+ lines.push("");
8215
+ return lines.join("\n");
8216
+ }
8217
+ for (const type of contentTypes) {
8218
+ lines.push(`### ${type.name} (${type.key})`);
8219
+ lines.push("");
8220
+ if (type.description) {
8221
+ lines.push(type.description);
8222
+ lines.push("");
8223
+ }
8224
+ lines.push(`- Has Pages: ${type.hasPages ? "yes" : "no"}`);
8225
+ if (type.routePattern) lines.push(`- Route Pattern: ${type.routePattern}`);
8226
+ if (type.titleField) lines.push(`- Title Field: ${type.titleField}`);
8227
+ if (type.isSingleton) lines.push("- Singleton: yes");
8228
+ lines.push("");
8229
+ lines.push("Fields:");
8230
+ if (type.fields.length === 0) {
8231
+ lines.push("- (none)");
8232
+ } else {
8233
+ lines.push(...renderFieldList(type.fields, 0));
8234
+ }
8235
+ lines.push("");
8236
+ }
8237
+ return lines.join("\n");
8238
+ }
8239
+ function renderConfigGuideSection() {
8240
+ return [
8241
+ "## riverbank.config.ts Guide",
8242
+ "",
8243
+ "Use `riverbank.config.ts` as the source of truth for the site schema, custom blocks, and CLI behavior.",
8244
+ "This section is static guidance for agents; the generated section below shows the live config.",
8245
+ "",
8246
+ "### Field Overrides (blockFieldOptions)",
8247
+ "",
8248
+ "Use `blockFieldOptions` to override the *options* for existing `select` fields.",
8249
+ "This only affects fields that use the `sdkSelect` UI widget (for example: the embed block layout picker).",
8250
+ "",
8251
+ "```ts",
8252
+ "export default defineConfig({",
8253
+ " siteId: 'your-site-id',",
8254
+ " blockFieldOptions: {",
8255
+ " 'block.embed': {",
8256
+ " layout: {",
8257
+ " options: [",
8258
+ " { value: 'showcase', label: 'Showcase Grid' },",
8259
+ " { value: 'list', label: 'Simple List' },",
8260
+ " ],",
8261
+ " },",
8262
+ " },",
8263
+ " 'custom.featured-posts': {",
8264
+ " style: {",
8265
+ " options: [",
8266
+ " { value: 'grid', label: 'Grid' },",
8267
+ " { value: 'carousel', label: 'Carousel' },",
8268
+ " ],",
8269
+ " },",
8270
+ " },",
8271
+ " },",
8272
+ "});",
8273
+ "```",
8274
+ "",
8275
+ "### Block Field Extensions (blockFieldExtensions)",
8276
+ "",
8277
+ "Use `blockFieldExtensions` to add *new* fields to built-in system blocks.",
8278
+ "Extended fields appear at the end of the block form and are available via `content` in `blockOverrides`.",
8279
+ "",
8280
+ "```ts",
8281
+ "export default defineConfig({",
8282
+ " siteId: 'your-site-id',",
8283
+ " blockFieldExtensions: {",
8284
+ " 'block.hero': {",
8285
+ " fields: [",
8286
+ " { id: 'videoBackground', type: 'media', label: 'Video Background', mediaKinds: ['video'] },",
8287
+ " { id: 'overlayOpacity', type: 'number', label: 'Overlay Opacity', defaultValue: 50 },",
8288
+ " ],",
8289
+ " },",
8290
+ " 'block.bodyText': {",
8291
+ " fields: [",
8292
+ " { id: 'layout', type: 'select', label: 'Layout', defaultValue: 'default',",
8293
+ " options: [",
8294
+ " { value: 'default', label: 'Default' },",
8295
+ " { value: 'wide', label: 'Wide' },",
8296
+ " ],",
8297
+ " },",
8298
+ " ],",
8299
+ " },",
8300
+ " },",
8301
+ "});",
8302
+ "```",
8303
+ "",
8304
+ "Rules:",
8305
+ "- Only system blocks (`block.*`) can be extended.",
8306
+ "- Extended field IDs must not collide with existing block fields.",
8307
+ "- If `required: true`, you must set a `defaultValue` to avoid breaking existing blocks.",
8308
+ "",
8309
+ "### Custom Blocks (customBlocks)",
8310
+ "",
8311
+ "Use `customBlocks` to define new block types with their own fields.",
8312
+ "Custom blocks must be rendered via `blockOverrides` in the SDK site.",
8313
+ "",
8314
+ "```ts",
8315
+ "export default defineConfig({",
8316
+ " siteId: 'your-site-id',",
8317
+ " customBlocks: [",
8318
+ " {",
8319
+ " id: 'custom.team-member',",
8320
+ " title: 'Team Member',",
8321
+ " titleSource: 'name',",
8322
+ " description: 'Profile card with bio and photo',",
8323
+ " category: 'content',",
8324
+ " icon: 'User',",
8325
+ " fields: [",
8326
+ " { id: 'name', type: 'text', label: 'Name', required: true },",
8327
+ " { id: 'role', type: 'text', label: 'Role' },",
8328
+ " { id: 'photo', type: 'media', label: 'Photo', mediaKinds: ['image'] },",
8329
+ " { id: 'bio', type: 'richText', label: 'Bio' },",
8330
+ " ],",
8331
+ " },",
8332
+ " ],",
8333
+ "});",
8334
+ "```",
8335
+ "",
8336
+ "### Field Types (for customBlocks and blockFieldExtensions)",
8337
+ "",
8338
+ "All field definitions use the same `FieldDefinition` format as system blocks.",
8339
+ "Supported `type` values:",
8340
+ "",
8341
+ "- `text`: Single or multiline text",
8342
+ "- `richText`: Rich text editor (markdown or HTML)",
8343
+ "- `media`: Image or video upload",
8344
+ "- `boolean`: Toggle/checkbox",
8345
+ "- `number`: Numeric input",
8346
+ "- `date`: Date picker",
8347
+ "- `time`: Time picker",
8348
+ "- `datetime`: Date + time picker",
8349
+ "- `slug`: Slug with optional source field",
8350
+ "- `url`: URL input (optionally allow relative)",
8351
+ "- `link`: Link picker",
8352
+ "- `select`: Single or multi-select options",
8353
+ "- `reference`: Reference another entry type",
8354
+ "- `repeater`: Repeatable list of fields",
8355
+ "- `group`: Group of fields",
8356
+ "- `modal`: Fields inside a modal dialog",
8357
+ "- `tabGroup`: Group fields by tabs",
8358
+ "- `presetOrCustom`: Preset list with custom fallback",
8359
+ "- `contentTypeSelect`: Content type picker (all/routable/nonRoutable)",
8360
+ "- `entryPicker`: Pick a content entry",
8361
+ "",
8362
+ "Tip: For `repeater`, define either `schema.fields` (monomorphic) or `polymorphic: true` with `itemTypes`."
8363
+ ].join("\n");
8364
+ }
8365
+ function renderCustomBlocksSection(customBlocks) {
8366
+ const lines = ["## Custom Blocks", ""];
8367
+ if (customBlocks.length === 0) {
8368
+ lines.push("No custom blocks defined.");
8369
+ lines.push("");
8370
+ return lines.join("\n");
8371
+ }
8372
+ for (const block of customBlocks) {
8373
+ lines.push(`### ${block.id}`);
8374
+ lines.push("");
8375
+ lines.push(`- Title: ${block.title}`);
8376
+ if (block.description) lines.push(`- Description: ${block.description}`);
8377
+ if (block.category) lines.push(`- Category: ${block.category}`);
8378
+ if (block.tags && block.tags.length > 0) lines.push(`- Tags: ${block.tags.join(", ")}`);
8379
+ lines.push("");
8380
+ lines.push("Fields:");
8381
+ if (block.fields.length === 0) {
8382
+ lines.push("- (none)");
8383
+ } else {
8384
+ lines.push(...renderFieldList(block.fields, 0));
8385
+ }
8386
+ lines.push("");
8387
+ }
8388
+ return lines.join("\n");
8389
+ }
8390
+ function renderBlockFieldExtensionsSection(extensions) {
8391
+ const lines = ["## Block Field Extensions", ""];
8392
+ if (!extensions || Object.keys(extensions).length === 0) {
8393
+ lines.push("No block field extensions defined.");
8394
+ lines.push("");
8395
+ return lines.join("\n");
8396
+ }
8397
+ for (const [blockId, extension] of Object.entries(extensions)) {
8398
+ lines.push(`### ${blockId}`);
8399
+ lines.push("");
8400
+ if (extension.fields.length === 0) {
8401
+ lines.push("- (none)");
8402
+ } else {
8403
+ lines.push(...renderFieldList(extension.fields, 0));
8404
+ }
8405
+ lines.push("");
8406
+ }
8407
+ return lines.join("\n");
8408
+ }
8409
+ function renderBlockFieldOptionsSection(options) {
8410
+ const lines = ["## Block Field Options", ""];
8411
+ if (!options || Object.keys(options).length === 0) {
8412
+ lines.push("No block field options defined.");
8413
+ lines.push("");
8414
+ return lines.join("\n");
8415
+ }
8416
+ for (const [blockId, fields4] of Object.entries(options)) {
8417
+ lines.push(`### ${blockId}`);
8418
+ lines.push("");
8419
+ for (const [fieldId, config3] of Object.entries(fields4)) {
8420
+ lines.push(`- ${fieldId}`);
8421
+ if (config3.options && config3.options.length > 0) {
8422
+ lines.push(...renderOptionsList(config3.options, 1));
8423
+ }
8424
+ }
8425
+ lines.push("");
8426
+ }
8427
+ return lines.join("\n");
8428
+ }
8429
+ function renderFieldList(fields4, depth) {
8430
+ const lines = [];
8431
+ const indent = " ".repeat(depth);
8432
+ for (const field of fields4) {
8433
+ const required = field.required ? " required" : "";
8434
+ const label = field.label ? ` - ${field.label}` : "";
8435
+ lines.push(`${indent}- ${field.id} (${field.type})${required}${label}`);
8436
+ if ("options" in field && Array.isArray(field.options) && field.options.length > 0) {
8437
+ lines.push(...renderOptionsList(field.options, depth + 1));
8438
+ }
8439
+ if ("fields" in field && Array.isArray(field.fields)) {
8440
+ lines.push(...renderFieldList(field.fields, depth + 1));
8441
+ }
8442
+ if ("schema" in field && field.schema && "fields" in field.schema) {
8443
+ lines.push(...renderFieldList(field.schema.fields, depth + 1));
8444
+ }
8445
+ if ("tabs" in field && Array.isArray(field.tabs)) {
8446
+ for (const tab of field.tabs) {
8447
+ lines.push(`${indent} - tab ${tab.id} (${tab.label})`);
8448
+ lines.push(...renderFieldList(tab.fields, depth + 2));
8449
+ }
8450
+ }
8451
+ }
8452
+ return lines;
8453
+ }
8454
+ function renderOptionsList(options, depth) {
8455
+ const indent = " ".repeat(depth);
8456
+ return options.map((option) => `${indent}- option: ${option.value} (${option.label})`);
8457
+ }
8458
+
8459
+ // src/cli/init-docs/index.ts
8460
+ var AGENTS_START = "<!-- RIVERBANK-CONTEXT-START -->";
8461
+ var AGENTS_END = "<!-- RIVERBANK-CONTEXT-END -->";
8462
+ async function initDocs(options) {
8463
+ const { rootDir, configPath, agentsPath, output } = options;
8464
+ const docsDir = path9__namespace.join(rootDir, "docs");
8465
+ const contextDir = path9__namespace.join(rootDir, "context");
8466
+ const workflowsDir = path9__namespace.join(docsDir, "workflows");
8467
+ const knowledgeDir = path9__namespace.join(contextDir, "knowledge");
8468
+ const brandDir = path9__namespace.join(contextDir, "brand");
8469
+ await ensureDir2(workflowsDir);
8470
+ await ensureDir2(knowledgeDir);
8471
+ await ensureDir2(brandDir);
8472
+ await writeFileIfMissing(path9__namespace.join(docsDir, "cli-reference.md"), cliReferenceTemplate());
8473
+ await writeFileIfMissing(path9__namespace.join(docsDir, "content-management.md"), contentManagementTemplate());
8474
+ await writeFileIfMissing(path9__namespace.join(workflowsDir, "create-page.md"), workflowCreatePageTemplate());
8475
+ await writeFileIfMissing(path9__namespace.join(workflowsDir, "add-block.md"), workflowAddBlockTemplate());
8476
+ await writeFileIfMissing(path9__namespace.join(workflowsDir, "publish-workflow.md"), workflowPublishTemplate());
8477
+ await writeFileIfMissing(path9__namespace.join(contextDir, "brief.md"), briefTemplate());
8478
+ await writeFileIfMissing(path9__namespace.join(knowledgeDir, "README.md"), knowledgeTemplate());
8479
+ await writeFileIfMissing(path9__namespace.join(brandDir, "README.md"), brandTemplate());
8480
+ const { config: config3, errorMessage } = await loadConfig(configPath, output);
8481
+ await upsertGeneratedDoc(
8482
+ path9__namespace.join(docsDir, "schema.md"),
8483
+ buildSchemaTemplate({ config: config3, errorMessage }),
8484
+ buildSchemaGeneratedSection({ config: config3, errorMessage })
8485
+ );
8486
+ await upsertGeneratedDoc(
8487
+ path9__namespace.join(docsDir, "block-types.md"),
8488
+ buildBlockTypesTemplate(),
8489
+ buildBlockTypesGeneratedSection()
8490
+ );
8491
+ await upsertAgentsSection(agentsPath);
8492
+ }
8493
+ async function loadConfig(configPath, output) {
8494
+ if (!configPath) {
8495
+ return { errorMessage: "No config path provided." };
8496
+ }
8497
+ try {
8498
+ const { loadConfigFile: loadConfigFile2 } = await Promise.resolve().then(() => (init_load_config(), load_config_exports));
8499
+ const rawConfig = await loadConfigFile2(configPath);
8500
+ const parsed = riverbankSiteConfigSchema.safeParse(rawConfig);
8501
+ if (!parsed.success) {
8502
+ return { errorMessage: `Config validation failed: ${parsed.error.message}` };
8503
+ }
8504
+ return { config: parsed.data };
8505
+ } catch (error) {
8506
+ const message = error instanceof Error ? error.message : String(error);
8507
+ output?.warn("Failed to load config; schema will be partial", { message });
8508
+ return { errorMessage: message };
8509
+ }
8510
+ }
8511
+ async function ensureDir2(dirPath) {
8512
+ await fs3__namespace.mkdir(dirPath, { recursive: true });
8513
+ }
8514
+ async function writeFileIfMissing(filePath, contents) {
8515
+ try {
8516
+ await fs3__namespace.access(filePath);
8517
+ } catch {
8518
+ await fs3__namespace.writeFile(filePath, contents, "utf-8");
8519
+ }
8520
+ }
8521
+ async function upsertGeneratedDoc(filePath, template, generatedSection) {
8522
+ const markers = getGeneratedMarkers();
8523
+ const contents = await readFileOrTemplate(filePath, template);
8524
+ const updated = replaceMarkedSection(contents, markers.start, markers.end, generatedSection);
8525
+ await fs3__namespace.writeFile(filePath, updated, "utf-8");
8526
+ }
8527
+ async function readFileOrTemplate(filePath, template) {
8528
+ try {
8529
+ return await fs3__namespace.readFile(filePath, "utf-8");
8530
+ } catch {
8531
+ return template;
8532
+ }
8533
+ }
8534
+ function replaceMarkedSection(content, startMarker, endMarker, section2) {
8535
+ const startIndex = content.indexOf(startMarker);
8536
+ const endIndex = content.indexOf(endMarker);
8537
+ if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
8538
+ const trimmed = content.trimEnd();
8539
+ return `${trimmed}
8540
+
8541
+ ${startMarker}
8542
+ ${section2}
8543
+ ${endMarker}
8544
+ `;
8545
+ }
8546
+ const before = content.slice(0, startIndex + startMarker.length);
8547
+ const after = content.slice(endIndex);
8548
+ return `${before}
8549
+ ${section2}
8550
+ ${after}`;
8551
+ }
8552
+ async function upsertAgentsSection(filePath) {
8553
+ const content = await readFileOrTemplate(filePath, "# AGENTS.md\n");
8554
+ const section2 = agentsSectionTemplate();
8555
+ const updated = replaceMarkedSection(content, AGENTS_START, AGENTS_END, section2);
8556
+ await fs3__namespace.writeFile(filePath, updated, "utf-8");
8557
+ }
8558
+ function cliReferenceTemplate() {
8559
+ return [
8560
+ "# CLI Reference",
8561
+ "",
8562
+ "Describe the SDK CLI commands available to agents.",
8563
+ "",
8564
+ "Include usage patterns, required flags, and common examples.",
8565
+ ""
8566
+ ].join("\n");
8567
+ }
8568
+ function contentManagementTemplate() {
8569
+ return [
8570
+ "# Content Management",
8571
+ "",
8572
+ "Describe how content is structured, edited, and published for this site.",
8573
+ ""
8574
+ ].join("\n");
8575
+ }
8576
+ function workflowCreatePageTemplate() {
8577
+ return [
8578
+ "# Workflow: Create a Page",
8579
+ "",
8580
+ "Describe the steps to create a page for this site.",
8581
+ "",
8582
+ "Include any required content types or blocks.",
8583
+ ""
8584
+ ].join("\n");
8585
+ }
8586
+ function workflowAddBlockTemplate() {
8587
+ return [
8588
+ "# Workflow: Add a Block",
8589
+ "",
8590
+ "Describe how to add blocks to an existing page.",
8591
+ ""
8592
+ ].join("\n");
8593
+ }
8594
+ function workflowPublishTemplate() {
8595
+ return [
8596
+ "# Workflow: Publish Content",
8597
+ "",
8598
+ "Describe how to publish content safely for this site.",
8599
+ ""
8600
+ ].join("\n");
8601
+ }
8602
+ function briefTemplate() {
8603
+ return [
8604
+ "# Site Brief",
8605
+ "",
8606
+ "Summarize the brand, tone, and goals for this site.",
8607
+ ""
8608
+ ].join("\n");
8609
+ }
8610
+ function knowledgeTemplate() {
8611
+ return [
8612
+ "# Knowledge Base",
8613
+ "",
8614
+ "Add domain knowledge, FAQs, and reference links for the site.",
8615
+ ""
8616
+ ].join("\n");
8617
+ }
8618
+ function brandTemplate() {
8619
+ return [
8620
+ "# Brand Assets",
8621
+ "",
8622
+ "List or link the brand assets available for this site.",
8623
+ ""
8624
+ ].join("\n");
8625
+ }
8626
+ function agentsSectionTemplate() {
8627
+ return [
8628
+ "## Riverbank SDK Context",
8629
+ "",
8630
+ "- This repo contains both the CMS and the SDK used by this site.",
8631
+ "- Agents can suggest or implement CMS changes when needed, not just local workarounds.",
8632
+ "- Use riverbank.config.ts as the source of truth for site schema and custom blocks.",
8633
+ "",
8634
+ "## Code Quality Guidelines (SDK + CMS)",
8635
+ "",
8636
+ "- Prefer functional, composable helpers over large classes.",
8637
+ "- Keep changes small and DRY; remove old code instead of shimming.",
8638
+ "- Keep route handlers thin and use helpers/services.",
8639
+ "- Use typed API clients; avoid hardcoded URLs."
8640
+ ].join("\n");
8641
+ }
8642
+
8643
+ // src/cli/commands/init-docs.ts
8644
+ async function runInitDocs(options, command) {
8645
+ const { output, isJsonOutput } = createCommandContext(command);
8646
+ const rootDir = path9__namespace.resolve(options.path ?? ".riverbank");
8647
+ const configPath = path9__namespace.resolve(options.config ?? path9__namespace.join(process.cwd(), "riverbank.config.ts"));
8648
+ const agentsPath = path9__namespace.resolve(process.cwd(), "AGENTS.md");
8649
+ await initDocs({
8650
+ rootDir,
8651
+ configPath,
8652
+ agentsPath,
8653
+ output
8654
+ });
8655
+ if (isJsonOutput) {
8656
+ output.json({ success: true, data: { rootDir } });
8657
+ return;
8658
+ }
8659
+ output.success("Agent docs initialized", { rootDir });
8660
+ }
8661
+ var initDocsCommand = new commander.Command("init-docs").description("Scaffold agent documentation and site context").option("--path <path>", "Destination directory (default: ./.riverbank)").option("--config <path>", "Path to riverbank.config.ts (default: ./riverbank.config.ts)").action(
8662
+ withErrorHandling(async (options, command) => {
8663
+ await runInitDocs(options, command);
8664
+ })
8665
+ );
8666
+ var backfillCommand = new commander.Command("backfill").description("Backfill identifiers for pages, blocks, and entries that don't have one").addHelpText("after", `
8667
+ This command generates stable identifiers for content created before the SDK was available.
8668
+ Identifiers are generated from titles (slugified) and are idempotent - running this
8669
+ command multiple times won't overwrite existing identifiers.
8670
+
8671
+ Examples:
8672
+ $ riverbankcms identifiers backfill
8673
+ $ riverbankcms identifiers backfill --remote
8674
+ `).action(
8675
+ withErrorHandling(async (_options, command) => {
8676
+ const { output, client } = createCommandContext(command);
8677
+ output.info("Backfilling identifiers for pages, blocks, and entries...");
8678
+ const result = await client.identifiers.backfill();
8679
+ const totalUpdated = result.pages.pagesUpdated + result.blocks.total + result.entries.entriesUpdated;
8680
+ if (totalUpdated === 0) {
8681
+ output.success("All content already has identifiers - nothing to backfill");
8682
+ return;
8683
+ }
8684
+ output.success("Backfill complete", {
8685
+ pages: {
8686
+ updated: result.pages.pagesUpdated,
8687
+ identifiers: result.pages.identifiersAssigned
8688
+ },
8689
+ blocks: {
8690
+ updated: result.blocks.total,
8691
+ byPage: Object.keys(result.blocks.byPage).length > 0 ? result.blocks.byPage : void 0
8692
+ },
8693
+ entries: {
8694
+ updated: result.entries.entriesUpdated,
8695
+ identifiers: result.entries.identifiersAssigned
8696
+ }
8697
+ });
8698
+ })
8699
+ );
8700
+ var identifiersCommand = new commander.Command("identifiers").description("Manage SDK identifiers").addHelpText("after", `
8701
+ Identifiers are stable, human-readable names used to reference content in the SDK.
8702
+ They are automatically generated when content is created via the SDK, but older
8703
+ content may need backfilling.
8704
+
8705
+ Commands:
8706
+ backfill Generate identifiers for content that doesn't have one
8707
+
8708
+ Examples:
8709
+ $ riverbankcms identifiers backfill
8710
+ `).addCommand(backfillCommand);
8711
+
8712
+ // src/cli/index.ts
8713
+ dotenv.config({ path: ".env.local" });
8714
+ dotenv.config({ path: ".env" });
8715
+ var program = new commander.Command();
8716
+ program.name("riverbankcms").description("Riverbank CMS SDK CLI - manage content and configuration").version("0.1.0").option("--json", "Output in JSON format for machine parsing").option("--quiet", "Minimal output (suppress non-essential messages)").option("--remote", "Use remote/production environment instead of local").addHelpText("after", `
8717
+ Environment Variables:
8718
+ Local environment (default):
8719
+ RIVERBANK_LOCAL_SITE_ID Site ID for local development
8720
+ RIVERBANK_LOCAL_DASHBOARD_URL Dashboard URL (e.g., http://localhost:4000)
8721
+ RIVERBANK_LOCAL_MGMT_API_KEY Management API key (bld_mgmt_sk_...)
8722
+
8723
+ Remote environment (--remote):
8724
+ RIVERBANK_REMOTE_SITE_ID Site ID for production
8725
+ RIVERBANK_REMOTE_DASHBOARD_URL Dashboard URL (e.g., https://dashboard.riverbankcms.com)
8726
+ RIVERBANK_REMOTE_MGMT_API_KEY Management API key (bld_mgmt_sk_...)
8727
+
8728
+ Examples:
8729
+ $ riverbankcms push-config --api-key $RIVERBANK_API_KEY
8730
+ $ riverbankcms pull # Pull all content from local
8731
+ $ riverbankcms pull --remote # Pull all content from production
8732
+ $ riverbankcms pull entries blog-post # Pull specific content type
8733
+ $ riverbankcms push # Push content to local
8734
+ $ riverbankcms push --remote --yes # Push content to production
5402
8735
 
5403
8736
  Run 'riverbankcms <command> --help' for detailed options.
5404
8737
  `);
5405
8738
  program.addCommand(pushConfigCommand);
8739
+ program.addCommand(pullCommand);
8740
+ program.addCommand(pushCommand);
8741
+ program.addCommand(entryCommand);
8742
+ program.addCommand(pageCommand);
8743
+ program.addCommand(blockCommand);
8744
+ program.addCommand(navigationCommand);
8745
+ program.addCommand(deleteCommand);
8746
+ program.addCommand(previewCommand);
8747
+ program.addCommand(initDocsCommand);
8748
+ program.addCommand(identifiersCommand);
5406
8749
  program.parse();
5407
8750
  //# sourceMappingURL=index.js.map
5408
8751
  //# sourceMappingURL=index.js.map