canopycms 0.0.67 → 0.0.68-int.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/README.md +24 -17
  2. package/dist/ai/handler.js +8 -0
  3. package/dist/api/admin-branch-health.js +17 -20
  4. package/dist/api/admin.d.ts +30 -9
  5. package/dist/api/admin.js +32 -2
  6. package/dist/api/assets.js +84 -76
  7. package/dist/api/branch-create-window.d.ts +12 -0
  8. package/dist/api/branch-create-window.js +13 -0
  9. package/dist/api/branch-review.d.ts +3 -3
  10. package/dist/api/branch-review.js +14 -5
  11. package/dist/api/branch-status.d.ts +5 -5
  12. package/dist/api/branch-status.js +26 -6
  13. package/dist/api/branch-withdraw.d.ts +2 -2
  14. package/dist/api/branch-withdraw.js +7 -2
  15. package/dist/api/branch.d.ts +15 -2
  16. package/dist/api/branch.js +118 -92
  17. package/dist/api/client.d.ts +18 -6
  18. package/dist/api/client.js +79 -12
  19. package/dist/api/content.d.ts +6 -5
  20. package/dist/api/content.js +44 -41
  21. package/dist/api/entries.js +16 -15
  22. package/dist/api/github-sync.d.ts +19 -1
  23. package/dist/api/github-sync.js +57 -10
  24. package/dist/api/index.d.ts +1 -1
  25. package/dist/api/reference-options.js +1 -1
  26. package/dist/api/resolve-references.js +14 -40
  27. package/dist/api/settings-helpers.d.ts +1 -1
  28. package/dist/api/settings-helpers.js +1 -4
  29. package/dist/api/user.d.ts +3 -0
  30. package/dist/api/user.js +3 -0
  31. package/dist/assets/asset-url.d.ts +22 -16
  32. package/dist/assets/asset-url.js +42 -25
  33. package/dist/assets/factory.d.ts +5 -0
  34. package/dist/assets/factory.js +5 -0
  35. package/dist/assets/finalize.js +3 -0
  36. package/dist/assets/index.d.ts +1 -1
  37. package/dist/assets/materialize.d.ts +153 -0
  38. package/dist/assets/materialize.js +402 -0
  39. package/dist/assets/store-local.d.ts +19 -5
  40. package/dist/assets/store-local.js +69 -10
  41. package/dist/assets/store-s3.d.ts +38 -7
  42. package/dist/assets/store-s3.js +128 -22
  43. package/dist/assets/transform-directives.d.ts +52 -13
  44. package/dist/assets/transform-directives.js +87 -27
  45. package/dist/assets/transform.d.ts +3 -3
  46. package/dist/assets/transform.js +13 -8
  47. package/dist/assets/types.d.ts +31 -4
  48. package/dist/auth/file-based-auth-cache.js +7 -8
  49. package/dist/authorization/content.d.ts +5 -4
  50. package/dist/authorization/content.js +5 -5
  51. package/dist/authorization/path.d.ts +11 -6
  52. package/dist/authorization/path.js +11 -6
  53. package/dist/authorization/types.d.ts +2 -2
  54. package/dist/branch-health.d.ts +7 -1
  55. package/dist/branch-health.js +8 -3
  56. package/dist/branch-metadata.d.ts +5 -5
  57. package/dist/branch-metadata.js +44 -36
  58. package/dist/branch-provisioning.d.ts +167 -0
  59. package/dist/branch-provisioning.js +501 -0
  60. package/dist/branch-schema-cache.d.ts +5 -3
  61. package/dist/branch-schema-cache.js +29 -11
  62. package/dist/branch-sparse.d.ts +23 -0
  63. package/dist/branch-sparse.js +118 -0
  64. package/dist/branch-workspace.d.ts +42 -3
  65. package/dist/branch-workspace.js +240 -94
  66. package/dist/build/asset-refs.d.ts +93 -0
  67. package/dist/build/asset-refs.js +251 -0
  68. package/dist/build-identity.d.ts +3 -0
  69. package/dist/build-identity.js +13 -0
  70. package/dist/cli/asset-refs.d.ts +52 -0
  71. package/dist/cli/asset-refs.js +194 -0
  72. package/dist/cli/cli.d.ts +15 -1
  73. package/dist/cli/cli.js +5928 -2195
  74. package/dist/cli/configured-asset-store.d.ts +12 -0
  75. package/dist/cli/configured-asset-store.js +46 -0
  76. package/dist/cli/generate-ai-content.js +2185 -587
  77. package/dist/cli/github-app-manifest.d.ts +4 -3
  78. package/dist/cli/github-app-manifest.js +4 -3
  79. package/dist/cli/init.js +30 -27
  80. package/dist/cli/migrate.js +12 -8
  81. package/dist/cli/sync.js +83 -37
  82. package/dist/cli/template-files/Dockerfile.cms.template +8 -0
  83. package/dist/cli/template-files/cdk-app.ts.template +4 -0
  84. package/dist/cli/template-files/cms-stack.ts.template +23 -12
  85. package/dist/cli/template-files/deploy-cms.yml.template +2 -0
  86. package/dist/client.d.ts +1 -1
  87. package/dist/client.js +1 -1
  88. package/dist/config/helpers.js +1 -2
  89. package/dist/config/schemas/config.d.ts +19 -32
  90. package/dist/config/schemas/config.js +9 -11
  91. package/dist/config/schemas/field.js +1 -0
  92. package/dist/config/schemas/media.d.ts +4 -13
  93. package/dist/config/schemas/media.js +5 -8
  94. package/dist/config/schemas/url.d.ts +4 -20
  95. package/dist/config/schemas/url.js +6 -22
  96. package/dist/config/types.d.ts +30 -20
  97. package/dist/config/validation.d.ts +6 -0
  98. package/dist/config/validation.js +37 -0
  99. package/dist/content-listing.d.ts +12 -12
  100. package/dist/content-listing.js +10 -6
  101. package/dist/content-reader.d.ts +11 -2
  102. package/dist/content-reader.js +22 -14
  103. package/dist/content-store.d.ts +37 -9
  104. package/dist/content-store.js +70 -43
  105. package/dist/content-tree.d.ts +1 -1
  106. package/dist/content-tree.js +2 -2
  107. package/dist/context.d.ts +27 -9
  108. package/dist/context.js +154 -70
  109. package/dist/editor/BranchManager.d.ts +7 -1
  110. package/dist/editor/BranchManager.js +29 -14
  111. package/dist/editor/CanopyEditor.d.ts +1 -1
  112. package/dist/editor/CanopyEditor.js +2 -6
  113. package/dist/editor/Editor.d.ts +4 -3
  114. package/dist/editor/Editor.js +32 -22
  115. package/dist/editor/FormRenderer.js +34 -18
  116. package/dist/editor/PreviewFrame.d.ts +24 -0
  117. package/dist/editor/PreviewFrame.js +140 -0
  118. package/dist/editor/admin/SystemHealthPanel.js +47 -7
  119. package/dist/editor/components/BranchesDrawer.d.ts +9 -0
  120. package/dist/editor/components/BranchesDrawer.js +4 -0
  121. package/dist/editor/components/NoEditPermissionNotice.d.ts +10 -0
  122. package/dist/editor/components/NoEditPermissionNotice.js +17 -0
  123. package/dist/editor/context/AssetContext.d.ts +18 -35
  124. package/dist/editor/context/AssetContext.js +20 -29
  125. package/dist/editor/context/index.d.ts +1 -1
  126. package/dist/editor/context/index.js +1 -1
  127. package/dist/editor/editor-config.d.ts +1 -2
  128. package/dist/editor/editor-config.js +0 -23
  129. package/dist/editor/editor-utils.d.ts +24 -21
  130. package/dist/editor/editor-utils.js +75 -62
  131. package/dist/editor/fields/BlockField.d.ts +1 -0
  132. package/dist/editor/fields/BlockField.js +6 -4
  133. package/dist/editor/fields/CodeField.d.ts +1 -0
  134. package/dist/editor/fields/CodeField.js +2 -2
  135. package/dist/editor/fields/DateTimeField.d.ts +1 -0
  136. package/dist/editor/fields/DateTimeField.js +2 -2
  137. package/dist/editor/fields/FieldDescription.d.ts +16 -0
  138. package/dist/editor/fields/FieldDescription.js +11 -0
  139. package/dist/editor/fields/ImageField.d.ts +1 -0
  140. package/dist/editor/fields/ImageField.js +3 -2
  141. package/dist/editor/fields/InlineGroupField.js +4 -1
  142. package/dist/editor/fields/MarkdownField.d.ts +1 -0
  143. package/dist/editor/fields/MarkdownField.js +115 -20
  144. package/dist/editor/fields/NumberField.d.ts +1 -0
  145. package/dist/editor/fields/NumberField.js +2 -2
  146. package/dist/editor/fields/NumberListField.d.ts +1 -0
  147. package/dist/editor/fields/NumberListField.js +2 -2
  148. package/dist/editor/fields/ObjectField.d.ts +1 -0
  149. package/dist/editor/fields/ObjectField.js +5 -2
  150. package/dist/editor/fields/ReferenceField.d.ts +1 -0
  151. package/dist/editor/fields/ReferenceField.js +5 -4
  152. package/dist/editor/fields/SelectField.d.ts +1 -0
  153. package/dist/editor/fields/SelectField.js +2 -2
  154. package/dist/editor/fields/StringListField.d.ts +1 -0
  155. package/dist/editor/fields/StringListField.js +2 -2
  156. package/dist/editor/fields/TextField.d.ts +1 -0
  157. package/dist/editor/fields/TextField.js +2 -2
  158. package/dist/editor/fields/ToggleField.d.ts +1 -0
  159. package/dist/editor/fields/ToggleField.js +7 -4
  160. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
  161. package/dist/editor/fields/entry-link/InsertEntryLink.js +3 -2
  162. package/dist/editor/fields/mdx-jsx-support.d.ts +1 -0
  163. package/dist/editor/fields/mdx-jsx-support.js +137 -0
  164. package/dist/editor/hooks/create-branch-request.d.ts +20 -0
  165. package/dist/editor/hooks/create-branch-request.js +52 -0
  166. package/dist/editor/hooks/useBranchActions.d.ts +13 -2
  167. package/dist/editor/hooks/useBranchActions.js +48 -17
  168. package/dist/editor/hooks/useBranchManager.d.ts +21 -0
  169. package/dist/editor/hooks/useBranchManager.js +204 -37
  170. package/dist/editor/hooks/useBranchesData.d.ts +6 -0
  171. package/dist/editor/hooks/useBranchesData.js +9 -3
  172. package/dist/editor/hooks/useCommentSystem.js +25 -6
  173. package/dist/editor/hooks/useDraftManager.d.ts +16 -1
  174. package/dist/editor/hooks/useDraftManager.js +193 -36
  175. package/dist/editor/hooks/useEntriesData.js +3 -3
  176. package/dist/editor/hooks/useEntryManager.d.ts +2 -1
  177. package/dist/editor/hooks/useEntryManager.js +34 -19
  178. package/dist/editor/media/AssetCard.d.ts +1 -1
  179. package/dist/editor/media/crop-math.d.ts +3 -4
  180. package/dist/editor/media/crop-math.js +11 -22
  181. package/dist/editor/media/editor-image-src.d.ts +7 -0
  182. package/dist/editor/media/editor-image-src.js +12 -0
  183. package/dist/editor/preview-asset-base.d.ts +8 -0
  184. package/dist/editor/preview-asset-base.js +13 -0
  185. package/dist/editor/preview-bridge.d.ts +9 -21
  186. package/dist/editor/preview-bridge.js +19 -139
  187. package/dist/editor/preview-path.d.ts +10 -0
  188. package/dist/editor/preview-path.js +20 -0
  189. package/dist/editor/theme.js +2 -2
  190. package/dist/entry-schema-registry.d.ts +4 -4
  191. package/dist/entry-schema-registry.js +10 -6
  192. package/dist/entry-schema.d.ts +27 -4
  193. package/dist/entry-schema.js +20 -3
  194. package/dist/git-manager.d.ts +173 -6
  195. package/dist/git-manager.js +594 -76
  196. package/dist/github-service.d.ts +15 -0
  197. package/dist/github-service.js +19 -1
  198. package/dist/http/handler.js +37 -13
  199. package/dist/http/index.d.ts +2 -0
  200. package/dist/http/index.js +2 -0
  201. package/dist/http/router.d.ts +2 -0
  202. package/dist/http/router.js +2 -1
  203. package/dist/http/types.d.ts +2 -0
  204. package/dist/http/worker-not-ready.d.ts +10 -0
  205. package/dist/http/worker-not-ready.js +24 -0
  206. package/dist/operating-mode/client-unsafe-strategy.js +0 -6
  207. package/dist/operating-mode/types.d.ts +1 -4
  208. package/dist/paths/branch-name.d.ts +5 -0
  209. package/dist/paths/branch-name.js +9 -0
  210. package/dist/paths/branch.d.ts +6 -2
  211. package/dist/paths/branch.js +16 -10
  212. package/dist/paths/index.d.ts +3 -3
  213. package/dist/paths/index.js +3 -3
  214. package/dist/paths/normalize.d.ts +7 -0
  215. package/dist/paths/normalize.js +9 -0
  216. package/dist/paths/validation.js +0 -2
  217. package/dist/preview.d.ts +8 -0
  218. package/dist/preview.js +7 -0
  219. package/dist/reference-resolver.d.ts +5 -21
  220. package/dist/reference-resolver.js +9 -44
  221. package/dist/resolve-canopy-user.js +2 -1
  222. package/dist/schema/schema-store.d.ts +54 -31
  223. package/dist/schema/schema-store.js +77 -31
  224. package/dist/server.d.ts +64 -9
  225. package/dist/server.js +48 -8
  226. package/dist/services.d.ts +18 -6
  227. package/dist/services.js +85 -70
  228. package/dist/settings-workspace.d.ts +23 -3
  229. package/dist/settings-workspace.js +197 -45
  230. package/dist/static/seo.d.ts +2 -14
  231. package/dist/static/seo.js +2 -25
  232. package/dist/submission-attribution.d.ts +73 -0
  233. package/dist/submission-attribution.js +221 -0
  234. package/dist/sync-core.d.ts +12 -1
  235. package/dist/sync-core.js +31 -13
  236. package/dist/task-queue/worker-status.d.ts +8 -0
  237. package/dist/task-queue/worker-status.js +17 -0
  238. package/dist/types.d.ts +31 -2
  239. package/dist/utils/content-serialize.d.ts +5 -2
  240. package/dist/utils/content-serialize.js +98 -24
  241. package/dist/utils/content-write-lock.d.ts +18 -7
  242. package/dist/utils/content-write-lock.js +18 -9
  243. package/dist/utils/debug.d.ts +8 -0
  244. package/dist/utils/debug.js +10 -2
  245. package/dist/utils/git.d.ts +32 -0
  246. package/dist/utils/git.js +42 -0
  247. package/dist/utils/occ-json-write.js +10 -2
  248. package/dist/utils/provision-log.d.ts +25 -0
  249. package/dist/utils/provision-log.js +49 -0
  250. package/dist/utils/provisioning-lock.d.ts +35 -5
  251. package/dist/utils/provisioning-lock.js +73 -12
  252. package/dist/utils/request-timing.d.ts +25 -0
  253. package/dist/utils/request-timing.js +101 -0
  254. package/dist/utils/sanitize-href.d.ts +8 -22
  255. package/dist/utils/sanitize-href.js +11 -28
  256. package/dist/utils/url-prefix.d.ts +30 -0
  257. package/dist/utils/url-prefix.js +61 -0
  258. package/dist/utils/yaml-source-splice.d.ts +58 -0
  259. package/dist/utils/yaml-source-splice.js +515 -0
  260. package/dist/validation/entry-validator.js +9 -3
  261. package/dist/version.d.ts +1 -0
  262. package/dist/version.js +3 -0
  263. package/dist/worker/canopy-state.d.ts +45 -0
  264. package/dist/worker/canopy-state.js +75 -0
  265. package/dist/worker/cms-worker.d.ts +8 -3
  266. package/dist/worker/cms-worker.js +38 -2
  267. package/dist/worker/git-sync.d.ts +30 -12
  268. package/dist/worker/git-sync.js +253 -58
  269. package/dist/worker/history-rewrite.d.ts +1 -1
  270. package/dist/worker/history-rewrite.js +1 -1
  271. package/dist/worker/provisioned-workspace.d.ts +35 -0
  272. package/dist/worker/provisioned-workspace.js +50 -0
  273. package/dist/worker/rebase.d.ts +1 -1
  274. package/dist/worker/rebase.js +95 -23
  275. package/dist/worker/remote-git-maintenance.d.ts +3 -0
  276. package/dist/worker/remote-git-maintenance.js +11 -0
  277. package/dist/worker/sparse-cone.d.ts +20 -0
  278. package/dist/worker/sparse-cone.js +132 -0
  279. package/dist/worker/task-runner.js +99 -14
  280. package/dist/worker/worker-context.d.ts +6 -1
  281. package/package.json +8 -2
