canopycms 0.0.67-int.90 → 0.0.67

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 (539) hide show
  1. package/README.md +30 -3
  2. package/dist/ai/generate.js +1 -27
  3. package/dist/ai/handler.d.ts +0 -4
  4. package/dist/ai/handler.js +0 -11
  5. package/dist/ai/index.d.ts +1 -8
  6. package/dist/ai/index.js +1 -8
  7. package/dist/ai/json-to-markdown.js +15 -58
  8. package/dist/ai/resolve-branch.d.ts +2 -7
  9. package/dist/ai/resolve-branch.js +2 -8
  10. package/dist/ai/strip-mdx.d.ts +5 -13
  11. package/dist/ai/strip-mdx.js +5 -17
  12. package/dist/ai/to-plain-text.d.ts +10 -28
  13. package/dist/ai/to-plain-text.js +10 -28
  14. package/dist/ai/transform-components.d.ts +2 -4
  15. package/dist/ai/transform-components.js +8 -19
  16. package/dist/ai/types.d.ts +5 -10
  17. package/dist/api/admin-branch-health.d.ts +4 -12
  18. package/dist/api/admin-branch-health.js +26 -66
  19. package/dist/api/admin.d.ts +4 -12
  20. package/dist/api/admin.js +10 -26
  21. package/dist/api/assets.d.ts +0 -25
  22. package/dist/api/assets.js +50 -91
  23. package/dist/api/branch-merge.d.ts +1 -1
  24. package/dist/api/branch-merge.js +0 -2
  25. package/dist/api/branch-review.d.ts +0 -8
  26. package/dist/api/branch-review.js +0 -13
  27. package/dist/api/branch-status.d.ts +0 -3
  28. package/dist/api/branch-status.js +8 -33
  29. package/dist/api/branch-withdraw.d.ts +0 -1
  30. package/dist/api/branch-withdraw.js +6 -9
  31. package/dist/api/branch.d.ts +7 -12
  32. package/dist/api/branch.js +7 -44
  33. package/dist/api/client.d.ts +71 -224
  34. package/dist/api/client.js +82 -233
  35. package/dist/api/comments.d.ts +2 -4
  36. package/dist/api/comments.js +0 -17
  37. package/dist/api/content.d.ts +0 -6
  38. package/dist/api/content.js +66 -142
  39. package/dist/api/entries-constants.d.ts +5 -9
  40. package/dist/api/entries-constants.js +5 -9
  41. package/dist/api/entries.d.ts +8 -13
  42. package/dist/api/entries.js +20 -51
  43. package/dist/api/github-sync.d.ts +2 -4
  44. package/dist/api/github-sync.js +4 -8
  45. package/dist/api/groups.d.ts +3 -0
  46. package/dist/api/groups.js +8 -21
  47. package/dist/api/guards.d.ts +4 -16
  48. package/dist/api/guards.js +3 -22
  49. package/dist/api/index.d.ts +4 -4
  50. package/dist/api/permissions.d.ts +2 -0
  51. package/dist/api/permissions.js +18 -40
  52. package/dist/api/reference-options.d.ts +0 -1
  53. package/dist/api/reference-options.js +3 -11
  54. package/dist/api/request-body-hash.d.ts +12 -23
  55. package/dist/api/request-body-hash.js +12 -23
  56. package/dist/api/resolve-references.d.ts +0 -1
  57. package/dist/api/resolve-references.js +13 -28
  58. package/dist/api/route-builder.d.ts +3 -7
  59. package/dist/api/route-builder.js +1 -1
  60. package/dist/api/routes.d.ts +12 -0
  61. package/dist/api/routes.js +47 -0
  62. package/dist/api/schema.d.ts +23 -33
  63. package/dist/api/schema.js +20 -74
  64. package/dist/api/settings-helpers.js +4 -5
  65. package/dist/api/types.d.ts +0 -5
  66. package/dist/api/user.js +0 -5
  67. package/dist/api/validators.d.ts +14 -52
  68. package/dist/api/validators.js +14 -52
  69. package/dist/assets/asset-prefixes.d.ts +4 -8
  70. package/dist/assets/asset-prefixes.js +4 -8
  71. package/dist/assets/asset-src.d.ts +5 -22
  72. package/dist/assets/asset-src.js +5 -21
  73. package/dist/assets/factory.d.ts +1 -2
  74. package/dist/assets/factory.js +1 -2
  75. package/dist/assets/finalize.d.ts +3 -1
  76. package/dist/assets/finalize.js +3 -1
  77. package/dist/assets/index.d.ts +3 -3
  78. package/dist/assets/index.js +1 -1
  79. package/dist/assets/keys.d.ts +5 -3
  80. package/dist/assets/keys.js +6 -3
  81. package/dist/assets/pipeline.d.ts +3 -2
  82. package/dist/assets/pipeline.js +22 -56
  83. package/dist/assets/sharp-loader.d.ts +2 -10
  84. package/dist/assets/sharp-loader.js +2 -10
  85. package/dist/assets/store-local.d.ts +1 -2
  86. package/dist/assets/store-local.js +1 -2
  87. package/dist/assets/store-s3.d.ts +3 -4
  88. package/dist/assets/svg-sanitizer.d.ts +6 -14
  89. package/dist/assets/svg-sanitizer.js +6 -14
  90. package/dist/assets/transform-directives.d.ts +3 -4
  91. package/dist/assets/transform-directives.js +1 -1
  92. package/dist/assets/transform.d.ts +19 -30
  93. package/dist/assets/transform.js +18 -31
  94. package/dist/auth/cache.d.ts +2 -3
  95. package/dist/auth/cache.js +2 -3
  96. package/dist/auth/caching-auth-plugin.d.ts +11 -22
  97. package/dist/auth/caching-auth-plugin.js +12 -14
  98. package/dist/auth/context-helpers.d.ts +6 -20
  99. package/dist/auth/context-helpers.js +5 -17
  100. package/dist/auth/file-based-auth-cache.d.ts +10 -21
  101. package/dist/auth/file-based-auth-cache.js +23 -51
  102. package/dist/auth/plugin.d.ts +29 -61
  103. package/dist/auth/plugin.js +9 -13
  104. package/dist/auth/types.d.ts +11 -20
  105. package/dist/authorization/branch.d.ts +30 -49
  106. package/dist/authorization/branch.js +16 -42
  107. package/dist/authorization/content.d.ts +11 -40
  108. package/dist/authorization/content.js +13 -45
  109. package/dist/authorization/groups/index.d.ts +0 -3
  110. package/dist/authorization/groups/index.js +0 -3
  111. package/dist/authorization/groups/loader.d.ts +14 -28
  112. package/dist/authorization/groups/loader.js +14 -38
  113. package/dist/authorization/groups/schema.d.ts +0 -6
  114. package/dist/authorization/groups/schema.js +4 -7
  115. package/dist/authorization/helpers.d.ts +9 -28
  116. package/dist/authorization/helpers.js +9 -28
  117. package/dist/authorization/index.d.ts +3 -25
  118. package/dist/authorization/index.js +3 -35
  119. package/dist/authorization/path.d.ts +6 -13
  120. package/dist/authorization/path.js +8 -25
  121. package/dist/authorization/permissions/index.d.ts +0 -3
  122. package/dist/authorization/permissions/index.js +0 -3
  123. package/dist/authorization/permissions/loader.d.ts +14 -27
  124. package/dist/authorization/permissions/loader.js +14 -32
  125. package/dist/authorization/permissions/schema.d.ts +1 -6
  126. package/dist/authorization/permissions/schema.js +6 -13
  127. package/dist/authorization/protected-branch.d.ts +36 -65
  128. package/dist/authorization/protected-branch.js +29 -41
  129. package/dist/authorization/settings-file-store.d.ts +38 -81
  130. package/dist/authorization/settings-file-store.js +38 -79
  131. package/dist/authorization/types.d.ts +4 -26
  132. package/dist/authorization/types.js +0 -6
  133. package/dist/authorization/validation.d.ts +1 -23
  134. package/dist/authorization/validation.js +1 -24
  135. package/dist/branch-health.d.ts +39 -67
  136. package/dist/branch-health.js +27 -43
  137. package/dist/branch-metadata-file.d.ts +16 -32
  138. package/dist/branch-metadata-file.js +13 -26
  139. package/dist/branch-metadata.d.ts +49 -80
  140. package/dist/branch-metadata.js +57 -87
  141. package/dist/branch-registry.d.ts +26 -64
  142. package/dist/branch-registry.js +44 -93
  143. package/dist/branch-schema-cache.d.ts +34 -124
  144. package/dist/branch-schema-cache.js +49 -155
  145. package/dist/branch-workspace.d.ts +4 -4
  146. package/dist/branch-workspace.js +15 -19
  147. package/dist/build/generate-ai-content.d.ts +1 -0
  148. package/dist/build/generate-ai-content.js +12 -25
  149. package/dist/build-canopy.d.ts +17 -27
  150. package/dist/build-canopy.js +16 -23
  151. package/dist/build-mode.d.ts +25 -27
  152. package/dist/build-mode.js +25 -29
  153. package/dist/cli/cli.d.ts +15 -26
  154. package/dist/cli/cli.js +601 -978
  155. package/dist/cli/generate-ai-content.js +351 -628
  156. package/dist/cli/github-app-manifest.d.ts +84 -0
  157. package/dist/cli/github-app-manifest.js +161 -0
  158. package/dist/cli/init-github-app.d.ts +110 -444
  159. package/dist/cli/init-github-app.js +236 -773
  160. package/dist/cli/init.js +39 -80
  161. package/dist/cli/migrate.d.ts +13 -10
  162. package/dist/cli/migrate.js +22 -30
  163. package/dist/cli/project-detect.d.ts +17 -31
  164. package/dist/cli/project-detect.js +31 -42
  165. package/dist/cli/project-root.d.ts +4 -11
  166. package/dist/cli/project-root.js +4 -11
  167. package/dist/cli/prompt.d.ts +10 -0
  168. package/dist/cli/prompt.js +67 -0
  169. package/dist/cli/sync.d.ts +0 -8
  170. package/dist/cli/sync.js +8 -28
  171. package/dist/cli/template-files/middleware.ts.template +15 -29
  172. package/dist/cli/templates.d.ts +2 -12
  173. package/dist/cli/templates.js +4 -4
  174. package/dist/client.d.ts +1 -1
  175. package/dist/comment-store.d.ts +32 -53
  176. package/dist/comment-store.js +32 -52
  177. package/dist/config/flatten.d.ts +0 -3
  178. package/dist/config/flatten.js +2 -12
  179. package/dist/config/helpers.js +2 -2
  180. package/dist/config/index.d.ts +2 -2
  181. package/dist/config/index.js +1 -4
  182. package/dist/config/schemas/collection.d.ts +0 -8
  183. package/dist/config/schemas/collection.js +0 -10
  184. package/dist/config/schemas/config.d.ts +6 -78
  185. package/dist/config/schemas/config.js +38 -54
  186. package/dist/config/schemas/field.d.ts +2 -242
  187. package/dist/config/schemas/field.js +9 -20
  188. package/dist/config/schemas/media.js +1 -1
  189. package/dist/config/schemas/permissions.js +0 -2
  190. package/dist/config/schemas/url.d.ts +5 -6
  191. package/dist/config/schemas/url.js +5 -6
  192. package/dist/config/types.d.ts +45 -33
  193. package/dist/config/types.js +0 -1
  194. package/dist/config.d.ts +1 -8
  195. package/dist/config.js +1 -9
  196. package/dist/content-id-index.d.ts +76 -188
  197. package/dist/content-id-index.js +79 -230
  198. package/dist/content-index-generation.d.ts +27 -48
  199. package/dist/content-index-generation.js +35 -84
  200. package/dist/content-index-registry.d.ts +15 -24
  201. package/dist/content-index-registry.js +5 -9
  202. package/dist/content-listing.d.ts +64 -107
  203. package/dist/content-listing.js +68 -118
  204. package/dist/content-reader.d.ts +20 -29
  205. package/dist/content-reader.js +10 -18
  206. package/dist/content-store.d.ts +210 -336
  207. package/dist/content-store.js +413 -657
  208. package/dist/content-tree.d.ts +44 -86
  209. package/dist/content-tree.js +26 -54
  210. package/dist/context.d.ts +67 -94
  211. package/dist/context.js +53 -72
  212. package/dist/dev-content-watcher.d.ts +18 -18
  213. package/dist/dev-content-watcher.js +41 -44
  214. package/dist/editor/BranchManager.d.ts +3 -6
  215. package/dist/editor/BranchManager.js +24 -44
  216. package/dist/editor/CanopyEditor.js +5 -2
  217. package/dist/editor/CommentsPanel.js +0 -2
  218. package/dist/editor/Editor.js +19 -76
  219. package/dist/editor/EditorAuthGate.d.ts +27 -0
  220. package/dist/editor/EditorAuthGate.js +140 -0
  221. package/dist/editor/EntryNavigator.js +0 -20
  222. package/dist/editor/FormRenderer.d.ts +1 -2
  223. package/dist/editor/FormRenderer.js +8 -23
  224. package/dist/editor/admin/SystemHealthPanel.js +20 -37
  225. package/dist/editor/admin/useSystemHealth.d.ts +2 -3
  226. package/dist/editor/admin/useSystemHealth.js +10 -12
  227. package/dist/editor/client-reference-resolver.d.ts +2 -5
  228. package/dist/editor/client-reference-resolver.js +2 -19
  229. package/dist/editor/comments/BranchComments.js +0 -4
  230. package/dist/editor/comments/EntryComments.js +0 -4
  231. package/dist/editor/comments/FieldWrapper.js +0 -4
  232. package/dist/editor/comments/InlineCommentThread.js +0 -2
  233. package/dist/editor/comments/ThreadCarousel.d.ts +0 -58
  234. package/dist/editor/comments/ThreadCarousel.js +0 -12
  235. package/dist/editor/components/EditorFooter.d.ts +0 -8
  236. package/dist/editor/components/EditorFooter.js +0 -5
  237. package/dist/editor/components/EditorHeader.d.ts +0 -31
  238. package/dist/editor/components/EditorHeader.js +21 -71
  239. package/dist/editor/components/EditorSidebar.d.ts +0 -19
  240. package/dist/editor/components/EditorSidebar.js +0 -16
  241. package/dist/editor/components/EntryCreateModal.d.ts +4 -4
  242. package/dist/editor/components/EntryCreateModal.js +7 -22
  243. package/dist/editor/components/RenameEntryModal.js +0 -4
  244. package/dist/editor/components/UserBadge.js +0 -10
  245. package/dist/editor/components/index.js +0 -2
  246. package/dist/editor/context/ApiClientContext.d.ts +8 -7
  247. package/dist/editor/context/ApiClientContext.js +39 -11
  248. package/dist/editor/context/AssetContext.d.ts +7 -17
  249. package/dist/editor/context/AssetContext.js +7 -17
  250. package/dist/editor/context/EditorIdentityContext.d.ts +13 -0
  251. package/dist/editor/context/EditorIdentityContext.js +7 -0
  252. package/dist/editor/context/EditorStateContext.d.ts +10 -41
  253. package/dist/editor/context/EditorStateContext.js +7 -23
  254. package/dist/editor/context/SWRProvider.d.ts +3 -6
  255. package/dist/editor/context/index.d.ts +1 -20
  256. package/dist/editor/context/index.js +1 -20
  257. package/dist/editor/editor-utils.d.ts +8 -47
  258. package/dist/editor/editor-utils.js +19 -72
  259. package/dist/editor/fields/BlockField.d.ts +2 -2
  260. package/dist/editor/fields/BlockField.js +1 -2
  261. package/dist/editor/fields/CodeField.d.ts +0 -1
  262. package/dist/editor/fields/CodeField.js +0 -1
  263. package/dist/editor/fields/DateTimeField.d.ts +15 -22
  264. package/dist/editor/fields/DateTimeField.js +17 -26
  265. package/dist/editor/fields/ImageField.d.ts +2 -2
  266. package/dist/editor/fields/ImageField.js +8 -14
  267. package/dist/editor/fields/InlineGroupField.d.ts +0 -1
  268. package/dist/editor/fields/InlineGroupField.js +0 -1
  269. package/dist/editor/fields/MarkdownField.d.ts +0 -1
  270. package/dist/editor/fields/MarkdownField.js +6 -11
  271. package/dist/editor/fields/MdxImageDialog.d.ts +1 -1
  272. package/dist/editor/fields/MdxImageDialog.js +0 -1
  273. package/dist/editor/fields/NumberField.d.ts +9 -19
  274. package/dist/editor/fields/NumberField.js +11 -22
  275. package/dist/editor/fields/NumberListField.d.ts +7 -10
  276. package/dist/editor/fields/NumberListField.js +7 -10
  277. package/dist/editor/fields/ObjectField.d.ts +5 -8
  278. package/dist/editor/fields/ObjectField.js +0 -1
  279. package/dist/editor/fields/ReferenceField.d.ts +1 -1
  280. package/dist/editor/fields/ReferenceField.js +11 -16
  281. package/dist/editor/fields/SelectField.d.ts +2 -2
  282. package/dist/editor/fields/SelectField.js +0 -1
  283. package/dist/editor/fields/StringListField.d.ts +0 -4
  284. package/dist/editor/fields/StringListField.js +4 -9
  285. package/dist/editor/fields/TextField.d.ts +0 -1
  286. package/dist/editor/fields/TextField.js +0 -1
  287. package/dist/editor/fields/ToggleField.d.ts +0 -1
  288. package/dist/editor/fields/ToggleField.js +0 -1
  289. package/dist/editor/fields/entry-link/EntryLinkContext.js +2 -4
  290. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +0 -3
  291. package/dist/editor/fields/entry-link/InsertEntryLink.js +0 -4
  292. package/dist/editor/group-manager/ExternalGroupsTab.d.ts +0 -3
  293. package/dist/editor/group-manager/ExternalGroupsTab.js +0 -1
  294. package/dist/editor/group-manager/GroupCard.d.ts +0 -3
  295. package/dist/editor/group-manager/GroupForm.d.ts +0 -3
  296. package/dist/editor/group-manager/InternalGroupsTab.d.ts +0 -3
  297. package/dist/editor/group-manager/InternalGroupsTab.js +0 -2
  298. package/dist/editor/group-manager/MemberList.d.ts +0 -3
  299. package/dist/editor/group-manager/hooks/useExternalGroupSearch.d.ts +0 -3
  300. package/dist/editor/group-manager/hooks/useExternalGroupSearch.js +0 -3
  301. package/dist/editor/group-manager/hooks/useGroupState.d.ts +0 -3
  302. package/dist/editor/group-manager/hooks/useGroupState.js +0 -5
  303. package/dist/editor/group-manager/hooks/useUserSearch.d.ts +0 -3
  304. package/dist/editor/group-manager/hooks/useUserSearch.js +0 -3
  305. package/dist/editor/group-manager/index.d.ts +0 -6
  306. package/dist/editor/group-manager/index.js +0 -10
  307. package/dist/editor/group-manager/types.d.ts +2 -5
  308. package/dist/editor/group-manager/types.js +0 -3
  309. package/dist/editor/hooks/index.d.ts +6 -11
  310. package/dist/editor/hooks/index.js +6 -12
  311. package/dist/editor/hooks/useBranchActions.d.ts +0 -14
  312. package/dist/editor/hooks/useBranchActions.js +0 -22
  313. package/dist/editor/hooks/useBranchManager.d.ts +2 -48
  314. package/dist/editor/hooks/useBranchManager.js +4 -64
  315. package/dist/editor/hooks/useBranchesData.d.ts +7 -6
  316. package/dist/editor/hooks/useBranchesData.js +7 -6
  317. package/dist/editor/hooks/useCommentSystem.d.ts +0 -49
  318. package/dist/editor/hooks/useCommentSystem.js +1 -36
  319. package/dist/editor/hooks/useDraftManager.d.ts +2 -35
  320. package/dist/editor/hooks/useDraftManager.js +23 -64
  321. package/dist/editor/hooks/useEditorLayout.d.ts +0 -20
  322. package/dist/editor/hooks/useEditorLayout.js +0 -23
  323. package/dist/editor/hooks/useEntriesData.d.ts +1 -0
  324. package/dist/editor/hooks/useEntriesData.js +2 -3
  325. package/dist/editor/hooks/useEntryLinkResolution.d.ts +2 -2
  326. package/dist/editor/hooks/useEntryLinkResolution.js +2 -2
  327. package/dist/editor/hooks/useEntryManager.d.ts +2 -27
  328. package/dist/editor/hooks/useEntryManager.js +29 -78
  329. package/dist/editor/hooks/useGroupManager.d.ts +0 -21
  330. package/dist/editor/hooks/useGroupManager.js +0 -21
  331. package/dist/editor/hooks/usePermissionManager.d.ts +0 -20
  332. package/dist/editor/hooks/usePermissionManager.js +0 -20
  333. package/dist/editor/hooks/useReferenceResolution.d.ts +2 -33
  334. package/dist/editor/hooks/useReferenceResolution.js +1 -48
  335. package/dist/editor/hooks/useSchemaManager.d.ts +8 -27
  336. package/dist/editor/hooks/useSchemaManager.js +0 -19
  337. package/dist/editor/hooks/useUserContext.d.ts +2 -9
  338. package/dist/editor/hooks/useUserContext.js +10 -10
  339. package/dist/editor/hooks/useUserMetadata.d.ts +0 -2
  340. package/dist/editor/hooks/useUserMetadata.js +0 -3
  341. package/dist/editor/media/AssetCard.d.ts +0 -1
  342. package/dist/editor/media/AssetCard.js +1 -2
  343. package/dist/editor/media/CropStep.d.ts +0 -1
  344. package/dist/editor/media/CropStep.js +0 -1
  345. package/dist/editor/media/MediaLibrary.d.ts +1 -2
  346. package/dist/editor/media/MediaLibrary.js +1 -2
  347. package/dist/editor/media/MediaLibraryBody.d.ts +0 -1
  348. package/dist/editor/media/MediaLibraryBody.js +0 -1
  349. package/dist/editor/media/crop-math.d.ts +1 -1
  350. package/dist/editor/media/crop-math.js +1 -1
  351. package/dist/editor/permission-manager/GroupSelector.d.ts +0 -3
  352. package/dist/editor/permission-manager/PermissionEditor.d.ts +0 -4
  353. package/dist/editor/permission-manager/PermissionEditor.js +0 -1
  354. package/dist/editor/permission-manager/PermissionLevelBadge.d.ts +0 -7
  355. package/dist/editor/permission-manager/PermissionLevelBadge.js +0 -4
  356. package/dist/editor/permission-manager/PermissionTree.d.ts +0 -4
  357. package/dist/editor/permission-manager/UserSelector.d.ts +0 -3
  358. package/dist/editor/permission-manager/constants.d.ts +0 -3
  359. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts +0 -3
  360. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js +0 -10
  361. package/dist/editor/permission-manager/hooks/usePermissionTree.d.ts +0 -14
  362. package/dist/editor/permission-manager/hooks/usePermissionTree.js +0 -11
  363. package/dist/editor/permission-manager/index.d.ts +0 -7
  364. package/dist/editor/permission-manager/index.js +0 -16
  365. package/dist/editor/permission-manager/types.d.ts +2 -12
  366. package/dist/editor/permission-manager/types.js +0 -3
  367. package/dist/editor/permission-manager/utils.d.ts +0 -28
  368. package/dist/editor/permission-manager/utils.js +9 -23
  369. package/dist/editor/preview-bridge.d.ts +5 -1
  370. package/dist/editor/preview-bridge.js +4 -4
  371. package/dist/editor/schema-editor/CollectionEditor.d.ts +0 -5
  372. package/dist/editor/schema-editor/CollectionEditor.js +9 -31
  373. package/dist/editor/schema-editor/EntryTypeEditor.d.ts +0 -6
  374. package/dist/editor/schema-editor/EntryTypeEditor.js +3 -24
  375. package/dist/editor/schema-editor/index.d.ts +0 -7
  376. package/dist/editor/schema-editor/index.js +0 -7
  377. package/dist/editor/utils/env.d.ts +3 -18
  378. package/dist/editor/utils/env.js +6 -25
  379. package/dist/entry-link-resolver.d.ts +13 -35
  380. package/dist/entry-link-resolver.js +20 -53
  381. package/dist/entry-schema-registry.d.ts +20 -60
  382. package/dist/entry-schema-registry.js +20 -69
  383. package/dist/entry-schema.d.ts +118 -223
  384. package/dist/entry-schema.js +46 -91
  385. package/dist/git-manager.d.ts +101 -200
  386. package/dist/git-manager.js +211 -384
  387. package/dist/github-service.d.ts +34 -75
  388. package/dist/github-service.js +37 -91
  389. package/dist/http/handler.d.ts +6 -37
  390. package/dist/http/handler.js +65 -120
  391. package/dist/http/router.d.ts +15 -32
  392. package/dist/http/router.js +34 -99
  393. package/dist/http/types.d.ts +15 -40
  394. package/dist/http/types.js +3 -7
  395. package/dist/id.d.ts +4 -10
  396. package/dist/id.js +4 -10
  397. package/dist/index.js +6 -9
  398. package/dist/operating-mode/client-safe-strategy.d.ts +7 -14
  399. package/dist/operating-mode/client-safe-strategy.js +8 -28
  400. package/dist/operating-mode/client-unsafe-strategy.d.ts +6 -16
  401. package/dist/operating-mode/client-unsafe-strategy.js +9 -34
  402. package/dist/operating-mode/client.d.ts +3 -10
  403. package/dist/operating-mode/client.js +3 -8
  404. package/dist/operating-mode/deployment-name-fixtures.d.ts +14 -18
  405. package/dist/operating-mode/deployment-name-fixtures.js +14 -18
  406. package/dist/operating-mode/deployment-name.d.ts +19 -29
  407. package/dist/operating-mode/deployment-name.js +29 -47
  408. package/dist/operating-mode/index.d.ts +4 -25
  409. package/dist/operating-mode/index.js +9 -31
  410. package/dist/operating-mode/mode-env.d.ts +32 -54
  411. package/dist/operating-mode/mode-env.js +36 -58
  412. package/dist/operating-mode/types.d.ts +24 -72
  413. package/dist/operating-mode/types.js +1 -7
  414. package/dist/paths/branch-name.d.ts +30 -40
  415. package/dist/paths/branch-name.js +30 -40
  416. package/dist/paths/branch.d.ts +4 -18
  417. package/dist/paths/branch.js +6 -22
  418. package/dist/paths/index.d.ts +2 -13
  419. package/dist/paths/index.js +3 -23
  420. package/dist/paths/normalize-server.d.ts +3 -15
  421. package/dist/paths/normalize-server.js +3 -15
  422. package/dist/paths/normalize.d.ts +13 -33
  423. package/dist/paths/normalize.js +13 -33
  424. package/dist/paths/resolve.d.ts +4 -17
  425. package/dist/paths/resolve.js +4 -22
  426. package/dist/paths/types.d.ts +12 -34
  427. package/dist/paths/types.js +5 -7
  428. package/dist/paths/validation.d.ts +22 -119
  429. package/dist/paths/validation.js +30 -148
  430. package/dist/reference-resolver.d.ts +0 -13
  431. package/dist/reference-resolver.js +0 -18
  432. package/dist/resolve-canopy-user.d.ts +16 -18
  433. package/dist/resolve-canopy-user.js +17 -26
  434. package/dist/resource-generation.d.ts +16 -25
  435. package/dist/resource-generation.js +34 -97
  436. package/dist/schema/meta-loader.d.ts +2 -14
  437. package/dist/schema/meta-loader.js +15 -64
  438. package/dist/schema/resolver.d.ts +1 -4
  439. package/dist/schema/resolver.js +1 -6
  440. package/dist/schema/schema-store-types.d.ts +0 -12
  441. package/dist/schema/schema-store.d.ts +61 -138
  442. package/dist/schema/schema-store.js +85 -282
  443. package/dist/schema/types.d.ts +0 -4
  444. package/dist/server.d.ts +62 -93
  445. package/dist/server.js +63 -94
  446. package/dist/services.d.ts +23 -47
  447. package/dist/services.js +55 -98
  448. package/dist/settings-workspace.d.ts +17 -30
  449. package/dist/settings-workspace.js +51 -81
  450. package/dist/static/index.d.ts +26 -52
  451. package/dist/static/index.js +33 -67
  452. package/dist/{worker/task-queue.d.ts → task-queue/cms-task-queue.d.ts} +7 -9
  453. package/dist/task-queue/cms-task-queue.js +13 -0
  454. package/dist/task-queue/task-queue-config.d.ts +3 -0
  455. package/dist/{worker → task-queue}/task-queue-config.js +1 -6
  456. package/dist/task-queue/task-queue.d.ts +16 -22
  457. package/dist/task-queue/task-queue.js +20 -44
  458. package/dist/task-queue/types.d.ts +0 -1
  459. package/dist/task-queue/worker-status.d.ts +35 -0
  460. package/dist/task-queue/worker-status.js +40 -0
  461. package/dist/types.d.ts +40 -51
  462. package/dist/url-collision.d.ts +26 -40
  463. package/dist/url-collision.js +47 -63
  464. package/dist/url-exclusivity-fixtures.d.ts +15 -20
  465. package/dist/url-exclusivity-fixtures.js +14 -19
  466. package/dist/url-path-resolver.d.ts +8 -16
  467. package/dist/url-path-resolver.js +19 -29
  468. package/dist/user.d.ts +8 -21
  469. package/dist/user.js +10 -19
  470. package/dist/utils/async-mutex.js +4 -6
  471. package/dist/utils/atomic-write.d.ts +2 -11
  472. package/dist/utils/atomic-write.js +2 -11
  473. package/dist/utils/body-field.d.ts +8 -15
  474. package/dist/utils/body-field.js +8 -15
  475. package/dist/utils/content-serialize.d.ts +18 -24
  476. package/dist/utils/content-serialize.js +109 -160
  477. package/dist/utils/content-write-lock.d.ts +51 -106
  478. package/dist/utils/content-write-lock.js +66 -111
  479. package/dist/utils/debug.d.ts +7 -23
  480. package/dist/utils/debug.js +7 -23
  481. package/dist/utils/entry-url.d.ts +11 -23
  482. package/dist/utils/entry-url.js +11 -26
  483. package/dist/utils/error.d.ts +25 -90
  484. package/dist/utils/error.js +60 -147
  485. package/dist/utils/flatten-group-fields.d.ts +5 -7
  486. package/dist/utils/flatten-group-fields.js +5 -7
  487. package/dist/utils/format.d.ts +1 -5
  488. package/dist/utils/format.js +1 -5
  489. package/dist/utils/fs.d.ts +3 -4
  490. package/dist/utils/fs.js +3 -4
  491. package/dist/utils/git.d.ts +52 -83
  492. package/dist/utils/git.js +79 -125
  493. package/dist/utils/logger.d.ts +35 -50
  494. package/dist/utils/logger.js +35 -52
  495. package/dist/utils/provisioning-lock.d.ts +23 -35
  496. package/dist/utils/provisioning-lock.js +49 -69
  497. package/dist/utils/sanitize-href.d.ts +5 -10
  498. package/dist/utils/sanitize-href.js +8 -18
  499. package/dist/utils/title-field.d.ts +17 -20
  500. package/dist/utils/title-field.js +18 -27
  501. package/dist/utils/typed-filename.d.ts +20 -48
  502. package/dist/utils/typed-filename.js +25 -61
  503. package/dist/utils/url-prefix.d.ts +3 -3
  504. package/dist/utils/url-prefix.js +3 -3
  505. package/dist/validation/block-structural-keys.d.ts +16 -31
  506. package/dist/validation/block-structural-keys.js +19 -29
  507. package/dist/validation/deletion-checker.d.ts +0 -34
  508. package/dist/validation/deletion-checker.js +1 -43
  509. package/dist/validation/entry-link-validator.d.ts +6 -13
  510. package/dist/validation/entry-link-validator.js +4 -13
  511. package/dist/validation/entry-type-reference-validator.d.ts +13 -18
  512. package/dist/validation/entry-type-reference-validator.js +13 -18
  513. package/dist/validation/entry-validator.d.ts +13 -25
  514. package/dist/validation/entry-validator.js +15 -32
  515. package/dist/validation/field-traversal.d.ts +3 -30
  516. package/dist/validation/field-traversal.js +1 -32
  517. package/dist/validation/reference-validator.d.ts +5 -24
  518. package/dist/validation/reference-validator.js +5 -30
  519. package/dist/worker/cms-worker.d.ts +172 -232
  520. package/dist/worker/cms-worker.js +241 -333
  521. package/dist/worker/git-sync.d.ts +32 -133
  522. package/dist/worker/git-sync.js +129 -191
  523. package/dist/worker/github-auth.d.ts +13 -19
  524. package/dist/worker/github-auth.js +13 -19
  525. package/dist/worker/history-rewrite.d.ts +53 -66
  526. package/dist/worker/history-rewrite.js +54 -60
  527. package/dist/worker/log.d.ts +24 -41
  528. package/dist/worker/log.js +25 -42
  529. package/dist/worker/rebase.d.ts +20 -110
  530. package/dist/worker/rebase.js +279 -354
  531. package/dist/worker/task-runner.d.ts +32 -66
  532. package/dist/worker/task-runner.js +126 -162
  533. package/dist/worker/worker-context.d.ts +41 -81
  534. package/package.json +4 -4
  535. package/dist/cli/template-files/middleware-clerk.ts.template +0 -37
  536. package/dist/worker/task-queue-config.d.ts +0 -8
  537. package/dist/worker/task-queue.js +0 -19
  538. package/dist/worker/worker-status.d.ts +0 -47
  539. package/dist/worker/worker-status.js +0 -52
