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/context.js CHANGED
@@ -13,58 +13,46 @@ function isLookupFailure(err) {
13
13
  return err.code === 'NOT_FOUND' || err.code === 'NO_SCHEMA_ITEM';
14
14
  }
15
15
  /**
16
- * True when a ContentStoreError should render as "not found" to page-level callers of
17
- * readByUrlPath rather than escape as a thrown error. FORBIDDEN is included so a
18
- * denied/anonymous read produces the adopter's ordinary `if (!result) return notFound()`
19
- * (404) instead of an unhandled 500 from the server component. The strict `read()` API is
20
- * unaffected and still throws.
16
+ * True when a ContentStoreError should render as "not found" to page-level
17
+ * callers of readByUrlPath rather than escape as a thrown error. FORBIDDEN is
18
+ * included so a denied or anonymous read reaches the adopter's ordinary
19
+ * `if (!result) return notFound()` (404) instead of an unhandled 500 from the
20
+ * server component. The strict `read()` API still throws.
21
21
  */
22
22
  function isPageSwallowable(err) {
23
23
  return isLookupFailure(err) || err.code === 'FORBIDDEN';
24
24
  }
25
25
  /**
26
- * Create a Canopy context that manages auth + content reading.
27
- * Framework-agnostic - the adapter provides the extractUser function.
26
+ * Create a Canopy context managing auth + content reading. Framework-agnostic:
27
+ * the adapter supplies extractUser.
28
28
  *
29
- * User extractor should apply bootstrap admin groups (via authResultToCanopyUser).
30
- *
31
- * NOTE: This function is synchronous because in practice, services are always
32
- * provided pre-created (async) by the framework adapter. The fallback path
33
- * that creates services from config cannot work correctly since createCanopyServices
34
- * is now async. Always pass services, not config.
29
+ * Synchronous, and takes pre-created `services` rather than config — building
30
+ * services is async, so there is no working fallback path from config here.
35
31
  */
