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/types.d.ts CHANGED
@@ -7,9 +7,9 @@ export type CanopyGroupId = string;
7
7
  * rule is expressed.
8
8
  *
9
9
  * There is deliberately no separate 'locked' state: 'submitted' already means
10
- * "locked while a reviewer looks at the PR", and request-changes/withdraw are
11
- * the unlocks. A future admin-freeze feature should reintroduce a status only
12
- * alongside real semantics (worker rebase skip list, a set/unset endpoint, UI).
10
+ * "locked while the PR is under review", with request-changes/withdraw as the
11
+ * unlocks. A future admin-freeze feature should add a status only alongside real
12
+ * semantics (worker rebase skip list, a set/unset endpoint, UI).
13
13
  */
14
14
  export type BranchStatus = 'editing' | 'submitted' | 'approved' | 'archived';
15
15
  export type SyncStatus = 'synced' | 'pending-sync' | 'sync-failed';
@@ -40,15 +40,14 @@ export interface BranchMetadata {
40
40
  /** ContentIds of entries where --theirs was applied during rebase; cleared on clean rebase */
41
41
  conflictFiles?: ContentId[];
42
42
  /**
43
- * Lifecycle state of the branch's PR as last observed by the worker's
44
- * merge-poll (or verified by markAsMerged). Absent until a PR exists and
45
- * has been observed. Draft PRs read 'open'.
43
+ * Lifecycle state of the branch's PR as last observed by the worker's merge-poll
44
+ * (or verified by markAsMerged). Absent until a PR exists and has been observed.
45
+ * Draft PRs read 'open'.
46
46
  */
47
47
  pullRequestState?: PullRequestState;
48
48
  /**
49
- * ISO timestamp stamped when the branch was archived because its PR
50
- * merged (worker auto-poll or manual markAsMerged). Absent for branches
51
- * archived any other way.
49
+ * ISO timestamp stamped when the branch was archived because its PR merged
50
+ * (worker auto-poll or markAsMerged). Absent for branches archived any other way.
52
51
  */
53
52
  mergedAt?: string;
54
53
  /**
@@ -62,30 +61,29 @@ export interface BranchMetadata {
62
61
  lastAt: string;
63
62
  };
64
63
  /**
65
- * Set by the worker's rebase loop when it rewrote history this deployment
66
- * had ALREADY published: the commit `remote.git` (and therefore GitHub)
67
- * held for this branch, which the rebase replaced.
64
+ * Set by the worker's rebase loop when it rewrote history this deployment had
65
+ * ALREADY published: the commit `remote.git` (and therefore GitHub) held for
66
+ * this branch, which the rebase replaced.
68
67
  *
69
- * Used as the `--force-with-lease` expected value on both hops, so a forced
70
- * push can only ever move a ref off the exact commit our own rebase rewrote
71
- * away -- never over anyone else's work.
68
+ * It is the `--force-with-lease` expected value on both hops, so a forced push
69
+ * can only move a ref off the exact commit our own rebase rewrote away, never
70
+ * over anyone else's work.
72
71
  *
73
- * Set once per rewrite episode and never advanced while still set: across
74
- * two rebases before any push lands, GitHub still holds the ORIGINAL
75
- * commit, so advancing the marker would aim the lease at a commit GitHub
76
- * never had. Cleared only once GitHub is confirmed to hold something else
77
- * (CmsWorker.pushBranchToGitHub) -- while it is set, it is the sole trigger
78
- * for the rebase loop's self-heal pass.
72
+ * Set once per rewrite episode and never advanced while still set: across two
73
+ * rebases before any push lands GitHub still holds the ORIGINAL commit, so
74
+ * advancing the marker would aim the lease at a commit GitHub never had.
75
+ * Cleared only once GitHub is confirmed to hold something else
76
+ * (CmsWorker.pushBranchToGitHub); while set, it is the sole trigger for the
77
+ * rebase loop's self-heal pass.
79
78
  */
80
79
  historyRewrittenFrom?: string;
81
80
  /**
82
- * Short, sanitized reason the worker's last GitHub sync task failed
83
- * permanently (set alongside `syncStatus: 'sync-failed'` by
84
- * CmsWorker.updateBranchMetadataOnFailure) -- e.g. a non-fast-forward push
85
- * rejection naming the branch. Absent until a task has failed permanently.
86
- * Cleared on the next successful sync task (CmsWorker.updateBranchMetadata
87
- * explicitly resets it to undefined) so a stale reason never survives a
88
- * later successful push.
81
+ * Short, sanitized reason the worker's last GitHub sync task failed permanently,
82
+ * set alongside `syncStatus: 'sync-failed'` by
83
+ * CmsWorker.updateBranchMetadataOnFailure -- e.g. a non-fast-forward push
84
+ * rejection naming the branch. Absent until a task has failed permanently, and
85
+ * reset to undefined by the next successful sync task, so a stale reason never
86
+ * survives a later successful push.
89
87
  */
90
88
  syncFailureReason?: string;
91
89
  }