@@ -24,9 +24,10 @@ export type PermissionSet = Readonly<Record<string, PermissionLevel>>;
24
24
  * - `metadata: read` — implied by any repository permission; declared so
25
25
  * this object states the whole surface, not just the non-automatic part.
26
26
  * Nothing else: no `issues`, `administration`, `actions`, or organisation
27
- * permissions. No `workflows` either, though GitHub may refuse to push
28
- * rebased history touching `.github/workflows/` — see
29
- * .claude/future-tasks/worker-push-refused-when-base-changes-workflows.md.
27
+ * permissions. No `workflows` either: GitHub refuses only workflow content it
28
+ * does not already hold, so rebasing onto a base that changed a workflow is
29
+ * accepted. Content that is new to GitHub fails the push task with the file
30
+ * named (`throwIfWorkflowRefusal` in worker/task-runner.ts).
30
31
  */
31
32
  export declare const CANOPY_APP_PERMISSIONS: PermissionSet;
32
33
  /**
@@ -22,9 +22,10 @@ const PERMISSION_LEVELS = ['read', 'write', 'admin'];
22
22
  * - `metadata: read` — implied by any repository permission; declared so
23
23
  * this object states the whole surface, not just the non-automatic part.
24
24
  * Nothing else: no `issues`, `administration`, `actions`, or organisation
25
- * permissions. No `workflows` either, though GitHub may refuse to push
26
- * rebased history touching `.github/workflows/` — see
27
- * .claude/future-tasks/worker-push-refused-when-base-changes-workflows.md.
25
+ * permissions. No `workflows` either: GitHub refuses only workflow content it
26
+ * does not already hold, so rebasing onto a base that changed a workflow is
27
+ * accepted. Content that is new to GitHub fails the push task with the file
28
+ * named (`throwIfWorkflowRefusal` in worker/task-runner.ts).
28
29
  */
