@riverbankcms/sdk 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/cli/index.js +583 -147
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/client/client.d.mts +2 -2
  4. package/dist/client/client.d.ts +2 -2
  5. package/dist/client/client.js +126 -30
  6. package/dist/client/client.js.map +1 -1
  7. package/dist/client/client.mjs +126 -30
  8. package/dist/client/client.mjs.map +1 -1
  9. package/dist/client/hooks.d.mts +2 -2
  10. package/dist/client/hooks.d.ts +2 -2
  11. package/dist/client/hooks.js +34 -20
  12. package/dist/client/hooks.js.map +1 -1
  13. package/dist/client/hooks.mjs +34 -20
  14. package/dist/client/hooks.mjs.map +1 -1
  15. package/dist/client/rendering/client.js +182 -123
  16. package/dist/client/rendering/client.js.map +1 -1
  17. package/dist/client/rendering/client.mjs +174 -109
  18. package/dist/client/rendering/client.mjs.map +1 -1
  19. package/dist/client/usePage-Dsi39Exp.d.ts +6915 -0
  20. package/dist/client/usePage-Im82JRRe.d.mts +6915 -0
  21. package/dist/server/{Layout-l2v4Qa6E.d.ts → Layout-CZ-kxKfl.d.ts} +1 -1
  22. package/dist/server/{Layout-D4J009eS.d.mts → Layout-ESG8zvrk.d.mts} +1 -1
  23. package/dist/server/{chunk-4YQJUL5W.mjs → chunk-5GCSRTIU.mjs} +8 -4
  24. package/dist/server/chunk-5GCSRTIU.mjs.map +1 -0
  25. package/dist/server/{chunk-YYO3RIFO.js → chunk-6ERSDFTY.js} +35 -21
  26. package/dist/server/chunk-6ERSDFTY.js.map +1 -0
  27. package/dist/server/{chunk-YXA4GAAQ.mjs → chunk-6VTKALLN.mjs} +2 -6
  28. package/dist/server/{chunk-YXA4GAAQ.mjs.map → chunk-6VTKALLN.mjs.map} +1 -1
  29. package/dist/server/{chunk-BYBJA6SP.mjs → chunk-A3UZ2LDH.mjs} +35 -21
  30. package/dist/server/chunk-A3UZ2LDH.mjs.map +1 -0
  31. package/dist/server/{chunk-OSF34JTQ.mjs → chunk-ADD3O2QO.mjs} +4 -4
  32. package/dist/server/{chunk-C6FIJC7T.mjs → chunk-BNHK7YOC.mjs} +2 -2
  33. package/dist/server/{chunk-TT5JWA4X.js → chunk-DAXWU3S3.js} +9 -9
  34. package/dist/server/{chunk-TT5JWA4X.js.map → chunk-DAXWU3S3.js.map} +1 -1
  35. package/dist/server/{chunk-7UPVCT3K.js → chunk-F2NDLDDA.js} +239 -154
  36. package/dist/server/chunk-F2NDLDDA.js.map +1 -0
  37. package/dist/server/{chunk-LNOUXALA.mjs → chunk-FUFPKTSI.mjs} +96 -11
  38. package/dist/server/chunk-FUFPKTSI.mjs.map +1 -0
  39. package/dist/server/{chunk-65A5HAUZ.mjs → chunk-GRFFJUCO.mjs} +3 -3
  40. package/dist/server/{chunk-65A5HAUZ.mjs.map → chunk-GRFFJUCO.mjs.map} +1 -1
  41. package/dist/server/{chunk-AEFWG657.mjs → chunk-HDHY4236.mjs} +2 -2
  42. package/dist/server/{chunk-2KCF2DNK.js → chunk-HE3RTUDX.js} +8 -8
  43. package/dist/server/{chunk-2KCF2DNK.js.map → chunk-HE3RTUDX.js.map} +1 -1
  44. package/dist/server/{chunk-RVDS7VSP.js → chunk-IJTJH4J3.js} +4 -4
  45. package/dist/server/{chunk-RVDS7VSP.js.map → chunk-IJTJH4J3.js.map} +1 -1
  46. package/dist/server/{chunk-P3NNN73G.js → chunk-K44OPKLA.js} +3 -3
  47. package/dist/server/{chunk-P3NNN73G.js.map → chunk-K44OPKLA.js.map} +1 -1
  48. package/dist/server/{chunk-EIJ27EZQ.js → chunk-KDCVCDW6.js} +10 -6
  49. package/dist/server/chunk-KDCVCDW6.js.map +1 -0
  50. package/dist/server/{chunk-7BVRA5MY.js → chunk-KGORQCHF.js} +9 -9
  51. package/dist/server/{chunk-7BVRA5MY.js.map → chunk-KGORQCHF.js.map} +1 -1
  52. package/dist/server/{chunk-WM646WI3.js → chunk-MFNWLB5G.js} +7 -7
  53. package/dist/server/{chunk-WM646WI3.js.map → chunk-MFNWLB5G.js.map} +1 -1
  54. package/dist/server/{chunk-EIVISR62.js → chunk-P4O3WSAR.js} +2 -6
  55. package/dist/server/chunk-P4O3WSAR.js.map +1 -0
  56. package/dist/server/{chunk-RBJFXNDM.mjs → chunk-PGZJUNCY.mjs} +4 -4
  57. package/dist/server/{chunk-ARNCLSQT.mjs → chunk-T5PAA22U.mjs} +2 -2
  58. package/dist/server/{chunk-T26N3P26.js → chunk-TLZHVGTL.js} +4 -4
  59. package/dist/server/{chunk-T26N3P26.js.map → chunk-TLZHVGTL.js.map} +1 -1
  60. package/dist/server/{chunk-P4K63SBZ.mjs → chunk-TR7MSLWL.mjs} +3 -3
  61. package/dist/server/{chunk-NFEGQTCC.mjs → chunk-WMJKH4XE.mjs} +8 -1
  62. package/dist/server/{chunk-4CV4JOE5.js → chunk-Z6ZWNWWR.js} +9 -2
  63. package/dist/server/chunk-Z6ZWNWWR.js.map +1 -0
  64. package/dist/server/{components-D2uCKCj7.d.ts → components-CE48wJM1.d.ts} +4 -4
  65. package/dist/server/{components-vtYEmmPF.d.mts → components-iEDvl2Yw.d.mts} +4 -4
  66. package/dist/server/components.d.mts +6 -6
  67. package/dist/server/components.d.ts +6 -6
  68. package/dist/server/components.js +7 -7
  69. package/dist/server/components.mjs +6 -6
  70. package/dist/server/config-validation.d.mts +3 -3
  71. package/dist/server/config-validation.d.ts +3 -3
  72. package/dist/server/config-validation.js +6 -6
  73. package/dist/server/config-validation.mjs +5 -5
  74. package/dist/server/config.d.mts +5 -5
  75. package/dist/server/config.d.ts +5 -5
  76. package/dist/server/config.js +6 -6
  77. package/dist/server/config.mjs +5 -5
  78. package/dist/server/data.d.mts +3 -3
  79. package/dist/server/data.d.ts +3 -3
  80. package/dist/server/data.js +4 -4
  81. package/dist/server/data.mjs +3 -3
  82. package/dist/server/env.js +1 -1
  83. package/dist/server/env.mjs +1 -1
  84. package/dist/server/{index-BxrAuL9K.d.ts → index-BHLK2mgQ.d.ts} +2 -2
  85. package/dist/server/{index-DfWg1Qle.d.mts → index-BrH_NIRO.d.mts} +2 -2
  86. package/dist/server/{index-2qnY7VH_.d.mts → index-Cgvb5fVQ.d.mts} +2 -2
  87. package/dist/server/{index-CH_dvF6n.d.ts → index-DTBg8eXj.d.ts} +2 -2
  88. package/dist/server/index.d.mts +6 -6
  89. package/dist/server/index.d.ts +6 -6
  90. package/dist/server/index.js +11 -11
  91. package/dist/server/index.mjs +2 -2
  92. package/dist/server/{loadContent-DECnsp4k.d.ts → loadContent-BUK6IVJf.d.ts} +26 -4
  93. package/dist/server/{loadContent-Du5kS8UM.d.mts → loadContent-au9Weoy0.d.mts} +26 -4
  94. package/dist/server/loadPage-AWYZ2QA2.mjs +11 -0
  95. package/dist/server/loadPage-CMHYAW2J.js +11 -0
  96. package/dist/server/{loadPage-AXNAERDS.js.map → loadPage-CMHYAW2J.js.map} +1 -1
  97. package/dist/server/{loadPage-VBorKlWv.d.mts → loadPage-DiHEl8BA.d.mts} +3 -3
  98. package/dist/server/{loadPage-BZohBxxf.d.ts → loadPage-JOIbF7ih.d.ts} +3 -3
  99. package/dist/server/metadata.d.mts +5 -5
  100. package/dist/server/metadata.d.ts +5 -5
  101. package/dist/server/metadata.js +1 -1
  102. package/dist/server/metadata.mjs +1 -1
  103. package/dist/server/navigation.d.mts +4 -8
  104. package/dist/server/navigation.d.ts +4 -8
  105. package/dist/server/navigation.js +3 -7
  106. package/dist/server/navigation.js.map +1 -1
  107. package/dist/server/navigation.mjs +2 -6
  108. package/dist/server/next/revalidate.js +1 -1
  109. package/dist/server/next/revalidate.mjs +1 -1
  110. package/dist/server/next/tags.js +1 -1
  111. package/dist/server/next/tags.mjs +1 -1
  112. package/dist/server/next.d.mts +7 -7
  113. package/dist/server/next.d.ts +7 -7
  114. package/dist/server/next.js +20 -16
  115. package/dist/server/next.js.map +1 -1
  116. package/dist/server/next.mjs +12 -8
  117. package/dist/server/next.mjs.map +1 -1
  118. package/dist/server/rendering/server.d.mts +5 -5
  119. package/dist/server/rendering/server.d.ts +5 -5
  120. package/dist/server/rendering/server.js +9 -9
  121. package/dist/server/rendering/server.mjs +8 -8
  122. package/dist/server/rendering.d.mts +8 -8
  123. package/dist/server/rendering.d.ts +8 -8
  124. package/dist/server/rendering.js +11 -11
  125. package/dist/server/rendering.mjs +10 -10
  126. package/dist/server/routing.d.mts +5 -5
  127. package/dist/server/routing.d.ts +5 -5
  128. package/dist/server/routing.js +2 -2
  129. package/dist/server/routing.mjs +2 -2
  130. package/dist/server/{schema-Z6-afHJG.d.mts → schema-DYtW0zEu.d.mts} +40 -0
  131. package/dist/server/{schema-Z6-afHJG.d.ts → schema-DYtW0zEu.d.ts} +40 -0
  132. package/dist/server/server.d.mts +6 -6
  133. package/dist/server/server.d.ts +6 -6
  134. package/dist/server/server.js +7 -7
  135. package/dist/server/server.mjs +6 -6
  136. package/dist/server/theme-bridge.js +9 -9
  137. package/dist/server/theme-bridge.mjs +3 -3
  138. package/dist/server/theme.js +1 -1
  139. package/dist/server/theme.mjs +1 -1
  140. package/dist/server/{types-DT30Qy7x.d.mts → types-BAM1kcGA.d.mts} +1 -1
  141. package/dist/server/{types-D0rPF8l5.d.ts → types-CmBB0Osp.d.ts} +2 -2
  142. package/dist/server/{types-BRQ_6yOc.d.mts → types-DDNKxQXw.d.mts} +2 -2
  143. package/dist/server/{types-D8XqwoVd.d.ts → types-DVesWaB7.d.ts} +1 -1
  144. package/dist/server/{types-CJfJwcuL.d.mts → types-M0CviVW2.d.mts} +1 -1
  145. package/dist/server/{types-CgSO0yxg.d.ts → types-_SNCu2ZZ.d.ts} +1 -1
  146. package/dist/server/{validation-Pv3Zs6dP.d.mts → validation-BA1TKthZ.d.mts} +2 -2
  147. package/dist/server/{validation-D1LaY1kQ.d.ts → validation-js7BCPN8.d.ts} +2 -2
  148. package/dist/server/webhooks.js +1 -1
  149. package/dist/server/webhooks.mjs +1 -1
  150. package/package.json +1 -1
  151. package/dist/server/chunk-4CV4JOE5.js.map +0 -1
  152. package/dist/server/chunk-4YQJUL5W.mjs.map +0 -1
  153. package/dist/server/chunk-7UPVCT3K.js.map +0 -1
  154. package/dist/server/chunk-BYBJA6SP.mjs.map +0 -1
  155. package/dist/server/chunk-EIJ27EZQ.js.map +0 -1
  156. package/dist/server/chunk-EIVISR62.js.map +0 -1
  157. package/dist/server/chunk-LNOUXALA.mjs.map +0 -1
  158. package/dist/server/chunk-YYO3RIFO.js.map +0 -1
  159. package/dist/server/loadPage-AXNAERDS.js +0 -11
  160. package/dist/server/loadPage-XR7ORQ2E.mjs +0 -11
  161. /package/dist/server/{chunk-OSF34JTQ.mjs.map → chunk-ADD3O2QO.mjs.map} +0 -0
  162. /package/dist/server/{chunk-C6FIJC7T.mjs.map → chunk-BNHK7YOC.mjs.map} +0 -0
  163. /package/dist/server/{chunk-AEFWG657.mjs.map → chunk-HDHY4236.mjs.map} +0 -0
  164. /package/dist/server/{chunk-RBJFXNDM.mjs.map → chunk-PGZJUNCY.mjs.map} +0 -0
  165. /package/dist/server/{chunk-ARNCLSQT.mjs.map → chunk-T5PAA22U.mjs.map} +0 -0
  166. /package/dist/server/{chunk-P4K63SBZ.mjs.map → chunk-TR7MSLWL.mjs.map} +0 -0
  167. /package/dist/server/{chunk-NFEGQTCC.mjs.map → chunk-WMJKH4XE.mjs.map} +0 -0
  168. /package/dist/server/{loadPage-XR7ORQ2E.mjs.map → loadPage-AWYZ2QA2.mjs.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -7,9 +7,9 @@ var fs6 = require('fs');
7
7
  var dotenv = require('dotenv');
8
8
  var commander = require('commander');
9
9
  var zod = require('zod');
10
- var prompts = require('prompts');
11
10
  var fs3 = require('fs/promises');
12
11
  var readline = require('readline');
12
+ var prompts = require('prompts');
13
13
  var equal = require('fast-deep-equal');
14
14
  var os = require('os');
15
15
  var child_process = require('child_process');
@@ -41,9 +41,9 @@ function _interopNamespace(e) {
41
41
 
42
42
  var path9__namespace = /*#__PURE__*/_interopNamespace(path9);
43
43
  var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
44
- var prompts__default = /*#__PURE__*/_interopDefault(prompts);
45
44
  var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
46
45
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
46
+ var prompts__default = /*#__PURE__*/_interopDefault(prompts);
47
47
  var equal__default = /*#__PURE__*/_interopDefault(equal);
48
48
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
49
49
  var simpleGit__default = /*#__PURE__*/_interopDefault(simpleGit);
@@ -506,7 +506,11 @@ var uiSchema = zod.z.object({
506
506
  layout: zod.z.enum(["stack", "grid"]).optional(),
507
507
  columns: zod.z.number().int().min(2).max(4).optional(),
508
508
  // Entry picker configuration
509
- contentTypeField: zod.z.string().optional()
509
+ contentTypeField: zod.z.string().optional(),
510
+ // Extras pattern: fields marked as extras are hidden behind a modal toggle
511
+ extras: zod.z.boolean().optional(),
512
+ // Render in block header instead of form body (used for section styles)
513
+ renderInHeader: zod.z.boolean().optional()
510
514
  }).partial();
511
515
  var baseFieldSchema = zod.z.object({
512
516
  id: zod.z.string().min(1, "Field id is required"),
@@ -1676,25 +1680,30 @@ function createButtonGroup(options = {}) {
1676
1680
  ui: { colSpan: 2 }
1677
1681
  }
1678
1682
  ];
1679
- const iconsGroup = {
1680
- id: "icons",
1681
- type: "group",
1682
- label: "Icons",
1683
- required: false,
1684
- ui: { preset: "disclosure", colSpan: 2 },
1685
- schema: {
1686
- fields: [
1687
- { id: "iconLeft", type: "media", label: "Left icon", required: false, mediaKinds: ["image"] },
1688
- { id: "iconRight", type: "media", label: "Right icon", required: false, mediaKinds: ["image"] }
1689
- ]
1683
+ const iconFields = [
1684
+ {
1685
+ id: "iconLeft",
1686
+ type: "media",
1687
+ label: "Left icon",
1688
+ required: false,
1689
+ mediaKinds: ["image"],
1690
+ ui: { extras: true }
1691
+ },
1692
+ {
1693
+ id: "iconRight",
1694
+ type: "media",
1695
+ label: "Right icon",
1696
+ required: false,
1697
+ mediaKinds: ["image"],
1698
+ ui: { extras: true }
1690
1699
  }
1691
- };
1700
+ ];
1692
1701
  return {
1693
1702
  id: groupId,
1694
1703
  type: "group",
1695
1704
  label: groupLabel,
1696
1705
  ui: { layout: "grid", columns: 2, flattenInRepeater, hideLabel: !showGroupLabel },
1697
- schema: { fields: [...mainFields, iconsGroup] },
1706
+ schema: { fields: [...mainFields, ...iconFields] },
1698
1707
  required: false
1699
1708
  };
1700
1709
  }
@@ -2990,6 +2999,10 @@ var BACKGROUND_POSITION_PRESETS = [
2990
2999
  BACKGROUND_POSITION_PRESETS.map((p) => p.value);
2991
3000
 
2992
3001
  // ../blocks/src/system/fields/background.ts
3002
+ var BACKGROUND_WIDGETS = {
3003
+ COLOR: "backgroundColor",
3004
+ GRADIENT: "backgroundGradient"
3005
+ };
2993
3006
  function createBackgroundField(options = {}) {
2994
3007
  const {
2995
3008
  id = "background",
@@ -3015,8 +3028,7 @@ function createBackgroundField(options = {}) {
3015
3028
  required: false,
3016
3029
  multiline: false,
3017
3030
  ui: {
3018
- // Use BackgroundColorWidget via widget override
3019
- widget: "backgroundColor"
3031
+ widget: BACKGROUND_WIDGETS.COLOR
3020
3032
  }
3021
3033
  }
3022
3034
  ]
@@ -3033,11 +3045,11 @@ function createBackgroundField(options = {}) {
3033
3045
  id: "gradient",
3034
3046
  type: "text",
3035
3047
  label: "Gradient",
3036
- description: "CSS gradient value (e.g., linear-gradient(to right, #ff0000, #00ff00)).",
3048
+ description: "Select a gradient from theme presets.",
3037
3049
  required: false,
3038
- multiline: true,
3050
+ multiline: false,
3039
3051
  ui: {
3040
- placeholder: "linear-gradient(to right, #ff0000, #00ff00)"
3052
+ widget: BACKGROUND_WIDGETS.GRADIENT
3041
3053
  }
3042
3054
  }
3043
3055
  ]
@@ -3096,7 +3108,7 @@ function createBackgroundField(options = {}) {
3096
3108
  id: "position",
3097
3109
  type: "presetOrCustom",
3098
3110
  label: "Position",
3099
- description: 'Anchor point for the image. Relevant for "Fill" and "Custom size" options.',
3111
+ description: 'Anchor point for scaled images. For "Fill" mode, the image focus point (if set) takes precedence.',
3100
3112
  required: false,
3101
3113
  presets: [...BACKGROUND_POSITION_PRESETS],
3102
3114
  customInput: {
@@ -3187,6 +3199,8 @@ function sectionStylesField(options = {}) {
3187
3199
  required: false,
3188
3200
  schema: { fields: fields4 },
3189
3201
  ui: {
3202
+ // Render in block header instead of form body
3203
+ renderInHeader: true,
3190
3204
  modalConfig: {
3191
3205
  buttonLabel: label,
3192
3206
  description: "Configure background and spacing for this section.",
@@ -5643,111 +5657,6 @@ var riverbankSiteConfigSchema = zod.z.object({
5643
5657
 
5644
5658
  // src/cli/push-config.ts
5645
5659
  init_load_config();
5646
- async function pushToDashboard(dashboardUrl, siteId, apiKey, config3) {
5647
- const pushUrl = `${dashboardUrl}/api/sites/${siteId}/sdk-config`;
5648
- console.log(`Pushing config to ${pushUrl}...`);
5649
- let response;
5650
- try {
5651
- response = await fetch(pushUrl, {
5652
- method: "POST",
5653
- headers: {
5654
- "Content-Type": "application/json",
5655
- "Authorization": `Bearer ${apiKey}`
5656
- },
5657
- body: JSON.stringify({ config: config3 }),
5658
- signal: AbortSignal.timeout(3e4)
5659
- });
5660
- } catch (error) {
5661
- const message = error instanceof Error ? error.message : "Unknown error";
5662
- throw new Error(`Failed to connect to dashboard: ${message}`);
5663
- }
5664
- if (!response.ok) {
5665
- let errorMessage = `Dashboard returned ${response.status}`;
5666
- try {
5667
- const errorBody = await response.json();
5668
- if (errorBody.error) {
5669
- errorMessage = errorBody.error;
5670
- if (errorBody.details) {
5671
- errorMessage += ":\n" + errorBody.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
5672
- }
5673
- }
5674
- } catch {
5675
- }
5676
- throw new Error(errorMessage);
5677
- }
5678
- }
5679
- async function pushConfigAction(options) {
5680
- try {
5681
- const rawConfig = await loadConfigFile(options.config);
5682
- console.log("Validating config...");
5683
- const parseResult = riverbankSiteConfigSchema.safeParse(rawConfig);
5684
- if (!parseResult.success) {
5685
- console.error("Invalid config:");
5686
- for (const issue of parseResult.error.issues) {
5687
- console.error(` - ${issue.path.join(".")}: ${issue.message}`);
5688
- }
5689
- process.exit(1);
5690
- }
5691
- const conflicts = validateFieldIdConflicts(parseResult.data.blockFieldExtensions);
5692
- if (conflicts.length > 0) {
5693
- console.error("Field ID conflicts detected in blockFieldExtensions:");
5694
- for (const conflict of conflicts) {
5695
- console.error(` - ${conflict.message}`);
5696
- }
5697
- process.exit(1);
5698
- }
5699
- const { siteId } = parseResult.data;
5700
- const apiKey = resolveManagementApiKey(options.apiKey, options.isRemote);
5701
- await pushToDashboard(options.dashboard, siteId, apiKey, parseResult.data);
5702
- console.log("Config pushed successfully!");
5703
- } catch (error) {
5704
- console.error("Error:", error instanceof Error ? error.message : error);
5705
- process.exit(1);
5706
- }
5707
- }
5708
- function resolveDashboardUrl(cliOption, isRemote) {
5709
- const envVar = isRemote ? "RIVERBANK_REMOTE_DASHBOARD_URL" : "RIVERBANK_LOCAL_DASHBOARD_URL";
5710
- const url = cliOption || process.env[envVar];
5711
- if (!url) {
5712
- console.error("Error: Dashboard URL is required.");
5713
- console.error(`Provide --dashboard <url> or set ${envVar} environment variable.`);
5714
- process.exit(1);
5715
- }
5716
- return url;
5717
- }
5718
- function resolveManagementApiKey(cliOption, isRemote) {
5719
- const envVar = isRemote ? "RIVERBANK_REMOTE_MGMT_API_KEY" : "RIVERBANK_LOCAL_MGMT_API_KEY";
5720
- const apiKey = cliOption || process.env[envVar];
5721
- if (!apiKey) {
5722
- console.error("Error: Management API key is required.");
5723
- console.error(`Provide --api-key <key> or set ${envVar} environment variable.`);
5724
- process.exit(1);
5725
- }
5726
- if (!apiKey.startsWith("bld_mgmt_sk_")) {
5727
- console.error(`Error: Invalid management API key format for ${envVar}.`);
5728
- console.error("Expected key starting with bld_mgmt_sk_.");
5729
- process.exit(1);
5730
- }
5731
- return apiKey;
5732
- }
5733
- var pushConfigCommand = new commander.Command("push-config").description("Push SDK config to dashboard").option("--api-key <key>", "Management API key (or set RIVERBANK_*_MGMT_API_KEY)").option("--dashboard <url>", "Dashboard URL (or set RIVERBANK_*_DASHBOARD_URL env var)").option("--config <path>", "Path to config file (default: ./riverbank.config.ts)").addHelpText("after", `
5734
- Description:
5735
- Syncs your local riverbank.config.ts to the CMS dashboard, including:
5736
- - Custom blocks
5737
- - Block field extensions
5738
- - Block field options
5739
- - Content types, pages, entries, and navigation
5740
-
5741
- Examples:
5742
- $ npx riverbankcms push-config
5743
- $ npx riverbankcms push-config --api-key bld_mgmt_sk_... --dashboard https://www.riverbankcms.com
5744
- $ npx riverbankcms push-config --config ./src/riverbank.config.ts
5745
- `).action((options, command) => {
5746
- const globalOpts = command.optsWithGlobals();
5747
- const isRemote = globalOpts.remote ?? false;
5748
- const dashboard = resolveDashboardUrl(options.dashboard, isRemote);
5749
- return pushConfigAction({ ...options, dashboard, isRemote });
5750
- });
5751
5660
 
5752
5661
  // src/client/management/http.ts
5753
5662
  var ManagementApiError = class extends Error {
@@ -5759,6 +5668,9 @@ var ManagementApiError = class extends Error {
5759
5668
  this.statusCode = statusCode;
5760
5669
  }
5761
5670
  };
5671
+ function is404Error(error) {
5672
+ return error instanceof ManagementApiError && error.statusCode === 404;
5673
+ }
5762
5674
  function createHttpClient(config3) {
5763
5675
  const baseUrl = `${config3.dashboardUrl}/api/sdk/${config3.siteId}`;
5764
5676
  const timeout = config3.timeout ?? 3e4;
@@ -5864,7 +5776,7 @@ function createEntryOperations(http) {
5864
5776
  `/entries/${encodeURIComponent(contentType)}/${encodeURIComponent(identifier)}`
5865
5777
  );
5866
5778
  } catch (error) {
5867
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5779
+ if (is404Error(error)) {
5868
5780
  return null;
5869
5781
  }
5870
5782
  throw error;
@@ -5906,7 +5818,7 @@ function createPageOperations(http) {
5906
5818
  `/pages/${encodeURIComponent(identifier)}`
5907
5819
  );
5908
5820
  } catch (error) {
5909
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5821
+ if (is404Error(error)) {
5910
5822
  return null;
5911
5823
  }
5912
5824
  throw error;
@@ -5939,7 +5851,7 @@ function createBlockOperations(http) {
5939
5851
  `/pages/${encodeURIComponent(pageIdentifier)}/blocks/${encodeURIComponent(blockIdentifier)}`
5940
5852
  );
5941
5853
  } catch (error) {
5942
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5854
+ if (is404Error(error)) {
5943
5855
  return null;
5944
5856
  }
5945
5857
  throw error;
@@ -5978,7 +5890,7 @@ function createNavigationOperations(http) {
5978
5890
  `/navigation/${encodeURIComponent(name)}`
5979
5891
  );
5980
5892
  } catch (error) {
5981
- if (error instanceof Error && "statusCode" in error && error.statusCode === 404) {
5893
+ if (is404Error(error)) {
5982
5894
  return null;
5983
5895
  }
5984
5896
  throw error;
@@ -6047,6 +5959,9 @@ function createPullOperations(http) {
6047
5959
  truncationMessage: entriesResult?.meta?.truncationMessage
6048
5960
  }
6049
5961
  };
5962
+ },
5963
+ async siteInfo() {
5964
+ return http.get("/pull/site-info");
6050
5965
  }
6051
5966
  };
6052
5967
  }
@@ -6069,6 +5984,122 @@ function createIdentifiersOperations(http) {
6069
5984
  };
6070
5985
  }
6071
5986
 
5987
+ // src/client/management/media.ts
5988
+ function createMediaOperations(config3) {
5989
+ const baseUrl = `${config3.dashboardUrl}/api/sdk/${config3.siteId}`;
5990
+ const timeout = config3.timeout ?? 6e4;
5991
+ async function formDataRequest(path13, formData) {
5992
+ const url = `${baseUrl}${path13}`;
5993
+ const headers = {
5994
+ "Authorization": `Bearer ${config3.managementApiKey}`
5995
+ // Note: Don't set Content-Type - let fetch set it with boundary
5996
+ };
5997
+ let response;
5998
+ try {
5999
+ response = await fetch(url, {
6000
+ method: "POST",
6001
+ headers,
6002
+ body: formData,
6003
+ signal: AbortSignal.timeout(timeout)
6004
+ });
6005
+ } catch (error) {
6006
+ if (error instanceof Error && error.name === "TimeoutError") {
6007
+ throw new ManagementApiError(
6008
+ `Request timed out after ${timeout}ms`,
6009
+ "sdk:timeout",
6010
+ void 0,
6011
+ void 0
6012
+ );
6013
+ }
6014
+ throw new ManagementApiError(
6015
+ error instanceof Error ? error.message : "Network request failed",
6016
+ "sdk:network-error",
6017
+ void 0,
6018
+ void 0
6019
+ );
6020
+ }
6021
+ const json = await response.json();
6022
+ if (!response.ok || !json.success) {
6023
+ if (json.error) {
6024
+ throw new ManagementApiError(
6025
+ json.error.message,
6026
+ json.error.code,
6027
+ json.error.details,
6028
+ response.status
6029
+ );
6030
+ }
6031
+ throw new ManagementApiError(
6032
+ `Request failed with status ${response.status}`,
6033
+ "sdk:http-error",
6034
+ void 0,
6035
+ response.status
6036
+ );
6037
+ }
6038
+ return json.data;
6039
+ }
6040
+ async function jsonRequest(path13, body) {
6041
+ const url = `${baseUrl}${path13}`;
6042
+ let response;
6043
+ try {
6044
+ response = await fetch(url, {
6045
+ method: "POST",
6046
+ headers: {
6047
+ "Authorization": `Bearer ${config3.managementApiKey}`,
6048
+ "Content-Type": "application/json"
6049
+ },
6050
+ body: JSON.stringify(body),
6051
+ signal: AbortSignal.timeout(3e4)
6052
+ });
6053
+ } catch (error) {
6054
+ if (error instanceof Error && error.name === "TimeoutError") {
6055
+ throw new ManagementApiError(
6056
+ "Request timed out after 30000ms",
6057
+ "sdk:timeout",
6058
+ void 0,
6059
+ void 0
6060
+ );
6061
+ }
6062
+ throw new ManagementApiError(
6063
+ error instanceof Error ? error.message : "Network request failed",
6064
+ "sdk:network-error",
6065
+ void 0,
6066
+ void 0
6067
+ );
6068
+ }
6069
+ const json = await response.json();
6070
+ if (!response.ok || !json.success) {
6071
+ if (json.error) {
6072
+ throw new ManagementApiError(
6073
+ json.error.message,
6074
+ json.error.code,
6075
+ json.error.details,
6076
+ response.status
6077
+ );
6078
+ }
6079
+ throw new ManagementApiError(
6080
+ `Request failed with status ${response.status}`,
6081
+ "sdk:http-error",
6082
+ void 0,
6083
+ response.status
6084
+ );
6085
+ }
6086
+ return json.data;
6087
+ }
6088
+ return {
6089
+ async upload(file) {
6090
+ const formData = new FormData();
6091
+ const blob = new Blob([new Uint8Array(file.data)], { type: file.contentType });
6092
+ formData.append("file", blob, file.filename);
6093
+ formData.append("storagePath", file.storagePath);
6094
+ return formDataRequest("/media/upload", formData);
6095
+ },
6096
+ async exists(storagePath) {
6097
+ const result = await jsonRequest("/media/exists", { storagePath });
6098
+ return result.exists;
6099
+ }
6100
+ };
6101
+ }
6102
+
6072
6103
  // src/client/management/index.ts
6073
6104
  function createManagementClient(config3) {
6074
6105
  if (!config3.dashboardUrl) {
@@ -6100,11 +6131,16 @@ function createManagementClient(config3) {
6100
6131
  settings: createSettingsOperations(http),
6101
6132
  pull: createPullOperations(http),
6102
6133
  preview: createPreviewOperations(http),
6103
- identifiers: createIdentifiersOperations(http)
6134
+ identifiers: createIdentifiersOperations(http),
6135
+ media: createMediaOperations(config3)
6104
6136
  };
6105
6137
  }
6106
6138
 
6107
6139
  // src/cli/env.ts
6140
+ function getEnvVarName(key, remote) {
6141
+ const target = remote ? "REMOTE" : "LOCAL";
6142
+ return `RIVERBANK_${target}_${key}`;
6143
+ }
6108
6144
  function getEnvPrefix(target) {
6109
6145
  return target === "remote" ? "RIVERBANK_REMOTE" : "RIVERBANK_LOCAL";
6110
6146
  }
@@ -6115,12 +6151,16 @@ function requireEnv(name) {
6115
6151
  }
6116
6152
  return value;
6117
6153
  }
6154
+ function getEnv(name, fallback) {
6155
+ return process.env[name] ?? fallback;
6156
+ }
6118
6157
  function loadEnvironment(remote) {
6119
6158
  const target = remote ? "remote" : "local";
6120
6159
  const prefix = getEnvPrefix(target);
6121
6160
  const siteId = requireEnv(`${prefix}_SITE_ID`);
6122
6161
  const dashboardUrl = requireEnv(`${prefix}_DASHBOARD_URL`);
6123
6162
  const managementApiKey = requireEnv(`${prefix}_MGMT_API_KEY`);
6163
+ const supabaseUrl = getEnv(`${prefix}_SUPABASE_URL`);
6124
6164
  if (!managementApiKey.startsWith("bld_mgmt_sk_")) {
6125
6165
  throw new Error(
6126
6166
  `Invalid management API key format for ${prefix}_MGMT_API_KEY. Expected key starting with bld_mgmt_sk_`
@@ -6133,10 +6173,20 @@ function loadEnvironment(remote) {
6133
6173
  `Invalid dashboard URL in ${prefix}_DASHBOARD_URL: ${dashboardUrl}. Expected format: http://localhost:4000 or https://dashboard.example.com`
6134
6174
  );
6135
6175
  }
6176
+ if (supabaseUrl) {
6177
+ try {
6178
+ new URL(supabaseUrl);
6179
+ } catch {
6180
+ throw new Error(
6181
+ `Invalid Supabase URL in ${prefix}_SUPABASE_URL: ${supabaseUrl}. Expected format: http://127.0.0.1:54321 or https://xxx.supabase.co`
6182
+ );
6183
+ }
6184
+ }
6136
6185
  return {
6137
6186
  siteId,
6138
6187
  dashboardUrl,
6139
- managementApiKey
6188
+ managementApiKey,
6189
+ supabaseUrl
6140
6190
  };
6141
6191
  }
6142
6192
 
@@ -6513,6 +6563,112 @@ function createListCommand(config3) {
6513
6563
  );
6514
6564
  }
6515
6565
 
6566
+ // src/cli/push-config.ts
6567
+ async function pushToDashboard(output, dashboardUrl, siteId, apiKey, config3) {
6568
+ const pushUrl = `${dashboardUrl}/api/sites/${siteId}/sdk-config`;
6569
+ output.info(`Pushing config to ${pushUrl}...`);
6570
+ let response;
6571
+ try {
6572
+ response = await fetch(pushUrl, {
6573
+ method: "POST",
6574
+ headers: {
6575
+ "Content-Type": "application/json",
6576
+ "Authorization": `Bearer ${apiKey}`
6577
+ },
6578
+ body: JSON.stringify({ config: config3 }),
6579
+ signal: AbortSignal.timeout(3e4)
6580
+ });
6581
+ } catch (error) {
6582
+ const message = error instanceof Error ? error.message : "Unknown error";
6583
+ throw new Error(`Failed to connect to dashboard: ${message}`);
6584
+ }
6585
+ if (!response.ok) {
6586
+ let errorMessage = `Dashboard returned ${response.status}`;
6587
+ try {
6588
+ const errorBody = await response.json();
6589
+ if (errorBody.error) {
6590
+ errorMessage = errorBody.error;
6591
+ if (errorBody.details) {
6592
+ errorMessage += ":\n" + errorBody.details.map((d) => ` - ${d.path}: ${d.message}`).join("\n");
6593
+ }
6594
+ }
6595
+ } catch {
6596
+ }
6597
+ throw new Error(errorMessage);
6598
+ }
6599
+ }
6600
+ async function pushConfigAction(output, options) {
6601
+ try {
6602
+ const rawConfig = await loadConfigFile(options.config);
6603
+ output.info("Validating config...");
6604
+ const parseResult = riverbankSiteConfigSchema.safeParse(rawConfig);
6605
+ if (!parseResult.success) {
6606
+ output.error("Invalid config", {
6607
+ issues: parseResult.error.issues.map((issue) => ({
6608
+ path: issue.path.join("."),
6609
+ message: issue.message
6610
+ }))
6611
+ });
6612
+ }
6613
+ const conflicts = validateFieldIdConflicts(parseResult.data.blockFieldExtensions);
6614
+ if (conflicts.length > 0) {
6615
+ output.error("Field ID conflicts detected in blockFieldExtensions", {
6616
+ conflicts: conflicts.map((c) => c.message)
6617
+ });
6618
+ }
6619
+ const { siteId } = parseResult.data;
6620
+ const apiKey = resolveManagementApiKey(output, options.apiKey, options.isRemote);
6621
+ await pushToDashboard(output, options.dashboard, siteId, apiKey, parseResult.data);
6622
+ output.success("Config pushed successfully!");
6623
+ } catch (error) {
6624
+ const message = error instanceof Error ? error.message : String(error);
6625
+ output.error(message);
6626
+ }
6627
+ }
6628
+ function resolveDashboardUrl(output, cliOption, isRemote) {
6629
+ const envVar = getEnvVarName("DASHBOARD_URL", isRemote);
6630
+ const url = cliOption || process.env[envVar];
6631
+ if (!url) {
6632
+ output.error("Dashboard URL is required", {
6633
+ suggestion: `Provide --dashboard <url> or set ${envVar} environment variable.`
6634
+ });
6635
+ }
6636
+ return url;
6637
+ }
6638
+ function resolveManagementApiKey(output, cliOption, isRemote) {
6639
+ const envVar = getEnvVarName("MGMT_API_KEY", isRemote);
6640
+ const apiKey = cliOption || process.env[envVar];
6641
+ if (!apiKey) {
6642
+ output.error("Management API key is required", {
6643
+ suggestion: `Provide --api-key <key> or set ${envVar} environment variable.`
6644
+ });
6645
+ }
6646
+ if (!apiKey.startsWith("bld_mgmt_sk_")) {
6647
+ output.error("Invalid management API key format", {
6648
+ expected: "Key starting with bld_mgmt_sk_",
6649
+ suggestion: `Check your ${envVar} environment variable.`
6650
+ });
6651
+ }
6652
+ return apiKey;
6653
+ }
6654
+ var pushConfigCommand = new commander.Command("push-config").description("Push SDK config to dashboard").option("--api-key <key>", "Management API key (or set RIVERBANK_*_MGMT_API_KEY)").option("--dashboard <url>", "Dashboard URL (or set RIVERBANK_*_DASHBOARD_URL env var)").option("--config <path>", "Path to config file (default: ./riverbank.config.ts)").addHelpText("after", `
6655
+ Description:
6656
+ Syncs your local riverbank.config.ts to the CMS dashboard, including:
6657
+ - Custom blocks
6658
+ - Block field extensions
6659
+ - Block field options
6660
+ - Content types, pages, entries, and navigation
6661
+
6662
+ Examples:
6663
+ $ npx riverbankcms push-config
6664
+ $ npx riverbankcms push-config --api-key bld_mgmt_sk_... --dashboard https://www.riverbankcms.com
6665
+ $ npx riverbankcms push-config --config ./src/riverbank.config.ts
6666
+ `).action((options, command) => {
6667
+ const { output, isRemote } = getOutputContext(command);
6668
+ const dashboard = resolveDashboardUrl(output, options.dashboard, isRemote);
6669
+ return pushConfigAction(output, { ...options, dashboard, isRemote });
6670
+ });
6671
+
6516
6672
  // src/cli/sync/mapper.ts
6517
6673
  function stripNavigationItemIds(items) {
6518
6674
  return items.map((item) => {
@@ -6616,6 +6772,53 @@ async function writeSettings(contentDir, pulledSettings) {
6616
6772
  await writeJsonFile(filePath, pulledSettings.settings);
6617
6773
  return filePath;
6618
6774
  }
6775
+ async function updateMetadataAfterPush(contentDir, remoteContent) {
6776
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6777
+ await ensureDir(metaDir);
6778
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6779
+ for (const [contentType, entries] of Object.entries(remoteContent.entries)) {
6780
+ const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
6781
+ const entriesMeta = {};
6782
+ for (const entry of entries) {
6783
+ entriesMeta[entry.identifier] = {
6784
+ createdAt: entry.createdAt,
6785
+ updatedAt: entry.updatedAt
6786
+ };
6787
+ }
6788
+ await writeJsonFile(metaPath, {
6789
+ pulledAt: now,
6790
+ entries: entriesMeta
6791
+ });
6792
+ }
6793
+ if (remoteContent.pages.length > 0) {
6794
+ const metaPath = path9__namespace.join(metaDir, "pages.json");
6795
+ const pagesMeta = {};
6796
+ for (const page of remoteContent.pages) {
6797
+ pagesMeta[page.identifier] = {
6798
+ createdAt: page.createdAt,
6799
+ updatedAt: page.updatedAt
6800
+ };
6801
+ }
6802
+ await writeJsonFile(metaPath, {
6803
+ pulledAt: now,
6804
+ pages: pagesMeta
6805
+ });
6806
+ }
6807
+ if (remoteContent.navigation.length > 0) {
6808
+ const metaPath = path9__namespace.join(metaDir, "navigation.json");
6809
+ const menusMeta = {};
6810
+ for (const menu of remoteContent.navigation) {
6811
+ menusMeta[menu.name] = {
6812
+ createdAt: menu.createdAt,
6813
+ updatedAt: menu.updatedAt
6814
+ };
6815
+ }
6816
+ await writeJsonFile(metaPath, {
6817
+ pulledAt: now,
6818
+ menus: menusMeta
6819
+ });
6820
+ }
6821
+ }
6619
6822
  async function fileExists(filePath) {
6620
6823
  try {
6621
6824
  await fs3__namespace.access(filePath);
@@ -6764,6 +6967,107 @@ async function readNavigationMeta(contentDir) {
6764
6967
  }
6765
6968
  }
6766
6969
 
6970
+ // src/cli/sync/media.ts
6971
+ var DEFAULT_RETRY_CONFIG = {
6972
+ maxRetries: 3,
6973
+ baseDelayMs: 1e3,
6974
+ // 1 second
6975
+ maxDelayMs: 1e4
6976
+ // 10 seconds
6977
+ };
6978
+ function sleep(ms) {
6979
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
6980
+ }
6981
+ function getBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
6982
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
6983
+ const jitter = exponentialDelay * (0.75 + Math.random() * 0.5);
6984
+ return Math.min(jitter, maxDelayMs);
6985
+ }
6986
+ async function withRetry(fn, options = {}) {
6987
+ const { maxRetries, baseDelayMs, maxDelayMs } = { ...DEFAULT_RETRY_CONFIG, ...options };
6988
+ let lastError;
6989
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
6990
+ try {
6991
+ return await fn();
6992
+ } catch (error) {
6993
+ lastError = error instanceof Error ? error : new Error(String(error));
6994
+ if (attempt < maxRetries) {
6995
+ const delay = getBackoffDelay(attempt, baseDelayMs, maxDelayMs);
6996
+ options.onRetry?.(attempt + 1, lastError);
6997
+ await sleep(delay);
6998
+ }
6999
+ }
7000
+ }
7001
+ throw lastError;
7002
+ }
7003
+ function extractMediaPaths(data) {
7004
+ const paths = /* @__PURE__ */ new Set();
7005
+ function walk(value) {
7006
+ if (value === null || value === void 0) {
7007
+ return;
7008
+ }
7009
+ if (Array.isArray(value)) {
7010
+ for (const item of value) {
7011
+ walk(item);
7012
+ }
7013
+ return;
7014
+ }
7015
+ if (typeof value === "object") {
7016
+ const obj = value;
7017
+ if (typeof obj.storagePath === "string" && obj.storagePath) {
7018
+ paths.add(obj.storagePath);
7019
+ }
7020
+ for (const key of Object.keys(obj)) {
7021
+ walk(obj[key]);
7022
+ }
7023
+ }
7024
+ }
7025
+ walk(data);
7026
+ return paths;
7027
+ }
7028
+ function buildStorageUrl(supabaseUrl, relativePath, siteId, bucket = "media") {
7029
+ const baseUrl = supabaseUrl.replace(/\/$/, "");
7030
+ const fullPath = `sites/${siteId}/${relativePath}`;
7031
+ return `${baseUrl}/storage/v1/object/public/${bucket}/${fullPath}`;
7032
+ }
7033
+ async function downloadMedia(url, options) {
7034
+ try {
7035
+ return await withRetry(
7036
+ async () => {
7037
+ const response = await fetch(url);
7038
+ if (!response.ok) {
7039
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
7040
+ throw new DownloadError(`HTTP ${response.status}`, response.status, false);
7041
+ }
7042
+ throw new DownloadError(`HTTP ${response.status}`, response.status, true);
7043
+ }
7044
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
7045
+ const arrayBuffer = await response.arrayBuffer();
7046
+ const data = Buffer.from(arrayBuffer);
7047
+ return { data, contentType };
7048
+ },
7049
+ {
7050
+ maxRetries: options?.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
7051
+ onRetry: (attempt, error) => {
7052
+ console.warn(`[media-sync] Retry ${attempt} for ${url}: ${error.message}`);
7053
+ }
7054
+ }
7055
+ );
7056
+ } catch (error) {
7057
+ const message = error instanceof Error ? error.message : String(error);
7058
+ console.error(`[media-sync] Download failed after retries: ${url} - ${message}`);
7059
+ return null;
7060
+ }
7061
+ }
7062
+ var DownloadError = class extends Error {
7063
+ constructor(message, status, retryable) {
7064
+ super(message);
7065
+ this.status = status;
7066
+ this.retryable = retryable;
7067
+ this.name = "DownloadError";
7068
+ }
7069
+ };
7070
+
6767
7071
  // src/cli/commands/pull.ts
6768
7072
  var DEFAULT_PAGE_LIMIT = 500;
6769
7073
  async function pullEntriesWithPagination(client, contentType, output) {
@@ -6848,21 +7152,105 @@ async function fetchAllContentPaginated(client, contentTypes, output) {
6848
7152
  meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: allMeta }
6849
7153
  };
6850
7154
  }
6851
- var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").option("--force", "Overwrite existing files without prompting").option("--yes", "Skip confirmation prompt (same as --force)").addHelpText("after", `
7155
+ async function syncMediaFiles(content, sourceSupabaseUrl, sourceSiteId, targetSiteId, targetClient, output) {
7156
+ const mediaPaths = extractMediaPaths(content);
7157
+ if (mediaPaths.size === 0) {
7158
+ output.info("No media files found in content");
7159
+ return;
7160
+ }
7161
+ output.info(`Found ${mediaPaths.size} media files to sync`);
7162
+ let synced = 0;
7163
+ let skipped = 0;
7164
+ let failed = 0;
7165
+ for (const relativePath of mediaPaths) {
7166
+ const filename = relativePath.split("/").pop() ?? "file";
7167
+ const targetPath = `sites/${targetSiteId}/${relativePath}`;
7168
+ try {
7169
+ const exists = await targetClient.media.exists(targetPath);
7170
+ if (exists) {
7171
+ skipped++;
7172
+ continue;
7173
+ }
7174
+ } catch (error) {
7175
+ if (process.env.DEBUG) {
7176
+ const message = error instanceof Error ? error.message : "Unknown error";
7177
+ console.warn(`[media-sync] Could not check if ${filename} exists: ${message}`);
7178
+ }
7179
+ }
7180
+ const url = buildStorageUrl(sourceSupabaseUrl, relativePath, sourceSiteId);
7181
+ const downloaded = await downloadMedia(url);
7182
+ if (!downloaded) {
7183
+ output.warn(` Failed to download: ${filename}`);
7184
+ failed++;
7185
+ continue;
7186
+ }
7187
+ try {
7188
+ await targetClient.media.upload({
7189
+ data: downloaded.data,
7190
+ filename,
7191
+ contentType: downloaded.contentType,
7192
+ storagePath: targetPath
7193
+ });
7194
+ synced++;
7195
+ } catch (error) {
7196
+ output.warn(` Failed to upload: ${filename}`);
7197
+ failed++;
7198
+ }
7199
+ }
7200
+ output.info(`Media sync: ${synced} synced, ${skipped} skipped (already exist), ${failed} failed`);
7201
+ }
7202
+ var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").option("--force", "Overwrite existing files without prompting").option("--yes", "Skip confirmation prompt (same as --force)").option("--sync-media", "Sync media files from source to target environment").addHelpText("after", `
6852
7203
  Examples:
6853
7204
  $ riverbankcms pull # Pull all content
6854
7205
  $ riverbankcms pull --remote # Pull from production
7206
+ $ riverbankcms pull --remote --sync-media # Pull from production and sync media to local
6855
7207
  $ riverbankcms pull entries # Pull all entries
6856
7208
  $ riverbankcms pull entries blog-post # Pull specific content type
6857
7209
  $ riverbankcms pull pages # Pull pages with blocks
6858
7210
  $ riverbankcms pull navigation # Pull navigation menus
6859
7211
  $ riverbankcms pull settings # Pull site settings
6860
7212
  $ riverbankcms pull --output ./src/content # Custom output directory
7213
+
7214
+ Media Sync:
7215
+ When using --sync-media, media files are:
7216
+ 1. Downloaded from the source environment's Supabase storage
7217
+ 2. Uploaded to the target environment via the management API
7218
+
7219
+ The storage URL is automatically fetched from the source CMS API.
7220
+ No additional environment variables are required beyond the standard
7221
+ RIVERBANK_*_DASHBOARD_URL, RIVERBANK_*_SITE_ID, and RIVERBANK_*_MGMT_API_KEY.
6861
7222
  `).action(
6862
7223
  withErrorHandling(
6863
7224
  async (scope, type, options, command) => {
6864
- const { output, client } = createCommandContext(command);
7225
+ const { output, client, isRemote } = createCommandContext(command);
6865
7226
  const contentDir = path9__namespace.resolve(options.output ?? "./content");
7227
+ let targetClient = null;
7228
+ let sourceSupabaseUrl = null;
7229
+ let sourceSiteId = null;
7230
+ let targetSiteId = null;
7231
+ if (options.syncMedia) {
7232
+ const sourceEnv = loadEnvironment(isRemote);
7233
+ const targetEnv = loadEnvironment(!isRemote);
7234
+ output.info("Fetching storage configuration from source...");
7235
+ try {
7236
+ const siteInfo = await client.pull.siteInfo();
7237
+ sourceSupabaseUrl = siteInfo.supabaseUrl;
7238
+ } catch (error) {
7239
+ const message = error instanceof Error ? error.message : "Unknown error";
7240
+ output.error(`Failed to get storage configuration from source: ${message}`, {
7241
+ suggestion: "Ensure the source CMS is running and accessible"
7242
+ });
7243
+ return;
7244
+ }
7245
+ sourceSiteId = sourceEnv.siteId;
7246
+ targetSiteId = targetEnv.siteId;
7247
+ targetClient = createManagementClient({
7248
+ dashboardUrl: targetEnv.dashboardUrl,
7249
+ managementApiKey: targetEnv.managementApiKey,
7250
+ siteId: targetEnv.siteId
7251
+ });
7252
+ output.info(`Media sync enabled: ${isRemote ? "remote" : "local"} -> ${isRemote ? "local" : "remote"}`);
7253
+ }
6866
7254
  if (await contentDirExists(contentDir) && !options.force && !options.yes) {
6867
7255
  if (!process.stdin.isTTY) {
6868
7256
  output.error("Content directory already exists and --yes not specified", {
@@ -6945,6 +7333,17 @@ Examples:
6945
7333
  const contentTypes = Object.keys(result.entries);
6946
7334
  result = await fetchAllContentPaginated(client, contentTypes, output);
6947
7335
  }
7336
+ if (targetClient && sourceSupabaseUrl && sourceSiteId && targetSiteId) {
7337
+ output.info("Syncing media files...");
7338
+ await syncMediaFiles(
7339
+ result,
7340
+ sourceSupabaseUrl,
7341
+ sourceSiteId,
7342
+ targetSiteId,
7343
+ targetClient,
7344
+ output
7345
+ );
7346
+ }
6948
7347
  const { totalCount: totalEntries } = await writeAllEntries(
6949
7348
  contentDir,
6950
7349
  result.entries,
@@ -7943,6 +8342,43 @@ Safety:
7943
8342
  output
7944
8343
  );
7945
8344
  reportSyncResults(output, result, result.errors.length > 0);
8345
+ if (result.errors.length === 0) {
8346
+ try {
8347
+ output.info("Updating local metadata...");
8348
+ const freshRemote = await client.pull.all();
8349
+ const entriesForMeta = {};
8350
+ if (freshRemote.meta.entries) {
8351
+ for (const [key, meta] of Object.entries(freshRemote.meta.entries)) {
8352
+ const [contentType, identifier] = key.split(":");
8353
+ if (contentType && identifier) {
8354
+ if (!entriesForMeta[contentType]) {
8355
+ entriesForMeta[contentType] = [];
8356
+ }
8357
+ entriesForMeta[contentType].push({
8358
+ identifier,
8359
+ updatedAt: meta.updatedAt,
8360
+ createdAt: meta.createdAt
8361
+ });
8362
+ }
8363
+ }
8364
+ }
8365
+ await updateMetadataAfterPush(contentDir, {
8366
+ entries: entriesForMeta,
8367
+ pages: freshRemote.pages.map((p) => ({
8368
+ identifier: p.identifier,
8369
+ updatedAt: p.updatedAt,
8370
+ createdAt: p.createdAt
8371
+ })),
8372
+ navigation: freshRemote.navigation.map((n) => ({
8373
+ name: n.name,
8374
+ updatedAt: n.updatedAt,
8375
+ createdAt: n.createdAt
8376
+ }))
8377
+ });
8378
+ } catch (metaError) {
8379
+ output.warn('Push succeeded but metadata update failed. Run "pull" to sync metadata.');
8380
+ }
8381
+ }
7946
8382
  } catch (error) {
7947
8383
  handleCommandError(error, output);
7948
8384
  }
@@ -11042,7 +11478,7 @@ var SimpleCache = class {
11042
11478
  };
11043
11479
 
11044
11480
  // src/version.ts
11045
- var SDK_VERSION = "0.8.0";
11481
+ var SDK_VERSION = "0.8.1";
11046
11482
 
11047
11483
  // src/client/error.ts
11048
11484
  var RiverbankApiError = class _RiverbankApiError extends Error {
@@ -11189,7 +11625,7 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
11189
11625
  };
11190
11626
 
11191
11627
  // src/client/resilience.ts
11192
- var DEFAULT_RETRY_CONFIG = {
11628
+ var DEFAULT_RETRY_CONFIG2 = {
11193
11629
  maxAttempts: 3,
11194
11630
  baseDelayMs: 200,
11195
11631
  maxDelayMs: 2e3,
@@ -11272,9 +11708,9 @@ function isTransientError(error) {
11272
11708
  return true;
11273
11709
  }
11274
11710
  function calculateBackoff(attempt, config3) {
11275
- const baseDelayMs = config3.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs;
11276
- const maxDelayMs = config3.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs;
11277
- const jitter = config3.jitter ?? DEFAULT_RETRY_CONFIG.jitter;
11711
+ const baseDelayMs = config3.baseDelayMs ?? DEFAULT_RETRY_CONFIG2.baseDelayMs;
11712
+ const maxDelayMs = config3.maxDelayMs ?? DEFAULT_RETRY_CONFIG2.maxDelayMs;
11713
+ const jitter = config3.jitter ?? DEFAULT_RETRY_CONFIG2.jitter;
11278
11714
  const exponential = baseDelayMs * Math.pow(2, attempt - 1);
11279
11715
  const capped = Math.min(exponential, maxDelayMs);
11280
11716
  if (jitter === "full") {
@@ -11378,7 +11814,7 @@ var CircuitBreaker = class {
11378
11814
  }
11379
11815
  };
11380
11816
  async function fetchWithTimeoutAndRetry(fetcher, config3) {
11381
- const maxAttempts = config3.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts;
11817
+ const maxAttempts = config3.maxAttempts ?? DEFAULT_RETRY_CONFIG2.maxAttempts;
11382
11818
  const requestTimeoutMs = config3.requestTimeoutMs ?? 8e3;
11383
11819
  let lastError;
11384
11820
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -11399,7 +11835,7 @@ async function fetchWithTimeoutAndRetry(fetcher, config3) {
11399
11835
  }
11400
11836
  if (attempt < maxAttempts) {
11401
11837
  const delay = getRetryDelay(error, attempt, config3);
11402
- await sleep(delay);
11838
+ await sleep2(delay);
11403
11839
  }
11404
11840
  }
11405
11841
  }
@@ -11430,7 +11866,7 @@ function getRetryDelay(error, attempt, config3) {
11430
11866
  }
11431
11867
  return calculateBackoff(attempt, config3);
11432
11868
  }
11433
- function sleep(ms) {
11869
+ function sleep2(ms) {
11434
11870
  return new Promise((resolve8) => setTimeout(resolve8, ms));
11435
11871
  }
11436
11872
  var CircuitOpenError = class extends Error {
@@ -11565,10 +12001,10 @@ function createRiverbankClient(config3) {
11565
12001
  const staleTtlMs = (config3.resilience?.staleTtlSec ?? 300) * 1e3;
11566
12002
  const requestTimeoutMs = config3.resilience?.requestTimeoutMs ?? (typeof window !== "undefined" ? DEFAULT_BROWSER_TIMEOUT_MS : DEFAULT_SERVER_TIMEOUT_MS);
11567
12003
  const retryConfig = {
11568
- maxAttempts: config3.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts,
11569
- baseDelayMs: config3.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,
11570
- maxDelayMs: config3.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs,
11571
- jitter: config3.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG.jitter,
12004
+ maxAttempts: config3.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG2.maxAttempts,
12005
+ baseDelayMs: config3.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG2.baseDelayMs,
12006
+ maxDelayMs: config3.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG2.maxDelayMs,
12007
+ jitter: config3.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG2.jitter,
11572
12008
  retryOn: config3.resilience?.retry?.retryOn
11573
12009
  };
11574
12010
  const circuitBreakerConfig = {