canopycms 0.0.10 → 0.0.12
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 +24 -27
- package/dist/ai/handler.d.ts +3 -3
- package/dist/ai/handler.d.ts.map +1 -1
- package/dist/ai/handler.js +6 -9
- package/dist/ai/handler.js.map +1 -1
- package/dist/ai/resolve-branch.d.ts +1 -2
- package/dist/ai/resolve-branch.d.ts.map +1 -1
- package/dist/ai/resolve-branch.js +8 -9
- package/dist/ai/resolve-branch.js.map +1 -1
- package/dist/api/branch.js +2 -2
- package/dist/api/branch.js.map +1 -1
- package/dist/api/github-sync.js +0 -2
- package/dist/api/github-sync.js.map +1 -1
- package/dist/api/settings-helpers.d.ts +3 -5
- package/dist/api/settings-helpers.d.ts.map +1 -1
- package/dist/api/settings-helpers.js +6 -19
- package/dist/api/settings-helpers.js.map +1 -1
- package/dist/auth/caching-auth-plugin.d.ts +7 -1
- package/dist/auth/caching-auth-plugin.d.ts.map +1 -1
- package/dist/auth/caching-auth-plugin.js +31 -3
- package/dist/auth/caching-auth-plugin.js.map +1 -1
- package/dist/auth/plugin.d.ts +1 -1
- package/dist/authorization/types.d.ts +1 -1
- package/dist/branch-registry.js +1 -1
- package/dist/branch-registry.js.map +1 -1
- package/dist/branch-schema-cache.d.ts +8 -13
- package/dist/branch-schema-cache.d.ts.map +1 -1
- package/dist/branch-schema-cache.js +55 -44
- package/dist/branch-schema-cache.js.map +1 -1
- package/dist/branch-workspace.d.ts +3 -0
- package/dist/branch-workspace.d.ts.map +1 -1
- package/dist/branch-workspace.js +20 -0
- package/dist/branch-workspace.js.map +1 -1
- package/dist/cli/cli.d.ts +20 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +196 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/generate-ai-content.js +1501 -723
- package/dist/cli/init.d.ts +2 -3
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +258 -2861
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/sync.d.ts +33 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +510 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/config/schemas/config.d.ts +5 -5
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +1 -1
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config-test.d.ts.map +1 -1
- package/dist/config-test.js +0 -1
- package/dist/config-test.js.map +1 -1
- package/dist/content-reader.js +1 -1
- package/dist/content-reader.js.map +1 -1
- package/dist/editor/BranchManager.d.ts.map +1 -1
- package/dist/editor/BranchManager.js +1 -3
- package/dist/editor/BranchManager.js.map +1 -1
- package/dist/git-manager.d.ts +2 -3
- package/dist/git-manager.d.ts.map +1 -1
- package/dist/git-manager.js +12 -4
- package/dist/git-manager.js.map +1 -1
- package/dist/operating-mode/client-safe-strategy.d.ts +1 -12
- package/dist/operating-mode/client-safe-strategy.d.ts.map +1 -1
- package/dist/operating-mode/client-safe-strategy.js +5 -42
- package/dist/operating-mode/client-safe-strategy.js.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.js +10 -68
- package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
- package/dist/operating-mode/index.d.ts +3 -3
- package/dist/operating-mode/index.d.ts.map +1 -1
- package/dist/operating-mode/index.js +2 -2
- package/dist/operating-mode/types.d.ts +2 -6
- package/dist/operating-mode/types.d.ts.map +1 -1
- package/dist/services.d.ts +6 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +52 -40
- package/dist/services.js.map +1 -1
- package/dist/settings-branch-utils.d.ts +2 -2
- package/dist/settings-branch-utils.js +3 -3
- package/dist/settings-branch-utils.js.map +1 -1
- package/dist/settings-workspace.d.ts +1 -2
- package/dist/settings-workspace.d.ts.map +1 -1
- package/dist/settings-workspace.js +1 -2
- package/dist/settings-workspace.js.map +1 -1
- package/dist/utils/fs.d.ts +3 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +15 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +17 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/worker/task-queue-config.d.ts +2 -4
- package/dist/worker/task-queue-config.d.ts.map +1 -1
- package/dist/worker/task-queue-config.js +3 -7
- package/dist/worker/task-queue-config.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,13 +1,701 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// dist/paths/normalize.js
|
|
12
|
+
function normalizeFilesystemPath(path13) {
|
|
13
|
+
return path13.split(/[\\/]+/).filter(Boolean).join("/");
|
|
14
|
+
}
|
|
15
|
+
function hasTraversalSequence(path13) {
|
|
16
|
+
const normalized = normalizeFilesystemPath(path13);
|
|
17
|
+
return normalized.includes("..");
|
|
18
|
+
}
|
|
19
|
+
function createLogicalPath(...segments) {
|
|
20
|
+
const normalized = segments.map((s) => normalizeFilesystemPath(s)).filter(Boolean).join("/");
|
|
21
|
+
if (hasTraversalSequence(normalized)) {
|
|
22
|
+
throw new Error(`Invalid path: contains traversal sequence: ${normalized}`);
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
var init_normalize = __esm({
|
|
27
|
+
"dist/paths/normalize.js"() {
|
|
28
|
+
"use strict";
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// dist/operating-mode/client-safe-strategy.js
|
|
33
|
+
function clientOperatingStrategy(mode) {
|
|
34
|
+
const cached = clientStrategyCache.get(mode);
|
|
35
|
+
if (cached)
|
|
36
|
+
return cached;
|
|
37
|
+
let strategy;
|
|
38
|
+
switch (mode) {
|
|
39
|
+
case "prod":
|
|
40
|
+
strategy = new ProdClientSafeStrategy();
|
|
41
|
+
break;
|
|
42
|
+
case "dev":
|
|
43
|
+
strategy = new DevClientSafeStrategy();
|
|
44
|
+
break;
|
|
45
|
+
default: {
|
|
46
|
+
const _exhaustive = mode;
|
|
47
|
+
throw new Error(`Unknown operating mode: ${_exhaustive}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
clientStrategyCache.set(mode, strategy);
|
|
51
|
+
return strategy;
|
|
52
|
+
}
|
|
53
|
+
function clearClientStrategyCache() {
|
|
54
|
+
clientStrategyCache.clear();
|
|
55
|
+
}
|
|
56
|
+
var ProdClientSafeStrategy, DevClientSafeStrategy, clientStrategyCache;
|
|
57
|
+
var init_client_safe_strategy = __esm({
|
|
58
|
+
"dist/operating-mode/client-safe-strategy.js"() {
|
|
59
|
+
"use strict";
|
|
60
|
+
ProdClientSafeStrategy = class {
|
|
61
|
+
constructor() {
|
|
62
|
+
this.mode = "prod";
|
|
63
|
+
}
|
|
64
|
+
// UI Feature Flags
|
|
65
|
+
supportsBranching() {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
supportsStatusBadge() {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
supportsComments() {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
supportsPullRequests() {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
// Simple Data
|
|
78
|
+
getPermissionsFileName() {
|
|
79
|
+
return "permissions.json";
|
|
80
|
+
}
|
|
81
|
+
getGroupsFileName() {
|
|
82
|
+
return "groups.json";
|
|
83
|
+
}
|
|
84
|
+
shouldCommit() {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
shouldPush() {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
DevClientSafeStrategy = class {
|
|
92
|
+
constructor() {
|
|
93
|
+
this.mode = "dev";
|
|
94
|
+
}
|
|
95
|
+
// UI Feature Flags
|
|
96
|
+
supportsBranching() {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
supportsStatusBadge() {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
supportsComments() {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
supportsPullRequests() {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// Simple Data
|
|
109
|
+
getPermissionsFileName() {
|
|
110
|
+
return "permissions.json";
|
|
111
|
+
}
|
|
112
|
+
getGroupsFileName() {
|
|
113
|
+
return "groups.json";
|
|
114
|
+
}
|
|
115
|
+
shouldCommit() {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
shouldPush() {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
clientStrategyCache = /* @__PURE__ */ new Map();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// dist/config/types.js
|
|
127
|
+
var primitiveFieldTypes, fieldTypes;
|
|
128
|
+
var init_types = __esm({
|
|
129
|
+
"dist/config/types.js"() {
|
|
130
|
+
"use strict";
|
|
131
|
+
primitiveFieldTypes = [
|
|
132
|
+
"string",
|
|
133
|
+
"number",
|
|
134
|
+
"boolean",
|
|
135
|
+
"datetime",
|
|
136
|
+
"rich-text",
|
|
137
|
+
"markdown",
|
|
138
|
+
"mdx",
|
|
139
|
+
"image",
|
|
140
|
+
"code"
|
|
141
|
+
];
|
|
142
|
+
fieldTypes = [
|
|
143
|
+
...primitiveFieldTypes,
|
|
144
|
+
"select",
|
|
145
|
+
"reference",
|
|
146
|
+
"object",
|
|
147
|
+
"block"
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// dist/config/schemas/field.js
|
|
153
|
+
import { z } from "zod";
|
|
154
|
+
var fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema;
|
|
155
|
+
var init_field = __esm({
|
|
156
|
+
"dist/config/schemas/field.js"() {
|
|
157
|
+
"use strict";
|
|
158
|
+
init_types();
|
|
159
|
+
fieldBaseSchema = z.object({
|
|
160
|
+
name: z.string().min(1),
|
|
161
|
+
label: z.string().optional(),
|
|
162
|
+
description: z.string().optional(),
|
|
163
|
+
required: z.boolean().optional(),
|
|
164
|
+
list: z.boolean().optional()
|
|
165
|
+
});
|
|
166
|
+
selectOptionSchema = z.union([
|
|
167
|
+
z.string(),
|
|
168
|
+
z.object({
|
|
169
|
+
label: z.string().min(1),
|
|
170
|
+
value: z.string().min(1)
|
|
171
|
+
})
|
|
172
|
+
]);
|
|
173
|
+
referenceOptionSchema = z.union([
|
|
174
|
+
z.string(),
|
|
175
|
+
z.object({
|
|
176
|
+
label: z.string().min(1),
|
|
177
|
+
value: z.string().min(1)
|
|
178
|
+
})
|
|
179
|
+
]);
|
|
180
|
+
primitiveFieldSchema = fieldBaseSchema.extend({
|
|
181
|
+
type: z.enum(primitiveFieldTypes)
|
|
182
|
+
});
|
|
183
|
+
selectFieldSchema = fieldBaseSchema.extend({
|
|
184
|
+
type: z.literal("select"),
|
|
185
|
+
options: z.array(selectOptionSchema).min(1)
|
|
186
|
+
});
|
|
187
|
+
referenceFieldSchema = fieldBaseSchema.extend({
|
|
188
|
+
type: z.literal("reference"),
|
|
189
|
+
collections: z.array(z.string().min(1)).min(1),
|
|
190
|
+
displayField: z.string().min(1).optional(),
|
|
191
|
+
options: z.array(referenceOptionSchema).optional()
|
|
192
|
+
});
|
|
193
|
+
fieldHolder = [z.never()];
|
|
194
|
+
blockSchema = z.object({
|
|
195
|
+
name: z.string().min(1),
|
|
196
|
+
label: z.string().optional(),
|
|
197
|
+
description: z.string().optional(),
|
|
198
|
+
fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
|
|
199
|
+
});
|
|
200
|
+
blockFieldSchema = fieldBaseSchema.extend({
|
|
201
|
+
type: z.literal("block"),
|
|
202
|
+
templates: z.array(blockSchema).min(1)
|
|
203
|
+
});
|
|
204
|
+
objectFieldSchema = fieldBaseSchema.extend({
|
|
205
|
+
type: z.literal("object"),
|
|
206
|
+
fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
|
|
207
|
+
});
|
|
208
|
+
customFieldSchema = z.lazy(() => fieldBaseSchema.extend({
|
|
209
|
+
type: z.string().min(1).refine((val) => !fieldTypes.includes(val), {
|
|
210
|
+
message: "Custom field types must not conflict with built-in types"
|
|
211
|
+
})
|
|
212
|
+
}).passthrough());
|
|
213
|
+
knownFieldSchema = z.discriminatedUnion("type", [
|
|
214
|
+
primitiveFieldSchema,
|
|
215
|
+
selectFieldSchema,
|
|
216
|
+
referenceFieldSchema,
|
|
217
|
+
objectFieldSchema,
|
|
218
|
+
blockFieldSchema
|
|
219
|
+
]);
|
|
220
|
+
fieldSchema = z.lazy(() => z.union([knownFieldSchema, customFieldSchema]));
|
|
221
|
+
fieldHolder[0] = fieldSchema;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// dist/config/schemas/collection.js
|
|
226
|
+
import { z as z2 } from "zod";
|
|
227
|
+
import { isAbsolute } from "pathe";
|
|
228
|
+
var relativePathSchema, entryTypeSchema, collectionSchema, rootCollectionSchema;
|
|
229
|
+
var init_collection = __esm({
|
|
230
|
+
"dist/config/schemas/collection.js"() {
|
|
231
|
+
"use strict";
|
|
232
|
+
init_field();
|
|
233
|
+
relativePathSchema = z2.string().min(1).refine((val) => !isAbsolute(val), { message: "Path must be relative" }).refine((val) => !val.split(/[\\/]+/).includes(".."), {
|
|
234
|
+
message: 'Path must not contain ".."'
|
|
235
|
+
}).transform((val) => val.split(/[\\/]+/).filter(Boolean).join("/"));
|
|
236
|
+
entryTypeSchema = z2.object({
|
|
237
|
+
name: z2.string().min(1),
|
|
238
|
+
format: z2.enum(["md", "mdx", "json"]),
|
|
239
|
+
schema: z2.array(z2.lazy(() => fieldSchema)).min(1),
|
|
240
|
+
label: z2.string().optional(),
|
|
241
|
+
description: z2.string().optional(),
|
|
242
|
+
default: z2.boolean().optional(),
|
|
243
|
+
maxItems: z2.number().int().positive().optional()
|
|
244
|
+
});
|
|
245
|
+
collectionSchema = z2.lazy(() => z2.object({
|
|
246
|
+
name: z2.string().min(1),
|
|
247
|
+
path: relativePathSchema,
|
|
248
|
+
label: z2.string().optional(),
|
|
249
|
+
description: z2.string().optional(),
|
|
250
|
+
entries: z2.array(entryTypeSchema).optional(),
|
|
251
|
+
collections: z2.array(collectionSchema).optional(),
|
|
252
|
+
order: z2.array(z2.string()).optional()
|
|
253
|
+
// Embedded IDs for ordering items
|
|
254
|
+
}).refine((data) => data.entries || data.collections, {
|
|
255
|
+
message: "Collection must have entries or collections"
|
|
256
|
+
}));
|
|
257
|
+
rootCollectionSchema = z2.object({
|
|
258
|
+
entries: z2.array(entryTypeSchema).optional(),
|
|
259
|
+
collections: z2.array(collectionSchema).optional(),
|
|
260
|
+
order: z2.array(z2.string()).optional()
|
|
261
|
+
// Embedded IDs for ordering items
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// dist/config/schemas/media.js
|
|
267
|
+
import { z as z3 } from "zod";
|
|
268
|
+
var mediaSchema;
|
|
269
|
+
var init_media = __esm({
|
|
270
|
+
"dist/config/schemas/media.js"() {
|
|
271
|
+
"use strict";
|
|
272
|
+
mediaSchema = z3.union([
|
|
273
|
+
z3.object({
|
|
274
|
+
adapter: z3.literal("local"),
|
|
275
|
+
publicBaseUrl: z3.string().url().optional()
|
|
276
|
+
}),
|
|
277
|
+
z3.object({
|
|
278
|
+
adapter: z3.literal("s3"),
|
|
279
|
+
bucket: z3.string().min(1),
|
|
280
|
+
region: z3.string().min(1),
|
|
281
|
+
publicBaseUrl: z3.string().url().optional()
|
|
282
|
+
}),
|
|
283
|
+
z3.object({
|
|
284
|
+
adapter: z3.literal("lfs"),
|
|
285
|
+
publicBaseUrl: z3.string().url().optional()
|
|
286
|
+
}),
|
|
287
|
+
z3.object({
|
|
288
|
+
adapter: z3.string().min(1),
|
|
289
|
+
publicBaseUrl: z3.string().url().optional()
|
|
290
|
+
})
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// dist/config/schemas/config.js
|
|
296
|
+
import { z as z4 } from "zod";
|
|
297
|
+
var defaultBranchAccessSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, deploymentNameSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE;
|
|
298
|
+
var init_config = __esm({
|
|
299
|
+
"dist/config/schemas/config.js"() {
|
|
300
|
+
"use strict";
|
|
301
|
+
init_collection();
|
|
302
|
+
init_media();
|
|
303
|
+
defaultBranchAccessSchema = z4.enum(["allow", "deny"]).default("deny");
|
|
304
|
+
defaultPathAccessSchema = z4.enum(["allow", "deny"]).default("deny");
|
|
305
|
+
defaultBaseBranchSchema = z4.string().default("main");
|
|
306
|
+
defaultRemoteNameSchema = z4.string().default("origin");
|
|
307
|
+
defaultRemoteUrlSchema = z4.string().min(1);
|
|
308
|
+
gitBotAuthorNameSchema = z4.string().min(1);
|
|
309
|
+
gitBotAuthorEmailSchema = z4.string().email();
|
|
310
|
+
githubTokenEnvVarSchema = z4.string().default("GITHUB_BOT_TOKEN");
|
|
311
|
+
operatingModeSchema = z4.enum(["prod", "dev"]).default("dev");
|
|
312
|
+
deployedAsSchema = z4.enum(["static", "server"]).default("server");
|
|
313
|
+
contentRootSchema = relativePathSchema.default("content");
|
|
314
|
+
sourceRootSchema = z4.string().min(1).optional();
|
|
315
|
+
deploymentNameSchema = z4.string().default("prod");
|
|
316
|
+
editorConfigSchema = z4.object({
|
|
317
|
+
title: z4.string().optional(),
|
|
318
|
+
subtitle: z4.string().optional(),
|
|
319
|
+
theme: z4.unknown().optional(),
|
|
320
|
+
previewBase: z4.record(z4.string()).optional(),
|
|
321
|
+
// UI handler functions (runtime only, don't serialize)
|
|
322
|
+
onAccountClick: z4.function().returns(z4.void()).optional(),
|
|
323
|
+
onLogoutClick: z4.function().returns(z4.void()).optional(),
|
|
324
|
+
// Optional: custom account component (e.g., Clerk's UserButton)
|
|
325
|
+
AccountComponent: z4.custom().optional()
|
|
326
|
+
});
|
|
327
|
+
CanopyConfigSchema = z4.object({
|
|
328
|
+
schema: rootCollectionSchema.optional(),
|
|
329
|
+
media: mediaSchema.optional(),
|
|
330
|
+
defaultBranchAccess: defaultBranchAccessSchema.optional(),
|
|
331
|
+
defaultPathAccess: defaultPathAccessSchema.optional(),
|
|
332
|
+
defaultBaseBranch: defaultBaseBranchSchema.optional(),
|
|
333
|
+
defaultRemoteName: defaultRemoteNameSchema.optional(),
|
|
334
|
+
defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
|
|
335
|
+
gitBotAuthorName: gitBotAuthorNameSchema,
|
|
336
|
+
gitBotAuthorEmail: gitBotAuthorEmailSchema,
|
|
337
|
+
githubTokenEnvVar: githubTokenEnvVarSchema.optional(),
|
|
338
|
+
mode: operatingModeSchema,
|
|
339
|
+
// Has .default(), so not optional in output type
|
|
340
|
+
deployedAs: deployedAsSchema,
|
|
341
|
+
// Has .default('server'), so always present after validation
|
|
342
|
+
settingsBranch: z4.string().optional(),
|
|
343
|
+
autoCreateSettingsPR: z4.boolean().optional(),
|
|
344
|
+
deploymentName: deploymentNameSchema.optional(),
|
|
345
|
+
contentRoot: contentRootSchema.default("content"),
|
|
346
|
+
sourceRoot: sourceRootSchema.optional(),
|
|
347
|
+
editor: editorConfigSchema.optional(),
|
|
348
|
+
authPlugin: z4.custom().optional()
|
|
349
|
+
});
|
|
350
|
+
DEFAULT_PROD_WORKSPACE = "/mnt/efs/workspace";
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// dist/config/schemas/permissions.js
|
|
355
|
+
import { z as z5 } from "zod";
|
|
356
|
+
var permissionTargetSchema, pathPermissionSchema;
|
|
357
|
+
var init_permissions = __esm({
|
|
358
|
+
"dist/config/schemas/permissions.js"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
permissionTargetSchema = z5.object({
|
|
361
|
+
allowedUsers: z5.array(z5.string()).optional(),
|
|
362
|
+
allowedGroups: z5.array(z5.string()).optional()
|
|
363
|
+
});
|
|
364
|
+
pathPermissionSchema = z5.object({
|
|
365
|
+
path: z5.string().min(1),
|
|
366
|
+
read: permissionTargetSchema.optional(),
|
|
367
|
+
edit: permissionTargetSchema.optional(),
|
|
368
|
+
review: permissionTargetSchema.optional()
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// dist/paths/types.js
|
|
374
|
+
var ROOT_COLLECTION_ID;
|
|
375
|
+
var init_types2 = __esm({
|
|
376
|
+
"dist/paths/types.js"() {
|
|
377
|
+
"use strict";
|
|
378
|
+
ROOT_COLLECTION_ID = "__rootcoll__";
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// dist/config/flatten.js
|
|
383
|
+
import { join, normalize } from "pathe";
|
|
384
|
+
var normalizePathValue, flattenSchema;
|
|
385
|
+
var init_flatten = __esm({
|
|
386
|
+
"dist/config/flatten.js"() {
|
|
387
|
+
"use strict";
|
|
388
|
+
init_normalize();
|
|
389
|
+
init_types2();
|
|
390
|
+
normalizePathValue = (val) => normalize(val).split("/").filter(Boolean).join("/");
|
|
391
|
+
flattenSchema = (root, basePath = "") => {
|
|
392
|
+
const flat = [];
|
|
393
|
+
const base = normalizePathValue(basePath || "");
|
|
394
|
+
const walkCollection = (collection, parentPath) => {
|
|
395
|
+
const normalizedPath = normalizePathValue(collection.path);
|
|
396
|
+
let logicalPath;
|
|
397
|
+
if (parentPath && parentPath !== base) {
|
|
398
|
+
logicalPath = join(parentPath, collection.name);
|
|
399
|
+
} else if (parentPath === base) {
|
|
400
|
+
logicalPath = join(base, normalizedPath);
|
|
401
|
+
} else {
|
|
402
|
+
logicalPath = normalizedPath;
|
|
403
|
+
}
|
|
404
|
+
const normalizedFull = normalizePathValue(logicalPath);
|
|
405
|
+
flat.push({
|
|
406
|
+
type: "collection",
|
|
407
|
+
logicalPath: createLogicalPath(normalizedFull),
|
|
408
|
+
name: collection.name,
|
|
409
|
+
label: collection.label,
|
|
410
|
+
description: collection.description,
|
|
411
|
+
contentId: collection.contentId,
|
|
412
|
+
parentPath: parentPath ? createLogicalPath(parentPath) : void 0,
|
|
413
|
+
entries: collection.entries,
|
|
414
|
+
collections: collection.collections,
|
|
415
|
+
order: collection.order
|
|
416
|
+
});
|
|
417
|
+
if (collection.entries) {
|
|
418
|
+
for (const entryType of collection.entries) {
|
|
419
|
+
const entryTypePath = join(normalizedFull, entryType.name);
|
|
420
|
+
flat.push({
|
|
421
|
+
type: "entry-type",
|
|
422
|
+
logicalPath: createLogicalPath(normalizePathValue(entryTypePath)),
|
|
423
|
+
name: entryType.name,
|
|
424
|
+
label: entryType.label,
|
|
425
|
+
description: entryType.description,
|
|
426
|
+
parentPath: createLogicalPath(normalizedFull),
|
|
427
|
+
format: entryType.format,
|
|
428
|
+
schema: entryType.schema,
|
|
429
|
+
schemaRef: entryType.schemaRef,
|
|
430
|
+
default: entryType.default,
|
|
431
|
+
maxItems: entryType.maxItems
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (collection.collections) {
|
|
436
|
+
for (const child of collection.collections) {
|
|
437
|
+
walkCollection(child, normalizedFull);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
if (base) {
|
|
442
|
+
flat.push({
|
|
443
|
+
type: "collection",
|
|
444
|
+
logicalPath: createLogicalPath(base),
|
|
445
|
+
name: base,
|
|
446
|
+
// Use base path as the name (e.g., 'content')
|
|
447
|
+
label: void 0,
|
|
448
|
+
// Root collection has no label
|
|
449
|
+
contentId: ROOT_COLLECTION_ID,
|
|
450
|
+
// Sentinel — root dir has no embedded ID
|
|
451
|
+
parentPath: void 0,
|
|
452
|
+
// No parent - this is the root
|
|
453
|
+
entries: root.entries,
|
|
454
|
+
collections: root.collections,
|
|
455
|
+
order: root.order
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (root.entries) {
|
|
459
|
+
for (const entryType of root.entries) {
|
|
460
|
+
const entryTypePath = base ? join(base, entryType.name) : entryType.name;
|
|
461
|
+
flat.push({
|
|
462
|
+
type: "entry-type",
|
|
463
|
+
logicalPath: createLogicalPath(normalizePathValue(entryTypePath)),
|
|
464
|
+
name: entryType.name,
|
|
465
|
+
label: entryType.label,
|
|
466
|
+
description: entryType.description,
|
|
467
|
+
parentPath: base ? createLogicalPath(base) : createLogicalPath(""),
|
|
468
|
+
// Now references the root collection (e.g., 'content')
|
|
469
|
+
format: entryType.format,
|
|
470
|
+
schema: entryType.schema,
|
|
471
|
+
schemaRef: entryType.schemaRef,
|
|
472
|
+
default: entryType.default,
|
|
473
|
+
maxItems: entryType.maxItems
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (root.collections) {
|
|
478
|
+
for (const collection of root.collections) {
|
|
479
|
+
walkCollection(collection, base || "");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return flat;
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// dist/config/validation.js
|
|
488
|
+
var init_validation = __esm({
|
|
489
|
+
"dist/config/validation.js"() {
|
|
490
|
+
"use strict";
|
|
491
|
+
init_config();
|
|
492
|
+
init_flatten();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// dist/config/helpers.js
|
|
497
|
+
var init_helpers = __esm({
|
|
498
|
+
"dist/config/helpers.js"() {
|
|
499
|
+
"use strict";
|
|
500
|
+
init_validation();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// dist/config/index.js
|
|
505
|
+
var init_config2 = __esm({
|
|
506
|
+
"dist/config/index.js"() {
|
|
507
|
+
"use strict";
|
|
508
|
+
init_types();
|
|
509
|
+
init_config();
|
|
510
|
+
init_field();
|
|
511
|
+
init_collection();
|
|
512
|
+
init_permissions();
|
|
513
|
+
init_media();
|
|
514
|
+
init_flatten();
|
|
515
|
+
init_validation();
|
|
516
|
+
init_helpers();
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// dist/config.js
|
|
521
|
+
var init_config3 = __esm({
|
|
522
|
+
"dist/config.js"() {
|
|
523
|
+
"use strict";
|
|
524
|
+
init_config2();
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// dist/operating-mode/client-unsafe-strategy.js
|
|
529
|
+
import path3 from "node:path";
|
|
530
|
+
function operatingStrategy(mode) {
|
|
531
|
+
const cached = strategyCache.get(mode);
|
|
532
|
+
if (cached)
|
|
533
|
+
return cached;
|
|
534
|
+
let strategy;
|
|
535
|
+
switch (mode) {
|
|
536
|
+
case "prod":
|
|
537
|
+
strategy = new ProdStrategy();
|
|
538
|
+
break;
|
|
539
|
+
case "dev":
|
|
540
|
+
strategy = new DevStrategy();
|
|
541
|
+
break;
|
|
542
|
+
default: {
|
|
543
|
+
const _exhaustive = mode;
|
|
544
|
+
throw new Error(`Unknown operating mode: ${_exhaustive}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
strategyCache.set(mode, strategy);
|
|
548
|
+
return strategy;
|
|
549
|
+
}
|
|
550
|
+
function clearStrategyCache() {
|
|
551
|
+
strategyCache.clear();
|
|
552
|
+
}
|
|
553
|
+
var ProdStrategy, DevStrategy, strategyCache;
|
|
554
|
+
var init_client_unsafe_strategy = __esm({
|
|
555
|
+
"dist/operating-mode/client-unsafe-strategy.js"() {
|
|
556
|
+
"use strict";
|
|
557
|
+
init_client_safe_strategy();
|
|
558
|
+
init_config3();
|
|
559
|
+
ProdStrategy = class extends ProdClientSafeStrategy {
|
|
560
|
+
// All client-safe methods inherited automatically from ProdClientSafeStrategy:
|
|
561
|
+
// - mode, supportsBranching(), supportsStatusBadge(), supportsComments()
|
|
562
|
+
// - supportsPullRequests(), getPermissionsFileName(), getGroupsFileName()
|
|
563
|
+
// - shouldCommit(), shouldPush()
|
|
564
|
+
// Add client-unsafe methods (use Node.js APIs)
|
|
565
|
+
getWorkspaceRoot(_sourceRoot) {
|
|
566
|
+
return path3.resolve(process.env.CANOPYCMS_WORKSPACE_ROOT ?? DEFAULT_PROD_WORKSPACE);
|
|
567
|
+
}
|
|
568
|
+
getContentRoot(sourceRoot) {
|
|
569
|
+
return path3.resolve(sourceRoot ?? process.cwd(), "content");
|
|
570
|
+
}
|
|
571
|
+
getContentBranchesRoot(sourceRoot) {
|
|
572
|
+
return path3.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
573
|
+
}
|
|
574
|
+
getContentBranchRoot(branchName, sourceRoot) {
|
|
575
|
+
return path3.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
576
|
+
}
|
|
577
|
+
getGitExcludePattern() {
|
|
578
|
+
return ".canopy-meta/";
|
|
579
|
+
}
|
|
580
|
+
getPermissionsFilePath(root) {
|
|
581
|
+
return path3.join(root, this.getPermissionsFileName());
|
|
582
|
+
}
|
|
583
|
+
getGroupsFilePath(root) {
|
|
584
|
+
return path3.join(root, this.getGroupsFileName());
|
|
585
|
+
}
|
|
586
|
+
getRemoteUrlConfig() {
|
|
587
|
+
return {
|
|
588
|
+
shouldAutoInitLocal: false,
|
|
589
|
+
defaultRemotePath: "",
|
|
590
|
+
envVarName: "CANOPYCMS_REMOTE_URL",
|
|
591
|
+
autoDetectRemotePath: path3.join(this.getWorkspaceRoot(), "remote.git")
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
requiresExistingRepo() {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
getSettingsBranchName(config) {
|
|
598
|
+
if (config.settingsBranch)
|
|
599
|
+
return config.settingsBranch;
|
|
600
|
+
const deploymentName = config.deploymentName ?? "prod";
|
|
601
|
+
return `canopycms-settings-${deploymentName}`;
|
|
602
|
+
}
|
|
603
|
+
getSettingsRoot(sourceRoot) {
|
|
604
|
+
return path3.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
605
|
+
}
|
|
606
|
+
usesSeparateSettingsBranch() {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
validateConfig(config) {
|
|
610
|
+
if (!config.gitBotAuthorName || !config.gitBotAuthorEmail) {
|
|
611
|
+
throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
shouldCreateSettingsPR(config) {
|
|
615
|
+
return config.autoCreateSettingsPR ?? true;
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
DevStrategy = class extends DevClientSafeStrategy {
|
|
619
|
+
// Inherits client-safe methods from DevClientSafeStrategy
|
|
620
|
+
getWorkspaceRoot(sourceRoot) {
|
|
621
|
+
return path3.resolve(sourceRoot ?? process.cwd(), ".canopy-dev");
|
|
622
|
+
}
|
|
623
|
+
getContentRoot(sourceRoot) {
|
|
624
|
+
return path3.resolve(sourceRoot ?? process.cwd(), "content");
|
|
625
|
+
}
|
|
626
|
+
getContentBranchesRoot(sourceRoot) {
|
|
627
|
+
return path3.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
628
|
+
}
|
|
629
|
+
getContentBranchRoot(branchName, sourceRoot) {
|
|
630
|
+
return path3.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
631
|
+
}
|
|
632
|
+
getGitExcludePattern() {
|
|
633
|
+
return ".canopy-meta/";
|
|
634
|
+
}
|
|
635
|
+
getPermissionsFilePath(root) {
|
|
636
|
+
return path3.join(root, this.getPermissionsFileName());
|
|
637
|
+
}
|
|
638
|
+
getGroupsFilePath(root) {
|
|
639
|
+
return path3.join(root, this.getGroupsFileName());
|
|
640
|
+
}
|
|
641
|
+
getRemoteUrlConfig() {
|
|
642
|
+
return {
|
|
643
|
+
shouldAutoInitLocal: true,
|
|
644
|
+
defaultRemotePath: ".canopy-dev/remote.git",
|
|
645
|
+
envVarName: "CANOPYCMS_REMOTE_URL"
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
requiresExistingRepo() {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
getSettingsBranchName(config) {
|
|
652
|
+
if (config.settingsBranch)
|
|
653
|
+
return config.settingsBranch;
|
|
654
|
+
const deploymentName = config.deploymentName ?? "local";
|
|
655
|
+
return `canopycms-settings-${deploymentName}`;
|
|
656
|
+
}
|
|
657
|
+
getSettingsRoot(sourceRoot) {
|
|
658
|
+
return path3.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
659
|
+
}
|
|
660
|
+
usesSeparateSettingsBranch() {
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
validateConfig(_config) {
|
|
664
|
+
}
|
|
665
|
+
shouldCreateSettingsPR(_config) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
strategyCache = /* @__PURE__ */ new Map();
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// dist/operating-mode/index.js
|
|
674
|
+
var operating_mode_exports = {};
|
|
675
|
+
__export(operating_mode_exports, {
|
|
676
|
+
clearClientStrategyCache: () => clearClientStrategyCache,
|
|
677
|
+
clearStrategyCache: () => clearStrategyCache,
|
|
678
|
+
clientOperatingStrategy: () => clientOperatingStrategy,
|
|
679
|
+
operatingStrategy: () => operatingStrategy
|
|
680
|
+
});
|
|
681
|
+
var init_operating_mode = __esm({
|
|
682
|
+
"dist/operating-mode/index.js"() {
|
|
683
|
+
"use strict";
|
|
684
|
+
init_client_safe_strategy();
|
|
685
|
+
init_client_unsafe_strategy();
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
1
689
|
// dist/cli/generate-ai-content.js
|
|
2
|
-
import
|
|
690
|
+
import path12 from "node:path";
|
|
3
691
|
import { createJiti } from "jiti";
|
|
4
692
|
|
|
5
693
|
// dist/build/generate-ai-content.js
|
|
6
|
-
import
|
|
7
|
-
import
|
|
694
|
+
import fs10 from "node:fs/promises";
|
|
695
|
+
import path11 from "node:path";
|
|
8
696
|
|
|
9
697
|
// dist/content-store.js
|
|
10
|
-
import
|
|
698
|
+
import fs4 from "node:fs/promises";
|
|
11
699
|
import path5 from "node:path";
|
|
12
700
|
import matter from "gray-matter";
|
|
13
701
|
|
|
@@ -35,23 +723,8 @@ import path2 from "node:path";
|
|
|
35
723
|
// dist/id.js
|
|
36
724
|
import { generate } from "short-uuid";
|
|
37
725
|
|
|
38
|
-
// dist/paths/normalize.js
|
|
39
|
-
function normalizeFilesystemPath(path12) {
|
|
40
|
-
return path12.split(/[\\/]+/).filter(Boolean).join("/");
|
|
41
|
-
}
|
|
42
|
-
function hasTraversalSequence(path12) {
|
|
43
|
-
const normalized = normalizeFilesystemPath(path12);
|
|
44
|
-
return normalized.includes("..");
|
|
45
|
-
}
|
|
46
|
-
function createLogicalPath(...segments) {
|
|
47
|
-
const normalized = segments.map((s) => normalizeFilesystemPath(s)).filter(Boolean).join("/");
|
|
48
|
-
if (hasTraversalSequence(normalized)) {
|
|
49
|
-
throw new Error(`Invalid path: contains traversal sequence: ${normalized}`);
|
|
50
|
-
}
|
|
51
|
-
return normalized;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
726
|
// dist/paths/validation.js
|
|
727
|
+
init_normalize();
|
|
55
728
|
var BASE58_PATTERN = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
56
729
|
var CONTENT_ID_PATTERN = new RegExp(`^${BASE58_PATTERN}{12}$`);
|
|
57
730
|
var PHYSICAL_SEGMENT_PATTERN = new RegExp(`\\.${BASE58_PATTERN}{12}(?:\\.[a-z]+)?$`);
|
|
@@ -301,13 +974,13 @@ function extractIdFromFilename(filename) {
|
|
|
301
974
|
return null;
|
|
302
975
|
}
|
|
303
976
|
async function resolveCollectionPath(root, logicalPath) {
|
|
304
|
-
const
|
|
305
|
-
const
|
|
977
|
+
const fs11 = await import("node:fs/promises");
|
|
978
|
+
const path13 = await import("node:path");
|
|
306
979
|
const segments = logicalPath.split("/").filter(Boolean);
|
|
307
980
|
let currentPath = root;
|
|
308
981
|
for (const segment of segments) {
|
|
309
982
|
try {
|
|
310
|
-
const entries = await
|
|
983
|
+
const entries = await fs11.readdir(currentPath, { withFileTypes: true });
|
|
311
984
|
const matchingDir = entries.find((entry) => {
|
|
312
985
|
if (!entry.isDirectory())
|
|
313
986
|
return false;
|
|
@@ -315,7 +988,7 @@ async function resolveCollectionPath(root, logicalPath) {
|
|
|
315
988
|
return logicalName === segment;
|
|
316
989
|
});
|
|
317
990
|
if (matchingDir) {
|
|
318
|
-
currentPath =
|
|
991
|
+
currentPath = path13.join(currentPath, matchingDir.name);
|
|
319
992
|
} else {
|
|
320
993
|
return null;
|
|
321
994
|
}
|
|
@@ -362,617 +1035,25 @@ function extractSlugFromFilename(filename, entryTypeName) {
|
|
|
362
1035
|
if (parts.length > 1) {
|
|
363
1036
|
return parts.slice(0, -1).join(".");
|
|
364
1037
|
}
|
|
365
|
-
return filename;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// dist/utils/format.js
|
|
369
|
-
var getFormatExtension = (format) => {
|
|
370
|
-
if (format === "md")
|
|
371
|
-
return ".md";
|
|
372
|
-
if (format === "mdx")
|
|
373
|
-
return ".mdx";
|
|
374
|
-
return ".json";
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
// dist/paths/branch.js
|
|
378
|
-
import path4 from "node:path";
|
|
379
|
-
|
|
380
|
-
// dist/operating-mode/client-safe-strategy.js
|
|
381
|
-
var ProdClientSafeStrategy = class {
|
|
382
|
-
constructor() {
|
|
383
|
-
this.mode = "prod";
|
|
384
|
-
}
|
|
385
|
-
// UI Feature Flags
|
|
386
|
-
supportsBranching() {
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
supportsStatusBadge() {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
supportsComments() {
|
|
393
|
-
return true;
|
|
394
|
-
}
|
|
395
|
-
supportsPullRequests() {
|
|
396
|
-
return true;
|
|
397
|
-
}
|
|
398
|
-
// Simple Data
|
|
399
|
-
getPermissionsFileName() {
|
|
400
|
-
return "permissions.json";
|
|
401
|
-
}
|
|
402
|
-
getGroupsFileName() {
|
|
403
|
-
return "groups.json";
|
|
404
|
-
}
|
|
405
|
-
shouldCommit() {
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
shouldPush() {
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
var LocalProdSimClientSafeStrategy = class {
|
|
413
|
-
constructor() {
|
|
414
|
-
this.mode = "prod-sim";
|
|
415
|
-
}
|
|
416
|
-
// UI Feature Flags
|
|
417
|
-
supportsBranching() {
|
|
418
|
-
return true;
|
|
419
|
-
}
|
|
420
|
-
supportsStatusBadge() {
|
|
421
|
-
return true;
|
|
422
|
-
}
|
|
423
|
-
supportsComments() {
|
|
424
|
-
return true;
|
|
425
|
-
}
|
|
426
|
-
supportsPullRequests() {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
// Simple Data
|
|
430
|
-
getPermissionsFileName() {
|
|
431
|
-
return "permissions.json";
|
|
432
|
-
}
|
|
433
|
-
getGroupsFileName() {
|
|
434
|
-
return "groups.json";
|
|
435
|
-
}
|
|
436
|
-
shouldCommit() {
|
|
437
|
-
return true;
|
|
438
|
-
}
|
|
439
|
-
shouldPush() {
|
|
440
|
-
return true;
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
var LocalSimpleClientSafeStrategy = class {
|
|
444
|
-
constructor() {
|
|
445
|
-
this.mode = "dev";
|
|
446
|
-
}
|
|
447
|
-
// UI Feature Flags
|
|
448
|
-
supportsBranching() {
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
supportsStatusBadge() {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
supportsComments() {
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
supportsPullRequests() {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
// Simple Data
|
|
461
|
-
getPermissionsFileName() {
|
|
462
|
-
return "permissions.json";
|
|
463
|
-
}
|
|
464
|
-
getGroupsFileName() {
|
|
465
|
-
return "groups.json";
|
|
466
|
-
}
|
|
467
|
-
shouldCommit() {
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
shouldPush() {
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
// dist/operating-mode/client-unsafe-strategy.js
|
|
476
|
-
import path3 from "node:path";
|
|
477
|
-
|
|
478
|
-
// dist/config/types.js
|
|
479
|
-
var primitiveFieldTypes = [
|
|
480
|
-
"string",
|
|
481
|
-
"number",
|
|
482
|
-
"boolean",
|
|
483
|
-
"datetime",
|
|
484
|
-
"rich-text",
|
|
485
|
-
"markdown",
|
|
486
|
-
"mdx",
|
|
487
|
-
"image",
|
|
488
|
-
"code"
|
|
489
|
-
];
|
|
490
|
-
var fieldTypes = [
|
|
491
|
-
...primitiveFieldTypes,
|
|
492
|
-
"select",
|
|
493
|
-
"reference",
|
|
494
|
-
"object",
|
|
495
|
-
"block"
|
|
496
|
-
];
|
|
497
|
-
|
|
498
|
-
// dist/config/schemas/config.js
|
|
499
|
-
import { z as z4 } from "zod";
|
|
500
|
-
|
|
501
|
-
// dist/config/schemas/collection.js
|
|
502
|
-
import { z as z2 } from "zod";
|
|
503
|
-
import { isAbsolute } from "pathe";
|
|
504
|
-
|
|
505
|
-
// dist/config/schemas/field.js
|
|
506
|
-
import { z } from "zod";
|
|
507
|
-
var fieldBaseSchema = z.object({
|
|
508
|
-
name: z.string().min(1),
|
|
509
|
-
label: z.string().optional(),
|
|
510
|
-
description: z.string().optional(),
|
|
511
|
-
required: z.boolean().optional(),
|
|
512
|
-
list: z.boolean().optional()
|
|
513
|
-
});
|
|
514
|
-
var selectOptionSchema = z.union([
|
|
515
|
-
z.string(),
|
|
516
|
-
z.object({
|
|
517
|
-
label: z.string().min(1),
|
|
518
|
-
value: z.string().min(1)
|
|
519
|
-
})
|
|
520
|
-
]);
|
|
521
|
-
var referenceOptionSchema = z.union([
|
|
522
|
-
z.string(),
|
|
523
|
-
z.object({
|
|
524
|
-
label: z.string().min(1),
|
|
525
|
-
value: z.string().min(1)
|
|
526
|
-
})
|
|
527
|
-
]);
|
|
528
|
-
var primitiveFieldSchema = fieldBaseSchema.extend({
|
|
529
|
-
type: z.enum(primitiveFieldTypes)
|
|
530
|
-
});
|
|
531
|
-
var selectFieldSchema = fieldBaseSchema.extend({
|
|
532
|
-
type: z.literal("select"),
|
|
533
|
-
options: z.array(selectOptionSchema).min(1)
|
|
534
|
-
});
|
|
535
|
-
var referenceFieldSchema = fieldBaseSchema.extend({
|
|
536
|
-
type: z.literal("reference"),
|
|
537
|
-
collections: z.array(z.string().min(1)).min(1),
|
|
538
|
-
displayField: z.string().min(1).optional(),
|
|
539
|
-
options: z.array(referenceOptionSchema).optional()
|
|
540
|
-
});
|
|
541
|
-
var fieldHolder = [z.never()];
|
|
542
|
-
var blockSchema = z.object({
|
|
543
|
-
name: z.string().min(1),
|
|
544
|
-
label: z.string().optional(),
|
|
545
|
-
description: z.string().optional(),
|
|
546
|
-
fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
|
|
547
|
-
});
|
|
548
|
-
var blockFieldSchema = fieldBaseSchema.extend({
|
|
549
|
-
type: z.literal("block"),
|
|
550
|
-
templates: z.array(blockSchema).min(1)
|
|
551
|
-
});
|
|
552
|
-
var objectFieldSchema = fieldBaseSchema.extend({
|
|
553
|
-
type: z.literal("object"),
|
|
554
|
-
fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
|
|
555
|
-
});
|
|
556
|
-
var customFieldSchema = z.lazy(() => fieldBaseSchema.extend({
|
|
557
|
-
type: z.string().min(1).refine((val) => !fieldTypes.includes(val), {
|
|
558
|
-
message: "Custom field types must not conflict with built-in types"
|
|
559
|
-
})
|
|
560
|
-
}).passthrough());
|
|
561
|
-
var knownFieldSchema = z.discriminatedUnion("type", [
|
|
562
|
-
primitiveFieldSchema,
|
|
563
|
-
selectFieldSchema,
|
|
564
|
-
referenceFieldSchema,
|
|
565
|
-
objectFieldSchema,
|
|
566
|
-
blockFieldSchema
|
|
567
|
-
]);
|
|
568
|
-
var fieldSchema = z.lazy(() => z.union([knownFieldSchema, customFieldSchema]));
|
|
569
|
-
fieldHolder[0] = fieldSchema;
|
|
570
|
-
|
|
571
|
-
// dist/config/schemas/collection.js
|
|
572
|
-
var relativePathSchema = z2.string().min(1).refine((val) => !isAbsolute(val), { message: "Path must be relative" }).refine((val) => !val.split(/[\\/]+/).includes(".."), {
|
|
573
|
-
message: 'Path must not contain ".."'
|
|
574
|
-
}).transform((val) => val.split(/[\\/]+/).filter(Boolean).join("/"));
|
|
575
|
-
var entryTypeSchema = z2.object({
|
|
576
|
-
name: z2.string().min(1),
|
|
577
|
-
format: z2.enum(["md", "mdx", "json"]),
|
|
578
|
-
schema: z2.array(z2.lazy(() => fieldSchema)).min(1),
|
|
579
|
-
label: z2.string().optional(),
|
|
580
|
-
description: z2.string().optional(),
|
|
581
|
-
default: z2.boolean().optional(),
|
|
582
|
-
maxItems: z2.number().int().positive().optional()
|
|
583
|
-
});
|
|
584
|
-
var collectionSchema = z2.lazy(() => z2.object({
|
|
585
|
-
name: z2.string().min(1),
|
|
586
|
-
path: relativePathSchema,
|
|
587
|
-
label: z2.string().optional(),
|
|
588
|
-
description: z2.string().optional(),
|
|
589
|
-
entries: z2.array(entryTypeSchema).optional(),
|
|
590
|
-
collections: z2.array(collectionSchema).optional(),
|
|
591
|
-
order: z2.array(z2.string()).optional()
|
|
592
|
-
// Embedded IDs for ordering items
|
|
593
|
-
}).refine((data) => data.entries || data.collections, {
|
|
594
|
-
message: "Collection must have entries or collections"
|
|
595
|
-
}));
|
|
596
|
-
var rootCollectionSchema = z2.object({
|
|
597
|
-
entries: z2.array(entryTypeSchema).optional(),
|
|
598
|
-
collections: z2.array(collectionSchema).optional(),
|
|
599
|
-
order: z2.array(z2.string()).optional()
|
|
600
|
-
// Embedded IDs for ordering items
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
// dist/config/schemas/media.js
|
|
604
|
-
import { z as z3 } from "zod";
|
|
605
|
-
var mediaSchema = z3.union([
|
|
606
|
-
z3.object({
|
|
607
|
-
adapter: z3.literal("local"),
|
|
608
|
-
publicBaseUrl: z3.string().url().optional()
|
|
609
|
-
}),
|
|
610
|
-
z3.object({
|
|
611
|
-
adapter: z3.literal("s3"),
|
|
612
|
-
bucket: z3.string().min(1),
|
|
613
|
-
region: z3.string().min(1),
|
|
614
|
-
publicBaseUrl: z3.string().url().optional()
|
|
615
|
-
}),
|
|
616
|
-
z3.object({
|
|
617
|
-
adapter: z3.literal("lfs"),
|
|
618
|
-
publicBaseUrl: z3.string().url().optional()
|
|
619
|
-
}),
|
|
620
|
-
z3.object({
|
|
621
|
-
adapter: z3.string().min(1),
|
|
622
|
-
publicBaseUrl: z3.string().url().optional()
|
|
623
|
-
})
|
|
624
|
-
]);
|
|
625
|
-
|
|
626
|
-
// dist/config/schemas/config.js
|
|
627
|
-
var defaultBranchAccessSchema = z4.enum(["allow", "deny"]).default("deny");
|
|
628
|
-
var defaultPathAccessSchema = z4.enum(["allow", "deny"]).default("deny");
|
|
629
|
-
var defaultBaseBranchSchema = z4.string().default("main");
|
|
630
|
-
var defaultRemoteNameSchema = z4.string().default("origin");
|
|
631
|
-
var defaultRemoteUrlSchema = z4.string().min(1);
|
|
632
|
-
var gitBotAuthorNameSchema = z4.string().min(1);
|
|
633
|
-
var gitBotAuthorEmailSchema = z4.string().email();
|
|
634
|
-
var githubTokenEnvVarSchema = z4.string().default("GITHUB_BOT_TOKEN");
|
|
635
|
-
var operatingModeSchema = z4.enum(["prod", "prod-sim", "dev"]).default("dev");
|
|
636
|
-
var deployedAsSchema = z4.enum(["static", "server"]).default("server");
|
|
637
|
-
var contentRootSchema = relativePathSchema.default("content");
|
|
638
|
-
var sourceRootSchema = z4.string().min(1).optional();
|
|
639
|
-
var deploymentNameSchema = z4.string().default("prod");
|
|
640
|
-
var editorConfigSchema = z4.object({
|
|
641
|
-
title: z4.string().optional(),
|
|
642
|
-
subtitle: z4.string().optional(),
|
|
643
|
-
theme: z4.unknown().optional(),
|
|
644
|
-
previewBase: z4.record(z4.string()).optional(),
|
|
645
|
-
// UI handler functions (runtime only, don't serialize)
|
|
646
|
-
onAccountClick: z4.function().returns(z4.void()).optional(),
|
|
647
|
-
onLogoutClick: z4.function().returns(z4.void()).optional(),
|
|
648
|
-
// Optional: custom account component (e.g., Clerk's UserButton)
|
|
649
|
-
AccountComponent: z4.custom().optional()
|
|
650
|
-
});
|
|
651
|
-
var CanopyConfigSchema = z4.object({
|
|
652
|
-
schema: rootCollectionSchema.optional(),
|
|
653
|
-
media: mediaSchema.optional(),
|
|
654
|
-
defaultBranchAccess: defaultBranchAccessSchema.optional(),
|
|
655
|
-
defaultPathAccess: defaultPathAccessSchema.optional(),
|
|
656
|
-
defaultBaseBranch: defaultBaseBranchSchema.optional(),
|
|
657
|
-
defaultRemoteName: defaultRemoteNameSchema.optional(),
|
|
658
|
-
defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
|
|
659
|
-
gitBotAuthorName: gitBotAuthorNameSchema,
|
|
660
|
-
gitBotAuthorEmail: gitBotAuthorEmailSchema,
|
|
661
|
-
githubTokenEnvVar: githubTokenEnvVarSchema.optional(),
|
|
662
|
-
mode: operatingModeSchema,
|
|
663
|
-
// Has .default(), so not optional in output type
|
|
664
|
-
deployedAs: deployedAsSchema,
|
|
665
|
-
// Has .default('server'), so always present after validation
|
|
666
|
-
settingsBranch: z4.string().optional(),
|
|
667
|
-
autoCreateSettingsPR: z4.boolean().optional(),
|
|
668
|
-
deploymentName: deploymentNameSchema.optional(),
|
|
669
|
-
contentRoot: contentRootSchema.default("content"),
|
|
670
|
-
sourceRoot: sourceRootSchema.optional(),
|
|
671
|
-
editor: editorConfigSchema.optional(),
|
|
672
|
-
authPlugin: z4.custom().optional()
|
|
673
|
-
});
|
|
674
|
-
var DEFAULT_PROD_WORKSPACE = "/mnt/efs/workspace";
|
|
675
|
-
|
|
676
|
-
// dist/config/schemas/permissions.js
|
|
677
|
-
import { z as z5 } from "zod";
|
|
678
|
-
var permissionTargetSchema = z5.object({
|
|
679
|
-
allowedUsers: z5.array(z5.string()).optional(),
|
|
680
|
-
allowedGroups: z5.array(z5.string()).optional()
|
|
681
|
-
});
|
|
682
|
-
var pathPermissionSchema = z5.object({
|
|
683
|
-
path: z5.string().min(1),
|
|
684
|
-
read: permissionTargetSchema.optional(),
|
|
685
|
-
edit: permissionTargetSchema.optional(),
|
|
686
|
-
review: permissionTargetSchema.optional()
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
// dist/config/flatten.js
|
|
690
|
-
import { join, normalize } from "pathe";
|
|
691
|
-
|
|
692
|
-
// dist/paths/types.js
|
|
693
|
-
var ROOT_COLLECTION_ID = "__rootcoll__";
|
|
694
|
-
|
|
695
|
-
// dist/config/flatten.js
|
|
696
|
-
var normalizePathValue = (val) => normalize(val).split("/").filter(Boolean).join("/");
|
|
697
|
-
var flattenSchema = (root, basePath = "") => {
|
|
698
|
-
const flat = [];
|
|
699
|
-
const base = normalizePathValue(basePath || "");
|
|
700
|
-
const walkCollection = (collection, parentPath) => {
|
|
701
|
-
const normalizedPath = normalizePathValue(collection.path);
|
|
702
|
-
let logicalPath;
|
|
703
|
-
if (parentPath && parentPath !== base) {
|
|
704
|
-
logicalPath = join(parentPath, collection.name);
|
|
705
|
-
} else if (parentPath === base) {
|
|
706
|
-
logicalPath = join(base, normalizedPath);
|
|
707
|
-
} else {
|
|
708
|
-
logicalPath = normalizedPath;
|
|
709
|
-
}
|
|
710
|
-
const normalizedFull = normalizePathValue(logicalPath);
|
|
711
|
-
flat.push({
|
|
712
|
-
type: "collection",
|
|
713
|
-
logicalPath: createLogicalPath(normalizedFull),
|
|
714
|
-
name: collection.name,
|
|
715
|
-
label: collection.label,
|
|
716
|
-
description: collection.description,
|
|
717
|
-
contentId: collection.contentId,
|
|
718
|
-
parentPath: parentPath ? createLogicalPath(parentPath) : void 0,
|
|
719
|
-
entries: collection.entries,
|
|
720
|
-
collections: collection.collections,
|
|
721
|
-
order: collection.order
|
|
722
|
-
});
|
|
723
|
-
if (collection.entries) {
|
|
724
|
-
for (const entryType of collection.entries) {
|
|
725
|
-
const entryTypePath = join(normalizedFull, entryType.name);
|
|
726
|
-
flat.push({
|
|
727
|
-
type: "entry-type",
|
|
728
|
-
logicalPath: createLogicalPath(normalizePathValue(entryTypePath)),
|
|
729
|
-
name: entryType.name,
|
|
730
|
-
label: entryType.label,
|
|
731
|
-
description: entryType.description,
|
|
732
|
-
parentPath: createLogicalPath(normalizedFull),
|
|
733
|
-
format: entryType.format,
|
|
734
|
-
schema: entryType.schema,
|
|
735
|
-
schemaRef: entryType.schemaRef,
|
|
736
|
-
default: entryType.default,
|
|
737
|
-
maxItems: entryType.maxItems
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
if (collection.collections) {
|
|
742
|
-
for (const child of collection.collections) {
|
|
743
|
-
walkCollection(child, normalizedFull);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
if (base) {
|
|
748
|
-
flat.push({
|
|
749
|
-
type: "collection",
|
|
750
|
-
logicalPath: createLogicalPath(base),
|
|
751
|
-
name: base,
|
|
752
|
-
// Use base path as the name (e.g., 'content')
|
|
753
|
-
label: void 0,
|
|
754
|
-
// Root collection has no label
|
|
755
|
-
contentId: ROOT_COLLECTION_ID,
|
|
756
|
-
// Sentinel — root dir has no embedded ID
|
|
757
|
-
parentPath: void 0,
|
|
758
|
-
// No parent - this is the root
|
|
759
|
-
entries: root.entries,
|
|
760
|
-
collections: root.collections,
|
|
761
|
-
order: root.order
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
if (root.entries) {
|
|
765
|
-
for (const entryType of root.entries) {
|
|
766
|
-
const entryTypePath = base ? join(base, entryType.name) : entryType.name;
|
|
767
|
-
flat.push({
|
|
768
|
-
type: "entry-type",
|
|
769
|
-
logicalPath: createLogicalPath(normalizePathValue(entryTypePath)),
|
|
770
|
-
name: entryType.name,
|
|
771
|
-
label: entryType.label,
|
|
772
|
-
description: entryType.description,
|
|
773
|
-
parentPath: base ? createLogicalPath(base) : createLogicalPath(""),
|
|
774
|
-
// Now references the root collection (e.g., 'content')
|
|
775
|
-
format: entryType.format,
|
|
776
|
-
schema: entryType.schema,
|
|
777
|
-
schemaRef: entryType.schemaRef,
|
|
778
|
-
default: entryType.default,
|
|
779
|
-
maxItems: entryType.maxItems
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
if (root.collections) {
|
|
784
|
-
for (const collection of root.collections) {
|
|
785
|
-
walkCollection(collection, base || "");
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
return flat;
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
// dist/operating-mode/client-unsafe-strategy.js
|
|
792
|
-
var ProdStrategy = class extends ProdClientSafeStrategy {
|
|
793
|
-
// All client-safe methods inherited automatically from ProdClientSafeStrategy:
|
|
794
|
-
// - mode, supportsBranching(), supportsStatusBadge(), supportsComments()
|
|
795
|
-
// - supportsPullRequests(), getPermissionsFileName(), getGroupsFileName()
|
|
796
|
-
// - shouldCommit(), shouldPush()
|
|
797
|
-
// Add client-unsafe methods (use Node.js APIs)
|
|
798
|
-
getWorkspaceRoot(_sourceRoot) {
|
|
799
|
-
return path3.resolve(process.env.CANOPYCMS_WORKSPACE_ROOT ?? DEFAULT_PROD_WORKSPACE);
|
|
800
|
-
}
|
|
801
|
-
getContentRoot(sourceRoot) {
|
|
802
|
-
return path3.resolve(sourceRoot ?? process.cwd(), "content");
|
|
803
|
-
}
|
|
804
|
-
getContentBranchesRoot(sourceRoot) {
|
|
805
|
-
return path3.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
806
|
-
}
|
|
807
|
-
getContentBranchRoot(branchName, sourceRoot) {
|
|
808
|
-
return path3.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
809
|
-
}
|
|
810
|
-
getGitExcludePattern() {
|
|
811
|
-
return ".canopy-meta/";
|
|
812
|
-
}
|
|
813
|
-
getPermissionsFilePath(root) {
|
|
814
|
-
return path3.join(root, this.getPermissionsFileName());
|
|
815
|
-
}
|
|
816
|
-
getGroupsFilePath(root) {
|
|
817
|
-
return path3.join(root, this.getGroupsFileName());
|
|
818
|
-
}
|
|
819
|
-
getRemoteUrlConfig() {
|
|
820
|
-
return {
|
|
821
|
-
shouldAutoInitLocal: false,
|
|
822
|
-
defaultRemotePath: "",
|
|
823
|
-
envVarName: "CANOPYCMS_REMOTE_URL",
|
|
824
|
-
autoDetectRemotePath: path3.join(this.getWorkspaceRoot(), "remote.git")
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
requiresExistingRepo() {
|
|
828
|
-
return false;
|
|
829
|
-
}
|
|
830
|
-
getSettingsBranchName(config) {
|
|
831
|
-
if (config.settingsBranch)
|
|
832
|
-
return config.settingsBranch;
|
|
833
|
-
const deploymentName = config.deploymentName ?? "prod";
|
|
834
|
-
return `canopycms-settings-${deploymentName}`;
|
|
835
|
-
}
|
|
836
|
-
getSettingsRoot(sourceRoot) {
|
|
837
|
-
return path3.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
838
|
-
}
|
|
839
|
-
usesSeparateSettingsBranch() {
|
|
840
|
-
return true;
|
|
841
|
-
}
|
|
842
|
-
validateConfig(config) {
|
|
843
|
-
if (!config.gitBotAuthorName || !config.gitBotAuthorEmail) {
|
|
844
|
-
throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
shouldCreateSettingsPR(config) {
|
|
848
|
-
return config.autoCreateSettingsPR ?? true;
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
var LocalProdSimStrategy = class extends LocalProdSimClientSafeStrategy {
|
|
852
|
-
// Inherits client-safe methods from LocalProdSimClientSafeStrategy
|
|
853
|
-
getWorkspaceRoot(sourceRoot) {
|
|
854
|
-
return path3.resolve(sourceRoot ?? process.cwd(), ".canopy-prod-sim");
|
|
855
|
-
}
|
|
856
|
-
getContentRoot(sourceRoot) {
|
|
857
|
-
return path3.resolve(sourceRoot ?? process.cwd(), "content");
|
|
858
|
-
}
|
|
859
|
-
getContentBranchesRoot(sourceRoot) {
|
|
860
|
-
return path3.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
861
|
-
}
|
|
862
|
-
getContentBranchRoot(branchName, sourceRoot) {
|
|
863
|
-
return path3.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
864
|
-
}
|
|
865
|
-
getGitExcludePattern() {
|
|
866
|
-
return ".canopy-meta/";
|
|
867
|
-
}
|
|
868
|
-
getPermissionsFilePath(root) {
|
|
869
|
-
return path3.join(root, this.getPermissionsFileName());
|
|
870
|
-
}
|
|
871
|
-
getGroupsFilePath(root) {
|
|
872
|
-
return path3.join(root, this.getGroupsFileName());
|
|
873
|
-
}
|
|
874
|
-
getRemoteUrlConfig() {
|
|
875
|
-
return {
|
|
876
|
-
shouldAutoInitLocal: true,
|
|
877
|
-
defaultRemotePath: ".canopy-prod-sim/remote.git",
|
|
878
|
-
envVarName: "CANOPYCMS_REMOTE_URL"
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
requiresExistingRepo() {
|
|
882
|
-
return false;
|
|
883
|
-
}
|
|
884
|
-
getSettingsBranchName(config) {
|
|
885
|
-
if (config.settingsBranch)
|
|
886
|
-
return config.settingsBranch;
|
|
887
|
-
const deploymentName = config.deploymentName ?? "prod";
|
|
888
|
-
return `canopycms-settings-${deploymentName}`;
|
|
889
|
-
}
|
|
890
|
-
getSettingsRoot(sourceRoot) {
|
|
891
|
-
return path3.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
892
|
-
}
|
|
893
|
-
usesSeparateSettingsBranch() {
|
|
894
|
-
return true;
|
|
895
|
-
}
|
|
896
|
-
validateConfig(_config) {
|
|
897
|
-
}
|
|
898
|
-
shouldCreateSettingsPR(_config) {
|
|
899
|
-
return false;
|
|
900
|
-
}
|
|
901
|
-
};
|
|
902
|
-
var LocalSimpleStrategy = class extends LocalSimpleClientSafeStrategy {
|
|
903
|
-
// Inherits: supportsBranching() returns false, getPermissionsFileName() returns 'permissions.local.json'
|
|
904
|
-
getWorkspaceRoot(sourceRoot) {
|
|
905
|
-
return path3.resolve(sourceRoot ?? process.cwd(), ".canopy-dev");
|
|
906
|
-
}
|
|
907
|
-
getContentRoot(sourceRoot) {
|
|
908
|
-
return path3.resolve(sourceRoot ?? process.cwd(), "content");
|
|
909
|
-
}
|
|
910
|
-
getContentBranchesRoot(_sourceRoot) {
|
|
911
|
-
throw new Error("No branching in dev mode");
|
|
912
|
-
}
|
|
913
|
-
getContentBranchRoot(_branchName, _sourceRoot) {
|
|
914
|
-
throw new Error("No branching in dev mode");
|
|
915
|
-
}
|
|
916
|
-
getGitExcludePattern() {
|
|
917
|
-
return ".canopy-meta/";
|
|
918
|
-
}
|
|
919
|
-
getPermissionsFilePath(root) {
|
|
920
|
-
return path3.join(this.getWorkspaceRoot(root), "settings", "permissions.json");
|
|
921
|
-
}
|
|
922
|
-
getGroupsFilePath(root) {
|
|
923
|
-
return path3.join(this.getWorkspaceRoot(root), "settings", "groups.json");
|
|
924
|
-
}
|
|
925
|
-
getRemoteUrlConfig() {
|
|
926
|
-
return {
|
|
927
|
-
shouldAutoInitLocal: false,
|
|
928
|
-
defaultRemotePath: "",
|
|
929
|
-
envVarName: "CANOPYCMS_REMOTE_URL"
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
requiresExistingRepo() {
|
|
933
|
-
return true;
|
|
934
|
-
}
|
|
935
|
-
getSettingsBranchName(config) {
|
|
936
|
-
return config.defaultBaseBranch ?? "main";
|
|
937
|
-
}
|
|
938
|
-
getSettingsRoot(sourceRoot) {
|
|
939
|
-
return path3.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
940
|
-
}
|
|
941
|
-
usesSeparateSettingsBranch() {
|
|
942
|
-
return false;
|
|
943
|
-
}
|
|
944
|
-
validateConfig(_config) {
|
|
945
|
-
}
|
|
946
|
-
shouldCreateSettingsPR(_config) {
|
|
947
|
-
return false;
|
|
948
|
-
}
|
|
949
|
-
};
|
|
950
|
-
var strategyCache = /* @__PURE__ */ new Map();
|
|
951
|
-
function operatingStrategy(mode) {
|
|
952
|
-
const cached = strategyCache.get(mode);
|
|
953
|
-
if (cached)
|
|
954
|
-
return cached;
|
|
955
|
-
let strategy;
|
|
956
|
-
switch (mode) {
|
|
957
|
-
case "prod":
|
|
958
|
-
strategy = new ProdStrategy();
|
|
959
|
-
break;
|
|
960
|
-
case "prod-sim":
|
|
961
|
-
strategy = new LocalProdSimStrategy();
|
|
962
|
-
break;
|
|
963
|
-
case "dev":
|
|
964
|
-
strategy = new LocalSimpleStrategy();
|
|
965
|
-
break;
|
|
966
|
-
default: {
|
|
967
|
-
const _exhaustive = mode;
|
|
968
|
-
throw new Error(`Unknown operating mode: ${_exhaustive}`);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
strategyCache.set(mode, strategy);
|
|
972
|
-
return strategy;
|
|
1038
|
+
return filename;
|
|
973
1039
|
}
|
|
974
1040
|
|
|
1041
|
+
// dist/utils/format.js
|
|
1042
|
+
var getFormatExtension = (format) => {
|
|
1043
|
+
if (format === "md")
|
|
1044
|
+
return ".md";
|
|
1045
|
+
if (format === "mdx")
|
|
1046
|
+
return ".mdx";
|
|
1047
|
+
return ".json";
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
// dist/paths/index.js
|
|
1051
|
+
init_normalize();
|
|
1052
|
+
|
|
975
1053
|
// dist/paths/branch.js
|
|
1054
|
+
init_operating_mode();
|
|
1055
|
+
import fs3 from "node:fs/promises";
|
|
1056
|
+
import path4 from "node:path";
|
|
976
1057
|
var BranchPathError = class extends Error {
|
|
977
1058
|
};
|
|
978
1059
|
function sanitizeBranchName(branchName) {
|
|
@@ -1003,6 +1084,11 @@ function resolveBranchPath(options) {
|
|
|
1003
1084
|
}
|
|
1004
1085
|
return { branchRoot, baseRoot: normalizedBase, branchName: safeBranch };
|
|
1005
1086
|
}
|
|
1087
|
+
async function ensureBranchRoot(options) {
|
|
1088
|
+
const result = resolveBranchPath(options);
|
|
1089
|
+
await fs3.mkdir(result.branchRoot, { recursive: true });
|
|
1090
|
+
return result;
|
|
1091
|
+
}
|
|
1006
1092
|
|
|
1007
1093
|
// dist/content-store.js
|
|
1008
1094
|
var ContentStoreError = class extends Error {
|
|
@@ -1045,11 +1131,11 @@ var ContentStore = class {
|
|
|
1045
1131
|
getSchemaItems() {
|
|
1046
1132
|
return this.schemaIndex.values();
|
|
1047
1133
|
}
|
|
1048
|
-
assertSchemaItem(
|
|
1049
|
-
const normalized = normalizeFilesystemPath(
|
|
1134
|
+
assertSchemaItem(path13) {
|
|
1135
|
+
const normalized = normalizeFilesystemPath(path13);
|
|
1050
1136
|
const item = this.schemaIndex.get(normalized);
|
|
1051
1137
|
if (!item) {
|
|
1052
|
-
throw new ContentStoreError(`Unknown schema item: ${
|
|
1138
|
+
throw new ContentStoreError(`Unknown schema item: ${path13}`);
|
|
1053
1139
|
}
|
|
1054
1140
|
return item;
|
|
1055
1141
|
}
|
|
@@ -1118,7 +1204,7 @@ var ContentStore = class {
|
|
|
1118
1204
|
let existingFilename;
|
|
1119
1205
|
let existingEntryType;
|
|
1120
1206
|
if (!id) {
|
|
1121
|
-
const entries = await
|
|
1207
|
+
const entries = await fs4.readdir(collectionRoot, { withFileTypes: true }).catch(() => []);
|
|
1122
1208
|
const existingFile = entries.find((entry) => {
|
|
1123
1209
|
if (entry.isDirectory())
|
|
1124
1210
|
return false;
|
|
@@ -1183,7 +1269,7 @@ var ContentStore = class {
|
|
|
1183
1269
|
async read(collectionPath, slug = "", options = {}) {
|
|
1184
1270
|
const schemaItem = this.assertSchemaItem(collectionPath);
|
|
1185
1271
|
const { absolutePath, relativePath } = await this.buildPaths(schemaItem, slug);
|
|
1186
|
-
const raw = await
|
|
1272
|
+
const raw = await fs4.readFile(absolutePath, "utf8");
|
|
1187
1273
|
let doc;
|
|
1188
1274
|
let format;
|
|
1189
1275
|
let fields;
|
|
@@ -1246,7 +1332,7 @@ var ContentStore = class {
|
|
|
1246
1332
|
const { absolutePath, relativePath, id } = await this.buildPaths(schemaItem, slug, {
|
|
1247
1333
|
entryTypeName
|
|
1248
1334
|
});
|
|
1249
|
-
await
|
|
1335
|
+
await fs4.mkdir(path5.dirname(absolutePath), { recursive: true });
|
|
1250
1336
|
if (input.format === "json") {
|
|
1251
1337
|
const json = JSON.stringify(input.data ?? {}, null, 2);
|
|
1252
1338
|
await atomicWriteFile(absolutePath, `${json}
|
|
@@ -1330,7 +1416,7 @@ var ContentStore = class {
|
|
|
1330
1416
|
const collection = this.assertCollection(collectionPath);
|
|
1331
1417
|
const { absolutePath, relativePath } = await this.buildPaths(collection, slug);
|
|
1332
1418
|
const id = idIndex.findByPath(relativePath);
|
|
1333
|
-
await
|
|
1419
|
+
await fs4.unlink(absolutePath);
|
|
1334
1420
|
if (id) {
|
|
1335
1421
|
idIndex.remove(id);
|
|
1336
1422
|
}
|
|
@@ -1358,7 +1444,7 @@ var ContentStore = class {
|
|
|
1358
1444
|
}
|
|
1359
1445
|
const { absolutePath: currentPath, relativePath: currentRelPath } = await this.buildPaths(collection, currentSlug);
|
|
1360
1446
|
try {
|
|
1361
|
-
await
|
|
1447
|
+
await fs4.access(currentPath);
|
|
1362
1448
|
} catch {
|
|
1363
1449
|
throw new ContentStoreError(`Entry not found: ${currentSlug}`);
|
|
1364
1450
|
}
|
|
@@ -1377,7 +1463,7 @@ var ContentStore = class {
|
|
|
1377
1463
|
const parentDir = path5.dirname(currentPath);
|
|
1378
1464
|
const newPath = path5.join(parentDir, newFilename);
|
|
1379
1465
|
try {
|
|
1380
|
-
const entries = await
|
|
1466
|
+
const entries = await fs4.readdir(parentDir, { withFileTypes: true });
|
|
1381
1467
|
for (const entry of entries) {
|
|
1382
1468
|
if (entry.isDirectory())
|
|
1383
1469
|
continue;
|
|
@@ -1391,7 +1477,7 @@ var ContentStore = class {
|
|
|
1391
1477
|
throw err;
|
|
1392
1478
|
}
|
|
1393
1479
|
}
|
|
1394
|
-
await
|
|
1480
|
+
await fs4.rename(currentPath, newPath);
|
|
1395
1481
|
const newRelativePath = path5.relative(this.root, newPath);
|
|
1396
1482
|
const entryId = idIndex.findByPath(currentRelPath);
|
|
1397
1483
|
if (entryId) {
|
|
@@ -1509,11 +1595,11 @@ var ContentStore = class {
|
|
|
1509
1595
|
};
|
|
1510
1596
|
|
|
1511
1597
|
// dist/branch-schema-cache.js
|
|
1512
|
-
import
|
|
1598
|
+
import fs6 from "node:fs/promises";
|
|
1513
1599
|
import path6 from "node:path";
|
|
1514
1600
|
|
|
1515
1601
|
// dist/schema/meta-loader.js
|
|
1516
|
-
import { promises as
|
|
1602
|
+
import { promises as fs5 } from "fs";
|
|
1517
1603
|
import { join as join2 } from "pathe";
|
|
1518
1604
|
import { z as z6 } from "zod";
|
|
1519
1605
|
import chokidar from "chokidar";
|
|
@@ -1547,7 +1633,7 @@ function stripEmbeddedIdFromName(name) {
|
|
|
1547
1633
|
async function scanForCollectionMeta(baseDir, relativePath = "") {
|
|
1548
1634
|
const collections = [];
|
|
1549
1635
|
try {
|
|
1550
|
-
const entries = await
|
|
1636
|
+
const entries = await fs5.readdir(baseDir, { withFileTypes: true });
|
|
1551
1637
|
for (const entry of entries) {
|
|
1552
1638
|
if (!entry.isDirectory())
|
|
1553
1639
|
continue;
|
|
@@ -1558,8 +1644,8 @@ async function scanForCollectionMeta(baseDir, relativePath = "") {
|
|
|
1558
1644
|
const absolutePath = join2(baseDir, folderName);
|
|
1559
1645
|
const metaPath = join2(absolutePath, ".collection.json");
|
|
1560
1646
|
try {
|
|
1561
|
-
await
|
|
1562
|
-
const content = await
|
|
1647
|
+
await fs5.access(metaPath);
|
|
1648
|
+
const content = await fs5.readFile(metaPath, "utf-8");
|
|
1563
1649
|
const parsed = JSON.parse(content);
|
|
1564
1650
|
const meta = collectionMetaSchema.parse(parsed);
|
|
1565
1651
|
collections.push({
|
|
@@ -1591,7 +1677,7 @@ async function loadCollectionMetaFiles(contentRoot) {
|
|
|
1591
1677
|
let root = null;
|
|
1592
1678
|
const rootMetaPath = join2(contentRoot, ".collection.json");
|
|
1593
1679
|
try {
|
|
1594
|
-
await
|
|
1680
|
+
await fs5.access(rootMetaPath);
|
|
1595
1681
|
} catch (err) {
|
|
1596
1682
|
if (err.code === "ENOENT") {
|
|
1597
1683
|
} else {
|
|
@@ -1599,7 +1685,7 @@ async function loadCollectionMetaFiles(contentRoot) {
|
|
|
1599
1685
|
}
|
|
1600
1686
|
}
|
|
1601
1687
|
try {
|
|
1602
|
-
const content = await
|
|
1688
|
+
const content = await fs5.readFile(rootMetaPath, "utf-8");
|
|
1603
1689
|
const parsed = JSON.parse(content);
|
|
1604
1690
|
root = rootCollectionMetaSchema.parse(parsed);
|
|
1605
1691
|
} catch (err) {
|
|
@@ -1690,35 +1776,44 @@ function isValidSchema(schema) {
|
|
|
1690
1776
|
}
|
|
1691
1777
|
|
|
1692
1778
|
// dist/branch-schema-cache.js
|
|
1779
|
+
init_flatten();
|
|
1693
1780
|
var SCHEMA_CACHE_VERSION = 2;
|
|
1781
|
+
var MTIME_CHECK_DEBOUNCE_MS = 1e3;
|
|
1782
|
+
async function isStaleByMtime(dir, cachedAt) {
|
|
1783
|
+
let entries;
|
|
1784
|
+
try {
|
|
1785
|
+
entries = await fs6.readdir(dir, { recursive: true, encoding: "utf-8" });
|
|
1786
|
+
} catch {
|
|
1787
|
+
return true;
|
|
1788
|
+
}
|
|
1789
|
+
for (const entry of entries) {
|
|
1790
|
+
if (!entry.endsWith(".collection.json"))
|
|
1791
|
+
continue;
|
|
1792
|
+
const full = path6.join(dir, entry);
|
|
1793
|
+
try {
|
|
1794
|
+
const stat = await fs6.stat(full);
|
|
1795
|
+
if (stat.mtimeMs > cachedAt.getTime())
|
|
1796
|
+
return true;
|
|
1797
|
+
} catch {
|
|
1798
|
+
return true;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1694
1803
|
var BranchSchemaCache = class {
|
|
1695
|
-
constructor(mode) {
|
|
1696
|
-
this.
|
|
1804
|
+
constructor(mode = "prod") {
|
|
1805
|
+
this.lastMtimeCheck = /* @__PURE__ */ new Map();
|
|
1806
|
+
this.devMode = mode === "dev";
|
|
1697
1807
|
}
|
|
1698
1808
|
/**
|
|
1699
1809
|
* Get schema for a branch (loads from cache or resolves fresh).
|
|
1700
1810
|
*
|
|
1701
|
-
* @param branchRoot - Root directory of the branch (e.g., .canopy-
|
|
1811
|
+
* @param branchRoot - Root directory of the branch (e.g., .canopy-dev/content-branches/main)
|
|
1702
1812
|
* @param entrySchemaRegistry - Map of schema names to field definitions
|
|
1703
1813
|
* @param contentRootName - Name of content directory (e.g., "content") from config
|
|
1704
1814
|
* @returns Resolved schema tree and flattened schema
|
|
1705
1815
|
*/
|
|
1706
1816
|
async getSchema(branchRoot, entrySchemaRegistry, contentRootName = "content") {
|
|
1707
|
-
if (this.mode === "dev") {
|
|
1708
|
-
if (!this.devModeCache) {
|
|
1709
|
-
const contentRoot = path6.join(branchRoot, contentRootName);
|
|
1710
|
-
const result = await resolveSchema(contentRoot, entrySchemaRegistry);
|
|
1711
|
-
if (!isValidSchema(result.schema)) {
|
|
1712
|
-
throw new Error(`No schema found in ${contentRoot}. Create .collection.json files with references to field schemas defined in your entry schema registry.`);
|
|
1713
|
-
}
|
|
1714
|
-
const flatSchema = flattenSchema(result.schema, contentRootName);
|
|
1715
|
-
this.devModeCache = {
|
|
1716
|
-
schema: result.schema,
|
|
1717
|
-
flatSchema
|
|
1718
|
-
};
|
|
1719
|
-
}
|
|
1720
|
-
return this.devModeCache;
|
|
1721
|
-
}
|
|
1722
1817
|
return this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName);
|
|
1723
1818
|
}
|
|
1724
1819
|
/**
|
|
@@ -1731,23 +1826,32 @@ var BranchSchemaCache = class {
|
|
|
1731
1826
|
const stalePath = path6.join(cacheDir, "schema-cache.stale");
|
|
1732
1827
|
let cacheData = null;
|
|
1733
1828
|
try {
|
|
1734
|
-
const staleExists = await
|
|
1829
|
+
const staleExists = await fs6.access(stalePath).then(() => true).catch(() => false);
|
|
1735
1830
|
if (!staleExists) {
|
|
1736
|
-
const cacheContent = await
|
|
1831
|
+
const cacheContent = await fs6.readFile(cachePath, "utf-8");
|
|
1737
1832
|
cacheData = JSON.parse(cacheContent);
|
|
1738
1833
|
}
|
|
1739
1834
|
} catch {
|
|
1740
1835
|
cacheData = null;
|
|
1741
1836
|
}
|
|
1742
1837
|
if (cacheData && cacheData.version === SCHEMA_CACHE_VERSION) {
|
|
1743
|
-
|
|
1838
|
+
const now = Date.now();
|
|
1839
|
+
const lastCheck = this.lastMtimeCheck.get(contentRoot) ?? 0;
|
|
1840
|
+
if (this.devMode && now - lastCheck >= MTIME_CHECK_DEBOUNCE_MS && await isStaleByMtime(contentRoot, new Date(cacheData.cachedAt))) {
|
|
1841
|
+
this.lastMtimeCheck.set(contentRoot, now);
|
|
1842
|
+
cacheData = null;
|
|
1843
|
+
} else {
|
|
1844
|
+
if (this.devMode)
|
|
1845
|
+
this.lastMtimeCheck.set(contentRoot, now);
|
|
1846
|
+
return { schema: cacheData.schema, flatSchema: cacheData.flatSchema };
|
|
1847
|
+
}
|
|
1744
1848
|
}
|
|
1745
1849
|
const result = await resolveSchema(contentRoot, entrySchemaRegistry);
|
|
1746
1850
|
if (!isValidSchema(result.schema)) {
|
|
1747
1851
|
throw new Error(`No schema found in ${contentRoot}. Create .collection.json files with references to field schemas defined in your entry schema registry.`);
|
|
1748
1852
|
}
|
|
1749
1853
|
const flatSchema = flattenSchema(result.schema, contentRootName);
|
|
1750
|
-
await
|
|
1854
|
+
await fs6.mkdir(cacheDir, { recursive: true });
|
|
1751
1855
|
const newCache = {
|
|
1752
1856
|
version: SCHEMA_CACHE_VERSION,
|
|
1753
1857
|
schema: result.schema,
|
|
@@ -1755,10 +1859,10 @@ var BranchSchemaCache = class {
|
|
|
1755
1859
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1756
1860
|
};
|
|
1757
1861
|
const tmpPath = path6.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
1758
|
-
await
|
|
1759
|
-
await
|
|
1862
|
+
await fs6.writeFile(tmpPath, JSON.stringify(newCache, null, 2), "utf-8");
|
|
1863
|
+
await fs6.rename(tmpPath, cachePath);
|
|
1760
1864
|
try {
|
|
1761
|
-
await
|
|
1865
|
+
await fs6.unlink(stalePath);
|
|
1762
1866
|
} catch {
|
|
1763
1867
|
}
|
|
1764
1868
|
return { schema: result.schema, flatSchema };
|
|
@@ -1769,24 +1873,10 @@ var BranchSchemaCache = class {
|
|
|
1769
1873
|
* @param branchRoot - Root directory of the branch
|
|
1770
1874
|
*/
|
|
1771
1875
|
async invalidate(branchRoot) {
|
|
1772
|
-
if (this.mode === "dev") {
|
|
1773
|
-
this.devModeCache = void 0;
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
1876
|
const cacheDir = path6.join(branchRoot, ".canopy-meta");
|
|
1777
1877
|
const stalePath = path6.join(cacheDir, "schema-cache.stale");
|
|
1778
|
-
await
|
|
1779
|
-
await
|
|
1780
|
-
}
|
|
1781
|
-
/**
|
|
1782
|
-
* Clear all caches (for testing).
|
|
1783
|
-
* In dev mode, clears in-memory cache.
|
|
1784
|
-
* In prod/prod-sim modes, this would need to traverse all branch directories.
|
|
1785
|
-
*/
|
|
1786
|
-
async clearAll() {
|
|
1787
|
-
if (this.mode === "dev") {
|
|
1788
|
-
this.devModeCache = void 0;
|
|
1789
|
-
}
|
|
1878
|
+
await fs6.mkdir(cacheDir, { recursive: true });
|
|
1879
|
+
await fs6.writeFile(stalePath, "", "utf-8");
|
|
1790
1880
|
}
|
|
1791
1881
|
};
|
|
1792
1882
|
|
|
@@ -2287,11 +2377,11 @@ function matchesBundleFilter(entry, filter, contentRoot) {
|
|
|
2287
2377
|
|
|
2288
2378
|
// dist/branch-metadata.js
|
|
2289
2379
|
import { randomUUID } from "node:crypto";
|
|
2290
|
-
import
|
|
2380
|
+
import fs8 from "node:fs/promises";
|
|
2291
2381
|
import path9 from "node:path";
|
|
2292
2382
|
|
|
2293
2383
|
// dist/branch-registry.js
|
|
2294
|
-
import
|
|
2384
|
+
import fs7 from "node:fs/promises";
|
|
2295
2385
|
import path8 from "node:path";
|
|
2296
2386
|
var REGISTRY_FILE = "branches.json";
|
|
2297
2387
|
var REGISTRY_STALE_FILE = "branches.stale.json";
|
|
@@ -2309,7 +2399,7 @@ var BranchRegistry = class {
|
|
|
2309
2399
|
*/
|
|
2310
2400
|
async list() {
|
|
2311
2401
|
try {
|
|
2312
|
-
const raw = await
|
|
2402
|
+
const raw = await fs7.readFile(this.registryPath, "utf8");
|
|
2313
2403
|
const parsed = JSON.parse(raw);
|
|
2314
2404
|
if (!parsed.version || !Array.isArray(parsed.branches)) {
|
|
2315
2405
|
return await this.regenerate();
|
|
@@ -2335,7 +2425,7 @@ var BranchRegistry = class {
|
|
|
2335
2425
|
*/
|
|
2336
2426
|
async invalidate() {
|
|
2337
2427
|
try {
|
|
2338
|
-
await
|
|
2428
|
+
await fs7.rename(this.registryPath, this.stalePath);
|
|
2339
2429
|
} catch (err) {
|
|
2340
2430
|
if (!isNotFoundError(err)) {
|
|
2341
2431
|
throw err;
|
|
@@ -2349,20 +2439,20 @@ var BranchRegistry = class {
|
|
|
2349
2439
|
async regenerate() {
|
|
2350
2440
|
const branches = await this.scanBranchDirectories();
|
|
2351
2441
|
const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
2352
|
-
await
|
|
2442
|
+
await fs7.mkdir(this.root, { recursive: true });
|
|
2353
2443
|
const snapshot = {
|
|
2354
2444
|
version: REGISTRY_VERSION,
|
|
2355
2445
|
branches
|
|
2356
2446
|
};
|
|
2357
|
-
await
|
|
2447
|
+
await fs7.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
2358
2448
|
try {
|
|
2359
|
-
await
|
|
2449
|
+
await fs7.rename(uniqueTempPath, this.registryPath);
|
|
2360
2450
|
} catch (err) {
|
|
2361
|
-
await
|
|
2451
|
+
await fs7.unlink(uniqueTempPath).catch(() => {
|
|
2362
2452
|
});
|
|
2363
2453
|
throw err;
|
|
2364
2454
|
}
|
|
2365
|
-
await
|
|
2455
|
+
await fs7.unlink(this.stalePath).catch(() => {
|
|
2366
2456
|
});
|
|
2367
2457
|
return branches;
|
|
2368
2458
|
}
|
|
@@ -2372,7 +2462,7 @@ var BranchRegistry = class {
|
|
|
2372
2462
|
async scanBranchDirectories() {
|
|
2373
2463
|
const branches = [];
|
|
2374
2464
|
try {
|
|
2375
|
-
const entries = await
|
|
2465
|
+
const entries = await fs7.readdir(this.root, { withFileTypes: true });
|
|
2376
2466
|
for (const entry of entries) {
|
|
2377
2467
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
2378
2468
|
continue;
|
|
@@ -2437,7 +2527,7 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
|
2437
2527
|
static async loadOnly(branchRoot) {
|
|
2438
2528
|
const filePath = path9.join(path9.resolve(branchRoot), BRANCH_META_DIR, BRANCH_META_FILE);
|
|
2439
2529
|
try {
|
|
2440
|
-
const raw = await
|
|
2530
|
+
const raw = await fs8.readFile(filePath, "utf8");
|
|
2441
2531
|
return JSON.parse(raw);
|
|
2442
2532
|
} catch (err) {
|
|
2443
2533
|
if (isNotFoundError(err)) {
|
|
@@ -2455,7 +2545,7 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
|
2455
2545
|
}
|
|
2456
2546
|
async load() {
|
|
2457
2547
|
try {
|
|
2458
|
-
const raw = await
|
|
2548
|
+
const raw = await fs8.readFile(this.filePath, "utf8");
|
|
2459
2549
|
const parsed = JSON.parse(raw);
|
|
2460
2550
|
const version = parsed.version ?? 0;
|
|
2461
2551
|
return { meta: parsed, version };
|
|
@@ -2479,11 +2569,11 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
|
2479
2569
|
version: newVersion,
|
|
2480
2570
|
writeId
|
|
2481
2571
|
};
|
|
2482
|
-
await
|
|
2572
|
+
await fs8.mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
2483
2573
|
const content = JSON.stringify(payload, null, 2) + "\n";
|
|
2484
2574
|
if (expectedVersion === null) {
|
|
2485
2575
|
try {
|
|
2486
|
-
await
|
|
2576
|
+
await fs8.writeFile(this.filePath, content, { flag: "wx" });
|
|
2487
2577
|
return { version: newVersion, writeId };
|
|
2488
2578
|
} catch (err) {
|
|
2489
2579
|
if (isFileExistsError(err)) {
|
|
@@ -2493,11 +2583,11 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
|
2493
2583
|
}
|
|
2494
2584
|
}
|
|
2495
2585
|
const tempPath = `${this.filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
2496
|
-
await
|
|
2586
|
+
await fs8.writeFile(tempPath, content, "utf-8");
|
|
2497
2587
|
try {
|
|
2498
2588
|
let currentVersion = null;
|
|
2499
2589
|
try {
|
|
2500
|
-
const current = JSON.parse(await
|
|
2590
|
+
const current = JSON.parse(await fs8.readFile(this.filePath, "utf-8"));
|
|
2501
2591
|
currentVersion = current.version ?? 0;
|
|
2502
2592
|
} catch {
|
|
2503
2593
|
currentVersion = null;
|
|
@@ -2505,13 +2595,13 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
|
2505
2595
|
if (currentVersion !== expectedVersion) {
|
|
2506
2596
|
throw new BranchMetadataConflictError();
|
|
2507
2597
|
}
|
|
2508
|
-
await
|
|
2509
|
-
const afterWrite = JSON.parse(await
|
|
2598
|
+
await fs8.rename(tempPath, this.filePath);
|
|
2599
|
+
const afterWrite = JSON.parse(await fs8.readFile(this.filePath, "utf-8"));
|
|
2510
2600
|
if (afterWrite.writeId !== writeId) {
|
|
2511
2601
|
throw new BranchMetadataConflictError();
|
|
2512
2602
|
}
|
|
2513
2603
|
} catch (err) {
|
|
2514
|
-
await
|
|
2604
|
+
await fs8.unlink(tempPath).catch(() => {
|
|
2515
2605
|
});
|
|
2516
2606
|
throw err;
|
|
2517
2607
|
}
|
|
@@ -2576,6 +2666,9 @@ var BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
|
2576
2666
|
await registry.invalidate();
|
|
2577
2667
|
}
|
|
2578
2668
|
};
|
|
2669
|
+
var getBranchMetadataFileManager = (branchRoot, baseRoot) => {
|
|
2670
|
+
return BranchMetadataFileManager.get(branchRoot, baseRoot);
|
|
2671
|
+
};
|
|
2579
2672
|
var loadBranchContext = async (options) => {
|
|
2580
2673
|
const { branchRoot, baseRoot } = resolveBranchPath({
|
|
2581
2674
|
branchName: options.branchName,
|
|
@@ -2605,19 +2698,704 @@ var STATIC_DEPLOY_USER = Object.freeze({
|
|
|
2605
2698
|
name: "Static Deploy"
|
|
2606
2699
|
});
|
|
2607
2700
|
|
|
2701
|
+
// dist/git-manager.js
|
|
2702
|
+
import fs9 from "node:fs/promises";
|
|
2703
|
+
import path10 from "node:path";
|
|
2704
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
2705
|
+
|
|
2706
|
+
// dist/utils/debug.js
|
|
2707
|
+
var LOG_LEVELS = {
|
|
2708
|
+
DEBUG: 0,
|
|
2709
|
+
INFO: 1,
|
|
2710
|
+
WARN: 2,
|
|
2711
|
+
ERROR: 3
|
|
2712
|
+
};
|
|
2713
|
+
var DebugLogger = class {
|
|
2714
|
+
constructor(options = {}) {
|
|
2715
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
2716
|
+
this.options = options;
|
|
2717
|
+
}
|
|
2718
|
+
shouldLog(level) {
|
|
2719
|
+
const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
|
|
2720
|
+
if (!enabled)
|
|
2721
|
+
return false;
|
|
2722
|
+
const minLevel = this.options.minLevel ?? "DEBUG";
|
|
2723
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
|
|
2724
|
+
}
|
|
2725
|
+
formatMessage(level, category, message) {
|
|
2726
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2727
|
+
const prefix = this.options.prefix ?? "CanopyCMS";
|
|
2728
|
+
return `[${timestamp}] [${prefix}:${category}] [${level}] ${message}`;
|
|
2729
|
+
}
|
|
2730
|
+
debug(category, message, data) {
|
|
2731
|
+
if (this.shouldLog("DEBUG")) {
|
|
2732
|
+
console.log(this.formatMessage("DEBUG", category, message), data ?? "");
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
info(category, message, data) {
|
|
2736
|
+
if (this.shouldLog("INFO")) {
|
|
2737
|
+
console.log(this.formatMessage("INFO", category, message), data ?? "");
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
warn(category, message, data) {
|
|
2741
|
+
if (this.shouldLog("WARN")) {
|
|
2742
|
+
console.warn(this.formatMessage("WARN", category, message), data ?? "");
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
error(category, message, data) {
|
|
2746
|
+
const msg = this.formatMessage("ERROR", category, message);
|
|
2747
|
+
if (this.shouldLog("ERROR")) {
|
|
2748
|
+
console.error(msg, data ?? "");
|
|
2749
|
+
}
|
|
2750
|
+
const throwOnError = this.options.throwOnError ?? false;
|
|
2751
|
+
if (throwOnError) {
|
|
2752
|
+
const errorMsg = data ? `${message}: ${JSON.stringify(data)}` : message;
|
|
2753
|
+
throw new Error(errorMsg);
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Start timing an operation
|
|
2758
|
+
*/
|
|
2759
|
+
time(label) {
|
|
2760
|
+
this.timers.set(label, Date.now());
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* End timing an operation and log the duration
|
|
2764
|
+
*/
|
|
2765
|
+
timeEnd(category, label) {
|
|
2766
|
+
const start = this.timers.get(label);
|
|
2767
|
+
if (start === void 0) {
|
|
2768
|
+
this.warn(category, `Timer '${label}' does not exist`);
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const duration = Date.now() - start;
|
|
2772
|
+
this.timers.delete(label);
|
|
2773
|
+
this.debug(category, `${label} completed`, { durationMs: duration });
|
|
2774
|
+
return duration;
|
|
2775
|
+
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Wrap an async function with automatic timing
|
|
2778
|
+
*/
|
|
2779
|
+
async timed(category, label, fn) {
|
|
2780
|
+
this.time(label);
|
|
2781
|
+
try {
|
|
2782
|
+
return await fn();
|
|
2783
|
+
} finally {
|
|
2784
|
+
this.timeEnd(category, label);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
function createDebugLogger(options) {
|
|
2789
|
+
return new DebugLogger(options);
|
|
2790
|
+
}
|
|
2791
|
+
var testLogger = createDebugLogger({
|
|
2792
|
+
enabled: process.env.E2E_DEBUG === "true",
|
|
2793
|
+
prefix: "E2E",
|
|
2794
|
+
throwOnError: false
|
|
2795
|
+
});
|
|
2796
|
+
|
|
2797
|
+
// dist/utils/git.js
|
|
2798
|
+
import { simpleGit } from "simple-git";
|
|
2799
|
+
async function detectHeadBranch(repoRoot, fallback = "main") {
|
|
2800
|
+
try {
|
|
2801
|
+
const git = simpleGit({ baseDir: repoRoot });
|
|
2802
|
+
const head = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
2803
|
+
return head && head !== "HEAD" ? head : fallback;
|
|
2804
|
+
} catch {
|
|
2805
|
+
return fallback;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// dist/git-manager.js
|
|
2810
|
+
var log = createDebugLogger({ prefix: "GitManager" });
|
|
2811
|
+
var remoteInitLocks = /* @__PURE__ */ new Map();
|
|
2812
|
+
var GitManager = class _GitManager {
|
|
2813
|
+
constructor(options, gitOptions) {
|
|
2814
|
+
this.repoPath = path10.resolve(options.repoPath);
|
|
2815
|
+
this.baseBranch = options.baseBranch ?? "main";
|
|
2816
|
+
this.remote = options.remote ?? "origin";
|
|
2817
|
+
this.git = simpleGit2({ baseDir: this.repoPath, ...gitOptions });
|
|
2818
|
+
}
|
|
2819
|
+
static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
|
|
2820
|
+
log.debug("git", "Cloning repository", {
|
|
2821
|
+
remoteUrl,
|
|
2822
|
+
targetPath,
|
|
2823
|
+
baseBranch
|
|
2824
|
+
});
|
|
2825
|
+
const git = simpleGit2();
|
|
2826
|
+
await git.clone(remoteUrl, targetPath, ["--branch", baseBranch, "--single-branch"]);
|
|
2827
|
+
log.debug("git", "Clone complete");
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Initializes a local bare git repository to simulate a remote for dev mode.
|
|
2831
|
+
*
|
|
2832
|
+
* This is idempotent - if the remote already exists, it will not be recreated.
|
|
2833
|
+
*
|
|
2834
|
+
* The remote is seeded with the current state of the baseBranch (e.g., 'main').
|
|
2835
|
+
* If you need to change the baseBranch or reset the simulation, delete
|
|
2836
|
+
* `.canopycms/remote.git` and `.canopycms/branches` and restart.
|
|
2837
|
+
*
|
|
2838
|
+
* @throws Error if not a git repo, no commits, or baseBranch doesn't exist
|
|
2839
|
+
*/
|
|
2840
|
+
static async ensureLocalSimulatedRemote(options) {
|
|
2841
|
+
const existingLock = remoteInitLocks.get(options.remotePath);
|
|
2842
|
+
if (existingLock) {
|
|
2843
|
+
log.debug("git", "Waiting for existing remote initialization", {
|
|
2844
|
+
remotePath: options.remotePath
|
|
2845
|
+
});
|
|
2846
|
+
await existingLock;
|
|
2847
|
+
try {
|
|
2848
|
+
const stat = await fs9.stat(options.remotePath);
|
|
2849
|
+
if (stat.isDirectory()) {
|
|
2850
|
+
log.debug("git", "Remote exists after waiting for lock");
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
} catch (err) {
|
|
2854
|
+
if (!isNotFoundError(err))
|
|
2855
|
+
throw err;
|
|
2856
|
+
log.debug("git", "Remote does not exist after lock, will retry initialization");
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
const lockPromise = log.timed("git", "ensureLocalSimulatedRemote", async () => {
|
|
2860
|
+
try {
|
|
2861
|
+
log.debug("git", "Initializing local simulated remote", {
|
|
2862
|
+
remotePath: options.remotePath,
|
|
2863
|
+
baseBranch: options.baseBranch
|
|
2864
|
+
});
|
|
2865
|
+
try {
|
|
2866
|
+
const stat = await fs9.stat(options.remotePath);
|
|
2867
|
+
if (stat.isDirectory()) {
|
|
2868
|
+
log.debug("git", "Remote already exists, skipping");
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
} catch (err) {
|
|
2872
|
+
if (!isNotFoundError(err))
|
|
2873
|
+
throw err;
|
|
2874
|
+
}
|
|
2875
|
+
let gitRoot = options.sourcePath;
|
|
2876
|
+
try {
|
|
2877
|
+
const sourceGit2 = simpleGit2({ baseDir: options.sourcePath });
|
|
2878
|
+
const result = await sourceGit2.raw(["rev-parse", "--show-toplevel"]);
|
|
2879
|
+
gitRoot = result.trim();
|
|
2880
|
+
} catch {
|
|
2881
|
+
gitRoot = options.sourcePath;
|
|
2882
|
+
}
|
|
2883
|
+
const sourceGit = simpleGit2({ baseDir: gitRoot });
|
|
2884
|
+
try {
|
|
2885
|
+
await sourceGit.status();
|
|
2886
|
+
} catch {
|
|
2887
|
+
throw new Error("Cannot initialize local simulated remote: current directory is not a git repository. Please initialize git or provide an explicit remoteUrl.");
|
|
2888
|
+
}
|
|
2889
|
+
let hasCommits = false;
|
|
2890
|
+
try {
|
|
2891
|
+
const log3 = await sourceGit.log(["-1"]);
|
|
2892
|
+
hasCommits = log3.total > 0;
|
|
2893
|
+
} catch {
|
|
2894
|
+
hasCommits = false;
|
|
2895
|
+
}
|
|
2896
|
+
if (!hasCommits) {
|
|
2897
|
+
throw new Error("Cannot initialize local simulated remote: repository has no commits. Please make an initial commit or provide an explicit remoteUrl.");
|
|
2898
|
+
}
|
|
2899
|
+
const branches = await sourceGit.branchLocal();
|
|
2900
|
+
if (!branches.all.includes(options.baseBranch)) {
|
|
2901
|
+
throw new Error(`Cannot initialize local simulated remote: base branch '${options.baseBranch}' does not exist locally. Please checkout '${options.baseBranch}' first or provide an explicit remoteUrl.`);
|
|
2902
|
+
}
|
|
2903
|
+
log.debug("git", "Creating bare remote repository");
|
|
2904
|
+
await fs9.mkdir(path10.dirname(options.remotePath), { recursive: true });
|
|
2905
|
+
await simpleGit2().raw([
|
|
2906
|
+
"init",
|
|
2907
|
+
"--bare",
|
|
2908
|
+
`--initial-branch=${options.baseBranch}`,
|
|
2909
|
+
options.remotePath
|
|
2910
|
+
]);
|
|
2911
|
+
const tempRemoteName = `__canopycms_init_${Date.now()}__`;
|
|
2912
|
+
try {
|
|
2913
|
+
await sourceGit.addRemote(tempRemoteName, options.remotePath);
|
|
2914
|
+
if (options.subdirectory) {
|
|
2915
|
+
const splitBranch = `__canopycms_split_${Date.now()}__`;
|
|
2916
|
+
try {
|
|
2917
|
+
await sourceGit.raw([
|
|
2918
|
+
"subtree",
|
|
2919
|
+
"split",
|
|
2920
|
+
"--prefix",
|
|
2921
|
+
options.subdirectory,
|
|
2922
|
+
"-b",
|
|
2923
|
+
splitBranch
|
|
2924
|
+
]);
|
|
2925
|
+
await sourceGit.push(tempRemoteName, `${splitBranch}:${options.baseBranch}`);
|
|
2926
|
+
await sourceGit.raw(["branch", "-D", splitBranch]);
|
|
2927
|
+
} catch (err) {
|
|
2928
|
+
try {
|
|
2929
|
+
await sourceGit.raw(["branch", "-D", splitBranch]);
|
|
2930
|
+
} catch {
|
|
2931
|
+
}
|
|
2932
|
+
throw err;
|
|
2933
|
+
}
|
|
2934
|
+
} else {
|
|
2935
|
+
await sourceGit.push(tempRemoteName, `${options.baseBranch}:${options.baseBranch}`);
|
|
2936
|
+
}
|
|
2937
|
+
} finally {
|
|
2938
|
+
try {
|
|
2939
|
+
await sourceGit.removeRemote(tempRemoteName);
|
|
2940
|
+
} catch {
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
log.debug("git", "Remote initialization complete");
|
|
2944
|
+
} finally {
|
|
2945
|
+
remoteInitLocks.delete(options.remotePath);
|
|
2946
|
+
}
|
|
2947
|
+
});
|
|
2948
|
+
remoteInitLocks.set(options.remotePath, lockPromise);
|
|
2949
|
+
await lockPromise;
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Find the git root directory
|
|
2953
|
+
* @returns Path to git root, or cwd if not in a git repo
|
|
2954
|
+
*/
|
|
2955
|
+
static async findGitRoot() {
|
|
2956
|
+
let gitRoot = process.cwd();
|
|
2957
|
+
try {
|
|
2958
|
+
const git = simpleGit2({ baseDir: process.cwd() });
|
|
2959
|
+
const result = await git.raw(["rev-parse", "--show-toplevel"]);
|
|
2960
|
+
gitRoot = result.trim();
|
|
2961
|
+
} catch {
|
|
2962
|
+
}
|
|
2963
|
+
return gitRoot;
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Validate that a git repository exists at the given path
|
|
2967
|
+
* @param repoPath - Path to check for .git directory
|
|
2968
|
+
* @throws Error if git repo doesn't exist
|
|
2969
|
+
*/
|
|
2970
|
+
static async validateGitRepoExists(repoPath) {
|
|
2971
|
+
try {
|
|
2972
|
+
const stat = await fs9.stat(path10.join(repoPath, ".git"));
|
|
2973
|
+
if (!stat.isDirectory()) {
|
|
2974
|
+
throw new Error(`Expected git repo at ${repoPath}`);
|
|
2975
|
+
}
|
|
2976
|
+
} catch (err) {
|
|
2977
|
+
if (isNotFoundError(err)) {
|
|
2978
|
+
throw new Error(`Expected git repo at ${repoPath}`);
|
|
2979
|
+
}
|
|
2980
|
+
throw err;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Resolves the remote URL for git operations following the priority:
|
|
2985
|
+
* 1. Explicit remoteUrl parameter
|
|
2986
|
+
* 2. Config defaultRemoteUrl
|
|
2987
|
+
* 3. Environment variable (mode-specific)
|
|
2988
|
+
* 4. Auto-initialized local remote (for dev mode)
|
|
2989
|
+
*
|
|
2990
|
+
* Uses strategy flags to determine behavior, GitManager executes the logic.
|
|
2991
|
+
*
|
|
2992
|
+
* @param options.sourceRoot - Optional source directory for monorepos. When provided,
|
|
2993
|
+
* this directory (relative to git root) is used as the source for the simulated remote.
|
|
2994
|
+
* Defaults to process.cwd().
|
|
2995
|
+
*
|
|
2996
|
+
* @returns Remote URL or undefined if no remote is needed
|
|
2997
|
+
*/
|
|
2998
|
+
static async resolveRemoteUrl(options) {
|
|
2999
|
+
const { operatingStrategy: operatingStrategy2 } = await Promise.resolve().then(() => (init_operating_mode(), operating_mode_exports));
|
|
3000
|
+
const strategy = operatingStrategy2(options.mode);
|
|
3001
|
+
const config = strategy.getRemoteUrlConfig();
|
|
3002
|
+
if (options.remoteUrl)
|
|
3003
|
+
return options.remoteUrl;
|
|
3004
|
+
if (options.defaultRemoteUrl)
|
|
3005
|
+
return options.defaultRemoteUrl;
|
|
3006
|
+
if (process.env[config.envVarName])
|
|
3007
|
+
return process.env[config.envVarName];
|
|
3008
|
+
if (config.autoDetectRemotePath) {
|
|
3009
|
+
try {
|
|
3010
|
+
const stat = await fs9.stat(config.autoDetectRemotePath);
|
|
3011
|
+
if (stat.isDirectory()) {
|
|
3012
|
+
log.debug("git", "Auto-detected local remote", {
|
|
3013
|
+
path: config.autoDetectRemotePath
|
|
3014
|
+
});
|
|
3015
|
+
return config.autoDetectRemotePath;
|
|
3016
|
+
}
|
|
3017
|
+
} catch {
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
if (config.shouldAutoInitLocal) {
|
|
3021
|
+
const gitRoot = await this.findGitRoot();
|
|
3022
|
+
const sourceRoot = options.sourceRoot;
|
|
3023
|
+
const sourcePath = sourceRoot ? path10.resolve(gitRoot, sourceRoot) : gitRoot;
|
|
3024
|
+
const localRemotePath = path10.join(sourcePath, config.defaultRemotePath);
|
|
3025
|
+
await this.ensureLocalSimulatedRemote({
|
|
3026
|
+
remotePath: localRemotePath,
|
|
3027
|
+
sourcePath: gitRoot,
|
|
3028
|
+
baseBranch: options.baseBranch,
|
|
3029
|
+
subdirectory: sourceRoot
|
|
3030
|
+
});
|
|
3031
|
+
return localRemotePath;
|
|
3032
|
+
}
|
|
3033
|
+
return void 0;
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Ensures a git workspace is initialized and ready for use.
|
|
3037
|
+
* Handles cloning, remote configuration, and branch checkout/creation.
|
|
3038
|
+
*
|
|
3039
|
+
* This centralizes the common initialization sequence used by both BranchWorkspaceManager
|
|
3040
|
+
* and SettingsWorkspaceManager.
|
|
3041
|
+
*
|
|
3042
|
+
* Note: Does NOT configure git author - that should be done before commits, not during init.
|
|
3043
|
+
*
|
|
3044
|
+
* @returns Configured GitManager instance for the workspace
|
|
3045
|
+
*/
|
|
3046
|
+
static async initializeWorkspace(options) {
|
|
3047
|
+
let baseBranch = options.baseBranch;
|
|
3048
|
+
if (!baseBranch && options.mode === "dev") {
|
|
3049
|
+
const sourceRoot = options.sourceRoot ? path10.resolve(process.cwd(), options.sourceRoot) : process.cwd();
|
|
3050
|
+
baseBranch = await detectHeadBranch(sourceRoot);
|
|
3051
|
+
}
|
|
3052
|
+
baseBranch = baseBranch ?? "main";
|
|
3053
|
+
const remoteName = options.remoteName ?? "origin";
|
|
3054
|
+
let repoExists = false;
|
|
3055
|
+
try {
|
|
3056
|
+
const stat = await fs9.stat(path10.join(options.workspacePath, ".git"));
|
|
3057
|
+
repoExists = stat.isDirectory();
|
|
3058
|
+
} catch (err) {
|
|
3059
|
+
if (!isNotFoundError(err))
|
|
3060
|
+
throw err;
|
|
3061
|
+
}
|
|
3062
|
+
let justCloned = false;
|
|
3063
|
+
if (!repoExists) {
|
|
3064
|
+
const remoteUrl = await _GitManager.resolveRemoteUrl({
|
|
3065
|
+
mode: options.mode,
|
|
3066
|
+
remoteUrl: options.remoteUrl,
|
|
3067
|
+
defaultRemoteUrl: options.defaultRemoteUrl,
|
|
3068
|
+
baseBranch,
|
|
3069
|
+
sourceRoot: options.sourceRoot
|
|
3070
|
+
});
|
|
3071
|
+
if (!remoteUrl) {
|
|
3072
|
+
throw new Error("CanopyCMS: defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is required to initialize workspace");
|
|
3073
|
+
}
|
|
3074
|
+
await _GitManager.cloneRepo(remoteUrl, options.workspacePath, baseBranch);
|
|
3075
|
+
justCloned = true;
|
|
3076
|
+
}
|
|
3077
|
+
const git = new _GitManager({
|
|
3078
|
+
repoPath: options.workspacePath,
|
|
3079
|
+
baseBranch,
|
|
3080
|
+
remote: remoteName
|
|
3081
|
+
});
|
|
3082
|
+
if (!justCloned) {
|
|
3083
|
+
const remoteUrl = await _GitManager.resolveRemoteUrl({
|
|
3084
|
+
mode: options.mode,
|
|
3085
|
+
remoteUrl: options.remoteUrl,
|
|
3086
|
+
defaultRemoteUrl: options.defaultRemoteUrl,
|
|
3087
|
+
baseBranch,
|
|
3088
|
+
sourceRoot: options.sourceRoot
|
|
3089
|
+
});
|
|
3090
|
+
if (remoteUrl) {
|
|
3091
|
+
await git.ensureRemote(remoteUrl);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
if (options.branchType === "orphan") {
|
|
3095
|
+
await git.createOrphanSettingsBranch(options.branchName, {});
|
|
3096
|
+
} else {
|
|
3097
|
+
await git.checkoutBranch(options.branchName);
|
|
3098
|
+
}
|
|
3099
|
+
await git.git.addConfig("canopycms.managed", "true");
|
|
3100
|
+
log.debug("git", "Marked workspace as CanopyCMS-managed", {
|
|
3101
|
+
workspacePath: options.workspacePath
|
|
3102
|
+
});
|
|
3103
|
+
return git;
|
|
3104
|
+
}
|
|
3105
|
+
async status() {
|
|
3106
|
+
const s = await this.git.status();
|
|
3107
|
+
return {
|
|
3108
|
+
files: s.files,
|
|
3109
|
+
ahead: s.ahead,
|
|
3110
|
+
behind: s.behind,
|
|
3111
|
+
current: s.current,
|
|
3112
|
+
tracking: s.tracking
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
async checkoutBranch(branch) {
|
|
3116
|
+
const branches = await this.git.branch();
|
|
3117
|
+
if (branches.all.includes(branch)) {
|
|
3118
|
+
await this.git.checkout(branch);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
const remoteRef = `${this.remote}/${this.baseBranch}`;
|
|
3122
|
+
try {
|
|
3123
|
+
await this.git.fetch(this.remote, this.baseBranch);
|
|
3124
|
+
} catch {
|
|
3125
|
+
}
|
|
3126
|
+
try {
|
|
3127
|
+
await this.git.checkoutBranch(branch, remoteRef);
|
|
3128
|
+
return;
|
|
3129
|
+
} catch {
|
|
3130
|
+
const baseExists = branches.all.includes(this.baseBranch);
|
|
3131
|
+
if (baseExists) {
|
|
3132
|
+
await this.git.checkout(["-B", branch, this.baseBranch]);
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
await this.git.checkoutLocalBranch(branch);
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
async pullBase() {
|
|
3139
|
+
await this.git.fetch(this.remote, this.baseBranch);
|
|
3140
|
+
await this.git.merge([`${this.remote}/${this.baseBranch}`]);
|
|
3141
|
+
}
|
|
3142
|
+
async pullCurrentBranch() {
|
|
3143
|
+
const branches = await this.git.branch();
|
|
3144
|
+
const currentBranch = branches.current;
|
|
3145
|
+
await this.git.fetch(this.remote, currentBranch);
|
|
3146
|
+
await this.git.merge([`${this.remote}/${currentBranch}`]);
|
|
3147
|
+
}
|
|
3148
|
+
async rebaseOntoBase() {
|
|
3149
|
+
await this.git.fetch(this.remote, this.baseBranch);
|
|
3150
|
+
await this.git.rebase([`${this.remote}/${this.baseBranch}`]);
|
|
3151
|
+
}
|
|
3152
|
+
async add(files) {
|
|
3153
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
3154
|
+
await this.git.add(fileArray);
|
|
3155
|
+
}
|
|
3156
|
+
async commit(message) {
|
|
3157
|
+
await this.git.commit(message);
|
|
3158
|
+
}
|
|
3159
|
+
async push(branch) {
|
|
3160
|
+
const target = branch ?? await this.git.revparse(["--abbrev-ref", "HEAD"]);
|
|
3161
|
+
await this.git.push(this.remote, `${target}:${target}`, ["--set-upstream"]);
|
|
3162
|
+
}
|
|
3163
|
+
async ensureAuthor(author) {
|
|
3164
|
+
const config = await this.git.listConfig();
|
|
3165
|
+
const isManaged = config.all["canopycms.managed"] === "true";
|
|
3166
|
+
if (!isManaged) {
|
|
3167
|
+
throw new Error(`Cannot set git bot author in non-managed repository (${this.repoPath}). Bot identity should only be set in CanopyCMS branch clones or test workspaces. If this is a test workspace, add "git config canopycms.managed true" to mark it as managed.`);
|
|
3168
|
+
}
|
|
3169
|
+
const currentName = config.all["user.name"];
|
|
3170
|
+
const currentEmail = config.all["user.email"];
|
|
3171
|
+
if (currentName !== author.name) {
|
|
3172
|
+
await this.git.addConfig("user.name", author.name);
|
|
3173
|
+
}
|
|
3174
|
+
if (currentEmail !== author.email) {
|
|
3175
|
+
await this.git.addConfig("user.email", author.email);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
async ensureRemote(remoteUrl) {
|
|
3179
|
+
const remotes = await this.git.getRemotes(true);
|
|
3180
|
+
const existing = remotes.find((r) => r.name === this.remote);
|
|
3181
|
+
if (!existing) {
|
|
3182
|
+
await this.git.addRemote(this.remote, remoteUrl);
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
const currentUrl = existing.refs.push ?? existing.refs.fetch;
|
|
3186
|
+
if (currentUrl && currentUrl !== remoteUrl) {
|
|
3187
|
+
await this.git.remote(["set-url", this.remote, remoteUrl]);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
/**
|
|
3191
|
+
* Check if working directory has uncommitted changes
|
|
3192
|
+
*/
|
|
3193
|
+
async hasUncommittedChanges() {
|
|
3194
|
+
const status = await this.status();
|
|
3195
|
+
return status.files.length > 0;
|
|
3196
|
+
}
|
|
3197
|
+
/**
|
|
3198
|
+
* Get list of uncommitted file paths
|
|
3199
|
+
*/
|
|
3200
|
+
async getUncommittedFiles() {
|
|
3201
|
+
const status = await this.status();
|
|
3202
|
+
return status.files.map((f) => f.path);
|
|
3203
|
+
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Force push (use with caution - for PR updates only)
|
|
3206
|
+
* Uses --force-with-lease for safer force pushes
|
|
3207
|
+
*/
|
|
3208
|
+
async forcePush(branch) {
|
|
3209
|
+
const target = branch ?? await this.git.revparse(["--abbrev-ref", "HEAD"]);
|
|
3210
|
+
await this.git.push(this.remote, target, ["--force-with-lease"]);
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Get remote URL for current repo
|
|
3214
|
+
*/
|
|
3215
|
+
async getRemoteUrl() {
|
|
3216
|
+
const remotes = await this.git.getRemotes(true);
|
|
3217
|
+
const remote = remotes.find((r) => r.name === this.remote);
|
|
3218
|
+
return remote?.refs.push || remote?.refs.fetch;
|
|
3219
|
+
}
|
|
3220
|
+
/**
|
|
3221
|
+
* Add a pattern to .git/info/exclude to prevent it from being committed/pushed.
|
|
3222
|
+
* This is used to exclude .canopy-meta/ from content branch workspaces.
|
|
3223
|
+
*
|
|
3224
|
+
* .git/info/exclude is a per-repository gitignore that never gets committed.
|
|
3225
|
+
* Perfect for runtime metadata that should never leave the workspace.
|
|
3226
|
+
*
|
|
3227
|
+
* This is idempotent - if the pattern already exists, it won't be added again.
|
|
3228
|
+
*/
|
|
3229
|
+
async ensureGitExclude(pattern) {
|
|
3230
|
+
const excludePath = path10.join(this.repoPath, ".git", "info", "exclude");
|
|
3231
|
+
await fs9.mkdir(path10.dirname(excludePath), { recursive: true });
|
|
3232
|
+
let content = "";
|
|
3233
|
+
try {
|
|
3234
|
+
content = await fs9.readFile(excludePath, "utf-8");
|
|
3235
|
+
} catch (err) {
|
|
3236
|
+
if (!isNotFoundError(err))
|
|
3237
|
+
throw err;
|
|
3238
|
+
}
|
|
3239
|
+
const lines = content.split("\n");
|
|
3240
|
+
if (lines.some((line) => line.trim() === pattern)) {
|
|
3241
|
+
log.debug("git", "Pattern already in .git/info/exclude", { pattern });
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
|
|
3245
|
+
const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
|
|
3246
|
+
await fs9.writeFile(excludePath, newContent, "utf-8");
|
|
3247
|
+
log.debug("git", "Added pattern to .git/info/exclude", { pattern });
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Create an orphan branch for settings (permissions/groups).
|
|
3251
|
+
*
|
|
3252
|
+
* Orphan branches have no shared history with other branches - they start fresh.
|
|
3253
|
+
* This is perfect for deployment-specific settings that shouldn't pollute content history.
|
|
3254
|
+
*
|
|
3255
|
+
* The branch contains only settings files in .canopy-meta/ (groups.json, permissions.json).
|
|
3256
|
+
*
|
|
3257
|
+
* @param branchName - Name of the orphan branch (e.g., 'canopycms-settings-prod')
|
|
3258
|
+
* @param initialFiles - Files to commit to the new branch (e.g., { 'permissions.json': '{}', 'groups.json': '{}' })
|
|
3259
|
+
*/
|
|
3260
|
+
async createOrphanSettingsBranch(branchName, initialFiles) {
|
|
3261
|
+
log.debug("git", "Creating orphan settings branch", { branchName });
|
|
3262
|
+
const branches = await this.git.branch();
|
|
3263
|
+
if (branches.all.includes(branchName)) {
|
|
3264
|
+
log.debug("git", "Orphan branch already exists", { branchName });
|
|
3265
|
+
await this.git.checkout(branchName);
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
await this.git.raw(["checkout", "--orphan", branchName]);
|
|
3269
|
+
try {
|
|
3270
|
+
await this.git.raw(["rm", "-rf", "."]);
|
|
3271
|
+
} catch {
|
|
3272
|
+
}
|
|
3273
|
+
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
3274
|
+
const absolutePath = path10.join(this.repoPath, filePath);
|
|
3275
|
+
await fs9.mkdir(path10.dirname(absolutePath), { recursive: true });
|
|
3276
|
+
await fs9.writeFile(absolutePath, content, "utf-8");
|
|
3277
|
+
await this.git.add(filePath);
|
|
3278
|
+
}
|
|
3279
|
+
await this.git.commit("Initialize settings branch", ["--allow-empty"]);
|
|
3280
|
+
log.debug("git", "Orphan settings branch created", { branchName });
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
|
|
3284
|
+
// dist/branch-workspace.js
|
|
3285
|
+
var log2 = createDebugLogger({ prefix: "BranchWorkspace" });
|
|
3286
|
+
var workspaceInitLocks = /* @__PURE__ */ new Map();
|
|
3287
|
+
var BranchWorkspaceManager = class {
|
|
3288
|
+
constructor(config) {
|
|
3289
|
+
this.config = config;
|
|
3290
|
+
}
|
|
3291
|
+
async ensureGitWorkspace(options) {
|
|
3292
|
+
return log2.timed("workspace", "ensureGitWorkspace", async () => {
|
|
3293
|
+
const existingLock = workspaceInitLocks.get(options.branchRoot);
|
|
3294
|
+
if (existingLock) {
|
|
3295
|
+
await existingLock;
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
const lockPromise = (async () => {
|
|
3299
|
+
try {
|
|
3300
|
+
log2.debug("workspace", "Ensuring git workspace", {
|
|
3301
|
+
branchName: options.branchName,
|
|
3302
|
+
mode: options.mode
|
|
3303
|
+
});
|
|
3304
|
+
await GitManager.initializeWorkspace({
|
|
3305
|
+
workspacePath: options.branchRoot,
|
|
3306
|
+
branchName: options.branchName,
|
|
3307
|
+
mode: options.mode,
|
|
3308
|
+
baseBranch: this.config.defaultBaseBranch,
|
|
3309
|
+
sourceRoot: this.config.sourceRoot,
|
|
3310
|
+
defaultRemoteUrl: this.config.defaultRemoteUrl,
|
|
3311
|
+
remoteUrl: options.remoteUrl,
|
|
3312
|
+
remoteName: this.config.defaultRemoteName,
|
|
3313
|
+
branchType: "content"
|
|
3314
|
+
});
|
|
3315
|
+
} finally {
|
|
3316
|
+
workspaceInitLocks.delete(options.branchRoot);
|
|
3317
|
+
}
|
|
3318
|
+
})();
|
|
3319
|
+
workspaceInitLocks.set(options.branchRoot, lockPromise);
|
|
3320
|
+
await lockPromise;
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
async openOrCreateBranch(options) {
|
|
3324
|
+
const { branchName, mode, basePathOverride, title, description, access, createdBy, remoteUrl } = options;
|
|
3325
|
+
const { branchRoot, baseRoot, branchName: safeName } = await ensureBranchRoot({
|
|
3326
|
+
mode,
|
|
3327
|
+
branchName,
|
|
3328
|
+
basePathOverride
|
|
3329
|
+
});
|
|
3330
|
+
await this.ensureGitWorkspace({
|
|
3331
|
+
branchRoot,
|
|
3332
|
+
branchName: safeName,
|
|
3333
|
+
mode,
|
|
3334
|
+
remoteUrl
|
|
3335
|
+
});
|
|
3336
|
+
const metadata = getBranchMetadataFileManager(branchRoot, baseRoot);
|
|
3337
|
+
const meta = await metadata.save({
|
|
3338
|
+
branch: {
|
|
3339
|
+
name: safeName,
|
|
3340
|
+
title,
|
|
3341
|
+
description,
|
|
3342
|
+
access,
|
|
3343
|
+
createdBy
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
return {
|
|
3347
|
+
branch: meta.branch,
|
|
3348
|
+
branchRoot,
|
|
3349
|
+
baseRoot
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
};
|
|
3353
|
+
async function loadOrCreateBranchContext(options) {
|
|
3354
|
+
if (isDeployedStatic(options.config)) {
|
|
3355
|
+
const cwd = process.cwd();
|
|
3356
|
+
return {
|
|
3357
|
+
branch: {
|
|
3358
|
+
name: options.branchName,
|
|
3359
|
+
status: "editing",
|
|
3360
|
+
access: {},
|
|
3361
|
+
createdBy: "__static_deploy__",
|
|
3362
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3363
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3364
|
+
},
|
|
3365
|
+
branchRoot: cwd,
|
|
3366
|
+
baseRoot: cwd
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
const existing = await loadBranchContext({
|
|
3370
|
+
branchName: options.branchName,
|
|
3371
|
+
mode: options.mode,
|
|
3372
|
+
basePathOverride: options.basePathOverride
|
|
3373
|
+
});
|
|
3374
|
+
if (existing)
|
|
3375
|
+
return existing;
|
|
3376
|
+
const manager = new BranchWorkspaceManager(options.config);
|
|
3377
|
+
return manager.openOrCreateBranch({
|
|
3378
|
+
branchName: options.branchName,
|
|
3379
|
+
mode: options.mode,
|
|
3380
|
+
basePathOverride: options.basePathOverride,
|
|
3381
|
+
createdBy: options.createdBy,
|
|
3382
|
+
remoteUrl: options.remoteUrl
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
|
|
2608
3386
|
// dist/ai/resolve-branch.js
|
|
2609
3387
|
async function resolveBranchRoot(config) {
|
|
2610
|
-
if (
|
|
3388
|
+
if (isDeployedStatic(config)) {
|
|
2611
3389
|
return process.cwd();
|
|
2612
3390
|
}
|
|
2613
3391
|
const baseBranch = config.defaultBaseBranch ?? "main";
|
|
2614
|
-
const context = await
|
|
3392
|
+
const context = await loadOrCreateBranchContext({
|
|
3393
|
+
config,
|
|
2615
3394
|
branchName: baseBranch,
|
|
2616
|
-
mode: config.mode
|
|
3395
|
+
mode: config.mode,
|
|
3396
|
+
createdBy: "canopycms-ai",
|
|
3397
|
+
remoteUrl: config.defaultRemoteUrl
|
|
2617
3398
|
});
|
|
2618
|
-
if (!context) {
|
|
2619
|
-
throw new Error(`Could not load branch context for "${baseBranch}". Ensure the branch exists and has been initialized.`);
|
|
2620
|
-
}
|
|
2621
3399
|
return context.branchRoot;
|
|
2622
3400
|
}
|
|
2623
3401
|
|
|
@@ -2641,15 +3419,15 @@ async function generateAIContentFiles(options) {
|
|
|
2641
3419
|
contentRoot: contentRootName,
|
|
2642
3420
|
config: aiConfig
|
|
2643
3421
|
});
|
|
2644
|
-
const absoluteOutputDir =
|
|
3422
|
+
const absoluteOutputDir = path11.resolve(outputDir) + path11.sep;
|
|
2645
3423
|
let fileCount = 0;
|
|
2646
3424
|
for (const [filePath, content] of result.files) {
|
|
2647
|
-
const absolutePath =
|
|
3425
|
+
const absolutePath = path11.resolve(path11.join(absoluteOutputDir, filePath));
|
|
2648
3426
|
if (!absolutePath.startsWith(absoluteOutputDir)) {
|
|
2649
3427
|
throw new Error(`Path traversal detected in AI content output: ${filePath}`);
|
|
2650
3428
|
}
|
|
2651
|
-
await
|
|
2652
|
-
await
|
|
3429
|
+
await fs10.mkdir(path11.dirname(absolutePath), { recursive: true });
|
|
3430
|
+
await fs10.writeFile(absolutePath, content, "utf-8");
|
|
2653
3431
|
fileCount++;
|
|
2654
3432
|
}
|
|
2655
3433
|
return { fileCount, outputDir: absoluteOutputDir };
|
|
@@ -2660,7 +3438,7 @@ var jiti = createJiti(import.meta.url);
|
|
|
2660
3438
|
async function generateAIContentCLI(options) {
|
|
2661
3439
|
const { projectDir, outputDir = "public/ai", configPath, appDir = "app" } = options;
|
|
2662
3440
|
console.log("\nCanopyCMS generate-ai-content\n");
|
|
2663
|
-
const canopyConfigPath =
|
|
3441
|
+
const canopyConfigPath = path12.join(projectDir, "canopycms.config.ts");
|
|
2664
3442
|
let canopyConfigModule;
|
|
2665
3443
|
try {
|
|
2666
3444
|
canopyConfigModule = await jiti.import(canopyConfigPath);
|
|
@@ -2671,7 +3449,7 @@ async function generateAIContentCLI(options) {
|
|
|
2671
3449
|
}
|
|
2672
3450
|
const configExport = canopyConfigModule.default ?? canopyConfigModule.config ?? canopyConfigModule;
|
|
2673
3451
|
const serverConfig = typeof configExport === "object" && configExport !== null && "server" in configExport ? configExport.server : configExport;
|
|
2674
|
-
const schemasPath =
|
|
3452
|
+
const schemasPath = path12.join(projectDir, appDir, "schemas.ts");
|
|
2675
3453
|
let entrySchemaRegistry = {};
|
|
2676
3454
|
try {
|
|
2677
3455
|
const schemasModule = await jiti.import(schemasPath);
|
|
@@ -2682,7 +3460,7 @@ async function generateAIContentCLI(options) {
|
|
|
2682
3460
|
let aiConfig;
|
|
2683
3461
|
if (configPath) {
|
|
2684
3462
|
try {
|
|
2685
|
-
const aiConfigModule = await jiti.import(
|
|
3463
|
+
const aiConfigModule = await jiti.import(path12.resolve(configPath));
|
|
2686
3464
|
aiConfig = aiConfigModule.aiContentConfig ?? aiConfigModule.default ?? aiConfigModule.config;
|
|
2687
3465
|
} catch (err) {
|
|
2688
3466
|
console.error(`Could not load AI config from ${configPath}`);
|
|
@@ -2699,7 +3477,7 @@ async function generateAIContentCLI(options) {
|
|
|
2699
3477
|
console.error("Make sure canopycms.config.ts uses defineCanopyConfig().");
|
|
2700
3478
|
process.exit(1);
|
|
2701
3479
|
}
|
|
2702
|
-
const resolvedOutput =
|
|
3480
|
+
const resolvedOutput = path12.resolve(projectDir, outputDir);
|
|
2703
3481
|
console.log(` Output: ${resolvedOutput}`);
|
|
2704
3482
|
console.log(` Mode: ${serverConfig.mode ?? "dev"}`);
|
|
2705
3483
|
const result = await generateAIContentFiles({
|