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