29
30
  export const CANOPY_APP_PERMISSIONS = {
30
31
  contents: 'write',
package/dist/cli/init.js CHANGED
@@ -115,7 +115,8 @@ var init_field = __esm({
115
115
  });
116
116
  objectFieldSchema = fieldBaseSchema.extend({
117
117
  type: z.literal("object"),
118
- fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
118
+ fields: z.array(z.lazy(() => fieldHolder[0])).min(1),
119
+ itemTitleField: z.string().min(1).optional()
119
120
  });
120
121
  inlineGroupFieldSchema = z.object({
121
122
  type: z.literal("group"),
@@ -191,7 +192,7 @@ function isImplicitlyOffOrigin(url) {
191
192
  return false;
192
193
  }
193
194
  }
194
- function isHttpUrlOrSameOriginPath(value, opts = {}) {
195
+ function isHttpUrlOrSameOriginPath(value) {
195
196
  if (value === "")
196
197
  return false;
197
198
  for (let index = 0; index < value.length; index++) {
@@ -215,9 +216,8 @@ function isHttpUrlOrSameOriginPath(value, opts = {}) {
215
216
  return false;
216
217
  }
217
218
  }
218
- if (isImplicitlyOffOrigin(value)) {
219
- return opts.allowProtocolRelative === true && value.startsWith("//") && value[2] !== "/";
220
- }
219
+ if (isImplicitlyOffOrigin(value))
220
+ return false;
221
221
  return value.startsWith("/") && value[1] !== "/";
222
222
  }
223
223
  var SENTINEL_BASE, DOT_SEGMENT;
@@ -231,7 +231,7 @@ var init_sanitize_href = __esm({
231
231
 
232
232
  // dist/config/schemas/url.js
233
233
  import { z as z3 } from "zod";
234
- var uploadTargetUrlSchema, assetMountUrlSchema;
234
+ var uploadTargetUrlSchema, previewPrefixSchema;
235
235
  var init_url = __esm({
236
236
  "dist/config/schemas/url.js"() {
237
237
  "use strict";
@@ -239,8 +239,8 @@ var init_url = __esm({
239
239
  uploadTargetUrlSchema = z3.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value), {
240
240
  message: 'must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
241
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'
242
+ previewPrefixSchema = z3.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value), {
243
+ message: 'editor.previewPrefix must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
244
244
  });
245
245
  }
246
246
  });
@@ -256,7 +256,6 @@ var init_media = __esm({
256
256
  mediaSchema = z4.discriminatedUnion("adapter", [
257
257
  z4.object({
258
258
  adapter: z4.literal("local"),
259
- publicBaseUrl: assetMountUrlSchema.optional(),
260
259
  /** Root directory for local asset storage. Defaults to the caller's dev-assets dir. */
261
260
  directory: relativePathSchema.optional()
262
261
  }).strict(),
@@ -264,7 +263,6 @@ var init_media = __esm({
264
263
  adapter: z4.literal("s3"),
265
264
  bucket: z4.string().min(1),
266
265
  region: z4.string().min(1),
267
- publicBaseUrl: assetMountUrlSchema.optional(),
268
266
  /**
269
267
  * Where the browser POSTs a presigned direct upload. Absolute http(s) URL or a
270
268
  * site-relative path; defaults to the S3 REST endpoint the AWS SDK returns.
@@ -274,18 +272,17 @@ var init_media = __esm({
274
272
  * signature is unaffected — a presigned POST's string-to-sign is the base64 policy
275
273
  * alone, so the host never enters it (pinned by store-s3.test.ts).
276
274
  *
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.
275
+ * NOT a prefix: nothing is joined onto this value; it replaces the POST target outright
276
+ * and is passed through byte-for-byte. Do not route it through `joinUrlPrefix` "for
277
+ * consistency" — that would strip the trailing slash and silently stop a CDN path
278
+ * pattern like `/asset-upload/*` from matching.
281
279
  */
282
280
  uploadUrl: uploadTargetUrlSchema.optional(),
283
281
  /** Max upload size in bytes for presigned direct uploads. Defaults to 50 MiB. */
284
282
  maxUploadBytes: z4.number().int().positive().optional()
285
283
  }).strict(),
286
284
  z4.object({
287
- adapter: z4.literal("lfs"),
288
- publicBaseUrl: assetMountUrlSchema.optional()
285
+ adapter: z4.literal("lfs")
289
286
  }).strict()
290
287
  ]);
291
288
  }
@@ -299,6 +296,7 @@ var init_config = __esm({
299
296
  "use strict";
300
297
  init_collection();
301
298
  init_media();
299
+ init_url();
302
300
  defaultBranchAccessSchema = z5.enum(["allow", "deny"]).default("deny");
303
301
  defaultPathAccessLevelSchema = z5.enum(["allow", "deny"]);
304
302
  defaultPathAccessLevelsSchema = z5.object({
@@ -329,7 +327,8 @@ var init_config = __esm({
329
327
  title: z5.string().optional(),
330
328
  subtitle: z5.string().optional(),
331
329
  theme: z5.unknown().optional(),
332
- previewBase: z5.record(z5.string()).optional(),
330
+ previewBase: z5.record(z5.union([z5.string(), z5.literal(false)])).optional(),
331
+ previewPrefix: previewPrefixSchema.optional(),
333
332
  // UI handler functions (runtime only, don't serialize)
334
333
  onAccountClick: z5.function().returns(z5.void()).optional(),
335
334
  onLogoutClick: z5.function().returns(z5.void()).optional(),
@@ -357,6 +356,9 @@ var init_config = __esm({
357
356
  defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
358
357
  gitBotAuthorName: gitBotAuthorNameSchema,
359
358
  gitBotAuthorEmail: gitBotAuthorEmailSchema,
359
+ // Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
360
+ gitEditedByTrailers: z5.boolean().optional(),
361
+ gitCoAuthoredByTrailers: z5.boolean().optional(),
360
362
  githubTokenEnvVar: githubTokenEnvVarSchema.optional(),
361
363
  // Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
362
364
  // validation loudly rather than silently running header-trusting dev auth semantics.
@@ -368,7 +370,6 @@ var init_config = __esm({
368
370
  // Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
369
371
  allowNetworkRemoteInProd: z5.boolean().optional(),
370
372
  settingsBranch: z5.string().optional(),
371
- autoCreateSettingsPR: z5.boolean().optional(),
372
373
  // `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
373
374
  // `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
374
375
  // env > config > modeDefault precedence chain `resolveDeploymentName`
@@ -384,7 +385,7 @@ var init_config = __esm({
384
385
  validateEntry: z5.custom().optional(),
385
386
  dev: devConfigSchema.optional()
386
387
  }).strict();
387
- DEFAULT_PROD_WORKSPACE = "/mnt/efs/workspace";
388
+ DEFAULT_PROD_WORKSPACE = "/mnt/efs";
388
389
  }
389
390
  });
390
391
 
@@ -889,6 +890,10 @@ var init_debug = __esm({
889
890
  throw new Error(errorMsg);
890
891
  }
891
892
  }
893
+ /**
894
+ * Keyed by label alone, so two overlapping timers with one label overwrite each other;
895
+ * anything that can run concurrently uses {@link timed}.
896
+ */
892
897
  time(label) {
893
898
  this.timers.set(label, Date.now());
894
899
  }
@@ -903,12 +908,16 @@ var init_debug = __esm({
903
908
  this.debug(category, `${label} completed`, { durationMs: duration });
904
909
  return duration;
905
910
  }
911
+ /**
912
+ * Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
913
+ * call, so concurrent spans with the same label on one logger never overwrite each other.
914
+ */
906
915
  async timed(category, label, fn) {
907
- this.time(label);
916
+ const start = Date.now();
908
917
  try {
909
918
  return await fn();
910
919
  } finally {
911
- this.timeEnd(category, label);
920
+ this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
912
921
  }
913
922
  }
914
923
  };
@@ -1525,9 +1534,6 @@ var ProdStrategy = class extends ProdClientSafeStrategy {
1525
1534
  throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
1526
1535
  }
1527
1536
  }
1528
- shouldCreateSettingsPR(config) {
1529
- return config.autoCreateSettingsPR ?? true;
1530
- }
1531
1537
  };
1532
1538
  var DevStrategy = class extends DevClientSafeStrategy {
1533
1539
  getWorkspaceRoot(sourceRoot) {
@@ -1574,9 +1580,6 @@ var DevStrategy = class extends DevClientSafeStrategy {
1574
1580
  }
1575
1581
  validateConfig(_config) {
1576
1582
  }
1577
- shouldCreateSettingsPR(_config) {
1578
- return false;
1579
- }
1580
1583
  };
1581
1584
  var strategyCache = /* @__PURE__ */ new Map();
1582
1585
  function operatingStrategy(mode) {
@@ -21,8 +21,9 @@ import { parseSlug } from '../paths/index.js';
21
21
  import { filePathExists } from '../utils/fs.js';
22
22
  import { loadCollectionMetaFiles } from '../schema/index.js';
23
23
  import { getErrorMessage } from '../utils/error.js';
24
- import { withLock } from '../utils/async-mutex.js';
25
- import { withOccFileLock, OccWriteConflictError } from '../utils/occ-json-write.js';
24
+ import { ContentWriteLockBusyError } from '../utils/content-write-lock.js';
25
+ import { OccWriteConflictError } from '../utils/occ-json-write.js';
26
+ import { withBranchSchemaLock } from '../schema/schema-store.js';
26
27
  import { BranchMetadataFileManager } from '../branch-metadata.js';
27
28
  import { invalidateBranchContentCaches } from '../content-index-generation.js';
28
29
  const MIGRATE_FORMATS = ['md', 'mdx', 'json', 'yaml'];
@@ -245,18 +246,21 @@ export async function migrate(options) {
245
246
  };
246
247
  if (isBranchClone) {
247
248
  // projectDir IS the branch root in this case (see the invariant note
248
- // above) — the same surrogate lock path SchemaOps uses for its own
249
- // schema mutations (schema-store.ts's withSchemaLock), so a migrate run
250
- // racing a live schema-editing request on this same branch clone
251
- // serializes against it instead of silently interleaving writes.
252
- const lockPath = path.join(projectDir, '.canopy-meta', 'schema');
249
+ // above). The renames rewrite the working tree the worker rebases, so this
250
+ // takes the same locks SchemaOps does for its own schema mutations: the
251
+ // content-write lock, then the schema surrogate.
253
252
  try {
254
- await withLock(lockPath, () => withOccFileLock(lockPath, applyOps));
253
+ await withBranchSchemaLock(projectDir, applyOps);
255
254
  }
256
255
  catch (err) {
257
256
  if (err instanceof OccWriteConflictError) {
258
257
  throw new MigrateError('Another process is modifying the schema, try again.');
259
258
  }
259
+ if (err instanceof ContentWriteLockBusyError) {
260
+ throw new MigrateError(err.outcome === 'unknown'
261
+ ? 'The branch was being synced while migrate ran, so it may be partly applied. Check its state before running migrate again.'
262
+ : err.message);
263
+ }
260
264
  throw err;
261
265
  }
262
266
  }
package/dist/cli/sync.js CHANGED
@@ -17,8 +17,10 @@ import path from 'node:path';
17
17
  import { simpleGit } from 'simple-git';
18
18
  import * as p from '@clack/prompts';
19
19
  import { filePathExists } from '../utils/fs.js';
20
+ import { isCanopyInternalPath, stageAllExceptCanopyState } from '../utils/git.js';
20
21
  import { assertWithinDir, copyDir, listFilesRecursive, pushContentToWorkspace, safeReplaceDir, SYNC_BASE_TAG, } from '../sync-core.js';
21
22
  import { invalidateBranchContentCaches } from '../content-index-generation.js';
23
+ import { ContentWriteLockBusyError, withContentWriteLock } from '../utils/content-write-lock.js';
22
24
  import { ensureGitExcludePattern } from '../git-manager.js';
23
25
  import { operatingStrategy } from '../operating-mode/index.js';
24
26
  /**
@@ -32,6 +34,42 @@ class SyncError extends Error {
32
34
  this.name = 'SyncError';
33
35
  }
34
36
  }
37
+ /**
38
+ * [SYNC-C1] Every sync step that rewrites a branch workspace (content replace, checkout, merge,
39
+ * merge abort) runs under the workspace's content-write lock, which the dev server's saves and
40
+ * the worker's rebase also take. Contention becomes a `SyncError`, so the CLI exits non-zero.
41
+ */
42
+ function lockBusyAsSyncError(err, branchName) {
43
+ if (!(err instanceof ContentWriteLockBusyError))
44
+ return err;
45
+ return new SyncError(err.outcome === 'not-run'
46
+ ? `Branch workspace "${branchName}" is busy (the worker is syncing it, or an editor save is in flight); nothing was changed. Try again in a moment.`
47
+ : `Branch workspace "${branchName}" was being synced during this run, so it may be partly updated. Check its state before running sync again.`);
48
+ }
49
+ async function withWorkspaceLock(branchPath, branchName, fn) {
50
+ try {
51
+ return await withContentWriteLock(branchPath, fn);
52
+ }
53
+ catch (err) {
54
+ throw lockBusyAsSyncError(err, branchName);
55
+ }
56
+ }
57
+ /**
58
+ * `git merge --abort` in a workspace left mid-merge, re-checking under the lock that it still is.
59
+ * Returns whether a merge was aborted.
60
+ */
61
+ async function abortWorkspaceMerge(branchPath, branchName) {
62
+ return withWorkspaceLock(branchPath, branchName, async () => {
63
+ const wsGit = simpleGit({ baseDir: branchPath });
64
+ if ((await wsGit.status()).conflicted.length === 0)
65
+ return false;
66
+ await wsGit.merge(['--abort']);
67
+ // The abort rewrote the clone's working tree — tell ContentStore ID
68
+ // indexes (the dev server is a separate process; on-disk marker).
69
+ await invalidateBranchContentCaches(branchPath);
70
+ return true;
71
+ });
72
+ }
35
73
  /** Detect the current git branch name from the working tree. */
36
74
  async function detectCurrentBranch(projectDir) {
37
75
  const git = simpleGit({ baseDir: projectDir });
@@ -140,22 +178,21 @@ async function syncPush(options) {
140
178
  initialValue: false,
141
179
  });
142
180
  if (!p.isCancel(shouldAbort) && shouldAbort) {
143
- await wsGit.merge(['--abort']);
144
- // The abort rewrote the clone's working tree — tell ContentStore ID
145
- // indexes (the dev server is a separate process; on-disk marker).
146
- await invalidateBranchContentCaches(branchPath);
147
- p.log.success('Merge aborted. Workspace restored to pre-merge state.');
181
+ if (await abortWorkspaceMerge(branchPath, branchName)) {
182
+ p.log.success('Merge aborted. Workspace restored to pre-merge state.');
183
+ }
148
184
  }
149
185
  }
150
186
  return { fileCount: 0 };
151
187
  }
152
- if (status.files.length > 0 && !options.force) {
153
- p.log.warn(`Branch workspace has ${status.files.length} uncommitted change(s) that will be committed to history then overwritten:`);
154
- for (const file of status.files.slice(0, 10)) {
188
+ const editorChanges = status.files.filter((f) => !isCanopyInternalPath(f.path));
189
+ if (editorChanges.length > 0 && !options.force) {
190
+ p.log.warn(`Branch workspace has ${editorChanges.length} uncommitted change(s) that will be committed to history then overwritten:`);
191
+ for (const file of editorChanges.slice(0, 10)) {
155
192
  p.log.warn(` ${file.path}`);
156
193
  }
157
- if (status.files.length > 10) {
158
- p.log.warn(` ... and ${status.files.length - 10} more`);
194
+ if (editorChanges.length > 10) {
195
+ p.log.warn(` ... and ${editorChanges.length - 10} more`);
159
196
  }
160
197
  const confirm = await p.confirm({
161
198
  message: 'Continue? Editor changes will be preserved in git history.',
@@ -166,20 +203,26 @@ async function syncPush(options) {
166
203
  return { fileCount: 0 };
167
204
  }
168
205
  }
169
- // Auto-commit uncommitted workspace changes to preserve in history
170
- if (status.files.length > 0) {
171
- await wsGit.add('-A');
172
- await wsGit.commit('sync: save editor state before push');
173
- p.log.info('Committed editor changes to history before push');
174
- }
175
206
  p.log.step(`Pushing content to branch workspace: ${branchName}`);
176
- // Copy working-tree content → workspace content dir, commit, and tag the sync base.
177
- const { fileCount } = await pushContentToWorkspace({
178
- srcContentDir,
179
- branchPath,
180
- contentRoot,
181
- baseTag: SYNC_BASE_TAG,
182
- });
207
+ // Commit any editor changes (re-read under the lock, so a save landing after
208
+ // the listing above is preserved too), copy working-tree content → workspace
209
+ // content dir, commit, and tag the sync base.
210
+ let fileCount;
211
+ try {
212
+ const result = await pushContentToWorkspace({
213
+ srcContentDir,
214
+ branchPath,
215
+ contentRoot,
216
+ baseTag: SYNC_BASE_TAG,
217
+ saveEditorStateMessage: 'sync: save editor state before push',
218
+ });
219
+ fileCount = result.fileCount;
220
+ if (result.savedEditorState)
221
+ p.log.info('Committed editor changes to history before push');
222
+ }
223
+ catch (err) {
224
+ throw lockBusyAsSyncError(err, branchName);
225
+ }
183
226
  if (fileCount === 0) {
184
227
  p.log.info('Content is already up to date — nothing to push');
185
228
  }
@@ -321,16 +364,23 @@ async function syncBoth(options) {
321
364
  initialValue: false,
322
365
  });
323
366
  if (!p.isCancel(shouldAbort) && shouldAbort) {
324
- await wsGit.merge(['--abort']);
325
- await invalidateBranchContentCaches(branchPath);
326
- p.log.success('Merge aborted. Workspace restored to pre-merge state.');
367
+ if (await abortWorkspaceMerge(branchPath, branchName)) {
368
+ p.log.success('Merge aborted. Workspace restored to pre-merge state.');
369
+ }
327
370
  }
328
371
  }
329
372
  return { pushed: 0, pulled: 0 };
330
373
  }
374
+ return withWorkspaceLock(branchPath, branchName, () => mergeIntoWorkspace(options, { branchName, branchPath, srcContentDir, contentRoot }));
375
+ }
376
+ /** The locked body of {@link syncBoth}: everything that rewrites the workspace. */
377
+ async function mergeIntoWorkspace(options, ws) {
378
+ const { branchName, branchPath, srcContentDir, contentRoot } = ws;
379
+ const wsGit = simpleGit({ baseDir: branchPath });
380
+ const status = await wsGit.status();
331
381
  // Auto-commit uncommitted workspace changes (preserves editor work for the merge)
332
- if (status.files.length > 0) {
333
- await wsGit.add('-A');
382
+ if (status.files.some((f) => !isCanopyInternalPath(f.path))) {
383
+ await stageAllExceptCanopyState(wsGit);
334
384
  await wsGit.commit('sync: save editor state before merge');
335
385
  p.log.info('Committed editor changes before merge');
336
386
  }
@@ -365,9 +415,9 @@ async function syncBoth(options) {
365
415
  await wsGit.raw(['branch', '-D', incomingBranch]).catch(() => { });
366
416
  throw err;
367
417
  }
368
- await wsGit.add('-A');
369
- const incomingStatus = await wsGit.status();
370
- if (incomingStatus.files.length === 0) {
418
+ await stageAllExceptCanopyState(wsGit);
419
+ const incomingFiles = (await wsGit.status()).files.filter((f) => !isCanopyInternalPath(f.path));
420
+ if (incomingFiles.length === 0) {
371
421
  await wsGit.checkout(currentBranch);
372
422
  await wsGit.raw(['branch', '-D', incomingBranch]);
373
423
  p.log.info('No working-tree changes to merge — pulling editor changes only');
@@ -410,7 +460,7 @@ async function syncBoth(options) {
410
460
  await wsGit.tag(['-f', SYNC_BASE_TAG]);
411
461
  p.log.success('Merged working-tree changes with editor changes');
412
462
  const pullResult = await syncPull({ ...options, branch: branchName, force: true });
413
- return { pushed: incomingStatus.files.length, pulled: pullResult.fileCount };
463
+ return { pushed: incomingFiles.length, pulled: pullResult.fileCount };
414
464
  }
415
465
  finally {
416
466
  await invalidateBranchContentCaches(branchPath);
@@ -428,14 +478,10 @@ async function syncAbort(options) {
428
478
  return;
429
479
  const branchPath = path.join(branchesDir, branchName);
430
480
  assertWithinDir(branchPath, branchesDir, '--branch');
431
- const wsGit = simpleGit({ baseDir: branchPath });
432
- const status = await wsGit.status();
433
- if (status.conflicted.length === 0) {
481
+ if (!(await abortWorkspaceMerge(branchPath, branchName))) {
434
482
  p.log.info(`Branch workspace "${branchName}" is not in a merge state — nothing to abort.`);
435
483
  return;
436
484
  }
437
- await wsGit.merge(['--abort']);
438
- await invalidateBranchContentCaches(branchPath);
439
485
  p.log.success(`Merge aborted in branch workspace "${branchName}". Workspace restored to pre-merge state.`);
440
486
  }
441
487
  /**
@@ -73,5 +73,13 @@ COPY --from=builder /app/public ./public
73
73
  # Lambda's filesystem is read-only outside /tmp. Next may write to .next/cache
74
74
  # at runtime; symlink it to /tmp so those writes succeed instead of failing (EROFS).
75
75
  RUN rm -rf .next/cache && ln -s /tmp .next/cache
76
+ # The CMS reports this in System health. A build arg, not a Lambda environment
77
+ # variable: a Lambda variable names the infrastructure's commit and would hide
78
+ # exactly the image/infrastructure skew this exists to reveal. Declared after
79
+ # every build layer, so with a warm layer cache a new revision re-runs only the
80
+ # metadata steps from here down.
81
+ ARG CANOPY_SOURCE_SHA=
82
+ ENV CANOPY_SOURCE_SHA=$CANOPY_SOURCE_SHA
83
+ LABEL org.opencontainers.image.revision=$CANOPY_SOURCE_SHA
76
84
  ENV PORT=8080 AWS_LWA_PORT=8080 HOSTNAME=0.0.0.0 NODE_ENV=production
77
85
  CMD ["node", "server.js"]
@@ -126,4 +126,8 @@ new CmsStack(app, '{{STACK_NAME}}', {
126
126
  // point your own existing distribution at it.
127
127
  domainName: process.env.CMS_DOMAIN_NAME || undefined,
128
128
  hostedZoneDomain: process.env.CMS_HOSTED_ZONE_DOMAIN || undefined,
129
+
130
+ // Optional, and NOT `required()`: the commit the CMS image is built from,
131
+ // shown in System health. Unset, System health reports it as "not set".
132
+ sourceRevision: process.env.CANOPY_SOURCE_SHA || undefined,
129
133
  })
@@ -77,6 +77,8 @@ export interface CmsStackProps extends StackProps {
77
77
  domainName?: string
78
78
  /** Optional Route53 hosted zone domain, e.g. 'example.org'. */
79
79
  hostedZoneDomain?: string
80
+ /** Optional. Commit the CMS image is built from; shown in System health. */
81
+ sourceRevision?: string
80
82
  }
81
83
 
82
84
  export class CmsStack extends Stack {
@@ -128,6 +130,10 @@ export class CmsStack extends Stack {
128
130
  // auth. The image's own `next build` stays in dev mode either way --
129
131
  // see Dockerfile.cms.
130
132
  NEXT_PUBLIC_CANOPY_MODE: 'prod',
133
+ // CDK hashes build args into the asset, so every new commit rebuilds
134
+ // the image, even one changing only the dockerignored infrastructure/
135
+ // -- that is what keeps the revision the CMS reports honest.
136
+ ...(props.sourceRevision ? { CANOPY_SOURCE_SHA: props.sourceRevision } : {}),
131
137
  },
132
138
  }),
133
139
  // The one place the CMS image's CPU architecture is decided. CDK derives
@@ -186,22 +192,21 @@ export class CmsStack extends Stack {
186
192
  },
187
193
 
188
194
  memorySize: 2048,
189
- reservedConcurrency: 10,
195
+ // A ceiling, free while idle. The editor's static chunks count against it
196
+ // until CloudFront caches them, and a cold editor load requests them
197
+ // together, so synth warns below 20. See the prop's doc comment.
198
+ reservedConcurrency: 50,
190
199
  })
191
200
 
192
- // Media support (uploads, on-demand image transforms). To enable it:
201
+ // Media support (uploads and image derivatives). To enable it:
193
202
  //
194
203
  // 1. add `AssetSupport` to the `canopycms-cdk` import at the top;
195
204
  // 2. uncomment the block below, moving it ABOVE `cmsService` (it has to
196
205
  // exist before you can pass its bucket);
197
206
  // 3. pass `assetBucket: assetSupport.bucket` to CanopyCmsService;
198
207
  // 4. pass `assetSupport` to CanopyCmsDistribution below - it attaches
199
- // both CloudFront behaviors in the only order that is safe
200
- // (`/assets/t/*` before `/assets/*`; CloudFront matches path
201
- // patterns in order, so the general pattern would otherwise
202
- // swallow the transform one). `cdk synth` fails loudly if a
203
- // hand-written `additionalBehaviors` ever gets that order wrong
204
- // instead.
208
+ // `/assets/t/*` before `/assets/*`, the order CloudFront needs, and
209
+ // `cdk synth` fails if a hand-written `additionalBehaviors` does not.
205
210
  //
206
211
  // `editorOrigins` is the S3 CORS allowlist for the editor's presigned
207
212
  // uploads, so it must list the origin the editor is served from. Include
@@ -211,11 +216,17 @@ export class CmsStack extends Stack {
211
216
  // CloudFront edge instead); with neither, a construct-created bucket
212
217
  // refuses to synth.
213
218
  //
214
- // Run `pnpm --filter canopycms-cdk run build:lambda` before deploying --
215
- // the transform Lambda needs its native sharp binary, and the construct
216
- // refuses to synth without it.
219
+ // `/assets/*` and `/assets/t/*` serve only what is in the bucket and
220
+ // compute nothing: an image derivative no one wrote is a 403. Write every
221
+ // derivative a site build references before releasing that build, with
222
+ // `canopycms collect-asset-refs <outDir>` after the build and then
223
+ // `canopycms materialize-assets --refs <outDir>/canopy-asset-refs.json`.
224
+ // To compute missing derivatives on request instead, pass
225
+ // `lazyPublicTransforms: true`; that deploys a transform Lambda, so inside
226
+ // this monorepo run `pnpm --filter canopycms-cdk run build:lambda` first
227
+ // (the published package ships it built).
217
228
  //
218
- // See the assets section of docs/deploying-to-aws.md.
229
+ // See "Media: the public image path" in docs/deploying-to-aws.md.
219
230
  //
220
231
  // const assetSupport = new AssetSupport(this, 'Assets', {
221
232
  // editorOrigins: [`https://${props.domainName}`],
@@ -241,4 +241,6 @@ jobs:
241
241
  CANOPYCMS_DEPLOYMENT_NAME: ${{ vars.CANOPYCMS_DEPLOYMENT_NAME }}
242
242
  CMS_DOMAIN_NAME: ${{ vars.CMS_DOMAIN_NAME }}
243
243
  CMS_HOSTED_ZONE_DOMAIN: ${{ vars.CMS_HOSTED_ZONE_DOMAIN }}
244
+ # The commit the CMS image is built from, reported in System health.
245
+ CANOPY_SOURCE_SHA: ${{ github.sha }}
244
246
  run: npx cdk deploy {{STACK_NAME}} --require-approval never
package/dist/client.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { CustomFieldRenderers, CustomFieldRenderProps } from './editor/Form
3
3
  export * from './editor/EditorPanes.js';
4
4
  export * from './editor/EntryNavigator.js';
5
5
  export * from './editor/Editor.js';
6
- export * from './editor/preview-bridge.js';
6
+ export { PreviewFrame } from './editor/PreviewFrame.js';
7
7
  export * from './editor/canopy-path.js';
8
8
  export * from './editor/theme.js';
9
9
  export * from './editor/editor-config.js';
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
  export * from './editor/EditorPanes.js';
3
3
  export * from './editor/EntryNavigator.js';
4
4
  export * from './editor/Editor.js';
5
- export * from './editor/preview-bridge.js';
5
+ export { PreviewFrame } from './editor/PreviewFrame.js';
6
6
  export * from './editor/canopy-path.js';
7
7
  export * from './editor/theme.js';
8
8
  export * from './editor/editor-config.js';
@@ -33,7 +33,7 @@ export function defineCanopyConfig(config) {
33
33
  server: validated,
34
34
  // Client config helper - extracts safe subset and merges overrides
35
35
  client: (clientOverrides) => {
36
- const { defaultBaseBranch, defaultActiveBranch, contentRoot, editor, mode, entryLinkUrl, media, basePath, } = validated;
36
+ const { defaultBaseBranch, defaultActiveBranch, contentRoot, editor, mode, entryLinkUrl, basePath, } = validated;
37
37
  // flatSchema is loaded dynamically by the editor via API (from .collection.json files)
38
38
  const clientConfig = {
39
39
  defaultBaseBranch,
@@ -42,7 +42,6 @@ export function defineCanopyConfig(config) {
42
42
  editor,
43
43
  mode,
44
44
  entryLinkUrl,
45
- assetBaseUrl: media?.publicBaseUrl,
46
45
  basePath,
47
46
  flatSchema: [],
48
47
  };