canopycms 0.0.66-int.85 → 0.0.66

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.
@@ -23,6 +23,57 @@ var init_logger = __esm({
23
23
  }
24
24
  });
25
25
 
26
+ // dist/utils/sanitize-href.js
27
+ function declaresScheme(url) {
28
+ return /^[a-z][a-z0-9+.-]*:/i.test(url.trim());
29
+ }
30
+ function isImplicitlyOffOrigin(url) {
31
+ if (declaresScheme(url))
32
+ return false;
33
+ try {
34
+ return new URL(url, SENTINEL_BASE).origin !== SENTINEL_BASE;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ function isHttpUrlOrSameOriginPath(value, opts = {}) {
40
+ if (value === "")
41
+ return false;
42
+ for (let index = 0; index < value.length; index++) {
43
+ const code = value.charCodeAt(index);
44
+ if (code <= 32 || code === 127)
45
+ return false;
46
+ }
47
+ if (value.includes("?") || value.includes("#"))
48
+ return false;
49
+ if (value.includes("\\"))
50
+ return false;
51
+ if (DOT_SEGMENT.test(value))
52
+ return false;
53
+ if (declaresScheme(value)) {
54
+ if (!/^https?:\/\//i.test(value))
55
+ return false;
56
+ try {
57
+ new URL(value);
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ if (isImplicitlyOffOrigin(value)) {
64
+ return opts.allowProtocolRelative === true && value.startsWith("//") && value[2] !== "/";
65
+ }
66
+ return value.startsWith("/") && value[1] !== "/";
67
+ }
68
+ var SENTINEL_BASE, DOT_SEGMENT;
69
+ var init_sanitize_href = __esm({
70
+ "dist/utils/sanitize-href.js"() {
71
+ "use strict";
72
+ SENTINEL_BASE = "https://relative.invalid";
73
+ DOT_SEGMENT = /(^|\/)(\.|%2e){1,2}(\/|$)/i;
74
+ }
75
+ });
76
+
26
77
  // dist/paths/normalize.js
27
78
  function normalizeFilesystemPath(path22) {
28
79
  return path22.split(/[\\/]+/).filter(Boolean).join("/");
@@ -322,82 +373,114 @@ var init_collection = __esm({
322
373
  }
323
374
  });
324
375
 
325
- // dist/config/schemas/media.js
376
+ // dist/config/schemas/url.js
326
377
  import { z as z4 } from "zod";
378
+ var uploadTargetUrlSchema, assetMountUrlSchema;
379
+ var init_url = __esm({
380
+ "dist/config/schemas/url.js"() {
381
+ "use strict";
382
+ init_sanitize_href();
383
+ uploadTargetUrlSchema = z4.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value), {
384
+ message: 'must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
385
+ });
386
+ assetMountUrlSchema = z4.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value, { allowProtocolRelative: true }), {
387
+ message: 'must be an absolute http(s) URL, a protocol-relative "//host" URL, or a site-relative path beginning with a single "/", with no query or fragment'
388
+ });
389
+ }
390
+ });
391
+
392
+ // dist/config/schemas/media.js
393
+ import { z as z5 } from "zod";
327
394
  var mediaSchema;
328
395
  var init_media = __esm({
329
396
  "dist/config/schemas/media.js"() {
330
397
  "use strict";
331
398
  init_collection();
332
- mediaSchema = z4.discriminatedUnion("adapter", [
333
- z4.object({
334
- adapter: z4.literal("local"),
335
- publicBaseUrl: z4.string().url().optional(),
399
+ init_url();
400
+ mediaSchema = z5.discriminatedUnion("adapter", [
401
+ z5.object({
402
+ adapter: z5.literal("local"),
403
+ publicBaseUrl: assetMountUrlSchema.optional(),
336
404
  /** Root directory for local asset storage. Defaults to the caller's dev-assets dir. */
337
405
  directory: relativePathSchema.optional()
338
- }),
339
- z4.object({
340
- adapter: z4.literal("s3"),
341
- bucket: z4.string().min(1),
342
- region: z4.string().min(1),
343
- publicBaseUrl: z4.string().url().optional(),
406
+ }).strict(),
407
+ z5.object({
408
+ adapter: z5.literal("s3"),
409
+ bucket: z5.string().min(1),
410
+ region: z5.string().min(1),
411
+ publicBaseUrl: assetMountUrlSchema.optional(),
412
+ /**
413
+ * Where the browser POSTs a presigned direct upload. Absolute http(s) URL or a
414
+ * site-relative path; defaults to the S3 REST endpoint the AWS SDK returns.
415
+ *
416
+ * Set this to route uploads through your own CDN so they are same-origin with the
417
+ * editor, which removes the need for a bucket CORS rule naming an exact origin. The
418
+ * signature is unaffected — a presigned POST's string-to-sign is the base64 policy
419
+ * alone, so the host never enters it (pinned by store-s3.test.ts).
420
+ *
421
+ * NOT a prefix. Unlike `publicBaseUrl`, nothing is joined onto this value; it replaces
422
+ * the POST target outright and is passed through byte-for-byte. Do not route it through
423
+ * `joinUrlPrefix` "for consistency" — that would strip the trailing slash and silently
424
+ * stop a CDN path pattern like `/asset-upload/*` from matching.
425
+ */
426
+ uploadUrl: uploadTargetUrlSchema.optional(),
344
427
  /** Max upload size in bytes for presigned direct uploads. Defaults to 50 MiB. */
345
- maxUploadBytes: z4.number().int().positive().optional()
346
- }),
347
- z4.object({
348
- adapter: z4.literal("lfs"),
349
- publicBaseUrl: z4.string().url().optional()
350
- })
428
+ maxUploadBytes: z5.number().int().positive().optional()
429
+ }).strict(),
430
+ z5.object({
431
+ adapter: z5.literal("lfs"),
432
+ publicBaseUrl: assetMountUrlSchema.optional()
433
+ }).strict()
351
434
  ]);
352
435
  }
353
436
  });
354
437
 
355
438
  // dist/config/schemas/config.js
356
- import { z as z5 } from "zod";
439
+ import { z as z6 } from "zod";
357
440
  var defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE;
358
441
  var init_config = __esm({
359
442
  "dist/config/schemas/config.js"() {
360
443
  "use strict";
361
444
  init_collection();
362
445
  init_media();
363
- defaultBranchAccessSchema = z5.enum(["allow", "deny"]).default("deny");
364
- defaultPathAccessLevelSchema = z5.enum(["allow", "deny"]);
365
- defaultPathAccessLevelsSchema = z5.object({
446
+ defaultBranchAccessSchema = z6.enum(["allow", "deny"]).default("deny");
447
+ defaultPathAccessLevelSchema = z6.enum(["allow", "deny"]);
448
+ defaultPathAccessLevelsSchema = z6.object({
366
449
  read: defaultPathAccessLevelSchema.optional(),
367
450
  edit: defaultPathAccessLevelSchema.optional(),
368
451
  review: defaultPathAccessLevelSchema.optional()
369
452
  }).strict();
370
- defaultPathAccessSchema = z5.union([defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema]).default("deny");
371
- defaultBaseBranchSchema = z5.string().default("main");
372
- defaultRemoteNameSchema = z5.string().default("origin");
373
- defaultRemoteUrlSchema = z5.string().min(1);
374
- gitBotAuthorNameSchema = z5.string().min(1);
375
- gitBotAuthorEmailSchema = z5.string().email();
376
- githubTokenEnvVarSchema = z5.string().default("GITHUB_BOT_TOKEN");
377
- operatingModeSchema = z5.enum(["prod", "dev"]);
378
- deployedAsSchema = z5.enum(["static", "server"]).default("server");
453
+ defaultPathAccessSchema = z6.union([defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema]).default("deny");
454
+ defaultBaseBranchSchema = z6.string().default("main");
455
+ defaultRemoteNameSchema = z6.string().default("origin");
456
+ defaultRemoteUrlSchema = z6.string().min(1);
457
+ gitBotAuthorNameSchema = z6.string().min(1);
458
+ gitBotAuthorEmailSchema = z6.string().email();
459
+ githubTokenEnvVarSchema = z6.string().default("GITHUB_BOT_TOKEN");
460
+ operatingModeSchema = z6.enum(["prod", "dev"]);
461
+ deployedAsSchema = z6.enum(["static", "server"]).default("server");
379
462
  contentRootSchema = relativePathSchema.default("content");
380
- sourceRootSchema = z5.string().min(1).optional();
381
- basePathSchema = z5.string().transform((value) => value.trim()).refine((value) => value.length > 0, {
463
+ sourceRootSchema = z6.string().min(1).optional();
464
+ basePathSchema = z6.string().transform((value) => value.trim()).refine((value) => value.length > 0, {
382
465
  message: "basePath must not be empty or only whitespace"
383
466
  }).optional();
384
- deploymentNameSchema = z5.string().default("prod");
385
- devContentSyncSchema = z5.enum(["off", "warn"]).default("warn");
386
- devConfigSchema = z5.object({
467
+ deploymentNameSchema = z6.string().default("prod");
468
+ devContentSyncSchema = z6.enum(["off", "warn"]).default("warn");
469
+ devConfigSchema = z6.object({
387
470
  contentSync: devContentSyncSchema.optional()
388
471
  });
389
- editorConfigSchema = z5.object({
390
- title: z5.string().optional(),
391
- subtitle: z5.string().optional(),
392
- theme: z5.unknown().optional(),
393
- previewBase: z5.record(z5.string()).optional(),
472
+ editorConfigSchema = z6.object({
473
+ title: z6.string().optional(),
474
+ subtitle: z6.string().optional(),
475
+ theme: z6.unknown().optional(),
476
+ previewBase: z6.record(z6.string()).optional(),
394
477
  // UI handler functions (runtime only, don't serialize)
395
- onAccountClick: z5.function().returns(z5.void()).optional(),
396
- onLogoutClick: z5.function().returns(z5.void()).optional(),
478
+ onAccountClick: z6.function().returns(z6.void()).optional(),
479
+ onLogoutClick: z6.function().returns(z6.void()).optional(),
397
480
  // Optional: custom account component (e.g., Clerk's UserButton)
398
- AccountComponent: z5.custom().optional()
481
+ AccountComponent: z6.custom().optional()
399
482
  });
400
- CanopyConfigSchema = z5.object({
483
+ CanopyConfigSchema = z6.object({
401
484
  media: mediaSchema.optional(),
402
485
  // No outer .optional() here: defaultBranchAccessSchema/defaultPathAccessSchema already
403
486
  // have .default('deny'), so the field is optional on input but always resolves to
@@ -413,7 +496,7 @@ var init_config = __esm({
413
496
  // unset must stay undefined after parsing so dev mode can detect the fork
414
497
  // point from git HEAD (see resolveBaseBranch in utils/git.ts).
415
498
  defaultBaseBranch: defaultBaseBranchSchema.optional(),
416
- defaultActiveBranch: z5.string().min(1).optional(),
499
+ defaultActiveBranch: z6.string().min(1).optional(),
417
500
  defaultRemoteName: defaultRemoteNameSchema.optional(),
418
501
  defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
419
502
  gitBotAuthorName: gitBotAuthorNameSchema,
@@ -427,9 +510,9 @@ var init_config = __esm({
427
510
  // Escape hatch for prod hosts that genuinely have internet access and intentionally
428
511
  // run git against a network remote (see GitManager.resolveRemoteUrl's prod-mode guard).
429
512
  // Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
430
- allowNetworkRemoteInProd: z5.boolean().optional(),
431
- settingsBranch: z5.string().optional(),
432
- autoCreateSettingsPR: z5.boolean().optional(),
513
+ allowNetworkRemoteInProd: z6.boolean().optional(),
514
+ settingsBranch: z6.string().optional(),
515
+ autoCreateSettingsPR: z6.boolean().optional(),
433
516
  // Deliberately `deploymentNameSchema.optional()`, NOT `deploymentNameSchema` alone.
434
517
  // deploymentNameSchema.default('prod') would make `parse(undefined)` resolve to
435
518
  // the literal 'prod' instead of staying `undefined` — collapsing the
@@ -445,9 +528,9 @@ var init_config = __esm({
445
528
  sourceRoot: sourceRootSchema.optional(),
446
529
  basePath: basePathSchema,
447
530
  editor: editorConfigSchema.optional(),
448
- authPlugin: z5.custom().optional(),
449
- entryLinkUrl: z5.custom().optional(),
450
- validateEntry: z5.custom().optional(),
531
+ authPlugin: z6.custom().optional(),
532
+ entryLinkUrl: z6.custom().optional(),
533
+ validateEntry: z6.custom().optional(),
451
534
  dev: devConfigSchema.optional()
452
535
  }).strict();
453
536
  DEFAULT_PROD_WORKSPACE = "/mnt/efs/workspace";
@@ -638,17 +721,17 @@ var init_deployment_name = __esm({
638
721
  });
639
722
 
640
723
  // dist/config/schemas/permissions.js
641
- import { z as z6 } from "zod";
724
+ import { z as z7 } from "zod";
642
725
  var permissionTargetSchema, pathPermissionSchema;
643
726
  var init_permissions = __esm({
644
727
  "dist/config/schemas/permissions.js"() {
645
728
  "use strict";
646
- permissionTargetSchema = z6.object({
647
- allowedUsers: z6.array(z6.string()).optional(),
648
- allowedGroups: z6.array(z6.string()).optional()
729
+ permissionTargetSchema = z7.object({
730
+ allowedUsers: z7.array(z7.string()).optional(),
731
+ allowedGroups: z7.array(z7.string()).optional()
649
732
  });
650
- pathPermissionSchema = z6.object({
651
- path: z6.string().min(1),
733
+ pathPermissionSchema = z7.object({
734
+ path: z7.string().min(1),
652
735
  read: permissionTargetSchema.optional(),
653
736
  edit: permissionTargetSchema.optional(),
654
737
  review: permissionTargetSchema.optional()
@@ -1257,6 +1340,9 @@ function findBodyFieldName(fields) {
1257
1340
  return "body";
1258
1341
  }
1259
1342
 
1343
+ // dist/utils/url-prefix.js
1344
+ init_sanitize_href();
1345
+
1260
1346
  // dist/static/seo.js
1261
1347
  var DEFAULT_SEO_FIELD_NAMES = {
1262
1348
  title: "metaTitle",
package/dist/cli/init.js CHANGED
@@ -178,82 +178,165 @@ var init_collection = __esm({
178
178
  }
179
179
  });
180
180
 
181
- // dist/config/schemas/media.js
181
+ // dist/utils/sanitize-href.js
182
+ function declaresScheme(url) {
183
+ return /^[a-z][a-z0-9+.-]*:/i.test(url.trim());
184
+ }
185
+ function isImplicitlyOffOrigin(url) {
186
+ if (declaresScheme(url))
187
+ return false;
188
+ try {
189
+ return new URL(url, SENTINEL_BASE).origin !== SENTINEL_BASE;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+ function isHttpUrlOrSameOriginPath(value, opts = {}) {
195
+ if (value === "")
196
+ return false;
197
+ for (let index = 0; index < value.length; index++) {
198
+ const code = value.charCodeAt(index);
199
+ if (code <= 32 || code === 127)
200
+ return false;
201
+ }
202
+ if (value.includes("?") || value.includes("#"))
203
+ return false;
204
+ if (value.includes("\\"))
205
+ return false;
206
+ if (DOT_SEGMENT.test(value))
207
+ return false;
208
+ if (declaresScheme(value)) {
209
+ if (!/^https?:\/\//i.test(value))
210
+ return false;
211
+ try {
212
+ new URL(value);
213
+ return true;
214
+ } catch {
215
+ return false;
216
+ }
217
+ }
218
+ if (isImplicitlyOffOrigin(value)) {
219
+ return opts.allowProtocolRelative === true && value.startsWith("//") && value[2] !== "/";
220
+ }
221
+ return value.startsWith("/") && value[1] !== "/";
222
+ }
223
+ var SENTINEL_BASE, DOT_SEGMENT;
224
+ var init_sanitize_href = __esm({
225
+ "dist/utils/sanitize-href.js"() {
226
+ "use strict";
227
+ SENTINEL_BASE = "https://relative.invalid";
228
+ DOT_SEGMENT = /(^|\/)(\.|%2e){1,2}(\/|$)/i;
229
+ }
230
+ });
231
+
232
+ // dist/config/schemas/url.js
182
233
  import { z as z3 } from "zod";
234
+ var uploadTargetUrlSchema, assetMountUrlSchema;
235
+ var init_url = __esm({
236
+ "dist/config/schemas/url.js"() {
237
+ "use strict";
238
+ init_sanitize_href();
239
+ uploadTargetUrlSchema = z3.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value), {
240
+ message: 'must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
241
+ });
242
+ assetMountUrlSchema = z3.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value, { allowProtocolRelative: true }), {
243
+ message: 'must be an absolute http(s) URL, a protocol-relative "//host" URL, or a site-relative path beginning with a single "/", with no query or fragment'
244
+ });
245
+ }
246
+ });
247
+
248
+ // dist/config/schemas/media.js
249
+ import { z as z4 } from "zod";
183
250
  var mediaSchema;
184
251
  var init_media = __esm({
185
252
  "dist/config/schemas/media.js"() {
186
253
  "use strict";
187
254
  init_collection();
188
- mediaSchema = z3.discriminatedUnion("adapter", [
189
- z3.object({
190
- adapter: z3.literal("local"),
191
- publicBaseUrl: z3.string().url().optional(),
255
+ init_url();
256
+ mediaSchema = z4.discriminatedUnion("adapter", [
257
+ z4.object({
258
+ adapter: z4.literal("local"),
259
+ publicBaseUrl: assetMountUrlSchema.optional(),
192
260
  /** Root directory for local asset storage. Defaults to the caller's dev-assets dir. */
193
261
  directory: relativePathSchema.optional()
194
- }),
195
- z3.object({
196
- adapter: z3.literal("s3"),
197
- bucket: z3.string().min(1),
198
- region: z3.string().min(1),
199
- publicBaseUrl: z3.string().url().optional(),
262
+ }).strict(),
263
+ z4.object({
264
+ adapter: z4.literal("s3"),
265
+ bucket: z4.string().min(1),
266
+ region: z4.string().min(1),
267
+ publicBaseUrl: assetMountUrlSchema.optional(),
268
+ /**
269
+ * Where the browser POSTs a presigned direct upload. Absolute http(s) URL or a
270
+ * site-relative path; defaults to the S3 REST endpoint the AWS SDK returns.
271
+ *
272
+ * Set this to route uploads through your own CDN so they are same-origin with the
273
+ * editor, which removes the need for a bucket CORS rule naming an exact origin. The
274
+ * signature is unaffected — a presigned POST's string-to-sign is the base64 policy
275
+ * alone, so the host never enters it (pinned by store-s3.test.ts).
276
+ *
277
+ * NOT a prefix. Unlike `publicBaseUrl`, nothing is joined onto this value; it replaces
278
+ * the POST target outright and is passed through byte-for-byte. Do not route it through
279
+ * `joinUrlPrefix` "for consistency" — that would strip the trailing slash and silently
280
+ * stop a CDN path pattern like `/asset-upload/*` from matching.
281
+ */
282
+ uploadUrl: uploadTargetUrlSchema.optional(),
200
283
  /** Max upload size in bytes for presigned direct uploads. Defaults to 50 MiB. */
201
- maxUploadBytes: z3.number().int().positive().optional()
202
- }),
203
- z3.object({
204
- adapter: z3.literal("lfs"),
205
- publicBaseUrl: z3.string().url().optional()
206
- })
284
+ maxUploadBytes: z4.number().int().positive().optional()
285
+ }).strict(),
286
+ z4.object({
287
+ adapter: z4.literal("lfs"),
288
+ publicBaseUrl: assetMountUrlSchema.optional()
289
+ }).strict()
207
290
  ]);
208
291
  }
209
292
  });
210
293
 
211
294
  // dist/config/schemas/config.js
212
- import { z as z4 } from "zod";
295
+ import { z as z5 } from "zod";
213
296
  var defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE;
214
297
  var init_config = __esm({
215
298
  "dist/config/schemas/config.js"() {
216
299
  "use strict";
217
300
  init_collection();
218
301
  init_media();
219
- defaultBranchAccessSchema = z4.enum(["allow", "deny"]).default("deny");
220
- defaultPathAccessLevelSchema = z4.enum(["allow", "deny"]);
221
- defaultPathAccessLevelsSchema = z4.object({
302
+ defaultBranchAccessSchema = z5.enum(["allow", "deny"]).default("deny");
303
+ defaultPathAccessLevelSchema = z5.enum(["allow", "deny"]);
304
+ defaultPathAccessLevelsSchema = z5.object({
222
305
  read: defaultPathAccessLevelSchema.optional(),
223
306
  edit: defaultPathAccessLevelSchema.optional(),
224
307
  review: defaultPathAccessLevelSchema.optional()
225
308
  }).strict();
226
- defaultPathAccessSchema = z4.union([defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema]).default("deny");
227
- defaultBaseBranchSchema = z4.string().default("main");
228
- defaultRemoteNameSchema = z4.string().default("origin");
229
- defaultRemoteUrlSchema = z4.string().min(1);
230
- gitBotAuthorNameSchema = z4.string().min(1);
231
- gitBotAuthorEmailSchema = z4.string().email();
232
- githubTokenEnvVarSchema = z4.string().default("GITHUB_BOT_TOKEN");
233
- operatingModeSchema = z4.enum(["prod", "dev"]);
234
- deployedAsSchema = z4.enum(["static", "server"]).default("server");
309
+ defaultPathAccessSchema = z5.union([defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema]).default("deny");
310
+ defaultBaseBranchSchema = z5.string().default("main");
311
+ defaultRemoteNameSchema = z5.string().default("origin");
312
+ defaultRemoteUrlSchema = z5.string().min(1);
313
+ gitBotAuthorNameSchema = z5.string().min(1);
314
+ gitBotAuthorEmailSchema = z5.string().email();
315
+ githubTokenEnvVarSchema = z5.string().default("GITHUB_BOT_TOKEN");
316
+ operatingModeSchema = z5.enum(["prod", "dev"]);
317
+ deployedAsSchema = z5.enum(["static", "server"]).default("server");
235
318
  contentRootSchema = relativePathSchema.default("content");
236
- sourceRootSchema = z4.string().min(1).optional();
237
- basePathSchema = z4.string().transform((value) => value.trim()).refine((value) => value.length > 0, {
319
+ sourceRootSchema = z5.string().min(1).optional();
320
+ basePathSchema = z5.string().transform((value) => value.trim()).refine((value) => value.length > 0, {
238
321
  message: "basePath must not be empty or only whitespace"
239
322
  }).optional();
240
- deploymentNameSchema = z4.string().default("prod");
241
- devContentSyncSchema = z4.enum(["off", "warn"]).default("warn");
242
- devConfigSchema = z4.object({
323
+ deploymentNameSchema = z5.string().default("prod");
324
+ devContentSyncSchema = z5.enum(["off", "warn"]).default("warn");
325
+ devConfigSchema = z5.object({
243
326
  contentSync: devContentSyncSchema.optional()
244
327
  });
245
- editorConfigSchema = z4.object({
246
- title: z4.string().optional(),
247
- subtitle: z4.string().optional(),
248
- theme: z4.unknown().optional(),
249
- previewBase: z4.record(z4.string()).optional(),
328
+ editorConfigSchema = z5.object({
329
+ title: z5.string().optional(),
330
+ subtitle: z5.string().optional(),
331
+ theme: z5.unknown().optional(),
332
+ previewBase: z5.record(z5.string()).optional(),
250
333
  // UI handler functions (runtime only, don't serialize)
251
- onAccountClick: z4.function().returns(z4.void()).optional(),
252
- onLogoutClick: z4.function().returns(z4.void()).optional(),
334
+ onAccountClick: z5.function().returns(z5.void()).optional(),
335
+ onLogoutClick: z5.function().returns(z5.void()).optional(),
253
336
  // Optional: custom account component (e.g., Clerk's UserButton)
254
- AccountComponent: z4.custom().optional()
337
+ AccountComponent: z5.custom().optional()
255
338
  });
256
- CanopyConfigSchema = z4.object({
339
+ CanopyConfigSchema = z5.object({
257
340
  media: mediaSchema.optional(),
258
341
  // No outer .optional() here: defaultBranchAccessSchema/defaultPathAccessSchema already
259
342
  // have .default('deny'), so the field is optional on input but always resolves to
@@ -269,7 +352,7 @@ var init_config = __esm({
269
352
  // unset must stay undefined after parsing so dev mode can detect the fork
270
353
  // point from git HEAD (see resolveBaseBranch in utils/git.ts).
271
354
  defaultBaseBranch: defaultBaseBranchSchema.optional(),
272
- defaultActiveBranch: z4.string().min(1).optional(),
355
+ defaultActiveBranch: z5.string().min(1).optional(),
273
356
  defaultRemoteName: defaultRemoteNameSchema.optional(),
274
357
  defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
275
358
  gitBotAuthorName: gitBotAuthorNameSchema,
@@ -283,9 +366,9 @@ var init_config = __esm({
283
366
  // Escape hatch for prod hosts that genuinely have internet access and intentionally
284
367
  // run git against a network remote (see GitManager.resolveRemoteUrl's prod-mode guard).
285
368
  // Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
286
- allowNetworkRemoteInProd: z4.boolean().optional(),
287
- settingsBranch: z4.string().optional(),
288
- autoCreateSettingsPR: z4.boolean().optional(),
369
+ allowNetworkRemoteInProd: z5.boolean().optional(),
370
+ settingsBranch: z5.string().optional(),
371
+ autoCreateSettingsPR: z5.boolean().optional(),
289
372
  // Deliberately `deploymentNameSchema.optional()`, NOT `deploymentNameSchema` alone.
290
373
  // deploymentNameSchema.default('prod') would make `parse(undefined)` resolve to
291
374
  // the literal 'prod' instead of staying `undefined` — collapsing the
@@ -301,9 +384,9 @@ var init_config = __esm({
301
384
  sourceRoot: sourceRootSchema.optional(),
302
385
  basePath: basePathSchema,
303
386
  editor: editorConfigSchema.optional(),
304
- authPlugin: z4.custom().optional(),
305
- entryLinkUrl: z4.custom().optional(),
306
- validateEntry: z4.custom().optional(),
387
+ authPlugin: z5.custom().optional(),
388
+ entryLinkUrl: z5.custom().optional(),
389
+ validateEntry: z5.custom().optional(),
307
390
  dev: devConfigSchema.optional()
308
391
  }).strict();
309
392
  DEFAULT_PROD_WORKSPACE = "/mnt/efs/workspace";
@@ -311,17 +394,17 @@ var init_config = __esm({
311
394
  });
312
395
 
313
396
  // dist/config/schemas/permissions.js
314
- import { z as z5 } from "zod";
397
+ import { z as z6 } from "zod";
315
398
  var permissionTargetSchema, pathPermissionSchema;
316
399
  var init_permissions = __esm({
317
400
  "dist/config/schemas/permissions.js"() {
318
401
  "use strict";
319
- permissionTargetSchema = z5.object({
320
- allowedUsers: z5.array(z5.string()).optional(),
321
- allowedGroups: z5.array(z5.string()).optional()
402
+ permissionTargetSchema = z6.object({
403
+ allowedUsers: z6.array(z6.string()).optional(),
404
+ allowedGroups: z6.array(z6.string()).optional()
322
405
  });
323
- pathPermissionSchema = z5.object({
324
- path: z5.string().min(1),
406
+ pathPermissionSchema = z6.object({
407
+ path: z6.string().min(1),
325
408
  read: permissionTargetSchema.optional(),
326
409
  edit: permissionTargetSchema.optional(),
327
410
  review: permissionTargetSchema.optional()
@@ -140,10 +140,13 @@ export class CmsStack extends Stack {
140
140
  // hand-written `additionalBehaviors` ever gets that order wrong
141
141
  // instead.
142
142
  //
143
- // `editorOrigins` is REQUIRED: it is the S3 CORS allowlist for the
144
- // editor's presigned uploads, so it must list the origin the editor is
145
- // served from. Include http://localhost:3000 only if you upload from a
146
- // local dev editor against this bucket.
143
+ // `editorOrigins` is the S3 CORS allowlist for the editor's presigned
144
+ // uploads, so it must list the origin the editor is served from. Include
145
+ // http://localhost:3000 only if you upload from a local dev editor
146
+ // against this bucket. It is optional only because `uploadBehavior` is
147
+ // the other way to satisfy the same requirement (CORS supplied at the
148
+ // CloudFront edge instead); with neither, a construct-created bucket
149
+ // refuses to synth.
147
150
  //
148
151
  // Run `pnpm --filter canopycms-cdk run build:lambda` before deploying --
149
152
  // the transform Lambda needs its native sharp binary, and the construct
@@ -81,9 +81,9 @@ export declare const editorConfigSchema: z.ZodObject<{
81
81
  export declare const CanopyConfigSchema: z.ZodObject<{
82
82
  media: z.ZodOptional<z.ZodDiscriminatedUnion<"adapter", [z.ZodObject<{
83
83
  adapter: z.ZodLiteral<"local">;
84
- publicBaseUrl: z.ZodOptional<z.ZodString>;
84
+ publicBaseUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
85
85
  directory: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, string, string>>;
86
- }, "strip", z.ZodTypeAny, {
86
+ }, "strict", z.ZodTypeAny, {
87
87
  adapter: "local";
88
88
  publicBaseUrl?: string | undefined;
89
89
  directory?: string | undefined;
@@ -95,24 +95,27 @@ export declare const CanopyConfigSchema: z.ZodObject<{
95
95
  adapter: z.ZodLiteral<"s3">;
96
96
  bucket: z.ZodString;
97
97
  region: z.ZodString;
98
- publicBaseUrl: z.ZodOptional<z.ZodString>;
98
+ publicBaseUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
99
+ uploadUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
99
100
  maxUploadBytes: z.ZodOptional<z.ZodNumber>;
100
- }, "strip", z.ZodTypeAny, {
101
+ }, "strict", z.ZodTypeAny, {
101
102
  adapter: "s3";
102
103
  bucket: string;
103
104
  region: string;
104
105
  publicBaseUrl?: string | undefined;
106
+ uploadUrl?: string | undefined;
105
107
  maxUploadBytes?: number | undefined;
106
108
  }, {
107
109
  adapter: "s3";
108
110
  bucket: string;
109
111
  region: string;
110
112
  publicBaseUrl?: string | undefined;
113
+ uploadUrl?: string | undefined;
111
114
  maxUploadBytes?: number | undefined;
112
115
  }>, z.ZodObject<{
113
116
  adapter: z.ZodLiteral<"lfs">;
114
- publicBaseUrl: z.ZodOptional<z.ZodString>;
115
- }, "strip", z.ZodTypeAny, {
117
+ publicBaseUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
118
+ }, "strict", z.ZodTypeAny, {
116
119
  adapter: "lfs";
117
120
  publicBaseUrl?: string | undefined;
118
121
  }, {
@@ -209,6 +212,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
209
212
  bucket: string;
210
213
  region: string;
211
214
  publicBaseUrl?: string | undefined;
215
+ uploadUrl?: string | undefined;
212
216
  maxUploadBytes?: number | undefined;
213
217
  } | {
214
218
  adapter: "lfs";
@@ -253,6 +257,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
253
257
  bucket: string;
254
258
  region: string;
255
259
  publicBaseUrl?: string | undefined;
260
+ uploadUrl?: string | undefined;
256
261
  maxUploadBytes?: number | undefined;
257
262
  } | {
258
263
  adapter: "lfs";