package/dist/services.js CHANGED
@@ -7,18 +7,15 @@ import { getDefaultBranchBase, sanitizeBranchName } from './paths/index.js';
7
7
  import { createGitHubService } from './github-service.js';
8
8
  import { operatingStrategy } from './operating-mode/index.js';
9
9
  import { BranchSchemaCache } from './branch-schema-cache.js';
10
- import { enqueueTask } from './worker/task-queue.js';
11
- import { getTaskQueueDir } from './worker/task-queue-config.js';
10
+ import { enqueueTask } from './task-queue/cms-task-queue.js';
11
+ import { getTaskQueueDir } from './task-queue/task-queue-config.js';
12
12
  import { detectHeadBranch } from './utils/git.js';
13
13
  import { readsFromCheckout } from './build-mode.js';
14
14
  /**
15
- * Create a per-instance active branch detector with its own 5-second TTL cache.
16
- *
17
- * Detection priority:
18
- * - If explicitly configured, use that value (both modes).
19
- * - Static deployments and builds read the checkout: defaultBaseBranch ?? 'main', no git.
20
- * - In dev mode, auto-detect from the current git HEAD branch.
21
- * - In prod mode, fall back to defaultBaseBranch ?? 'main'.
15
+ * A per-instance active-branch detector with its own 5s TTL cache, in priority
16
+ * order: an explicitly configured value; `defaultBaseBranch ?? 'main'` with no
17
+ * git for static deployments and builds; git HEAD in dev; and
18
+ * `defaultBaseBranch ?? 'main'` in prod.
22
19
  */