@@ -105,9 +103,9 @@ export interface BranchContextWithSchema extends BranchContext {
105
103
  flatSchema: import('./config/index.js').FlatSchemaItem[];
106
104
  }
107
105
  /**
108
- * Wire shape of the worker's self-reported status file (worker-status.json,
109
- * written under the task queue dir). Written by the CmsWorker daemon
110
- * (PR-W1); this type is read-only here — GET /admin/status parses it as-is.
106
+ * Wire shape of the worker's self-reported status file (worker-status.json, under
107
+ * the task queue dir), written by the CmsWorker daemon. Read-only here: GET
108
+ * /admin/status parses it as-is.
111
109
  */
112
110
  export interface WorkerStatusReport {
113
111
  version: 1;
@@ -126,12 +124,9 @@ export interface WorkerStatusReport {
126
124
  skippedDirty: string[];
127
125
  /**
128
126
  * [SYNC-C1] Branches skipped because a content write held the branch's
129
- * cross-host content-write lock (utils/content-write-lock.ts) -- the
130
- * worker yields and retries next cycle.
131
- *
132
- * Optional for the same reason as `tracked` below: absent from a
133
- * worker-status.json written by a worker predating that lock. Readers must
134
- * tolerate its absence.
127
+ * cross-host content-write lock (utils/content-write-lock.ts); the worker
128
+ * yields and retries next cycle. Optional for the same reason as `tracked`
129
+ * below, and readers must tolerate its absence.
135
130
  */
136
131
  skippedLocked?: string[];
137
132
  failed: {
@@ -139,15 +134,11 @@ export interface WorkerStatusReport {
139
134
  error: string;
140
135
  }[];
141
136
  /**
142
- * Outcome of reconciling remote.git's `refs/heads/*` against GitHub's
143
- * fetched tips (worker/git-sync.ts's `reconcileTrackedBranches`) --
144
- * the non-destructive replacement for the old fetch refspec that used
145
- * to write GitHub's refs directly into `refs/heads/*`.
146
- */
147
- /**
148
- * Optional: absent from a worker-status.json written by a worker that
149
- * predates the tracked-branch reconcile (PR #168). Readers must tolerate
150
- * its absence rather than assume every status file has it.
137
+ * Outcome of reconciling remote.git's `refs/heads/*` against GitHub's fetched
138
+ * tips, non-destructively (worker/git-sync.ts's `reconcileTrackedBranches`).
139
+ * Optional: a status file written by a worker without that reconcile has no
140
+ * such key, and readers must tolerate its absence rather than assume every
141
+ * status file has it.
151
142
  */
152
143
  tracked?: {
153
144
  created: string[];
@@ -155,11 +146,9 @@ export interface WorkerStatusReport {
155
146
  ahead: string[];
156
147
  diverged: string[];
157
148
  /**
158
- * Optional for the same reason as `tracked` itself: absent from a
159
- * status file written before the rebase loop began publishing rewritten
160
- * history. Branches here diverged from GitHub because THIS worker
161
- * rebased them and the GitHub push is still queued -- expected and
162
- * self-resolving, unlike `diverged`.
149
+ * Optional for the same reason as `tracked` itself. Branches here diverged
150
+ * from GitHub because THIS worker rebased them and the GitHub push is still
151
+ * queued -- expected and self-resolving, unlike `diverged`.
163
152
  */
164
153
  rewritten?: string[];
165
154
  };
@@ -3,48 +3,36 @@
3
3
  *
4
4
  * The build-time half (`assertNoDuplicateUrlPaths`, static/index.ts) detects the state; this
5
5
  * refuses to create it. Neither replaces the other: content is git-backed, so it also arrives by
6
- * merge, by PR, by direct commit and by adopters retrofitting an existing repo — none of which
7
- * pass through this boundary — while the build guard cannot help an editor who is about to author
8
- * the collision right now.
9
- *
10
- * A SCHEMA edit alone can also create one, with no content write at all: `updateEntryType`
11
- * changing a format changes the listing's `validExts`, which can flip a previously-unlisted
12
- * `guide.index.{id}.json` into a published entry and so into a contested pair. Another reason
13
- * neither half of this invariant subsumes the other.
6
+ * merge, PR, direct commit and retrofit of an existing repo — none of which pass this boundary —
7
+ * while the build guard cannot help an editor about to author the collision right now. A SCHEMA
8
+ * edit alone can create one with no content write at all: `updateEntryType` changing a format
9
+ * changes the listing's `validExts`, which can turn a `guide.index.{id}.json` the listing skipped
10
+ * into a published entry, and so into a contested pair.
14
11
  *
15
12
  * Deliberately formulated on `urlPath`, not on names. An entry whose slug matches a sibling
16
13
  * collection is only a problem when that collection ALSO has an index entry:
17
14
  *
18
- * content/docs/page.guides.{id}.json + content/docs/guides.{id}/ (no index) -> fine.
19
- * A landing page plus a folder of children. Both are reachable; nothing is contested.
20
- *
21
- * content/docs/page.guides.{id}.json + content/docs/guides.{id}/doc.index.{id}.md
22
- * -> contested. Both compute /docs/guides, so exactly one of them is reachable and the
23
- * other silently has no route.
15
+ * content/docs/page.guides.{id}.json + content/docs/guides.{id}/ (no index) -> fine: a
16
+ * landing page plus a folder of children, both reachable, nothing contested.
17
+ * content/docs/page.guides.{id}.json + content/docs/guides.{id}/doc.index.{id}.md -> contested:
18
+ * both compute /docs/guides, so exactly one is reachable and the other has no route.
24
19
  *
25
- * A name-collision rule would forbid the first, which is a shape adopters legitimately build.
26
- *
27
- * Everything here works on PHYSICAL directories, because that is what the write path holds when
28
- * it needs the answer, and because the on-disk names carry the content IDs that logical paths
29
- * hide (`guides.{id}/`, `doc.index.{id}.md`).
20
+ * A name-collision rule would forbid the first, a shape adopters legitimately build. Everything
21
+ * here works on PHYSICAL directories, because that is what the write path holds when it needs the
22
+ * answer, and the on-disk names carry the content IDs logical paths hide (`guides.{id}/`).
30
23
  *
31
24
  * SCOPE, and do not widen it by accident: this guards the URL an ENTRY claims -- the `urlPath`
32
- * that `computeEntryUrl` derives and `listEntries` publishes. It is NOT "every URL the site
33
- * serves", and the two are not the same set. A framework adapter can route an entry somewhere
34
- * else — adopters add hand-written routes Canopy knows nothing about, and a framework adapter can
35
- * rewrite a sitemap `<loc>` after enumeration (`generateContentSitemap`'s `pathFor` option, in
36
- * `canopycms-next/src/static.ts`, does exactly that). Those URLs are
37
- * invisible here by design -- there is no filesystem state to check them against at write time.
38
- * A future change that tries to make this the authority on "the set of URLs this site claims"
39
- * would be reaching past what the write boundary can actually see.
25
+ * `computeEntryUrl` derives and `listEntries` publishes -- not "every URL the site serves".
26
+ * Hand-written adopter routes, and a sitemap `<loc>` a framework adapter rewrites after
27
+ * enumeration (`generateContentSitemap`'s `pathFor`), are invisible here by design: at write time
28
+ * there is no filesystem state to check them against.
40
29
  */
41
30
  /** One entry that already claims the `urlPath` a pending write would produce. */
42
31
  export interface UrlPathClaimant {
43
32
  /**
44
- * Which of the two shapes was found. Callers word their own message from this, because the
45
- * actionable advice differs: for `sibling-collection-index` the author should rename their new
46
- * entry, for `parent-entry` they are adding the index entry that makes an existing pair
47
- * contested.
33
+ * Which of the two shapes was found. Callers word their own message from this: for
34
+ * `sibling-collection-index` the author should rename their new entry, for `parent-entry` they
35
+ * are adding the index entry that makes an existing pair contested.
48
36
  */
49
37
  kind: 'sibling-collection-index' | 'parent-entry';
50
38
  /** Absolute path of the file already claiming that URL. */
@@ -53,11 +41,10 @@ export interface UrlPathClaimant {
53
41
  name: string;
54
42
  }
55
43
  /**
56
- * The index entry file directly inside `dir`, or null.
57
- *
58
- * Slug extraction is entry-type-agnostic (each candidate's own type is read out of its filename),
59
- * so an index entry of ANY entry type counts — same reasoning as the existing same-slug guard,
60
- * which is deliberately cross-type because the URL does not care what type serves it.
44
+ * The index entry file directly inside `dir`, or null. Slug extraction is entry-type-agnostic
45
+ * (each candidate's type is read out of its own filename), so an index entry of ANY type counts —
46
+ * like the same-slug guard, deliberately cross-type because the URL does not care what type
47
+ * serves it.
61
48
  */
62
49
  export declare function findIndexEntryIn(dir: string): Promise<string | null>;
63
50
  /** The entry file directly inside `dir` whose slug is `slug`, or null. */
@@ -69,11 +56,10 @@ export declare function findEntryBySlugIn(dir: string, slug: string): Promise<st
69
56
  * Only the cross-collection shapes are checked here. An entry colliding with another entry in the
70
57
  * SAME collection is already refused upstream (`ContentStore.buildPaths` resolves a write by a
71
58
  * type-agnostic slug scan, and the `expectedVersion: null` create-intent guard turns that into a
72
- * conflict), so re-checking it would be duplicate work and a second source of truth.
59
+ * conflict), so re-checking it would be a second source of truth.
73
60
  *
74
- * Two directory reads at worst, and only on create/rename — an ordinary save of an existing entry
75
- * never calls this, because the entry's URL is not changing and the collision (if any) predates
76
- * the write.
61
+ * Two directory reads at worst, and only on create/rename — an ordinary save never calls this,
62
+ * because the entry's URL is not changing and any collision predates the write.
77
63
  *
78
64
  * @param contentRoot Absolute path of the content root. A root-level index entry claims `/`, which
79
65
  * nothing above it can contest, so the `parent-entry` check stops here rather than walking out
@@ -8,18 +8,16 @@ import { parseTypedFilename } from './utils/typed-filename.js';
8
8
  * Every extension `getFormatExtension` can return.
9
9
  *
10
10
  * Compared CASE-SENSITIVELY, deliberately, because `listCollectionEntries` compares
11
- * case-sensitively (`d.name.endsWith(ext)` against lowercase extensions). Lowercasing here made
12
- * the guard looser than the listing: a `doc.index.{id}.MD` file counted as a claimant while the
13
- * listing skipped it silently — publishing no URL, tripping no build error, and blocking a write
14
- * the build would have accepted.
11
+ * case-sensitively (`d.name.endsWith(ext)` against lowercase extensions). Lowercasing here makes
12
+ * the guard looser than the listing: a `doc.index.{id}.MD` file would count as a claimant while
13
+ * the listing skips it silently — publishing no URL, tripping no build error, and blocking a
14
+ * write the build would have accepted.
15
15
  *
16
- * KNOWN RESIDUAL LOOSENESS, stated because the alternative is a comment that overclaims: the
17
- * listing accepts only the extensions of a collection's OWN configured entry-type formats, while
18
- * this accepts all four. A hand-authored `doc.index.{id}.json` inside an md-only collection
19
- * therefore claims no URL yet still blocks a sibling write. Closing that means threading each
20
- * collection's configured formats into a module that is deliberately schema-free, across three
21
- * call sites, and going from a physical directory back to a schema item to do it — a change with
22
- * more room to introduce a new defect than the narrow one it fixes. Tracked in
16
+ * KNOWN RESIDUAL LOOSENESS: the listing accepts only the extensions of a collection's OWN
17
+ * configured entry-type formats, while this accepts all four, so a hand-authored
18
+ * `doc.index.{id}.json` inside an md-only collection claims no URL yet still blocks a sibling
19
+ * write. Closing it means threading each collection's configured formats into a deliberately
20
+ * schema-free module; tracked in
23
21
  * .claude/future-tasks/url-collision-guard-superset-of-listing.md.
24
22
  *
25
23
  * A literal rather than derived: deriving it needs the `ContentFormat` union this module has no
@@ -55,53 +53,44 @@ async function findChildCollectionDir(dir, name) {
55
53
  *
56
54
  * This must recognise the set `listEntries` recognises, as closely as a schema-free module can,
57
55
  * because the invariant is defined over the `urlPath`s `listEntries` publishes and the build-time
58
- * half checks those. (One documented gap remains — see `CONTENT_EXTENSIONS`.) Hence
59
- * `parseTypedFilename` — the same `{type}.{slug}.{id}.{ext}` grammar `listCollectionEntries` uses
60
- * — rather than `extractSlugFromFilename`, which answers a different and much looser question
61
- * ("what would I call this file?") and happily names a slug for things that are not entries.
56
+ * half checks those. Hence `parseTypedFilename` — the same `{type}.{slug}.{id}.{ext}` grammar
57
+ * `listCollectionEntries` uses — rather than `extractSlugFromFilename`, which answers the much
58
+ * looser "what would I call this file?" and happily names a slug for things that are not entries.
62
59
  *
63
60
  * Getting this wrong OVER-BLOCKS, which is worse here than under-blocking: a hand-authored
64
- * `index.md` with no content ID, an `index.md~` editor backup, or a colocated `guides.png` are all
65
- * non-entries that claim no URL, so the build guard ignores them — but a looser scan counted them
66
- * as claimants and refused a legitimate write, telling the author to "remove that collection's
67
- * index entry" about a file Canopy does not consider an entry. That lands hardest on repos being
68
- * retrofitted onto CanopyCMS, which is precisely the audience this guard's migration note
69
- * addresses.
61
+ * `index.md` with no content ID, an `index.md~` editor backup or a colocated `guides.png` are
62
+ * non-entries that claim no URL, so the build guard ignores them — but a looser scan counts them
63
+ * as claimants and refuses a legitimate write, telling the author to "remove that collection's
64
+ * index entry" about a file Canopy does not consider an entry. Worst for retrofitted repos.
70
65
  *
71
- * Parsed structurally (no `entryTypes` list), which is the SECOND of three known ways this
72
- * claimant set is a strict superset of the listing's — all of them over-blocks, all of them
73
- * tracked together in .claude/future-tasks/url-collision-guard-superset-of-listing.md:
66
+ * The claimant set is nonetheless a strict superset of the listing's in three ways, all
67
+ * over-blocking and all needing the same fix — the schema this module deliberately does not have
68
+ * — so they are one task (.claude/future-tasks/url-collision-guard-superset-of-listing.md):
74
69
  *
75
70
  * 1. extension — see `CONTENT_EXTENSIONS` above;
76
- * 2. entry TYPE — a file whose type is not in the collection's config is skipped by the
77
- * listing but counted here. An earlier version of this comment dismissed that as "already
78
- * reported by the listing's malformed-file guard". That is true only in BUILD mode; in the
79
- * editor, where this guard actually runs, `listCollectionEntries` skips such a file with a
80
- * debug-gated warning and nothing surfaces. Reached by the accident
81
- * `looksLikeMalformedEntry` itself names as most common: renaming an entry type in
82
- * `.collection.json` without renaming the files;
71
+ * 2. entry TYPE — parsing is structural (no `entryTypes` list), so a file whose type is not in
72
+ * the collection's config is skipped by the listing but counted here. In the editor, where
73
+ * this guard runs, the listing skips such a file with a debug-gated warning and nothing
74
+ * surfaces; only a BUILD reports it. Reached by renaming an entry type in `.collection.json`
75
+ * without renaming the files — the accident `looksLikeMalformedEntry` names as most common;
83
76
  * 3. collection-hood — `findChildCollectionDir` matches any child directory by name, but a
84
77
  * directory with no `.collection.json` is not a collection, so nothing inside it publishes
85
78
  * a URL. Narrow (the file inside still needs valid entry grammar) but the same direction.
86
- *
87
- * All three need the same fix — the schema this module deliberately does not have — so they are
88
- * one task, not three.
89
79
  */
90
80
  function entrySlugOf(filename) {
91
81
  // `parseTypedFilename` strips the extension without checking it, so an editor backup
92
82
  // (`doc.index.{id}.md~`) parses exactly like the entry it shadows. `listCollectionEntries`
93
- // additionally requires a CONFIGURED format extension, so it skips those -- and this must skip
94
- // them too, or the guard refuses a write over a file the build guard never counted.
83
+ // additionally requires a configured format extension, so this must skip those too, or the
84
+ // guard refuses a write over a file the build guard never counted.
95
85
  if (!CONTENT_EXTENSIONS.some((ext) => filename.endsWith(ext)))
96
86
  return null;
97
87
  return parseTypedFilename(filename)?.slug ?? null;
98
88
  }
99
89
  /**
100
- * The index entry file directly inside `dir`, or null.
101
- *
102
- * Slug extraction is entry-type-agnostic (each candidate's own type is read out of its filename),
103
- * so an index entry of ANY entry type counts — same reasoning as the existing same-slug guard,
104
- * which is deliberately cross-type because the URL does not care what type serves it.
90
+ * The index entry file directly inside `dir`, or null. Slug extraction is entry-type-agnostic
91
+ * (each candidate's type is read out of its own filename), so an index entry of ANY type counts —
92
+ * like the same-slug guard, deliberately cross-type because the URL does not care what type
93
+ * serves it.
105
94
  */
106
95
  export async function findIndexEntryIn(dir) {
107
96
  for (const entry of await readDirSafe(dir)) {
@@ -130,11 +119,10 @@ export async function findEntryBySlugIn(dir, slug) {
130
119
  * Only the cross-collection shapes are checked here. An entry colliding with another entry in the
131
120
  * SAME collection is already refused upstream (`ContentStore.buildPaths` resolves a write by a
132
121
  * type-agnostic slug scan, and the `expectedVersion: null` create-intent guard turns that into a
133
- * conflict), so re-checking it would be duplicate work and a second source of truth.
122
+ * conflict), so re-checking it would be a second source of truth.
134
123
  *
135
- * Two directory reads at worst, and only on create/rename — an ordinary save of an existing entry
136
- * never calls this, because the entry's URL is not changing and the collision (if any) predates
137
- * the write.
124
+ * Two directory reads at worst, and only on create/rename — an ordinary save never calls this,
125
+ * because the entry's URL is not changing and any collision predates the write.
138
126
  *
139
127
  * @param contentRoot Absolute path of the content root. A root-level index entry claims `/`, which
140
128
  * nothing above it can contest, so the `parent-entry` check stops here rather than walking out
@@ -150,16 +138,13 @@ export async function findUrlPathClaimant(opts) {
150
138
  return null; // a root index claims '/', uncontestable
151
139
  const parentDir = path.dirname(collectionDir);
152
140
  const ownName = extractSlugFromFilename(path.basename(collectionDir));
153
- // A collection literally NAMED "index" collapses onto ITS OWN path (`<parentPath>/index`),
154
- // not onto `<parentPath>` -- see `computeEntryUrl`. So the only thing that could contest it
155
- // is another entry in the parent whose slug is ALSO the literal string "index" -- but per
156
- // that same forward rule, such an entry IS the parent's own index/landing entry, which
157
- // collapses onto `<parentPath>` itself, a different URL. Nothing in the parent can ever claim
158
- // `<parentPath>/index`, so skip the lookup below rather than let it match the parent's own
159
- // index entry and report a collision that does not exist. Without this, a collection named
160
- // "index" could never have a landing page under any parent that has one of its own --
161
- // ironic, since `resolveUrlPathCandidates`' candidate 2 was deliberately engineered to keep a
162
- // collection named "index" addressable in the first place.
141
+ // A collection literally NAMED "index" collapses onto ITS OWN path (`<parentPath>/index`), not
142
+ // onto `<parentPath>` -- see `computeEntryUrl`. The only entry that could contest it is a
143
+ // parent entry whose slug is also "index", but by that same forward rule such an entry IS the
144
+ // parent's own landing entry, at `<parentPath>`, a different URL. Nothing in the parent can
145
+ // claim `<parentPath>/index`, so skip the lookup rather than let it match the parent's index
146
+ // entry and report a collision that does not exist -- otherwise a collection named "index"
147
+ // could never have a landing page under a parent that has one of its own.
163
148
  if (isIndexSlug(ownName))
164
149
  return null;
165
150
  const claimant = await findEntryBySlugIn(parentDir, ownName);
@@ -168,14 +153,13 @@ export async function findUrlPathClaimant(opts) {
168
153
  // A plain entry sits at the same URL as a same-named child collection's index entry.
169
154
  //
170
155
  // KNOWN DEPENDENCY: this resolves ONE child collection by name, and the index direction above
171
- // likewise looks only for a parent ENTRY. Neither considers a second collection sharing the
172
- // same name, because nothing currently stops one being created -- `createCollectionInner` does
173
- // not check sibling names at all (tracked in collection-sibling-name-uniqueness.md). With two
174
- // `guides.{id}` directories side by side you can write an index entry into each, and both
175
- // writes pass this guard while two entries end up claiming one URL. The build guard still
176
- // catches it. Fixing sibling-name uniqueness closes this without changing anything here --
177
- // which is why that task is a prerequisite for this guard being complete, not an unrelated
178
- // tidy-up.
156
+ // likewise looks only for a parent ENTRY. Neither considers a second collection sharing the same
157
+ // name, because nothing stops one being created -- `createCollectionInner` does not check
158
+ // sibling names (tracked in collection-sibling-name-uniqueness.md). With two `guides.{id}`
159
+ // directories side by side, an index entry can be written into each: both writes pass this
160
+ // guard, two entries claim one URL, and only the build guard catches it. Fixing sibling-name
161
+ // uniqueness closes this without changing anything here, so that task is a prerequisite for
162
+ // this guard being complete.
179
163
  const childDir = await findChildCollectionDir(collectionDir, slug);
180
164
  if (!childDir)
181
165
  return null;
@@ -2,22 +2,18 @@
2
2
  * The one-URL-per-entry invariant, as a reusable probe + report.
3
3
  *
4
4
  * `listEntries` assigns every entry exactly one `urlPath` and documents the round trip through
5
- * `readByUrlPath` as safe. The reverse direction has repeatedly been the looser of the two: it
6
- * has answered at URLs no forward surface emits, and those extra URLs were found one at a time,
7
- * a release apart, by adopters (see
8
- * `.claude/future-tasks/resolved/url-resolver-index-entry-extra-url.md`, then
9
- * `resolved/readbyurlpath-entry-type-candidate-phantom-url.md`). This module exists so the whole
10
- * invariant is asserted at once instead: enumerate, then probe every ADJACENT URL the resolver
11
- * would try and require it to be a miss.
5
+ * `readByUrlPath` as safe. The reverse direction is the looser of the two and can answer at URLs
6
+ * no forward surface emits, so this module asserts the whole invariant at once: enumerate, then
7
+ * probe every ADJACENT URL the resolver would try and require it to be a miss.
12
8
  *
13
- * Deliberately free of `vitest` -- this is a plain `src` module in the shape of
9
+ * Deliberately free of `vitest` -- a plain `src` module in the shape of
14
10
  * `operating-mode/deployment-name-fixtures.ts`, so more than one test file can import it without
15
11
  * dragging that file's `vi.mock` calls and `describe` blocks along. It therefore ASSERTS NOTHING:
16
12
  * it returns a report and the caller does the expecting, which also puts the offending URLs in
17
13
  * the assertion message rather than behind a boolean.
18
14
  *
19
15
  * Scope of the invariant it checks: entries written in the `{type}.{slug}.{id}.{ext}` grammar
20
- * with a type their collection declares -- i.e. exactly the set `listEntries` can see. A legacy
16
+ * with a type their collection declares -- exactly the set `listEntries` can see. A legacy
21
17
  * untyped file (`overview.json`) is invisible to enumeration and is deliberately NOT probed here;
22
18
  * see `.claude/future-tasks/legacy-untyped-files-url-addressable.md`.
23
19
  */
@@ -26,9 +22,8 @@ import type { ListEntriesItem } from './content-listing.js';
26
22
  import type { CanopyContext } from './context.js';
27
23
  import { type DuplicateUrlPath } from './static/index.js';
28
24
  /**
29
- * Every URL adjacent to the published set that the resolver would actually attempt.
30
- *
31
- * Three families, each one a shape that HAS resolved at some point in this package's history:
25
+ * Every URL adjacent to the published set that the resolver would actually attempt, in three
26
+ * families:
32
27
  *
33
28
  * 1. `/<collection>/<entryTypeName>` -- `resolveUrlPathCandidates`' index-fallback candidate lands
34
29
  * on a registered entry-TYPE schema item, which `ContentStore.buildPaths` delegates to the
@@ -40,16 +35,16 @@ import { type DuplicateUrlPath } from './static/index.js';
40
35
  * spelling its collapsed URL replaced.
41
36
  *
42
37
  * Entry-type names are appended VERBATIM, after `computeEntryUrl` has lowercased the collection
43
- * part. That asymmetry is real, not sloppiness: `flattenSchema` puts `entryType.name` into the
44
- * logical path unchanged and `normalizeFilesystemPath` does not lowercase, so only the declared
45
- * spelling can reach the entry-type schema item at all. A lowercased probe would miss the schema
46
- * item, return null for the wrong reason, and pass vacuously.
38
+ * part. That asymmetry is real: `flattenSchema` puts `entryType.name` into the logical path
39
+ * unchanged and `normalizeFilesystemPath` does not lowercase, so only the declared spelling can
40
+ * reach the entry-type schema item at all. A lowercased probe would miss the schema item, return
41
+ * null for the wrong reason, and pass vacuously.
47
42
  */
48
43
  export declare const buildProbeUrls: (schema: RootCollectionConfig, items: readonly ListEntriesItem[], contentRoot?: string) => string[];
49
44
  export interface UrlExclusivityReport {
50
45
  /** Every `urlPath` the listing published, sorted. */
51
46
  published: string[];
52
- /** URLs claimed by more than one entry. A precondition failure, not a resolver finding. */
47
+ /** URLs claimed by more than one entry. A precondition failure, not a resolver result. */
53
48
  duplicates: DuplicateUrlPath[];
54
49
  /** Published URLs `readByUrlPath` did NOT resolve -- the round trip broken going forward. */
55
50
  unresolved: string[];
@@ -65,12 +60,12 @@ export interface UrlExclusivityReport {
65
60
  phantoms: string[];
66
61
  }
67
62
  /**
68
- * Enumerate, round-trip, then probe. See `UrlExclusivityReport` for what each field means and
63
+ * Enumerate, round-trip, then probe. See `UrlExclusivityReport` for the fields and
69
64
  * `buildProbeUrls` for which URLs are probed.
70
65
  *
71
66
  * A probe whose LOWERCASED form is published is skipped rather than asserted on: a collection
72
67
  * literally named `index` publishes `/docs/index`, which family 3 also generates, and whether
73
- * `/docs/Index` should resolve is a separate, pre-existing question about collection-path case
74
- * sensitivity that this invariant does not speak to.
68
+ * `/docs/Index` should resolve is a separate question about collection-path case sensitivity that
69
+ * this invariant does not speak to.
75
70
  */
76
71
  export declare const collectUrlExclusivityReport: (ctx: Pick<CanopyContext, "listEntries" | "readByUrlPath">, schema: RootCollectionConfig, contentRoot?: string) => Promise<UrlExclusivityReport>;
@@ -2,22 +2,18 @@
2
2
  * The one-URL-per-entry invariant, as a reusable probe + report.
3
3
  *
4
4
  * `listEntries` assigns every entry exactly one `urlPath` and documents the round trip through
5
- * `readByUrlPath` as safe. The reverse direction has repeatedly been the looser of the two: it
6
- * has answered at URLs no forward surface emits, and those extra URLs were found one at a time,
7
- * a release apart, by adopters (see
8
- * `.claude/future-tasks/resolved/url-resolver-index-entry-extra-url.md`, then
9
- * `resolved/readbyurlpath-entry-type-candidate-phantom-url.md`). This module exists so the whole
10
- * invariant is asserted at once instead: enumerate, then probe every ADJACENT URL the resolver
11
- * would try and require it to be a miss.
5
+ * `readByUrlPath` as safe. The reverse direction is the looser of the two and can answer at URLs
6
+ * no forward surface emits, so this module asserts the whole invariant at once: enumerate, then
7
+ * probe every ADJACENT URL the resolver would try and require it to be a miss.
12
8
  *
13
- * Deliberately free of `vitest` -- this is a plain `src` module in the shape of
9
+ * Deliberately free of `vitest` -- a plain `src` module in the shape of
14
10
  * `operating-mode/deployment-name-fixtures.ts`, so more than one test file can import it without
15
11
  * dragging that file's `vi.mock` calls and `describe` blocks along. It therefore ASSERTS NOTHING:
16
12
  * it returns a report and the caller does the expecting, which also puts the offending URLs in
17
13
  * the assertion message rather than behind a boolean.
18
14
  *
19
15
  * Scope of the invariant it checks: entries written in the `{type}.{slug}.{id}.{ext}` grammar
20
- * with a type their collection declares -- i.e. exactly the set `listEntries` can see. A legacy
16
+ * with a type their collection declares -- exactly the set `listEntries` can see. A legacy
21
17
  * untyped file (`overview.json`) is invisible to enumeration and is deliberately NOT probed here;
22
18
  * see `.claude/future-tasks/legacy-untyped-files-url-addressable.md`.
23
19
  */
@@ -27,9 +23,8 @@ import { computeEntryUrl } from './utils/entry-url.js';
27
23
  /** Append raw segments to a URL base, without normalizing their case (see buildProbeUrls). */
28
24
  const joinUrl = (base, ...segments) => (base === '/' ? '' : base) + segments.map((s) => `/${s}`).join('');
29
25
  /**
30
- * Every URL adjacent to the published set that the resolver would actually attempt.
31
- *
32
- * Three families, each one a shape that HAS resolved at some point in this package's history:
26
+ * Every URL adjacent to the published set that the resolver would actually attempt, in three
27
+ * families:
33
28
  *
34
29
  * 1. `/<collection>/<entryTypeName>` -- `resolveUrlPathCandidates`' index-fallback candidate lands
35
30
  * on a registered entry-TYPE schema item, which `ContentStore.buildPaths` delegates to the
@@ -41,10 +36,10 @@ const joinUrl = (base, ...segments) => (base === '/' ? '' : base) + segments.map
41
36
  * spelling its collapsed URL replaced.
42
37
  *
43
38
  * Entry-type names are appended VERBATIM, after `computeEntryUrl` has lowercased the collection
44
- * part. That asymmetry is real, not sloppiness: `flattenSchema` puts `entryType.name` into the
45
- * logical path unchanged and `normalizeFilesystemPath` does not lowercase, so only the declared
46
- * spelling can reach the entry-type schema item at all. A lowercased probe would miss the schema
47
- * item, return null for the wrong reason, and pass vacuously.
39
+ * part. That asymmetry is real: `flattenSchema` puts `entryType.name` into the logical path
40
+ * unchanged and `normalizeFilesystemPath` does not lowercase, so only the declared spelling can
41
+ * reach the entry-type schema item at all. A lowercased probe would miss the schema item, return
42
+ * null for the wrong reason, and pass vacuously.
48
43
  */
49
44
  export const buildProbeUrls = (schema, items, contentRoot = 'content') => {
50
45
  const collections = flattenSchema(schema, contentRoot).filter((i) => i.type === 'collection');
@@ -73,13 +68,13 @@ export const buildProbeUrls = (schema, items, contentRoot = 'content') => {
73
68
  return [...probes];
74
69
  };
75
70
  /**
76
- * Enumerate, round-trip, then probe. See `UrlExclusivityReport` for what each field means and
71
+ * Enumerate, round-trip, then probe. See `UrlExclusivityReport` for the fields and
77
72
  * `buildProbeUrls` for which URLs are probed.
78
73
  *
79
74
  * A probe whose LOWERCASED form is published is skipped rather than asserted on: a collection
80
75
  * literally named `index` publishes `/docs/index`, which family 3 also generates, and whether
81
- * `/docs/Index` should resolve is a separate, pre-existing question about collection-path case
82
- * sensitivity that this invariant does not speak to.
76
+ * `/docs/Index` should resolve is a separate question about collection-path case sensitivity that
77
+ * this invariant does not speak to.
83
78
  */
84
79
  export const collectUrlExclusivityReport = async (ctx, schema, contentRoot = 'content') => {
85
80
  const items = await ctx.listEntries();
@@ -1,23 +1,15 @@
1
1
  /**
2
- * Resolves a URL path to candidate entryPath/slug pairs for content lookup.
2
+ * Resolve a URL path to the candidate entryPath/slug pairs a lookup should try, in order:
3
3
  *
4
- * Returns an ordered list of attempts:
5
- * 1. Direct entry: last segment is slug, rest is collection path — SKIPPED when that slug is
6
- * literally 'index' (see below)
7
- * 2. Index fallback: full path is collection, slug is 'index'
4
+ * 1. Direct entry: last segment is the slug, the rest is the collection path — SKIPPED when that
5
+ * slug is an index slug (see below).
6
+ * 2. Index fallback: the whole path is a collection and the slug is 'index'.
8
7
  *
9
- * This is the REVERSE of the forward collection+slug -> url rule, and the two must agree on how
10
- * many URLs an entry answers at. The forward rule (`computeEntryUrl` in utils/entry-url.ts, which
8
+ * This is the REVERSE of the forward collection+slug -> url rule and must agree with it about how
9
+ * many URLs an entry answers at. The forward rule — `computeEntryUrl` (utils/entry-url.ts), which
11
10
  * `listEntries` publishes as `item.urlPath` and reference resolution stamps on every resolved
12
- * reference; and `defaultBuildPath` in content-tree.ts for tree nodes) collapses an `index` slug
13
- * onto its collection's path and never emits a trailing `/index`. Candidate 1 used to match it
14
- * anyway, so an index entry ALSO answered at a `.../index` URL that no forward surface publishes
15
- * — enumeration and resolution disagreeing about how many URLs exist. Adopters paid for that with
16
- * per-route entryType gates whose only job was to reject the phantom.
17
- *
18
- * @param urlPath - URL path like '/docs/guides/getting-started' or 'docs/guides'
19
- * @param contentRoot - Content root directory name (default: 'content')
20
- * @returns Array of { entryPath, slug } candidates to try in order
11
+ * reference, plus `defaultBuildPath` (content-tree.ts) for tree nodes — collapses an `index` slug
12
+ * onto its collection's path and never emits a trailing `/index`.
21
13
  */
22
14
  export declare function resolveUrlPathCandidates(urlPath: string, contentRoot: string): Array<{
23
15
  entryPath: string;