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