@riverbankcms/sdk 0.4.3 → 0.5.0

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