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
@@ -164,13 +164,13 @@ export interface EntryWithUnknownKeys {
164
164
  *
165
165
  * The inverse of `findInvalidEntries`, and non-fatal by design: an unknown key is stale data,
166
166
  * not broken data. A build must not go red for it — the page still renders, it is just quietly
167
- * missing whatever the renamed field used to supply. `warnUnknownEntryKeys` is the reporting
167
+ * missing whatever value the renamed field would supply. `warnUnknownEntryKeys` is the reporting
168
168
  * half. Both sit at module scope alongside `findInvalidEntries`/`assertBuildEntriesValid` and
169
169
  * are deliberately NOT on `canopycms/server`, matching those two — the build wires them itself.
170
170
  *
171
171
  * Skips items with no resolved schema, and items whose schema is empty — "no schema" is not
172
172
  * "every key is unknown". Same `normalizeDatesDeep` pass as the validity scan, so a hand-authored
173
- * `date: 2024-01-15` is a plain value here too.
173
+ * `date: YYYY-MM-DD` is a plain value here too.
174
174
  */
175
175
  export declare function findEntriesWithUnknownKeys(items: readonly BuildScanItem[]): EntryWithUnknownKeys[];
176
176
  /**
@@ -203,28 +203,18 @@ type UrlScanItem = Pick<ListEntriesItem, 'entryPath' | 'urlPath'>;
203
203
  /**
204
204
  * Find every `urlPath` claimed by two or more entries.
205
205
  *
206
- * `listEntries` assigns each entry exactly one `urlPath` and documents it as round-trip safe with
207
- * `readByUrlPath`. That guarantee is per-entry, not per-URL: nothing stops two DIFFERENT entries
208
- * computing the same one, and when they do exactly one of them is reachable while the other
209
- * silently has no route at all. The known ways to get there:
206
+ * `listEntries` gives each entry one `urlPath`, documented round-trip safe with `readByUrlPath`
207
+ * — but that guarantee is per-entry, not per-URL: two different entries can compute the same
208
+ * one, leaving exactly one reachable and the other silently unrouted. Known causes: an entry
209
+ * beside a sibling collection that also has an `index` entry (collapses onto the same path —
210
+ * the write boundary now refuses authoring this too, but merge/PR/retrofit content bypasses it;
211
+ * a sibling with NO index entry is a different, uncontested shape); slugs differing only by case
212
+ * (`urlPath` is lowercased); or two entries sharing a slug in one collection (also
213
+ * write-boundary-refused via `ContentStore.buildPaths`, same bypass routes).
210
214
  *
211
- * - An entry whose slug matches a sibling collection that also has an `index` entry — the index
212
- * collapses onto the collection's path, which is the entry's path too. The write boundary now
213
- * refuses to author this too (url-collision.ts), so reaching it means the content arrived some
214
- * other way — the same merge/PR/retrofit routes as the same-slug case below. (An entry beside a
215
- * sibling collection with NO index entry is a different, legitimate shape: a landing page plus
216
- * a folder of children, nothing contested.)
217
- * - Two entries whose slugs differ only by case, since `urlPath` is lowercased.
218
- * - Two entries with the same slug in one collection. The write boundary already refuses this
219
- * (`ContentStore.buildPaths` resolves by slug across entry types, and the create-intent guard
220
- * turns a hit into a conflict), but content also arrives by merge, by PR, and by adopters
221
- * retrofitting an existing repo — none of which pass through that boundary.
222
- *
223
- * Exported so adopters can assert on it directly instead of hand-rolling a content-integrity
224
- * test; `assertNoDuplicateUrlPaths` is what the build itself uses.
225
- *
226
- * Pure. Results are sorted by `urlPath`, with each `entryPaths` sorted too, so messages are
227
- * stable across runs (`listEntries` resolves collections in parallel, so its own order is not).
215
+ * Exported for adopters to assert on directly instead of hand-rolling a content-integrity test;
216
+ * `assertNoDuplicateUrlPaths` is what the build uses. Pure — results and each `entryPaths` are
217
+ * sorted, since `listEntries` resolves collections in parallel and its own order is not stable.
228
218
  */
229
219
  export declare function findDuplicateUrlPaths(items: readonly UrlScanItem[]): DuplicateUrlPath[];
230
220
  /**
@@ -240,7 +230,7 @@ export declare function findDuplicateUrlPaths(items: readonly UrlScanItem[]): Du
240
230
  * collision at RESOLUTION time regardless of routing. `readByUrlPath` picks one of the two
241
231
  * whatever any `generateStaticParams` filter says, so the un-routed entry is not innocent; it is
242
232
  * shadowing (or being shadowed by) the routed one at the same URL. `rootPath` narrows the scan
243
- * because it narrows what was loaded at all; `filter` deliberately does not.
233
+ * because it narrows what was loaded; `filter` deliberately does not.
244
234
  *
245
235
  * Related but not redundant: `canopycms-next`'s `dedupeSitemapItems` warns on the same collision
246
236
  * at the sitemap. It stays, because it also covers entry-vs-`extraUrls` collisions (adopter-supplied
@@ -258,36 +248,20 @@ export interface UnroutableSlugEntry {
258
248
  /** What the slug-routability scan needs off a listing item. */
259
249
  type SlugScanItem = Pick<ListEntriesItem, 'entryPath' | 'urlPath' | 'slug'>;
260
250
  /**
261
- * Find every listed entry whose `slug` cannot pass `parseSlug` — the validation `readByUrlPath`
262
- * runs on every URL-resolution candidate it tries (`context.ts`).
263
- *
264
- * The filename grammar `parseTypedFilename` parses (`{type}.{slug}.{id}.{ext}`) deliberately
265
- * allows a slug to contain dots, anchoring the split on the type and ID instead (see that
266
- * function's doc comment) — and it does not run the parsed slug through `parseSlug` before
267
- * handing it back, by design: recovering `{type, slug, id}` structurally, without validating
268
- * every part, is its whole contract. `listCollectionEntries` inherits that: it lists the file and
269
- * computes a `urlPath` for it, same as any other entry.
270
- *
271
- * But `parseSlug` requires `^[a-z0-9][a-z0-9-]*$` — no dots — so a listed entry whose slug
272
- * contains one is unreachable through the one path everything else assumes works:
273
- * `readByUrlPath` tries the URL's last segment as a candidate slug and skips any candidate that
274
- * fails `parseSlug` (treating it as a miss, not an error) before it ever calls `read()`. The
275
- * entry still builds, still gets a `generateStaticParams` entry, still gets a sitemap `<loc>` —
276
- * and 404s the moment anything actually visits it. That breaks the `Round-trip safe` contract
277
- * `content-listing.ts` documents on `urlPath` (`readByUrlPath(item.urlPath)` resolves to the same
278
- * entry) and is exactly the silent-page-loss failure mode this guard's siblings
279
- * (`assertBuildEntriesValid`, `assertNoDuplicateUrlPaths`) exist to make loud instead.
251
+ * Find every listed entry whose `slug` cannot pass `parseSlug` — the check `readByUrlPath` runs
252
+ * on every URL-resolution candidate it tries (`context.ts`).
280
253
  *
281
- * The write boundary refuses to MINT such a slug: `api/content.ts` and `ContentStore.write()`
282
- * both run `parseSlug` on a create, and `renameEntry()` runs it on the new slug (the [SLUG]
283
- * guards). That enforcement is deliberately create-only — an entry that already carries a
284
- * non-conforming slug stays saveable and renameable, because renaming it is the only way to fix
285
- * this build failure, and refusing reads or edits would turn a red build into unreachable data.
254
+ * The filename grammar (`parseTypedFilename`) deliberately allows a slug to contain dots and
255
+ * hands it back unvalidated, so a listed entry can carry one. But `parseSlug` requires
256
+ * `^[a-z0-9][a-z0-9-]*$` — no dots — so `readByUrlPath` skips that candidate as a miss before
257
+ * ever calling `read()`. The entry still builds, gets a `generateStaticParams` entry and a
258
+ * sitemap `<loc>`, and then 404s on every visit — breaking the `Round-trip safe` contract
259
+ * `content-listing.ts` documents on `urlPath`.
286
260
  *
287
- * So this guard still fires, and is still needed, for every slug the write boundary never saw:
288
- * hand-authored files, scripted migrations, content merged in over git, a repo being retrofitted
289
- * onto CanopyCMS (this guard's intended audience, same as its siblings), and entries created
290
- * before the write-time rule existed.
261
+ * The write boundary (`parseSlug` in `api/content.ts` and `ContentStore.write`/`renameEntry`)
262
+ * refuses such a slug on create and rename only, so this guard covers every slug it never saw:
263
+ * hand-authored files, migrations, git-merged content, and a repo being retrofitted onto
264
+ * CanopyCMS.
291
265
  */
292
266
  export declare function findUnroutableSlugs(items: readonly SlugScanItem[]): UnroutableSlugEntry[];
293
267
  /**
@@ -27,20 +27,12 @@ async function enumerateRoutableEntries(ctx, rootPath, phaseLabel, resolveRefere
27
27
  // an abandoned schema-invalid scaffold shipping into a static build silently drops that page's
28
28
  // route (or worse, renders broken), which is worse than a red build.
29
29
  //
30
- // The unknown-key warning runs BEFORE all three throwing guards, so a build about to go red
31
- // still prints everything it found rather than dying on the first problem.
32
- //
33
- // All four run on the RAW listing, before the caller's `filter` — a filtered-out entry still
34
- // occupies its URL as far as every other route is concerned. `rootPath` scoping does narrow
35
- // them, since that happens inside `listEntries`; that is the intended escape hatch, `filter`
36
- // is not.
37
- //
38
- // Slug routability is asserted first, ahead of schema validity and duplicate URLs: an entry
39
- // whose slug cannot round-trip through `readByUrlPath` has no URL at all as far as any OTHER
40
- // guard is concerned, so it is the most fundamental of the three problems a listed entry can
41
- // have. Schema validity is asserted before duplicate URLs for the same reason one level down:
42
- // a schema-invalid entry is the more fundamental problem, and an adopter fixing it may remove
43
- // the duplicate on the way.
30
+ // These four guards run in a fixed order on the RAW listing, before the caller's `filter` —
31
+ // `rootPath` narrows them, `filter` deliberately does not. The unknown-key warning runs first,
32
+ // so a build about to go red still reports everything found. Slug routability runs next: an
33
+ // unroutable entry has no URL at all as far as the other three are concerned, the most
34
+ // fundamental problem a listed entry can have. Schema validity runs before duplicate URLs for
35
+ // the same "most fundamental first" reason, one level down.
44
36
  if (isBuildMode()) {
45
37
  warnUnknownEntryKeys(entries, phaseLabel);
46
38
  assertRoutableSlugs(entries, phaseLabel);
@@ -109,7 +101,7 @@ export async function collectStaticPaths(ctx, options = {}) {
109
101
  * Deep-walk a plain data value (objects and arrays only — the shapes YAML/JSON parsing can
110
102
  * produce), converting every `Date` instance to its ISO string.
111
103
  *
112
- * gray-matter parses unquoted YAML dates in hand-authored frontmatter (`date: 2024-01-15`) into
104
+ * gray-matter parses unquoted YAML dates in hand-authored frontmatter (`date: YYYY-MM-DD`) into
113
105
  * JS `Date` objects rather than strings. The shared validator's datetime check requires a string
114
106
  * (see `validateScalar` in entry-validator.ts), so a legitimate hand-authored or migrated entry
115
107
  * would otherwise fail this build guard. CMS-authored entries round-trip as quoted strings, so
@@ -175,13 +167,13 @@ const UNKNOWN_KEY_REPORT_LIMIT = 20;
175
167
  *
176
168
  * The inverse of `findInvalidEntries`, and non-fatal by design: an unknown key is stale data,
177
169
  * not broken data. A build must not go red for it — the page still renders, it is just quietly
178
- * missing whatever the renamed field used to supply. `warnUnknownEntryKeys` is the reporting
170
+ * missing whatever value the renamed field would supply. `warnUnknownEntryKeys` is the reporting
179
171
  * half. Both sit at module scope alongside `findInvalidEntries`/`assertBuildEntriesValid` and
180
172
  * are deliberately NOT on `canopycms/server`, matching those two — the build wires them itself.
181
173
  *
182
174
  * Skips items with no resolved schema, and items whose schema is empty — "no schema" is not
183
175
  * "every key is unknown". Same `normalizeDatesDeep` pass as the validity scan, so a hand-authored
184
- * `date: 2024-01-15` is a plain value here too.
176
+ * `date: YYYY-MM-DD` is a plain value here too.
185
177
  */
186
178
  export function findEntriesWithUnknownKeys(items) {
187
179
  const found = [];
@@ -254,28 +246,18 @@ export function assertBuildEntriesValid(items, phaseLabel) {
254
246
  /**
255
247
  * Find every `urlPath` claimed by two or more entries.
256
248
  *
257
- * `listEntries` assigns each entry exactly one `urlPath` and documents it as round-trip safe with
258
- * `readByUrlPath`. That guarantee is per-entry, not per-URL: nothing stops two DIFFERENT entries
259
- * computing the same one, and when they do exactly one of them is reachable while the other
260
- * silently has no route at all. The known ways to get there:
261
- *
262
- * - An entry whose slug matches a sibling collection that also has an `index` entry — the index
263
- * collapses onto the collection's path, which is the entry's path too. The write boundary now
264
- * refuses to author this too (url-collision.ts), so reaching it means the content arrived some
265
- * other way — the same merge/PR/retrofit routes as the same-slug case below. (An entry beside a
266
- * sibling collection with NO index entry is a different, legitimate shape: a landing page plus
267
- * a folder of children, nothing contested.)
268
- * - Two entries whose slugs differ only by case, since `urlPath` is lowercased.
269
- * - Two entries with the same slug in one collection. The write boundary already refuses this
270
- * (`ContentStore.buildPaths` resolves by slug across entry types, and the create-intent guard
271
- * turns a hit into a conflict), but content also arrives by merge, by PR, and by adopters
272
- * retrofitting an existing repo — none of which pass through that boundary.
249
+ * `listEntries` gives each entry one `urlPath`, documented round-trip safe with `readByUrlPath`
250
+ * — but that guarantee is per-entry, not per-URL: two different entries can compute the same
251
+ * one, leaving exactly one reachable and the other silently unrouted. Known causes: an entry
252
+ * beside a sibling collection that also has an `index` entry (collapses onto the same path —
253
+ * the write boundary now refuses authoring this too, but merge/PR/retrofit content bypasses it;
254
+ * a sibling with NO index entry is a different, uncontested shape); slugs differing only by case
255
+ * (`urlPath` is lowercased); or two entries sharing a slug in one collection (also
256
+ * write-boundary-refused via `ContentStore.buildPaths`, same bypass routes).
273
257
  *
274
- * Exported so adopters can assert on it directly instead of hand-rolling a content-integrity
275
- * test; `assertNoDuplicateUrlPaths` is what the build itself uses.
276
- *
277
- * Pure. Results are sorted by `urlPath`, with each `entryPaths` sorted too, so messages are
278
- * stable across runs (`listEntries` resolves collections in parallel, so its own order is not).
258
+ * Exported for adopters to assert on directly instead of hand-rolling a content-integrity test;
259
+ * `assertNoDuplicateUrlPaths` is what the build uses. Pure — results and each `entryPaths` are
260
+ * sorted, since `listEntries` resolves collections in parallel and its own order is not stable.
279
261
  */
280
262
  export function findDuplicateUrlPaths(items) {
281
263
  const byUrl = new Map();
@@ -307,7 +289,7 @@ export function findDuplicateUrlPaths(items) {
307
289
  * collision at RESOLUTION time regardless of routing. `readByUrlPath` picks one of the two
308
290
  * whatever any `generateStaticParams` filter says, so the un-routed entry is not innocent; it is
309
291
  * shadowing (or being shadowed by) the routed one at the same URL. `rootPath` narrows the scan
310
- * because it narrows what was loaded at all; `filter` deliberately does not.
292
+ * because it narrows what was loaded; `filter` deliberately does not.
311
293
  *
312
294
  * Related but not redundant: `canopycms-next`'s `dedupeSitemapItems` warns on the same collision
313
295
  * at the sitemap. It stays, because it also covers entry-vs-`extraUrls` collisions (adopter-supplied
@@ -334,36 +316,20 @@ export function assertNoDuplicateUrlPaths(items, phaseLabel) {
334
316
  'lowercased). Rename or remove one of the colliding entries, then rebuild.');
335
317
  }
336
318
  /**
337
- * Find every listed entry whose `slug` cannot pass `parseSlug` — the validation `readByUrlPath`
338
- * runs on every URL-resolution candidate it tries (`context.ts`).
339
- *
340
- * The filename grammar `parseTypedFilename` parses (`{type}.{slug}.{id}.{ext}`) deliberately
341
- * allows a slug to contain dots, anchoring the split on the type and ID instead (see that
342
- * function's doc comment) — and it does not run the parsed slug through `parseSlug` before
343
- * handing it back, by design: recovering `{type, slug, id}` structurally, without validating
344
- * every part, is its whole contract. `listCollectionEntries` inherits that: it lists the file and
345
- * computes a `urlPath` for it, same as any other entry.
346
- *
347
- * But `parseSlug` requires `^[a-z0-9][a-z0-9-]*$` — no dots — so a listed entry whose slug
348
- * contains one is unreachable through the one path everything else assumes works:
349
- * `readByUrlPath` tries the URL's last segment as a candidate slug and skips any candidate that
350
- * fails `parseSlug` (treating it as a miss, not an error) before it ever calls `read()`. The
351
- * entry still builds, still gets a `generateStaticParams` entry, still gets a sitemap `<loc>` —
352
- * and 404s the moment anything actually visits it. That breaks the `Round-trip safe` contract
353
- * `content-listing.ts` documents on `urlPath` (`readByUrlPath(item.urlPath)` resolves to the same
354
- * entry) and is exactly the silent-page-loss failure mode this guard's siblings
355
- * (`assertBuildEntriesValid`, `assertNoDuplicateUrlPaths`) exist to make loud instead.
319
+ * Find every listed entry whose `slug` cannot pass `parseSlug` — the check `readByUrlPath` runs
320
+ * on every URL-resolution candidate it tries (`context.ts`).
356
321
  *
357
- * The write boundary refuses to MINT such a slug: `api/content.ts` and `ContentStore.write()`
358
- * both run `parseSlug` on a create, and `renameEntry()` runs it on the new slug (the [SLUG]
359
- * guards). That enforcement is deliberately create-only — an entry that already carries a
360
- * non-conforming slug stays saveable and renameable, because renaming it is the only way to fix
361
- * this build failure, and refusing reads or edits would turn a red build into unreachable data.
322
+ * The filename grammar (`parseTypedFilename`) deliberately allows a slug to contain dots and
323
+ * hands it back unvalidated, so a listed entry can carry one. But `parseSlug` requires
324
+ * `^[a-z0-9][a-z0-9-]*$` — no dots — so `readByUrlPath` skips that candidate as a miss before
325
+ * ever calling `read()`. The entry still builds, gets a `generateStaticParams` entry and a
326
+ * sitemap `<loc>`, and then 404s on every visit — breaking the `Round-trip safe` contract
327
+ * `content-listing.ts` documents on `urlPath`.
362
328
  *
363
- * So this guard still fires, and is still needed, for every slug the write boundary never saw:
364
- * hand-authored files, scripted migrations, content merged in over git, a repo being retrofitted
365
- * onto CanopyCMS (this guard's intended audience, same as its siblings), and entries created
366
- * before the write-time rule existed.
329
+ * The write boundary (`parseSlug` in `api/content.ts` and `ContentStore.write`/`renameEntry`)
330
+ * refuses such a slug on create and rename only, so this guard covers every slug it never saw:
331
+ * hand-authored files, migrations, git-merged content, and a repo being retrofitted onto
332
+ * CanopyCMS.
367
333
  */
368
334
  export function findUnroutableSlugs(items) {
369
335
  const found = [];
@@ -1,17 +1,15 @@
1
1
  /**
2
- * CanopyCMS task queue — re-exports from the generic task-queue module
3
- * with CMS-specific action types and a WorkerTask alias.
4
- */
5
- import type { Task, TaskQueueLogger } from '../task-queue/index.js';
6
- /**
7
- * Actions the EC2 worker can execute on behalf of Lambda.
2
+ * The CMS queue contract: the generic queue in ./task-queue plus the CMS
3
+ * action vocabulary, a WorkerTask alias and the debug logger. Published as
4
+ * `canopycms/worker/task-queue`.
8
5
  */
6
+ import type { Task, TaskQueueLogger } from './types.js';
7
+ /** Actions the EC2 worker can execute on behalf of Lambda. */
9
8
  export type TaskAction = 'push-and-create-pr' | 'push-and-update-pr' | 'push-and-create-or-update-pr' | 'convert-to-draft' | 'close-pr' | 'delete-remote-branch' | 'push-branch';
10
9
  /** A task with a CMS-specific action. */
11
10
  export type WorkerTask = Task & {
12
11
  action: TaskAction;
13
12
  };
14
13
  export declare const cmsTaskQueueLogger: TaskQueueLogger;
15
- export { enqueueTask, dequeueTask, completeTask, failTask, retryTask, requeueFailedTask, recoverOrphanedTasks, cleanupOldTasks, getTask, getTask as getTaskResult, // backward-compatible alias
16
- listTasks, getQueueStats, listCorruptTaskFiles, } from '../task-queue/index.js';
17
- export type { Task, TaskStatus, QueueStats, TaskQueueLogger, CorruptTaskFile } from '../task-queue/index.js';
14
+ export { enqueueTask, dequeueTask, completeTask, failTask, retryTask, requeueFailedTask, recoverOrphanedTasks, cleanupOldTasks, getTask, listTasks, getQueueStats, listCorruptTaskFiles, } from './task-queue.js';
15
+ export type { Task, TaskStatus, QueueStats, TaskQueueLogger, CorruptTaskFile } from './types.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The CMS queue contract: the generic queue in ./task-queue plus the CMS
3
+ * action vocabulary, a WorkerTask alias and the debug logger. Published as
4
+ * `canopycms/worker/task-queue`.
5
+ */
6
+ import { createDebugLogger } from '../utils/debug.js';
7
+ const debugLogger = createDebugLogger({ prefix: 'TaskQueue' });
8
+ export const cmsTaskQueueLogger = {
9
+ debug(message, data) {
10
+ debugLogger.debug('task', message, data);
11
+ },
12
+ };
13
+ export { enqueueTask, dequeueTask, completeTask, failTask, retryTask, requeueFailedTask, recoverOrphanedTasks, cleanupOldTasks, getTask, listTasks, getQueueStats, listCorruptTaskFiles, } from './task-queue.js';
@@ -0,0 +1,3 @@
1
+ import type { CanopyConfig } from '../config/index.js';
2
+ /** The task queue directory for async worker operations. */
3
+ export declare function getTaskQueueDir(config: Pick<CanopyConfig, 'mode'>): string;
@@ -1,11 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { DEFAULT_PROD_WORKSPACE } from '../config/index.js';
3
- /**
4
- * Get the task queue directory for async worker operations.
5
- *
6
- * In prod mode: {CANOPYCMS_WORKSPACE_ROOT}/.tasks
7
- * In dev mode: {cwd}/.canopy-dev/.tasks
8
- */
3
+ /** The task queue directory for async worker operations. */
9
4
  export function getTaskQueueDir(config) {
10
5
  switch (config.mode) {
11
6
  case 'prod': {
@@ -11,9 +11,9 @@
11
11
  * Designed for shared filesystems (EFS/NFS) where one process enqueues
12
12
  * and another dequeues. No external dependencies — only Node.js stdlib.
13
13
  *
14
- * IMPORTANT: Single-consumer only. The dequeue operation is not atomic across
15
- * processes — the worker lock (acquireLock) ensures only one consumer runs at
16
- * a time. Do not run multiple dequeue consumers concurrently.
14
+ * IMPORTANT: single-consumer only — dequeue is not atomic across processes;
15
+ * the worker lock (acquireLock) must serialize consumers, so never run more
16
+ * than one dequeue consumer concurrently.
17
17
  */
18
18
  import type { Task, TaskStatus, QueueStats, TaskQueueLogger, CorruptTaskFile } from './types.js';
19
19
  /**
@@ -50,28 +50,22 @@ export declare function failTask(taskDir: string, taskId: string, error: string,
50
50
  */
51
51
  export declare function retryTask(taskDir: string, taskId: string, error: string, logger?: TaskQueueLogger): Promise<void>;
52
52
  /**
53
- * Requeue a permanently-failed task for another attempt. Reads
54
- * failed/{taskId}.json and creates a BRAND NEW pending task with a fresh
55
- * `id` — the original ID is never reused. This matters because both
56
- * `dequeueTask` and `recoverOrphanedTasks` dedup against completed/failed by
57
- * ID: a pending file that reused the original ID would be silently deleted
58
- * (without running) the moment it was scanned, since its ID already exists
59
- * in failed/.
53
+ * Requeue a permanently-failed task: reads failed/{taskId}.json and creates a
54
+ * brand-new pending task with a fresh `id`, never the original — `dequeueTask`
55
+ * and `recoverOrphanedTasks` dedup completed/failed by ID, so reusing it would
56
+ * get the pending copy silently deleted, unrun, the moment it's scanned.
60
57
  *
61
- * The new task carries `payload.requeuedFrom` for provenance, starts at
62
- * `retryCount: 0`, and gets an honest fresh `createdAt` (it goes to the back
63
- * of the FIFO queue rather than jumping ahead on the original timestamp).
58
+ * The new task carries `payload.requeuedFrom`, `retryCount: 0`, and a fresh
59
+ * `createdAt` (it re-joins the back of the FIFO queue, not the original slot).
64
60
  *
65
- * Write-then-unlink ordering: the new pending file is written FIRST, and the
66
- * failed original is unlinked only after that succeeds (best-effort — unlink
67
- * failures are swallowed). A crash between the two steps leaves the failed
68
- * original in place alongside the new pending task, which is harmless
69
- * (duplicate history, not a lost task); the reverse order could lose the
70
- * task entirely if the process died after the unlink but before the write.
61
+ * Write-then-unlink: the new pending file is written FIRST; the failed
62
+ * original is unlinked only after (best-effort). A crash between the two
63
+ * leaves both — harmless duplication — but the reverse order could lose the
64
+ * task if the process dies after the unlink and before the write.
71
65
  *
72
- * Returns `{ error: 'not-found' }` if there is no such file in failed/, or
73
- * `{ error: 'unparseable' }` if the file exists but isn't valid task JSON
74
- * (callers should tell the admin to delete it instead of retrying it).
66
+ * Returns `{ error: 'not-found' }` if failed/{taskId}.json doesn't exist, or
67
+ * `{ error: 'unparseable' }` if it isn't valid task JSON (tell the admin to
68
+ * delete it rather than retry).
75
69
  */
76
70
  export declare function requeueFailedTask(taskDir: string, taskId: string, logger?: TaskQueueLogger): Promise<{
77
71
  newTaskId: string;
@@ -11,24 +11,20 @@
11
11
  * Designed for shared filesystems (EFS/NFS) where one process enqueues
12
12
  * and another dequeues. No external dependencies — only Node.js stdlib.
13
13
  *
14
- * IMPORTANT: Single-consumer only. The dequeue operation is not atomic across
15
- * processes — the worker lock (acquireLock) ensures only one consumer runs at
16
- * a time. Do not run multiple dequeue consumers concurrently.
14
+ * IMPORTANT: single-consumer only — dequeue is not atomic across processes;
15
+ * the worker lock (acquireLock) must serialize consumers, so never run more
16
+ * than one dequeue consumer concurrently.
17
17
  */
18
18
  import fs from 'node:fs/promises';
19
19
  import path from 'node:path';
20
20
  import crypto from 'node:crypto';
21
21
  const DEFAULT_MAX_RETRIES = 3;
22
- // Silent no-op logger
23
22
  const nullLogger = { debug: () => { } };
24
23
  import { atomicWriteFile } from '../utils/atomic-write.js';
25
24
  // Local helper — only stdlib dependency, keeps task-queue easy to extract.
26
25
  function isNotFoundError(err) {
27
26
  return err instanceof Error && 'code' in err && err.code === 'ENOENT';
28
27
  }
29
- // ============================================================================
30
- // Core queue operations
31
- // ============================================================================
32
28
  /**
33
29
  * Enqueue a task. Writes a JSON file to pending/{id}.json.
34
30
  * Returns the generated task ID.
@@ -219,28 +215,22 @@ export async function retryTask(taskDir, taskId, error, logger = nullLogger) {
219
215
  logger.debug('Retrying task', { id: taskId, retryCount, backoffMs });
220
216
  }
221
217
  /**
222
- * Requeue a permanently-failed task for another attempt. Reads
223
- * failed/{taskId}.json and creates a BRAND NEW pending task with a fresh
224
- * `id` — the original ID is never reused. This matters because both
225
- * `dequeueTask` and `recoverOrphanedTasks` dedup against completed/failed by
226
- * ID: a pending file that reused the original ID would be silently deleted
227
- * (without running) the moment it was scanned, since its ID already exists
228
- * in failed/.
218
+ * Requeue a permanently-failed task: reads failed/{taskId}.json and creates a
219
+ * brand-new pending task with a fresh `id`, never the original — `dequeueTask`
220
+ * and `recoverOrphanedTasks` dedup completed/failed by ID, so reusing it would
221
+ * get the pending copy silently deleted, unrun, the moment it's scanned.
229
222
  *
230
- * The new task carries `payload.requeuedFrom` for provenance, starts at
231
- * `retryCount: 0`, and gets an honest fresh `createdAt` (it goes to the back
232
- * of the FIFO queue rather than jumping ahead on the original timestamp).
223
+ * The new task carries `payload.requeuedFrom`, `retryCount: 0`, and a fresh
224
+ * `createdAt` (it re-joins the back of the FIFO queue, not the original slot).
233
225
  *
234
- * Write-then-unlink ordering: the new pending file is written FIRST, and the
235
- * failed original is unlinked only after that succeeds (best-effort — unlink
236
- * failures are swallowed). A crash between the two steps leaves the failed
237
- * original in place alongside the new pending task, which is harmless
238
- * (duplicate history, not a lost task); the reverse order could lose the
239
- * task entirely if the process died after the unlink but before the write.
226
+ * Write-then-unlink: the new pending file is written FIRST; the failed
227
+ * original is unlinked only after (best-effort). A crash between the two
228
+ * leaves both — harmless duplication — but the reverse order could lose the
229
+ * task if the process dies after the unlink and before the write.
240
230
  *
241
- * Returns `{ error: 'not-found' }` if there is no such file in failed/, or
242
- * `{ error: 'unparseable' }` if the file exists but isn't valid task JSON
243
- * (callers should tell the admin to delete it instead of retrying it).
231
+ * Returns `{ error: 'not-found' }` if failed/{taskId}.json doesn't exist, or
232
+ * `{ error: 'unparseable' }` if it isn't valid task JSON (tell the admin to
233
+ * delete it rather than retry).
244
234
  */
245
235
  export async function requeueFailedTask(taskDir, taskId, logger = nullLogger) {
246
236
  const failedPath = path.join(taskDir, 'failed', `${taskId}.json`);
@@ -272,9 +262,6 @@ export async function requeueFailedTask(taskDir, taskId, logger = nullLogger) {
272
262
  logger.debug('Requeued failed task', { originalId: taskId, newTaskId });
273
263
  return { newTaskId };
274
264
  }
275
- // ============================================================================
276
- // Recovery & maintenance
277
- // ============================================================================
278
265
  /**
279
266
  * Recover orphaned tasks stuck in processing/.
280
267
  * Moves tasks whose file mtime is older than maxAgeMs back to pending/.
@@ -339,15 +326,10 @@ export async function recoverOrphanedTasks(taskDir, maxAgeMs = 5 * 60_000, logge
339
326
  export async function cleanupOldTasks(taskDir, maxAgeMs = 30 * 24 * 60 * 60_000, logger = nullLogger) {
340
327
  const now = Date.now();
341
328
  let cleaned = 0;
342
- // `corrupt` included: unparseable task files are quarantined there by
343
- // dequeue and orphan recovery and surfaced in admin listing, but the
344
- // retention sweep never covered the directory, so it grew forever with
345
- // deletion available only as a manual per-file admin action. Any recurring
346
- // producer of malformed task JSON -- a partial write surviving a crash, a
347
- // bad deploy writing schema-drifted tasks for a week -- accumulated files no
348
- // automated path removed. Same stamp-based retention as the other two: a
349
- // quarantined file older than the window has long since been triaged or
350
- // forgotten.
329
+ // `corrupt` files (quarantined by dequeue and orphan recovery, shown in
330
+ // the admin listing) get the same stamp-based retention as completed and
331
+ // failed, because a quarantined file older than the window has been
332
+ // triaged or forgotten.
351
333
  for (const subdir of ['completed', 'failed', 'corrupt']) {
352
334
  const dir = path.join(taskDir, subdir);
353
335
  let files;
@@ -376,9 +358,6 @@ export async function cleanupOldTasks(taskDir, maxAgeMs = 30 * 24 * 60 * 60_000,
376
358
  }
377
359
  return cleaned;
378
360
  }
379
- // ============================================================================
380
- // Query operations (for status UIs, monitoring)
381
- // ============================================================================
382
361
  /**
383
362
  * Get a specific task by ID. Searches all status directories.
384
363
  */
@@ -487,9 +466,6 @@ export async function listCorruptTaskFiles(taskDir, limit = 50, logger = nullLog
487
466
  entries.sort((a, b) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime());
488
467
  return entries.slice(0, limit);
489
468
  }
490
- // ============================================================================
491
- // Internal helpers
492
- // ============================================================================
493
469
  /** Read up to `maxBytes` from the start of a file, as utf-8 text. */
494
470
  async function readSnippet(filePath, maxBytes) {
495
471
  const handle = await fs.open(filePath, 'r');
@@ -31,7 +31,6 @@ export interface QueueStats {
31
31
  }
32
32
  /**
33
33
  * Optional logger interface.
34
- * Pass your own logger to `createTaskQueue()`, or omit for silent operation.
35
34
  */
36
35
  export interface TaskQueueLogger {
37
36
  debug(message: string, data?: Record<string, unknown>): void;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * worker-status.json: the CmsWorker daemon's self-reported liveness/health
3
+ * snapshot, written under the task queue directory
4
+ * (`{taskDir}/worker-status.json`) and read tolerantly by the admin API
5
+ * (api/admin.ts's `readWorkerStatus`). Wire shape: `WorkerStatusReport` in
6
+ * ../types.
7
+ *
8
+ * Single-writer by intent (only the lock-holding CmsWorker -- see
9
+ * cms-worker.ts's `acquireLock`, DEP-C2), but correctness does not rest on
10
+ * that: every write is a FULL regeneration landed by an atomic temp-file rename
11
+ * (utils/atomic-write.ts), never a read-modify-write, so whichever write lands
12
+ * last is one writer's complete, self-consistent snapshot. That covers the one
13
+ * window where two holders overlap -- after a lock compromise the old holder's
14
+ * `stop()` drains for up to `taskTimeoutMs` while a new holder is already
15
+ * running against the same workspace.
16
+ *
17
+ * Readers are stale-tolerant by design: this is a liveness signal, not
18
+ * authoritative state, and NFS/EFS attribute-cache staleness (see
19
+ * docs/concurrency.md) means a reader can see a snapshot a cache window old
20
+ * however carefully it was written.
21
+ */
22
+ import type { WorkerStatusReport } from '../types.js';
23
+ export declare const WORKER_STATUS_FILE = "worker-status.json";
24
+ /**
25
+ * Write the worker's status report to `{taskDir}/worker-status.json`.
26
+ *
27
+ * Full-file regeneration, never a partial update: pass the complete report,
28
+ * not a delta. Stamps `updatedAt`; every other field is the caller's.
29
+ *
30
+ * Throws rather than swallowing. Callers that cannot let a status-write failure
31
+ * break their own operation wrap this at the call site (`syncGit`,
32
+ * `processTaskQueue`, `start()`); any that don't fall back on scheduleLoop's
33
+ * per-cycle catch, which logs and continues.
34
+ */
35
+ export declare function writeWorkerStatus(taskDir: string, report: WorkerStatusReport): Promise<void>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * worker-status.json: the CmsWorker daemon's self-reported liveness/health
3
+ * snapshot, written under the task queue directory
4
+ * (`{taskDir}/worker-status.json`) and read tolerantly by the admin API
5
+ * (api/admin.ts's `readWorkerStatus`). Wire shape: `WorkerStatusReport` in
6
+ * ../types.
7
+ *
8
+ * Single-writer by intent (only the lock-holding CmsWorker -- see
9
+ * cms-worker.ts's `acquireLock`, DEP-C2), but correctness does not rest on
10
+ * that: every write is a FULL regeneration landed by an atomic temp-file rename
11
+ * (utils/atomic-write.ts), never a read-modify-write, so whichever write lands
12
+ * last is one writer's complete, self-consistent snapshot. That covers the one
13
+ * window where two holders overlap -- after a lock compromise the old holder's
14
+ * `stop()` drains for up to `taskTimeoutMs` while a new holder is already
15
+ * running against the same workspace.
16
+ *
17
+ * Readers are stale-tolerant by design: this is a liveness signal, not
18
+ * authoritative state, and NFS/EFS attribute-cache staleness (see
19
+ * docs/concurrency.md) means a reader can see a snapshot a cache window old
20
+ * however carefully it was written.
21
+ */
22
+ import path from 'node:path';
23
+ import { atomicWriteFile } from '../utils/atomic-write.js';
24
+ export const WORKER_STATUS_FILE = 'worker-status.json';
25
+ /**
26
+ * Write the worker's status report to `{taskDir}/worker-status.json`.
27
+ *
28
+ * Full-file regeneration, never a partial update: pass the complete report,
29
+ * not a delta. Stamps `updatedAt`; every other field is the caller's.
30
+ *
31
+ * Throws rather than swallowing. Callers that cannot let a status-write failure
32
+ * break their own operation wrap this at the call site (`syncGit`,
33
+ * `processTaskQueue`, `start()`); any that don't fall back on scheduleLoop's
34
+ * per-cycle catch, which logs and continues.
35
+ */
36
+ export async function writeWorkerStatus(taskDir, report) {
37
+ const filePath = path.join(taskDir, WORKER_STATUS_FILE);
38
+ const stamped = { ...report, updatedAt: new Date().toISOString() };
39
+ await atomicWriteFile(filePath, JSON.stringify(stamped, null, 2));
40
+ }