23
20
  function createActiveBranchDetector() {
24
21
  let cache = null;
@@ -33,10 +30,9 @@ function createActiveBranchDetector() {
33
30
  if (cache && now < cache.expiresAt) {
34
31
  return cache.value;
35
32
  }
36
- // Always use cwd for branch detection — git walks up to find .git.
37
- // sourceRoot is about content location, not the repo root.
38
- // On detached HEAD or no git repo, detectHeadBranch returns the
39
- // base-branch fallback instead of throwing.
33
+ // Detect from cwd, never sourceRoot: git walks up to find .git, and
34
+ // sourceRoot locates content, not the repo root. On a detached HEAD or
35
+ // outside a repo, detectHeadBranch falls back instead of throwing.
40
36
  const branch = await detectHeadBranch(process.cwd(), config.defaultBaseBranch ?? 'main');
41
37
  cache = { value: branch, expiresAt: now + 5000 };
42
38
  return branch;
@@ -45,8 +41,8 @@ function createActiveBranchDetector() {
45
41
  };
46
42
  }
47
43
  /**
48
- * Parse bootstrap admin IDs from environment variable.
49
- * These users are always treated as Admins regardless of group membership.
44
+ * Bootstrap admin IDs from the environment. These users are treated as Admins
45
+ * whatever their group membership.
50
46
  */
51
47
  export const getBootstrapAdminIds = () => {
52
48
  const envVar = process.env.CANOPY_BOOTSTRAP_ADMIN_IDS;
@@ -58,28 +54,17 @@ export const getBootstrapAdminIds = () => {
58
54
  .filter(Boolean));
59
55
  };
60
56
  /**
61
- * Create reusable helpers from a validated CanopyConfig.
62
- * Intended to be called once at startup and injected where needed
63
- * (e.g., request handlers, loaders).
64
- *
65
- * Schema is now loaded per-branch via BranchSchemaCache, not at startup.
66
- *
67
- * @param config - Validated Canopy configuration
68
- * @param options - Optional settings including entry schema registry
57
+ * Build the reusable helpers from a validated CanopyConfig — once at startup,
58
+ * then injected into request handlers and loaders. Schema is not loaded here;
59
+ * BranchSchemaCache loads it per branch.
69
60
  */
70
61
  export const createCanopyServices = async (config, options = {}) => {
71
62
  return _createCanopyServicesInternal(config, options);
72
63
  };
73
- /**
74
- * Create services for testing.
75
- * Schema is loaded per-branch via BranchSchemaCache.
76
- *
77
- * @param config - Validated Canopy configuration
78
- * @param options - Optional settings including entry schema registry
79
- */
64
+ /** {@link createCanopyServices} for tests, with branch identity pinned. */
80
65
  export const createTestCanopyServices = async (config, options = {}) => {
81
- // In tests, pin both branch identity fields to avoid auto-detecting from
82
- // git HEAD (which varies depending on the developer's working branch).
66
+ // Pin both branch identity fields rather than auto-detect from git HEAD,
67
+ // which varies with the developer's working branch.
83
68
  const testConfig = {
84
69
  ...config,
85
70
  defaultBaseBranch: config.defaultBaseBranch ?? 'main',
@@ -87,35 +72,28 @@ export const createTestCanopyServices = async (config, options = {}) => {
87
72
  };
88
73
  return _createCanopyServicesInternal(testConfig, options);
89
74
  };
90
- /**
91
- * Internal implementation shared by both production and test functions.
92
- * Not exported - use createCanopyServices() or createTestCanopyServices().
93
- */
75
+ /** Shared implementation; call createCanopyServices or createTestCanopyServices. */
94
76
  async function _createCanopyServicesInternal(config, options) {
95
- // Validate mode-specific requirements (e.g., prod requires git bot credentials for GitHub)
77
+ // Mode-specific requirements, e.g. prod's git bot credentials for GitHub.
96
78
  const strategy = operatingStrategy(config.mode);
97
79
  strategy.validateConfig(config);
98
- // Resolve branch identity once (see ARCHITECTURE.md "Branch Identity"):
99
- // - active branch: which workspace to serve from (dev: git HEAD, prod: base)
100
- // - base branch: fork point for new editing branches (dev: git HEAD when
101
- // unset, otherwise the configured value; prod: configured value or 'main')
102
- // Bake both into config so all downstream code reads one consistent value.
103
- // The detector has its own per-instance 5s TTL cache for git HEAD checks.
80
+ // Resolve branch identity once and bake both into config, so downstream code
81
+ // reads one consistent value (see ARCHITECTURE.md "Branch Identity"): the
82
+ // active branch is the workspace to serve from, the base branch the fork
83
+ // point for new editing branches.
104
84
  const detectActiveBranch = createActiveBranchDetector();
105
85
  const explicitActiveBranch = config.defaultActiveBranch;
106
86
  const explicitBaseBranch = config.defaultBaseBranch;
107
87
  const defaultActiveBranch = await detectActiveBranch(config);
108
- // When unset, the base branch follows the same dev-mode HEAD detection
109
- // (matching resolveBaseBranch in utils/git.ts, the canonical definition used
110
- // by workspace provisioning). Reuses the detector's 5s TTL cache.
88
+ // Unset, the base branch follows the same dev-mode HEAD detection, matching
89
+ // resolveBaseBranch in utils/git.ts — the canonical definition workspace
90
+ // provisioning uses.
111
91
  const defaultBaseBranch = explicitBaseBranch ??
112
92
  (config.mode === 'dev' && !readsFromCheckout(config)
113
93
  ? await detectActiveBranch({ ...config, defaultActiveBranch: undefined })
114
94
  : 'main');
115
95
  config = { ...config, defaultActiveBranch, defaultBaseBranch };
116
- // Load bootstrap admin IDs from environment
117
96
  const bootstrapAdminIds = getBootstrapAdminIds();
118
- // Create per-branch schema cache (or use provided one for testing)
119
97
  const branchSchemaCache = options.branchSchemaCache ?? new BranchSchemaCache(config.mode);
120
98
  const checkBranchAccess = createCheckBranchAccess(config.defaultBranchAccess ?? 'deny', config);
121
99
  // Content access loads permissions dynamically from the settings branch (orphan git branch)
@@ -124,8 +102,6 @@ async function _createCanopyServicesInternal(config, options) {
124
102
  const strategy = operatingStrategy(config.mode);
125
103
  const settingsRoot = strategy.getSettingsRoot();
126
104
  const branchName = strategy.getSettingsBranchName(config);
127
- // Use SettingsWorkspaceManager to ensure git workspace for settings
128
- // This is Lambda-safe because the lock is in-memory per process
129
105
  const manager = new SettingsWorkspaceManager(config);
130
106
  await manager.ensureGitWorkspace({
131
107
  settingsRoot,
@@ -182,14 +158,12 @@ async function _createCanopyServicesInternal(config, options) {
182
158
  });
183
159
  await git.checkoutBranch(options.context.branch.name);
184
160
  const status = await git.status();
185
- // Commit and push are gated on two DIFFERENT questions. Committing
186
- // cleans the working tree, so gating the push on "tree is dirty" (as a
187
- // single combined check) makes a retry after a failed push a silent
188
- // no-op: the earlier commit already cleaned the tree, so the retry sees
189
- // nothing to commit, skips the whole block, and reports success even
190
- // though the commit never reached the remote. Push instead whenever
191
- // there's something new to send -- we just committed, or the local
192
- // branch already had unpushed commits from an earlier failed attempt.
161
+ // Commit and push answer two DIFFERENT questions. Committing cleans the
162
+ // working tree, so one combined "tree is dirty" gate makes a retry after a
163
+ // failed push a silent no-op: nothing left to commit, the block is skipped,
164
+ // and success is reported though the commit never reached the remote. Push
165
+ // whenever there is something new to send — we just committed, or the local
166
+ // branch already had unpushed commits from an earlier attempt.
193
167
  let committed = false;
194
168
  if (status.files.length > 0) {
195
169
  await git.add('.');
@@ -200,7 +174,6 @@ async function _createCanopyServicesInternal(config, options) {
200
174
  await git.push(options.context.branch.name);
201
175
  }
202
176
  };
203
- // Create GitHub service if applicable (only for modes that support pull requests)
204
177
  // Must be initialized before closures that reference it (commitToSettingsBranch)
205
178
  let githubService;
206
179
  if (operatingStrategy(config.mode).supportsPullRequests()) {
@@ -220,7 +193,6 @@ async function _createCanopyServicesInternal(config, options) {
220
193
  }
221
194
  const commitToSettingsBranch = async (options) => {
222
195
  const mode = config.mode;
223
- // Check if this mode supports git operations
224
196
  if (!operatingStrategy(mode).shouldCommit()) {
225
197
  return { committed: false, pushed: false };
226
198
  }
@@ -228,9 +200,9 @@ async function _createCanopyServicesInternal(config, options) {
228
200
  // Settings workspace: no ContentStore roots here, skip the index marker
229
201
  const git = createGitManagerFor(options.branchRoot, { skipIndexMarker: true });
230
202
  try {
231
- // Pull latest changes from remote settings branch (not base branch!)
232
- // Settings branches are orphan branches and should never merge from main
233
- // Note: BranchWorkspaceManager already ensured we're on the settings branch
203
+ // Pull the remote SETTINGS branch, never the base branch: settings
204
+ // branches are orphans and must never merge from main.
205
+ // BranchWorkspaceManager has already put us on the settings branch.
234
206
  try {
235
207
  await git.pullCurrentBranch();
236
208
  }
@@ -239,17 +211,11 @@ async function _createCanopyServicesInternal(config, options) {
239
211
  // pushed, so the remote has no ref to pull. Anything else — a
240
212
  // GitConflictError, a merge that cannot proceed, a broken workspace —
241
213
  // must surface (the outer catch turns it into an error result).
242
- // A blanket catch here previously logged every failure as "normal for
243
- // first commit", which is how pullCurrentBranch could stay broken on
244
- // every call without anyone noticing: the settings branch then silently
245
- // never converged with the remote, and each save reported
246
- // `committed: true, pushed: false` from the push below, forever.
247
214
  if (!(err instanceof GitRemoteRefMissingError))
248
215
  throw err;
249
216
  console.info('CanopyCMS: settings branch has no remote ref yet, nothing to pull ' +
250
217
  '(normal for the first settings commit)');
251
218
  }
252
- // Commit
253
219
  await git.ensureAuthor({
254
220
  name: config.gitBotAuthorName,
255
221
  email: config.gitBotAuthorEmail,
@@ -269,16 +235,11 @@ async function _createCanopyServicesInternal(config, options) {
269
235
  }
270
236
  // Create or update PR — dual-path like content branches (api/github-sync.ts)
271
237
  if (options.createPR !== false) {
272
- // Both permissions and groups are read live from the settings
273
- // workspace (getSettingsBranchRoot) — never from this PR's base
274
- // branch — so the change already took effect the moment it was
275
- // committed and pushed above, before this PR even exists. Merging
276
- // does not (re-)activate anything; it only records the change on
277
- // `base` for review/audit history. Previously worded as "will be
278
- // persisted when this PR is merged", which implied merging was what
279
- // made the change durable/live — false for both settings files, and
280
- // for groups specifically the read side didn't even look at this
281
- // branch until that bug was fixed (see resolve-canopy-user.ts).
238
+ // Permissions and groups are read live from the settings workspace
239
+ // (getSettingsBranchRoot), never from this PR's base branch, so the
240
+ // change took effect when it was committed and pushed above, before
241
+ // this PR existed. Merging re-activates nothing; it only records the
242
+ // change on `base` for review and audit history.
282
243
  const settingsPRBody = 'Automated PR for permission and group changes. These changes already took ' +
283
244
  'effect in the CMS when they were saved — merging this PR does not change ' +
284
245
  "what's live; it only records the change here for review and audit history.";
@@ -286,10 +247,9 @@ async function _createCanopyServicesInternal(config, options) {
286
247
  if (githubService) {
287
248
  let prUrl;
288
249
  try {
289
- // Settings-branch PRs deliberately never pass markReadyIfDraft:
290
- // unlike content submits, a settings sync has no explicit "submit
291
- // for review" step, so an existing draft PR here should stay
292
- // draft until an admin is ready — don't "helpfully" enable this.
250
+ // Settings-branch PRs never pass markReadyIfDraft: a settings sync
251
+ // has no explicit "submit for review" step the way a content submit
252
+ // does, so an existing draft PR stays draft until an admin says so.
293
253
  const result = await githubService.createOrUpdatePR({
294
254
  head: settingsBranch,
295
255
  base: config.defaultBaseBranch ?? 'main',
@@ -335,8 +295,6 @@ async function _createCanopyServicesInternal(config, options) {
335
295
  };
336
296
  const operatingMode = config.mode;
337
297
  const modeStrategy = operatingStrategy(operatingMode);
338
- // Create branch registry only in branching modes
339
- // Settings are now in separate directory, no filtering needed
340
298
  const registry = modeStrategy.supportsBranching()
341
299
  ? new BranchRegistry(getDefaultBranchBase(operatingMode))
342
300
  : undefined;
@@ -361,16 +319,15 @@ async function _createCanopyServicesInternal(config, options) {
361
319
  // Static deployments and builds serve from the checkout — no git HEAD to track
362
320
  if (readsFromCheckout(services.config))
363
321
  return;
364
- // Explicitly configured values are respected — never overridden by
365
- // git HEAD detection. Only re-detect what the adopter left unset.
322
+ // An explicitly configured value is never overridden by HEAD detection;
323
+ // only what the adopter left unset is re-detected.
366
324
  if (explicitActiveBranch && explicitBaseBranch)
367
325
  return;
368
- // Re-detect from git HEAD (5s TTL cache prevents excessive shell-outs).
369
- // Silently switch — the public dev site should reflect the current branch
370
- // just like code hot-reloads. The editor is pinned to its own branch via
371
- // URL params, so this only affects non-editor content serving. The base
372
- // branch (fork point) follows HEAD too when unset, so workspaces
373
- // provisioned mid-session fork from the developer's current branch.
326
+ // The switch is silent, so the dev site tracks the current branch the way
327
+ // code hot-reloads; the editor is pinned to its own branch via URL params,
328
+ // so only non-editor content serving is affected. The base branch follows
329
+ // HEAD too when unset, so a workspace provisioned mid-session forks from
330
+ // the developer's current branch.
374
331
  const fresh = await detectActiveBranch({
375
332
  ...services.config,
376
333
  defaultActiveBranch: undefined,
@@ -386,11 +343,11 @@ async function _createCanopyServicesInternal(config, options) {
386
343
  changed = true;
387
344
  }
388
345
  if (changed) {
389
- // Note: closures in this function (getSettingsBranchRoot, checkContentAccess,
390
- // createGitManagerFor, etc.) capture the original `config` local variable.
391
- // Only the branch identity fields change here — git operations on existing
392
- // branches use the fork point recorded in branch metadata, and consumers
393
- // that need the fresh values must read services.config.
346
+ // The closures above (getSettingsBranchRoot, checkContentAccess,
347
+ // createGitManagerFor, …) captured the original `config` local. Only
348
+ // branch identity changes here: git operations on existing branches use
349
+ // the fork point in branch metadata, and anything needing the fresh
350
+ // values must read services.config.
394
351
  services.config = next;
395
352
  }
396
353
  },
@@ -2,24 +2,17 @@ import type { CanopyConfig } from './config/index.js';
2
2
  import type { OperatingMode } from './operating-mode/index.js';
3
3
  /**
4
4
  * Directory the cross-process init lock is anchored on — a dedicated sibling of
5
- * the settings root, and NOT the settings root itself.
5
+ * the settings root, NOT the settings root itself.
6
6
  *
7
- * Two properties this name has to satisfy:
8
- *
9
- * 1. **It must not pre-create the settings root.** `acquireProvisioningLock`
10
- * mkdir's its target, and `GitManager.initializeWorkspace` clones INTO the
11
- * settings root — `git clone` refuses a destination that already has content
12
- * in it. So the lock lives beside the settings root, never inside it.
13
- * 2. **Its marker must not collide with another lock's marker.** Since
14
- * 2026-08-20 `acquireProvisioningLock` anchors proper-lockfile on the lock
15
- * MARKER's own path rather than on the directory holding it, so its
16
- * in-process registry key is the on-disk lock identity and two live locks
17
- * can no longer clobber each other's bookkeeping (see docs/concurrency.md,
18
- * "Anchor path matters"). A dedicated dot-directory is still the right home
19
- * for this marker: `path.dirname(settingsRoot)` is where
20
- * `ensureLocalSimulatedRemote` puts `.remote-init.lock`, and settings init
21
- * calls into it while holding this lock, so keeping the two markers in
22
- * separate directories keeps that nesting obvious rather than incidental.
7
+ * It must not pre-create the settings root: `acquireProvisioningLock` mkdir's
8
+ * its target, and `GitManager.initializeWorkspace` clones INTO the settings
9
+ * root, which `git clone` refuses if anything is already there. A dedicated
10
+ * dot-directory also keeps this marker out of `path.dirname(settingsRoot)`,
11
+ * where `ensureLocalSimulatedRemote` puts `.remote-init.lock` — settings init
12
+ * calls into that while holding this lock, so separate directories make the
13
+ * nesting obvious rather than incidental. For how a lock's anchor path is
14
+ * chosen, see utils/provisioning-lock.ts and docs/concurrency.md.
15
+ * @internal Exported for tests.
23
16
  */
24
17
  export declare function settingsInitLockTarget(settingsRoot: string): string;
25
18
  export interface EnsureSettingsWorkspaceOptions {
@@ -29,20 +22,14 @@ export interface EnsureSettingsWorkspaceOptions {
29
22
  remoteUrl?: string;
30
23
  }
31
24
  /**
32
- * Manages settings filesystem workspace and git operations.
33
- *
34
- * Settings are stored separately from content branches:
35
- * - prod/dev: Orphan git branches (no shared history with content)
36
- *
37
- * Unlike BranchWorkspaceManager, this does not:
38
- * - Create or manage metadata files
39
- * - Interact with the branch registry
40
- * - Check for special cases (settings are always settings)
25
+ * The settings filesystem workspace and its git operations. Settings live on an
26
+ * orphan git branch, sharing no history with content, and unlike
27
+ * BranchWorkspaceManager this writes no metadata files and never touches the
28
+ * branch registry.
41
29
  *
42
- * Uses two layers of locking, mirroring BranchWorkspaceManager:
43
- * - In-memory Promise lock for within-process serialization (Lambda request lifecycle)
44
- * - `acquireProvisioningLock` (proper-lockfile) for cross-process/cross-host
45
- * synchronization of the init itself (multiple Lambda containers on EFS)
30
+ * Two locking layers, mirroring BranchWorkspaceManager: an in-memory promise
31
+ * lock within the process, and `acquireProvisioningLock` (proper-lockfile)
32
+ * across processes and hosts.
46
33
  */
47
34
  export declare class SettingsWorkspaceManager {
48
35
  private readonly config;
@@ -6,41 +6,34 @@ import { getErrorMessage } from './utils/error.js';
6
6
  import { acquireProvisioningLock } from './utils/provisioning-lock.js';
7
7
  import { RESERVED_SETTINGS_BRANCH_PREFIX } from './paths/index.js';
8
8
  const log = createDebugLogger({ prefix: 'SettingsWorkspace' });
9
- // In-memory lock to prevent concurrent workspace initialization within the same process.
10
- // Settings only need one lock (not per-branch like content branches).
9
+ // In-memory lock against concurrent init within one process. One lock suffices,
10
+ // unlike content branches, which need one per branch.
11
11
  let settingsInitLock = null;
12
12
  const SETTINGS_INIT_LOCK_DIR = '.settings-init';
13
13
  const SETTINGS_INIT_LOCK_NAME = 'lock';
14
14
  /**
15
15
  * Directory the cross-process init lock is anchored on — a dedicated sibling of
16
- * the settings root, and NOT the settings root itself.
16
+ * the settings root, NOT the settings root itself.
17
17
  *
18
- * Two properties this name has to satisfy:
19
- *
20
- * 1. **It must not pre-create the settings root.** `acquireProvisioningLock`
21
- * mkdir's its target, and `GitManager.initializeWorkspace` clones INTO the
22
- * settings root — `git clone` refuses a destination that already has content
23
- * in it. So the lock lives beside the settings root, never inside it.
24
- * 2. **Its marker must not collide with another lock's marker.** Since
25
- * 2026-08-20 `acquireProvisioningLock` anchors proper-lockfile on the lock
26
- * MARKER's own path rather than on the directory holding it, so its
27
- * in-process registry key is the on-disk lock identity and two live locks
28
- * can no longer clobber each other's bookkeeping (see docs/concurrency.md,
29
- * "Anchor path matters"). A dedicated dot-directory is still the right home
30
- * for this marker: `path.dirname(settingsRoot)` is where
31
- * `ensureLocalSimulatedRemote` puts `.remote-init.lock`, and settings init
32
- * calls into it while holding this lock, so keeping the two markers in
33
- * separate directories keeps that nesting obvious rather than incidental.
18
+ * It must not pre-create the settings root: `acquireProvisioningLock` mkdir's
19
+ * its target, and `GitManager.initializeWorkspace` clones INTO the settings
20
+ * root, which `git clone` refuses if anything is already there. A dedicated
21
+ * dot-directory also keeps this marker out of `path.dirname(settingsRoot)`,
22
+ * where `ensureLocalSimulatedRemote` puts `.remote-init.lock` — settings init
23
+ * calls into that while holding this lock, so separate directories make the
24
+ * nesting obvious rather than incidental. For how a lock's anchor path is
25
+ * chosen, see utils/provisioning-lock.ts and docs/concurrency.md.
26
+ * @internal Exported for tests.
34
27
  */
35
28
  export function settingsInitLockTarget(settingsRoot) {
36
29
  return path.join(path.dirname(path.resolve(settingsRoot)), SETTINGS_INIT_LOCK_DIR);
37
30
  }
38
31
  /**
39
- * Whether a settings workspace actually holds settings data yet. Used by the
40
- * rename guard to tell an already-populated workspace (refuse) apart from a
41
- * clone that was interrupted before its orphan branch was ever created
42
- * (harmless — let init finish). File names come from the operating-mode
43
- * strategy so this stays in step with whatever the mode calls them.
32
+ * Whether a settings workspace holds settings data yet. The rename guard uses
33
+ * it to tell an already-populated workspace (refuse) from a clone interrupted
34
+ * before its orphan branch existed (harmless — let init finish). File names
35
+ * come from the operating-mode strategy so this stays in step with whatever
36
+ * the mode calls them.
44
37
  */
45
38
  async function settingsFilesPresent(settingsRoot) {
46
39
  const names = ['permissions.json', 'groups.json'];
@@ -56,34 +49,28 @@ async function settingsFilesPresent(settingsRoot) {
56
49
  return false;
57
50
  }
58
51
  /**
59
- * Rename guard (settings-branch protection). Refuses to boot when an
52
+ * Rename guard (settings-branch protection): refuse to boot when an
60
53
  * ALREADY-POPULATED settings workspace would be re-orphaned under a different
61
54
  * branch name.
62
55
  *
63
- * Trace of what happens without it: GitManager.initializeWorkspace sees an
64
- * existing .git (no clone happens) and goes straight to
65
- * createOrphanSettingsBranch(branchName). If that name isn't already known, git
66
- * runs `checkout --orphan <name>` + `rm -rf .` + an empty `--allow-empty`
67
- * commit — orphan branches share no history, so this isn't a "migration", it
56
+ * Without it, GitManager.initializeWorkspace sees an existing .git, skips the
57
+ * clone, and calls createOrphanSettingsBranch(branchName); for an unknown name
58
+ * git then runs `checkout --orphan <name>` + `rm -rf .` + an empty commit.
59
+ * Orphan branches share no history, so that is not a migration — it
68
60
  * PERMANENTLY WIPES permissions.json/groups.json with nothing to recover from.
69
- * That almost always means deploymentName / settingsBranch /
70
- * CANOPYCMS_DEPLOYMENT_NAME changed on a live deployment (see
61
+ * The trigger is almost always deploymentName / settingsBranch /
62
+ * CANOPYCMS_DEPLOYMENT_NAME changing on a live deployment (see
71
63
  * resolveDeploymentName in operating-mode/deployment-name.ts).
72
64
  *
73
- * What this deliberately does NOT refuse, and why it matters:
74
- * initializeWorkspace clones at the BASE branch and only creates the orphan
75
- * branch several steps later (addConfig, resolveRemoteUrl, ensureRemote come in
76
- * between). If that first init is interrupted — a Lambda timeout on a slow EFS
77
- * clone, an OOM, a spot interruption — it leaves a perfectly valid repo sitting
78
- * on the base branch with NO settings files in it. Refusing on "branch differs"
79
- * alone would then brick the deployment permanently on every subsequent boot, to
80
- * protect data that was never written. So the refusal additionally requires
81
- * evidence that this really is a populated settings workspace: either it is
82
- * checked out on some OTHER settings branch, or the settings files are actually
83
- * on disk.
65
+ * The refusal therefore needs evidence of a populated workspace — checked out
66
+ * on some OTHER settings branch, or settings files on disk — not just a
67
+ * differing branch name. initializeWorkspace clones at the BASE branch and
68
+ * creates the orphan branch several steps later, so an interrupted first init
69
+ * (Lambda timeout on a slow EFS clone, OOM, spot interruption) leaves a valid
70
+ * repo on the base branch with no settings files, and refusing on name alone
71
+ * would brick that deployment on every boot to protect data never written.
84
72
  *
85
- * This function is NEVER gated on holding the init lock — see its two call
86
- * sites in ensureGitWorkspace and the comment there.
73
+ * NEVER gated on holding the init lock — see its two call sites below.
87
74
  */
88
75
  async function assertSettingsWorkspaceIdentity(options) {
89
76
  const repoExists = await GitManager.repoExistsAt(options.settingsRoot);
@@ -121,20 +108,14 @@ async function assertSettingsWorkspaceIdentity(options) {
121
108
  }
122
109
  }
123
110
  /**
124
- * Manages settings filesystem workspace and git operations.
125
- *
126
- * Settings are stored separately from content branches:
127
- * - prod/dev: Orphan git branches (no shared history with content)
128
- *
129
- * Unlike BranchWorkspaceManager, this does not:
130
- * - Create or manage metadata files
131
- * - Interact with the branch registry
132
- * - Check for special cases (settings are always settings)
111
+ * The settings filesystem workspace and its git operations. Settings live on an
112
+ * orphan git branch, sharing no history with content, and unlike
113
+ * BranchWorkspaceManager this writes no metadata files and never touches the
114
+ * branch registry.
133
115
  *
134
- * Uses two layers of locking, mirroring BranchWorkspaceManager:
135
- * - In-memory Promise lock for within-process serialization (Lambda request lifecycle)
136
- * - `acquireProvisioningLock` (proper-lockfile) for cross-process/cross-host
137
- * synchronization of the init itself (multiple Lambda containers on EFS)
116
+ * Two locking layers, mirroring BranchWorkspaceManager: an in-memory promise
117
+ * lock within the process, and `acquireProvisioningLock` (proper-lockfile)
118
+ * across processes and hosts.
138
119
  */
139
120
  export class SettingsWorkspaceManager {
140
121
  constructor(config) {
@@ -147,7 +128,6 @@ export class SettingsWorkspaceManager {
147
128
  await settingsInitLock;
148
129
  return;
149
130
  }
150
- // Create new in-memory lock promise
151
131
  settingsInitLock = (async () => {
152
132
  try {
153
133
  log.debug('workspace', 'Ensuring settings git workspace', {
@@ -156,34 +136,26 @@ export class SettingsWorkspaceManager {
156
136
  });
157
137
  // Rename guard, run LOCK-FREE and unconditionally, before any waiting.
158
138
  // A deployment whose settings-branch name no longer matches the
159
- // workspace on disk is misconfigured, not contended: it must refuse
160
- // immediately rather than queue behind a live provisioner for what can
161
- // be minutes. Running it here also keeps the guarantee the guard has
162
- // always had — no code path reaches initializeWorkspace without it
163
- // having run in THIS process.
139
+ // workspace on disk is misconfigured, not contended, so it must refuse
140
+ // immediately rather than queue behind a live provisioner for minutes.
141
+ // Running it here also means no path reaches initializeWorkspace
142
+ // without the guard having run in THIS process.
164
143
  await assertSettingsWorkspaceIdentity(options);
165
144
  // Layer 2: cross-process/cross-host lock around the init itself
166
145
  // (proper-lockfile: heartbeat-refreshed while the holder lives, so a
167
146
  // slow EFS clone is not mistaken for a crash, and patient retries so a
168
147
  // loser WAITS instead of racing into a concurrent clone).
169
- //
170
- // This replaced a bespoke O_CREAT|O_EXCL + 30s-mtime scheme whose
171
- // return value was only ever used to decide whether to release: the
172
- // loser proceeded into initializeWorkspace anyway, concurrently with
173
- // the holder, where it could see a half-written .git, classify it
174
- // corrupt, and `rm -rf` it out from under the in-flight clone.
175
148
  const releaseLock = await acquireProvisioningLock(settingsInitLockTarget(options.settingsRoot), SETTINGS_INIT_LOCK_NAME);
176
149
  try {
177
- // Re-run the guard on the now-stable state. If we waited above, the
178
- // previous holder may have created the workspace (or moved it onto
179
- // its own settings branch) after we sampled it, and acting on that
180
- // stale sample is exactly the destructive path the guard exists to
181
- // stop. Still not gated on any "did I win the race" flag — there is
182
- // no such flag by design; every process either holds the lock here
183
- // or has already thrown.
150
+ // Re-run the guard on the now-stable state: a previous holder may
151
+ // have created the workspace, or moved it onto its own settings
152
+ // branch, after we sampled it above, and acting on that stale
153
+ // sample is the destructive path the guard exists to stop. Not
154
+ // gated on any "did I win the race" flag — by design there is none;
155
+ // every process here either holds the lock or has already thrown.
184
156
  await assertSettingsWorkspaceIdentity(options);
185
- // GitManager.initializeWorkspace is idempotent (checks for .git),
186
- // so it's safe to call even if another process just finished init.
157
+ // initializeWorkspace is idempotent (it checks for .git), so this
158
+ // is safe even if another process just finished init.
187
159
  await GitManager.initializeWorkspace({
188
160
  workspacePath: options.settingsRoot,
189
161
  branchName: options.branchName,
@@ -209,11 +181,9 @@ export class SettingsWorkspaceManager {
209
181
  }
210
182
  }
211
183
  finally {
212
- // Always clean up the in-memory lock when done (success or failure)
213
184
  settingsInitLock = null;
214
185
  }
215
186
  })();
216
- // Wait for initialization to complete
217
187
  await settingsInitLock;
218
188
  });
219
189
  }