36
32
  export function createCanopyContext(options) {
37
33
  const services = options.services;
38
- /**
39
- * Get the current user.
40
- * Returns STATIC_DEPLOY_USER for static deployments or during build, otherwise delegates to adapter.
41
- */
34
+ /** STATIC_DEPLOY_USER on a static deployment or during build, else the adapter's user. */
42
35
  const getUser = async () => {
43
- // Static deployment or build phase: no request context, use synthetic admin user
36
+ // No request context in either phase, so use the synthetic admin.
44
37
  if (isDeployedStatic(services.config) || isBuildMode()) {
45
38
  return STATIC_DEPLOY_USER;
46
39
  }
47
- // Runtime: delegate to adapter-provided user extractor
48
- // (adapter should use authResultToCanopyUser to apply bootstrap admins)
49
40
  return await options.extractUser();
50
41
  };
51
- /**
52
- * Get the context for the current request.
53
- * Call this in server components/routes to get auth-aware reader.
54
- */
42
+ /** The auth-aware context for this request; call it in server components and routes. */
55
43
  const getContext = async () => {
56
44
  // Dev mode follows the developer's git HEAD (no-op in prod/static or when
57
45
  // defaultActiveBranch is explicit). Same contract as the HTTP API handler —
58
46
  // switching branches mid-session updates what getCanopy() serves.
59
47
  await services.refreshActiveBranch();
60
48
  const user = await getUser();
61
- // Create base content reader
62
49
  const baseReader = createContentReader({ services });
63
- // Wrap reader to inject user automatically, validating strings → branded types at this
64
- // boundary. `extra` carries options that are NOT part of the public `read` surface -- today
65
- // just readByUrlPath's URL-addressability gate. Kept as a separate inner function rather than
66
- // an optional second parameter on the `CanopyContext['read']`-typed closure so the two call
67
- // sites stay visible and nobody widens the public API by accident.
50
+ // Injects the user and validates strings → branded types at this boundary.
51
+ // `extra` carries options that are NOT part of the public `read` surface —
52
+ // today just readByUrlPath's URL-addressability gate. A separate inner
53
+ // function rather than an optional second parameter on the
54
+ // `CanopyContext['read']`-typed closure, so the two call sites stay visible
55
+ // and nobody widens the public API by accident.
68
56
  const readWithOptions = async (input, extra) => {
69
57
  const entryPath = createLogicalPath(input.entryPath);
70
58
  let slug;
@@ -93,9 +81,9 @@ export function createCanopyContext(options) {
93
81
  return null;
94
82
  const { branch, resolveReferences } = options ?? {};
95
83
  for (const candidate of candidates) {
96
- // Skip candidates whose slug isn't a valid slug (e.g. URL paths like /favicon.ico or
97
- // Next internals that the [...slug] route catches). These can never match an entry, so
98
- // treat them as a miss rather than letting read() throw an "Invalid slug" error.
84
+ // Skip a candidate whose slug isn't valid (/favicon.ico, Next internals
85
+ // the [...slug] route catches). None can match an entry, so treat it as
86
+ // a miss rather than let read() throw "Invalid slug".
99
87
  if (!parseSlug(candidate.slug).ok)
100
88
  continue;
101
89
  try {
@@ -105,17 +93,17 @@ export function createCanopyContext(options) {
105
93
  branch,
106
94
  resolveReferences,
107
95
  },
108
- // This is a read BY PUBLISHED URL, so it must accept only what listEntries publishes.
109
- // See ReadContentInput.urlAddressableOnly for the two rules and why they live in the
110
- // reader (which holds the branch-correct schema) rather than in the candidate builder
111
- // (which is pure and schema-free by design).
96
+ // A read BY PUBLISHED URL accepts only what listEntries publishes.
97
+ // See ReadContentInput.urlAddressableOnly for the two rules, and
98
+ // why they live in the reader (which holds the branch-correct
99
+ // schema) rather than the candidate builder (pure and schema-free).
112
100
  { urlAddressableOnly: true });
113
101
  }
114
102
  catch (err) {
115
- // Swallow "not found" errors from trying candidate paths, and FORBIDDEN (a denied
116
- // or anonymous read renders as a 404 via the adopter's `if (!result) return
117
- // notFound()` rather than an unhandled 500). Re-throw real errors (validation,
118
- // corruption, non-ContentStoreError).
103
+ // Swallow a candidate path's "not found", and FORBIDDEN so a denied
104
+ // or anonymous read renders as the adopter's 404 rather than an
105
+ // unhandled 500. Real errors (validation, corruption, anything not a
106
+ // ContentStoreError) rethrow.
119
107
  if (err instanceof ContentStoreError && isPageSwallowable(err)) {
120
108
  if (err.code === 'FORBIDDEN') {
121
109
  log.debug('readByUrlPath', 'Read denied, treating as not-found: ' + err.message, {
@@ -155,41 +143,34 @@ export function createCanopyContext(options) {
155
143
  return schemaContextPromise;
156
144
  };
157
145
  /**
158
- * Path-ACL predicate for the batch reads (listEntries / buildContentTree). Memoized
159
- * per getContext call, like the schema context above.
160
- *
161
- * These two are the only content reads on this context that did NOT enforce path
162
- * permissions: `read`/`readByUrlPath` go through the content reader, which checks per
163
- * entry, while the listing primitives took no user at all. Since `CanopyContext` is the
164
- * request-scoped, ACL-enforcing context that page code is told to use, an unfiltered
165
- * listing there disclosed full entry `data` for paths the user cannot `read()` directly.
146
+ * Path-ACL predicate for the batch reads (listEntries / buildContentTree),
147
+ * memoized per getContext call like the schema context above. Without it an
148
+ * unfiltered listing on this request-scoped, ACL-enforcing context would
149
+ * disclose full entry `data` for paths the user cannot `read()` directly.
166
150
  *
167
- * `services.createContentAccessChecker` is the existing batch primitive (api/entries.ts
168
- * uses the same one): it resolves the request-constant work — branch access, the
169
- * settings/permissions root, and the rule set — exactly once, and returns a synchronous
170
- * per-path check. So the per-entry cost here is an admin short-circuit or a minimatch
171
- * per configured rule, with no additional I/O.
151
+ * `services.createContentAccessChecker` is the shared batch primitive
152
+ * (api/entries.ts uses it too): it resolves the request-constant work —
153
+ * branch access, the settings/permissions root, the rule set — once, and
154
+ * returns a synchronous per-path check, so the per-entry cost is an admin
155
+ * short-circuit or one minimatch per configured rule, with no extra I/O.
172
156
  *
173
- * Returns an empty object (no predicate → unfiltered, today's behavior) at build time,
174
- * on static deployments, and for the synthetic admin user. All three short-circuits are
175
- * load-bearing, not just an optimization: `createContentAccessChecker` grants that user
176
- * unconditional access (path checks bypass entirely for an Admins-group user, see
177
- * authorization/path.ts), so the predicate would always be a no-op — but building it
178
- * still costs a getSettingsBranchRoot() call, which in modes with a separate settings
179
- * branch (prod and dev) means provisioning/cloning that branch's git workspace. That is
180
- * an EFS round trip in prod, and it is exactly the unwanted cost for `createBuildCanopy`
181
- * (see build-canopy.ts): a standalone script's whole point is running outside a request
182
- * or Next.js build phase, so neither of the other two guards fires for it, and without
183
- * this one every such script paid for a settings-workspace clone it never needed and,
184
- * in an environment where that workspace cannot be provisioned, would hard-fail on.
157
+ * The empty object (no predicate → unfiltered) at build time, on static
158
+ * deployments, and for the synthetic admin is load-bearing, not an
159
+ * optimization. That user has unconditional access anyway (path checks
160
+ * bypass entirely for an Admins-group user — authorization/path.ts), but
161
+ * BUILDING the checker costs a getSettingsBranchRoot() call, which in modes
162
+ * with a separate settings branch provisions that branch's git workspace: an
163
+ * EFS round trip in prod. `createBuildCanopy` (build-canopy.ts) runs outside
164
+ * a request or Next.js build phase, so neither other guard fires for it, and
165
+ * without this one every such script pays for a settings-workspace clone it
166
+ * never needed — and hard-fails where that workspace cannot be provisioned.
185
167
  *
186
- * Compared by reference to the exported STATIC_DEPLOY_USER singleton (not by group
187
- * membership) so this stays scoped to the synthetic build/admin identity specifically —
188
- * a real authenticated admin hitting `getCanopy()` at request time still goes through
189
- * the real check, same as any other user.
168
+ * Compared by reference to the STATIC_DEPLOY_USER singleton, not by group
169
+ * membership, so it stays scoped to the synthetic build identity: a real
170
+ * authenticated admin at request time still goes through the real check.
190
171
  *
191
- * Deliberately NOT wrapped in a try/catch: createContentAccessChecker is fail-loud by
192
- * contract, and swallowing here would silently serve an unfiltered listing.
172
+ * Deliberately NOT wrapped in try/catch — createContentAccessChecker is
173
+ * fail-loud by contract, and swallowing would serve an unfiltered listing.
193
174
  */
194
175
  let visibilityPromise = null;
195
176
  const resolveVisibilityImpl = async () => {
@@ -3,26 +3,23 @@
3
3
  *
4
4
  * In dev, the editor and dev server read content from a branch clone under
5
5
  * `.canopy-dev/content-branches/<branch>/`, while the static build reads the working tree directly.
6
- * When a developer edits working-tree `content/**` outside the editor, the dev server silently serves
7
- * stale content until `canopycms sync push` runs. This watcher surfaces that divergence:
6
+ * A developer editing working-tree `content/**` outside the editor therefore gets stale content
7
+ * from the dev server until `canopycms sync push` runs. This watcher reports that divergence
8
+ * ('warn', the default: a warning naming the diverged files, at startup and on content changes) or
9
+ * does nothing ('off').
8
10
  *
9
- * - 'warn' (default): log a warning naming the diverged files (on startup and on content changes).
10
- * - 'off': do nothing.
11
+ * There is intentionally NO auto-push mode: overwriting the branch clone from the working tree
12
+ * would clobber uncommitted editor "Save" state with no Canopy-level recovery. Reconciliation goes
13
+ * through the interactive, conflict-aware `canopycms sync push` instead.
11
14
  *
12
- * There is intentionally NO auto-push mode: overwriting the branch clone from the working tree would
13
- * silently clobber uncommitted editor "Save" state with no Canopy-level recovery for the editor.
14
- * Reconciliation goes through the interactive `canopycms sync push` (conflict-aware) instead.
15
+ * Divergence is a CONDITION, not an event: it holds until someone runs `sync push`, so the report
16
+ * must not repeat into a scrolling request log (`reportOnce`) yet must be hard to scroll past (the
17
+ * gutter block). A log cannot hold a condition; see
18
+ * `.claude/future-tasks/dev-divergence-in-app-surface.md` for the in-app surface that would
19
+ * replace the terminal as its primary home.
15
20
  *
16
- * All logic lives here in the core package; framework adapters just call startDevContentWatcher() once
17
- * at dev startup. See packages/canopycms-next/src/context-wrapper.ts for the Next wiring.
18
- *
19
- * ## Divergence is a CONDITION, not an event
20
- *
21
- * It stays true until someone runs `sync push`, so the two things this module has to get right are
22
- * (a) not re-printing the same condition into a scrolling request log, and (b) making the one printing
23
- * of it hard to scroll past. Both are handled below by `reportOnce` + the gutter formatting; see
24
- * `.claude/future-tasks/dev-divergence-in-app-surface.md` for the in-app surface that would replace
25
- * the terminal as the primary home for this.
21
+ * All logic lives here; framework adapters call startDevContentWatcher() once at dev startup (see
22
+ * packages/canopycms-next/src/context-wrapper.ts for the Next wiring).
26
23
  */
27
24
  import type { CanopyServices } from './services.js';
28
25
  import type { DevContentSyncMode } from './config/types.js';
@@ -36,7 +33,10 @@ export interface StartDevContentWatcherOptions {
36
33
  /** Warning sink. Defaults to console.warn. */
37
34
  warn?: (message: string) => void;
38
35
  }
39
- /** Test-only: dispose every armed watcher and drop all cross-module dedupe state. */
36
+ /**
37
+ * Test-only: dispose every armed watcher and drop all cross-module dedupe state.
38
+ * @internal Exported for tests.
39
+ */
40
40
  export declare function resetDevContentWatchersForTests(): void;
41
41
  /**
42
42
  * Start watching the working-tree content directory for divergence from the served dev branch clone.
@@ -3,26 +3,23 @@
3
3
  *
4
4
  * In dev, the editor and dev server read content from a branch clone under
5
5
  * `.canopy-dev/content-branches/<branch>/`, while the static build reads the working tree directly.
6
- * When a developer edits working-tree `content/**` outside the editor, the dev server silently serves
7
- * stale content until `canopycms sync push` runs. This watcher surfaces that divergence:
6
+ * A developer editing working-tree `content/**` outside the editor therefore gets stale content
7
+ * from the dev server until `canopycms sync push` runs. This watcher reports that divergence
8
+ * ('warn', the default: a warning naming the diverged files, at startup and on content changes) or
9
+ * does nothing ('off').
8
10
  *
9
- * - 'warn' (default): log a warning naming the diverged files (on startup and on content changes).
10
- * - 'off': do nothing.
11
+ * There is intentionally NO auto-push mode: overwriting the branch clone from the working tree
12
+ * would clobber uncommitted editor "Save" state with no Canopy-level recovery. Reconciliation goes
13
+ * through the interactive, conflict-aware `canopycms sync push` instead.
11
14
  *
12
- * There is intentionally NO auto-push mode: overwriting the branch clone from the working tree would
13
- * silently clobber uncommitted editor "Save" state with no Canopy-level recovery for the editor.
14
- * Reconciliation goes through the interactive `canopycms sync push` (conflict-aware) instead.
15
+ * Divergence is a CONDITION, not an event: it holds until someone runs `sync push`, so the report
16
+ * must not repeat into a scrolling request log (`reportOnce`) yet must be hard to scroll past (the
17
+ * gutter block). A log cannot hold a condition; see
18
+ * `.claude/future-tasks/dev-divergence-in-app-surface.md` for the in-app surface that would
19
+ * replace the terminal as its primary home.
15
20
  *
16
- * All logic lives here in the core package; framework adapters just call startDevContentWatcher() once
17
- * at dev startup. See packages/canopycms-next/src/context-wrapper.ts for the Next wiring.
18
- *
19
- * ## Divergence is a CONDITION, not an event
20
- *
21
- * It stays true until someone runs `sync push`, so the two things this module has to get right are
22
- * (a) not re-printing the same condition into a scrolling request log, and (b) making the one printing
23
- * of it hard to scroll past. Both are handled below by `reportOnce` + the gutter formatting; see
24
- * `.claude/future-tasks/dev-divergence-in-app-surface.md` for the in-app surface that would replace
25
- * the terminal as the primary home for this.
21
+ * All logic lives here; framework adapters call startDevContentWatcher() once at dev startup (see
22
+ * packages/canopycms-next/src/context-wrapper.ts for the Next wiring).
26
23
  */
27
24
  import fsSync from 'node:fs';
28
25
  import path from 'node:path';
@@ -33,18 +30,16 @@ import { getErrorMessage } from './utils/error.js';
33
30
  import { diffContentTrees, isContentTreeDiffEmpty } from './sync-core.js';
34
31
  /**
35
32
  * Per-category cap on listed files. Deliberately small: the block is a prompt to run `sync push`,
36
- * not a diff viewer, and a 30-line wall of paths is exactly as easy to scroll past as a 1-line
37
- * warning. Each list carries its true count, so nothing is hidden -- only elided.
33
+ * not a diff viewer, and a wall of paths is as easy to scroll past as a one-line warning. Each
34
+ * list carries its true count, so nothing is hidden -- only elided.
38
35
  */
39
36
  const MAX_LISTED_FILES = 5;
40
37
  /**
41
- * The registry lives on `globalThis`, NOT in module scope.
42
- *
43
- * Next's dev server compiles the server graph per route bundle and evaluates each copy in its own
44
- * module scope, so a module-level `Map` is re-created empty per bundle -- every copy then believes it
45
- * is the first watcher, arms its own, and re-prints the startup warning. (The duplicated
46
- * "CanopyCMS dev-auth: Auto-configured ..." line in a dev log is the same effect on a different
47
- * module-level latch.) A `globalThis` property is the one thing shared across those evaluations.
38
+ * The registry lives on `globalThis`, NOT in module scope: Next's dev server compiles the server
39
+ * graph per route bundle and evaluates each copy in its own module scope, so a module-level `Map`
40
+ * is re-created empty per bundle -- every copy then believes it is the first watcher, arms its own,
41
+ * and re-prints the startup warning. A `globalThis` property is the one thing shared across those
42
+ * evaluations.
48
43
  */
49
44
  const REGISTRY_KEY = '__canopycmsDevContentWatchers__';
50
45
  function watcherRegistry() {
@@ -56,7 +51,10 @@ function watcherRegistry() {
56
51
  host[REGISTRY_KEY] = created;
57
52
  return created;
58
53
  }
59
- /** Test-only: dispose every armed watcher and drop all cross-module dedupe state. */
54
+ /**
55
+ * Test-only: dispose every armed watcher and drop all cross-module dedupe state.
56
+ * @internal Exported for tests.
57
+ */
60
58
  export function resetDevContentWatchersForTests() {
61
59
  const registry = watcherRegistry();
62
60
  for (const state of registry.values())
@@ -64,9 +62,9 @@ export function resetDevContentWatchersForTests() {
64
62
  registry.clear();
65
63
  }
66
64
  /**
67
- * One body line of the notice block. The left gutter (rather than a full box) is deliberate: content
68
- * paths are long and unpredictable, and a right border would force either wrapping math or truncation
69
- * of the very filenames the block exists to name.
65
+ * One body line of the notice block. A left gutter rather than a full box: content paths are long
66
+ * and unpredictable, and a right border would force either wrapping math or truncation of the very
67
+ * filenames the block exists to name.
70
68
  */
71
69
  function body(line) {
72
70
  const gutter = pc.yellow('|');
@@ -84,8 +82,8 @@ function formatList(label, files) {
84
82
  }
85
83
  function formatDivergenceWarning(branch, diff) {
86
84
  const total = diff.changed.length + diff.added.length + diff.removed.length;
87
- // Blank lines top and bottom: the block's job is to be findable in a scrolling request log, and
88
- // whitespace separation does more for that than any amount of in-block decoration.
85
+ // Blank lines top and bottom: whitespace separation does more to make this block findable in a
86
+ // scrolling request log than any amount of in-block decoration.
89
87
  return [
90
88
  '',
91
89
  `${pc.bgYellow(pc.black(' CanopyCMS '))} ${pc.bold(pc.yellow('working-tree content has diverged'))}`,
@@ -121,17 +119,16 @@ export function startDevContentWatcher(services, options = {}) {
121
119
  const sourceRoot = options.sourceRoot ?? services.config.sourceRoot ?? process.cwd();
122
120
  const contentRoot = services.config.contentRoot || 'content';
123
121
  const strategy = operatingStrategy('dev');
124
- // Must pass the resolved contentRoot local above -- getContentRoot has no
125
- // default of its own (by design: a default here is what let this watcher
126
- // silently no-op for a non-default contentRoot before this fix).
122
+ // Pass the resolved contentRoot local above: getContentRoot has no default of its own, by
123
+ // design -- a default here silently no-ops this watcher for a non-default contentRoot.
127
124
  const workingTreeContentDir = strategy.getContentRoot(contentRoot, sourceRoot);
128
125
  const warn = options.warn ?? ((message) => console.warn(message));
129
126
  // Nothing to watch if the working tree has no content directory (e.g. unit-test configs).
130
127
  if (!fsSync.existsSync(workingTreeContentDir))
131
128
  return noop;
132
- // Resolve the branch fresh each check: the dev server silently follows git-HEAD switches
129
+ // Resolve the branch fresh each check: the dev server follows git-HEAD switches
133
130
  // (services.refreshActiveBranch reassigns config.defaultActiveBranch), so the comparison target
134
- // must track whatever branch is currently being served. An explicit options.branch pins it.
131
+ // must track whatever branch is currently served. An explicit options.branch pins it.
135
132
  const resolveBranch = () => options.branch ??
136
133
  services.config.defaultActiveBranch ??
137
134
  services.config.defaultBaseBranch ??
@@ -147,9 +144,9 @@ export function startDevContentWatcher(services, options = {}) {
147
144
  // ENTRY deliberately survives, carrying lastMessage/reportedDivergence into the new watcher.
148
145
  watcherState.dispose?.();
149
146
  /**
150
- * Emit `message` unless it is verbatim what was emitted last for this directory. Covers the
151
- * divergence block, the resolved notice and the error paths alike: in every case a repeat means
152
- * "the condition is unchanged", which the reader already knows.
147
+ * Emit `message` unless it is verbatim what was emitted last for this directory -- divergence
148
+ * block, resolved notice and error paths alike, since in every case a repeat means "the
149
+ * condition is unchanged", which the reader already knows.
153
150
  */
154
151
  const reportOnce = (message) => {
155
152
  if (watcherState.lastMessage === message)
@@ -174,8 +171,8 @@ export function startDevContentWatcher(services, options = {}) {
174
171
  return;
175
172
  const diff = await diffContentTrees(workingTreeContentDir, branchContentDir);
176
173
  if (isContentTreeDiffEmpty(diff)) {
177
- // Close the loop: a condition that was announced needs its retraction announced too, or the
178
- // reader is left believing the dev server is still serving stale content.
174
+ // An announced condition needs its retraction announced too, or the reader is left
175
+ // believing the dev server is still serving stale content.
179
176
  if (watcherState.reportedDivergence) {
180
177
  watcherState.reportedDivergence = false;
181
178
  reportOnce(formatResolvedNotice(branch));
@@ -201,9 +198,9 @@ export function startDevContentWatcher(services, options = {}) {
201
198
  watcher.on('add', () => void check());
202
199
  watcher.on('change', () => void check());
203
200
  watcher.on('unlink', () => void check());
204
- // Handle watcher errors (e.g. inotify ENOSPC/EMFILE) so an unhandled 'error' event can't crash dev.
201
+ // Watcher errors (e.g. inotify ENOSPC/EMFILE) must be handled, or an unhandled 'error' event
202
+ // crashes dev.
205
203
  watcher.on('error', (err) => reportOnce(`CanopyCMS: dev content-sync watcher error: ${getErrorMessage(err)}`));
206
- // Initial divergence check at startup.
207
204
  void check();
208
205
  const dispose = () => {
209
206
  void watcher.close();
@@ -3,6 +3,7 @@ import type { OperatingMode } from '../operating-mode/index.js';
3
3
  import type { ConflictStatus, PullRequestState, SyncStatus } from '../types.js';
4
4
  import type { CommentThread } from '../comment-store.js';
5
5
  import type { UserSearchResult } from '../auth/types.js';
6
+ /** @internal Exported for tests. */
6
7
  export interface BranchSummary {
7
8
  name: string;
8
9
  status: string;
@@ -32,12 +33,7 @@ export interface BranchSummary {
32
33
  * status) -- see `useBranchManager.tsx`'s `BranchSummary.submitBlocked` and
33
34
  * `BranchWriteProtection.submitBlockedIncludingStatus` for the full
34
35
  * rationale. `getBranchPermissions`'s `canSubmit` consumes this directly
35
- * instead of re-deriving `status === 'editing' && !isProtected` --
36
- * PR #189's stated invariant is "consume the server flag, don't
37
- * re-derive", and shipping only the base-branch-only `submitBlocked` flag
38
- * (rejected in #205, since that's the same value as `isProtected` and
39
- * would still have left the status half re-derived here) would not have
40
- * removed the re-derivation this replaces.
36
+ * instead of re-deriving `status === 'editing' && !isProtected`.
41
37
  */
42
38
  submitBlocked?: boolean;
43
39
  }
@@ -52,6 +48,7 @@ export interface UserContext {
52
48
  * - Users in branch ACL can submit/withdraw
53
49
  * - System branches can be submitted/withdrawn by anyone with access
54
50
  * - Admins/Reviewers always have access
51
+ * @internal Exported for tests.
55
52
  */
56
53
  export declare const getBranchPermissions: (branch: BranchSummary, user: UserContext | undefined) => {
57
54
  canSubmit: boolean;
@@ -15,6 +15,7 @@ import { formatRelativeTime } from './relative-time.js';
15
15
  * - Users in branch ACL can submit/withdraw
16
16
  * - System branches can be submitted/withdrawn by anyone with access
17
17
  * - Admins/Reviewers always have access
18
+ * @internal Exported for tests.
18
19
  */
19
20
  export const getBranchPermissions = (branch, user) => {
20
21
  if (!user) {
@@ -32,44 +33,30 @@ export const getBranchPermissions = (branch, user) => {
32
33
  // auto-provision marker (createdBy: 'canopycms-system') would otherwise let
33
34
  // anyone with general access submit/withdraw/delete it.
34
35
  const isSystemBranch = branch.createdBy === 'canopycms-system' && !branch.isProtected;
35
- // Check if user is in branch ACL
36
36
  const userInACL = (branch.access?.users?.includes(user.userId) ||
37
37
  user.groups?.some((g) => branch.access?.groups?.includes(g))) ??
38
38
  false;
39
- // Can perform workflow actions if: creator OR in ACL OR (system branch AND has basic access) OR privileged
40
39
  const canPerformWorkflowActions = userIsCreator || userInACL || isSystemBranch || userIsAdmin || userIsReviewer;
41
40
  // Submit: can perform workflow actions AND the server says submit isn't
42
41
  // blocked. `submitBlocked` is the server's own compound answer -- status
43
42
  // past 'editing' OR the protected base branch -- computed once in
44
43
  // `getBranchWriteProtection` (protected-branch.ts) and threaded through
45
- // unchanged; this used to re-derive the same conjunction locally
46
- // (`branch.status === 'editing' && !branch.isProtected`), which is exactly
47
- // the drift hazard PR #189's "consume the server flag, don't re-derive"
48
- // invariant exists to prevent. See `BranchSummary.submitBlocked`'s doc
49
- // comment above for why a bare `submitBlocked` flag (the base-branch-only
50
- // meaning, as rejected in #205) would not have been enough on its own.
44
+ // unchanged.
51
45
  //
52
- // `?? true` and not a bare `!branch.submitBlocked`: the field is optional on
53
- // this type (as `isProtected`/`readOnly` beside it are), so a missing value
54
- // must mean BLOCKED, not allowed. Dropping the default would reintroduce, one
55
- // layer below, exactly the fail-open shape this change set exists to remove --
56
- // `useBranchManager.tsx` already defaults the same flag to `true` when the
57
- // wire omits it, and a second, contradictory default here would quietly undo
58
- // that for anything constructing a BranchSummary directly.
46
+ // `?? true`, not a bare `!branch.submitBlocked`: the field is optional (like
47
+ // `isProtected`/`readOnly` beside it), so a missing value must mean BLOCKED,
48
+ // not allowed -- matching the `?? true` default `useBranchManager.tsx`
49
+ // already applies to the same flag. A `?? false` default here would
50
+ // fail-open for anything constructing a BranchSummary directly.
59
51
  const canSubmit = canPerformWorkflowActions && !(branch.submitBlocked ?? true);
60
- // Withdraw: Can perform workflow actions AND branch is submitted or approved.
61
- // Allowed even when the PR was closed without merging -- that's the
62
- // deliberate recovery path for a closed-unmerged PR (a later resubmit
63
- // opens a fresh PR). The server skips the now-impossible draft conversion
64
- // in that case; see api/branch-withdraw.ts. Not additionally gated on
65
- // !branch.isProtected: withdraw is the recovery path for a protected branch
66
- // wrongly stuck in 'submitted' -- creator/ACL/privileged users can still
67
- // reach it (the system-branch grant above already excludes them).
68
- //
69
- // 'approved' is included because withdraw is that status's ONLY
70
- // non-destructive exit (request-changes still requires 'submitted', and the
71
- // submit status gate now refuses a non-editing branch). Surfacing it here is
72
- // what makes the exit reachable -- the server accepting it is not enough.
52
+ // Withdraw: can perform workflow actions AND branch is submitted or
53
+ // approved -- 'approved' is included because withdraw is that status's only
54
+ // non-destructive exit, and getBranchPermissions must grant canWithdraw for
55
+ // it to be reachable. Allowed even when the PR was closed without merging:
56
+ // that's the deliberate recovery path for a closed-unmerged PR (server-side
57
+ // skip in api/branch-withdraw.ts). Not additionally gated on
58
+ // !branch.isProtected either: withdraw is also how a creator/ACL/privileged
59
+ // user recovers a protected branch wrongly stuck in 'submitted'.
73
60
  const canWithdraw = canPerformWorkflowActions && (branch.status === 'submitted' || branch.status === 'approved');
74
61
  // Delete: Admin or creator (but not if submitted or approved -- both mean
75
62
  // an open PR, the latter already reviewed and awaiting merge -- and never
@@ -125,28 +112,21 @@ export const BranchManager = ({ branches, mode, user, onSelect, onCreate, onDele
125
112
  cursor: 'pointer',
126
113
  }, children: "View PR" })] }))] }), b.access && (_jsxs(Group, { gap: 6, children: [b.access.users?.map((u) => onGetUserMetadata ? (_jsx(UserBadge, { userId: u, getUserMetadata: onGetUserMetadata, variant: "avatar-only", size: "xs", showEmailTooltip: true }, u)) : (_jsx(Badge, { variant: "outline", color: "neutral", children: u }, u))), b.access.groups?.map((g) => (_jsx(Badge, { variant: "outline", color: "neutral", children: g }, g)))] }))] }), _jsxs(Group, { gap: 8, children: [_jsx(Button, { size: "xs", variant: "light", onClick: () => onSelect?.(b.name), "data-testid": `switch-to-branch-button-${b.name}`, children: "Open" }), b.status === 'submitted' || b.status === 'approved' ? (_jsx(Tooltip, { label: "Only the branch creator can withdraw", disabled: perms.canWithdraw, children: _jsx(Button, { size: "xs", variant: "light", color: "orange", onClick: () => onWithdraw?.(b.name), disabled: !perms.canWithdraw, "data-testid": `withdraw-branch-button-${b.name}`, children: "Withdraw" }) })) : (_jsx(Tooltip, { label: b.isProtected
127
114
  ? 'The base branch cannot be submitted'
128
- : // A status arm, because canSubmit now folds in
129
- // the server's status rule as well as the
130
- // base-branch one. Without it, an archived (or
131
- // any non-editing) branch showed a disabled
132
- // Submit reading "Only the branch creator can
133
- // submit" -- to the creator. That is precisely
134
- // the permission-vs-no-available-transition
135
- // confusion #205 removed from EditorHeader, and
136
- // folding status into canSubmit reintroduced it
137
- // one component over.
115
+ : // A status arm: canSubmit folds in the server's status rule as
116
+ // well as the base-branch one, so an archived (or any
117
+ // non-editing) branch reads its own reason instead of the
118
+ // generic "Only the branch creator can submit".
138
119
  b.status !== 'editing'
139
120
  ? `A branch with status "${b.status}" cannot be submitted`
140
121
  : 'Only the branch creator can submit', disabled: perms.canSubmit, children: _jsx(Button, { size: "xs", variant: "light", color: "green", onClick: () => onSubmit?.(b.name), disabled: !perms.canSubmit, "data-testid": `submit-branch-button-${b.name}`, children: "Submit" }) })), _jsx(Tooltip, { label: b.pullRequestState === 'closed'
141
122
  ? 'PR was closed on GitHub without merging'
142
123
  : 'Only Reviewers or Admins can request changes', disabled: perms.canRequestChanges, children: _jsx(Button, { size: "xs", variant: "outline", color: "neutral", onClick: () => onRequestChanges?.(b.name), disabled: !perms.canRequestChanges, "data-testid": `request-changes-branch-button-${b.name}`, children: "Request changes" }) }), _jsx(Tooltip, { label: b.isProtected
143
124
  ? 'The base branch cannot be deleted'
144
- : // A status arm, mirroring the Submit tooltip below: without
145
- // it, a branch blocked by its own status (open PR, reviewed
146
- // or not) showed "Only Admin or branch creator can delete" --
147
- // to the very admin/creator being blamed for a permission they
148
- // do have. 'approved' is included alongside 'submitted' since
149
- // both mean an open PR is blocking deletion.
125
+ : // A status arm, mirroring the Submit tooltip above: without it,
126
+ // a branch blocked by its own status reads the generic "Only
127
+ // Admin or branch creator can delete" instead of naming the open
128
+ // PR. 'approved' is included alongside 'submitted' since both
129
+ // mean an open PR is blocking deletion.
150
130
  b.status === 'submitted' || b.status === 'approved'
151
131
  ? `Cannot delete branch with open PR (status "${b.status}")`
152
132
  : 'Only Admin or branch creator can delete', disabled: perms.canDelete, children: _jsx(Button, { size: "xs", variant: "outline", color: "red", onClick: () => onDelete?.(b.name), disabled: !perms.canDelete, "data-testid": `delete-branch-button-${b.name}`, children: "Delete" }) })] })] }) }, b.name));
@@ -2,7 +2,8 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useMemo } from 'react';
4
4
  import { Editor } from './Editor.js';
5
- import { ApiClientProvider, SWRProvider } from './context/index.js';
5
+ import { ApiClientProvider } from './context/index.js';
6
+ import { EditorAuthGate } from './EditorAuthGate.js';
6
7
  import { buildEditorCollections, buildPreviewBaseByCollection } from './editor-config.js';
7
8
  export const CanopyEditor = ({ config, entries = [], initialSelectedId, initialValues, renderPreview, onCreateEntry, branchName, customRenderers, }) => {
8
9
  const collections = useMemo(() => buildEditorCollections(config.flatSchema), [config.flatSchema]);
@@ -16,6 +17,8 @@ export const CanopyEditor = ({ config, entries = [], initialSelectedId, initialV
16
17
  const resolvedTitle = config.editor?.title ?? 'CanopyCMS Editor';
17
18
  const resolvedSubtitle = config.editor?.subtitle;
18
19
  const resolvedTheme = config.editor?.theme ?? undefined;
19
- return (_jsx(SWRProvider, { children: _jsx(ApiClientProvider, { basePath: config.basePath, children: _jsx(Editor, { entries: entries, title: resolvedTitle, subtitle: resolvedSubtitle, branchName: resolvedBranchName, operatingMode: config.mode, initialSelectedId: initialSelectedId, initialValues: initialValues, renderPreview: renderPreview, onCreateEntry: onCreateEntry, customRenderers: customRenderers, collections: collections, contentRoot: config.contentRoot, entryLinkUrl: config.entryLinkUrl, previewBaseByCollection: previewBase, assetBaseUrl: config.assetBaseUrl, basePath: config.basePath, themeOptions: resolvedTheme, AccountComponent: config.editor?.AccountComponent, onAccountClick: config.editor?.onAccountClick, onLogoutClick: config.editor?.onLogoutClick }) }) }));
20
+ // The gate owns the SWRProvider (keyed by user id), so it sits between the API client and the
21
+ // editor: see EditorAuthGate for what a signed-out or lapsed session renders.
22
+ return (_jsx(ApiClientProvider, { basePath: config.basePath, children: _jsx(EditorAuthGate, { SignInComponent: config.editor?.SignInComponent, themeOptions: resolvedTheme, children: _jsx(Editor, { entries: entries, title: resolvedTitle, subtitle: resolvedSubtitle, branchName: resolvedBranchName, operatingMode: config.mode, initialSelectedId: initialSelectedId, initialValues: initialValues, renderPreview: renderPreview, onCreateEntry: onCreateEntry, customRenderers: customRenderers, collections: collections, contentRoot: config.contentRoot, entryLinkUrl: config.entryLinkUrl, previewBaseByCollection: previewBase, assetBaseUrl: config.assetBaseUrl, basePath: config.basePath, themeOptions: resolvedTheme, AccountComponent: config.editor?.AccountComponent, onAccountClick: config.editor?.onAccountClick, onLogoutClick: config.editor?.onLogoutClick }) }) }));
20
23
  };
21
24
  export default CanopyEditor;
@@ -16,14 +16,12 @@ export const CommentsPanel = ({ branchName, comments, canResolve, onAddComment,
16
16
  setIsSubmitting(true);
17
17
  try {
18
18
  if (replyTo) {
19
- // Reply to existing thread - get context from the thread
20
19
  const thread = comments.find((t) => t.id === replyTo);
21
20
  if (thread) {
22
21
  await onAddComment(newCommentText, thread.type, thread.entryPath, thread.canopyPath, replyTo);
23
22
  }
24
23
  }
25
24
  else {
26
- // New branch-level comment (default for panel)
27
25
  await onAddComment(newCommentText, 'branch', undefined, undefined, undefined);
28
26
  }
29
27
  setNewCommentText('');