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
@@ -6,19 +6,15 @@ import { canopyLogWarn } from './logger.js';
6
6
  /**
7
7
  * Shared option set for both provisioning-lock variants.
8
8
  *
9
- * **The lock is anchored on the lock file's own path, not its directory.**
10
- * proper-lockfile keys its module-level `locks{}` bookkeeping (refresh timer,
11
- * release fn) by the TARGET path passed to `lock()` — not by `lockfilePath`.
12
- * Passing the shared branches directory made every branch under one root alias
13
- * a single registry entry: acquiring `.b.init.lock` overwrote `.a.init.lock`'s
14
- * entry, so releasing A tore down B's refresh timer, made B's own release fail
15
- * with `ERELEASED`, and leaked B's lock directory on disk until `stale`
16
- * expired. The orphaned refresh timer then `stat`ed a path its owner had
17
- * already deleted, raising `ECOMPROMISED`. Anchoring here makes the registry
18
- * key identical to the on-disk lock identity, so two live locks can never
19
- * share a key. `realpath: false` because the anchor path is the lock marker
20
- * itself and does not exist before we create it (realpath would ENOENT).
21
- * See docs/concurrency.md ("Anchor path matters").
9
+ * **The lock is anchored on the lock file's own path, not its directory.** proper-lockfile keys
10
+ * its module-level `locks{}` bookkeeping (refresh timer, release fn) by the TARGET path passed
11
+ * to `lock()`, not by `lockfilePath`, so passing a shared parent makes every branch under one
12
+ * root alias a single registry entry: releasing one tears down the other's refresh timer, fails
13
+ * its release with `ERELEASED`, leaks its lock directory until `stale` expires, and leaves an
14
+ * orphaned timer to `stat` a deleted path and raise `ECOMPROMISED`. Anchoring on the marker
15
+ * makes the registry key identical to the on-disk lock identity, so two live locks can never
16
+ * share one. `realpath: false` because that anchor path does not exist before we create it
17
+ * (realpath would ENOENT). See docs/concurrency.md ("Anchor path matters").
22
18
  */
23
19
  function provisioningLockOptions(lockPath, retries, onCompromised) {
24
20
  return {
@@ -26,30 +22,27 @@ function provisioningLockOptions(lockPath, retries, onCompromised) {
26
22
  realpath: false,
27
23
  retries,
28
24
  stale: 30_000,
29
- // proper-lockfile invokes this from inside its refresh timer, so ANY throw
30
- // escaping here is an uncaught exception that kills the process -- the very
31
- // failure the handler exists to prevent. Call sites are told not to throw
32
- // (see OnLockCompromised); this makes it structural rather than a
33
- // convention, and also covers the LOGGER throwing: under `CI=true`,
34
- // vitest's `onConsoleLog` turns any console write into a throw.
25
+ // proper-lockfile invokes this from inside its refresh timer, so ANY throw escaping here is
26
+ // an uncaught exception that kills the process. Call sites are told not to throw (see
27
+ // OnLockCompromised); this makes it structural rather than a convention, and also covers the
28
+ // LOGGER throwing -- under `CI=true`, vitest's `onConsoleLog` turns a console write into a
29
+ // throw.
35
30
  onCompromised: (err) => {
36
31
  try {
37
32
  if (onCompromised) {
38
33
  onCompromised(err);
39
34
  return;
40
35
  }
41
- // Default: log and let the holder finish. proper-lockfile's own default
42
- // rethrows from the refresh timer, which protects nothing -- by the time
43
- // a compromise is reported the mutual exclusion is already gone.
44
- // `canopyLogWarn` (not the debug logger) because this means "two
45
- // holders may now be live": it must be visible without CANOPYCMS_DEBUG.
36
+ // Default: log and let the holder finish. proper-lockfile's own default rethrows from
37
+ // the refresh timer, which protects nothing -- by the time a compromise is reported the
38
+ // mutual exclusion is already gone. `canopyLogWarn`, not the debug logger, because "two
39
+ // holders may now be live" must be visible without CANOPYCMS_DEBUG.
46
40
  canopyLogWarn(`[canopy] Provisioning lock compromised mid-hold for ${lockPath}:`, getErrorMessage(err));
47
41
  }
48
42
  catch {
49
- // Last resort: a raw stderr write goes around both a replaced logger
50
- // and vitest's console interception, so the compromise still leaves a
51
- // trace. Guarded in turn, because nothing above this line is allowed
52
- // to throw out of a refresh timer.
43
+ // Last resort: a raw stderr write goes around both a replaced logger and vitest's console
44
+ // interception, so the compromise still leaves a trace. Guarded in turn, because nothing
45
+ // here is allowed to throw out of a refresh timer.
53
46
  try {
54
47
  process.stderr.write(`[canopy] lock compromised for ${lockPath}; its handler or logger threw\n`);
55
48
  }
@@ -61,12 +54,11 @@ function provisioningLockOptions(lockPath, retries, onCompromised) {
61
54
  };
62
55
  }
63
56
  /**
64
- * proper-lockfile's release rejects with `ERELEASED` once the lock has been
65
- * marked compromised (`setLockAsCompromised` sets `released = true` before the
66
- * caller ever gets to release). Callers hold this in a `finally`, so letting
67
- * that escape would convert a COMPLETED operation into a spurious failure.
68
- * There is nothing left to release in that state, so swallow just that code and
69
- * let every other release error propagate to the caller that asked for it.
57
+ * proper-lockfile's release rejects with `ERELEASED` once the lock has been marked compromised
58
+ * (`setLockAsCompromised` sets `released = true` before the caller gets to release). Callers hold
59
+ * this in a `finally`, so letting it escape converts a COMPLETED operation into a spurious
60
+ * failure. Nothing is left to release in that state, so swallow just that code and let every
61
+ * other release error propagate.
70
62
  */
71
63
  function releaseIgnoringAlreadyReleased(release, lockPath) {
72
64
  return async () => {
@@ -75,9 +67,8 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
75
67
  }
76
68
  catch (err) {
77
69
  if (isNodeError(err) && err.code === 'ERELEASED') {
78
- // Deliberately does not name a cause: ERELEASED is raised both by a
79
- // lock compromised mid-hold AND by a plain double-release bug, and
80
- // from here the two are indistinguishable.
70
+ // Deliberately does not name a cause: ERELEASED is raised both by a lock compromised
71
+ // mid-hold and by a plain double-release bug, indistinguishable from here.
81
72
  canopyLogWarn(`[canopy] Lock at ${lockPath} was already released (compromised mid-hold, or released twice); nothing to release`);
82
73
  return;
83
74
  }
@@ -86,51 +77,40 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
86
77
  };
87
78
  }
88
79
  /**
89
- * Acquire a cross-process filesystem lock for content provisioning.
80
+ * Acquire a cross-process filesystem lock for content provisioning. Returns a release function —
81
+ * always call it in a `finally`.
90
82
  *
91
- * In-memory locks only serialize work within a single process. Build tools such
92
- * as Next.js static generation provision content from parallel worker
93
- * *processes*, so two processes can otherwise race while creating the same git
94
- * workspace (e.g. "cannot mkdir remote.git: File exists" or "destination path
95
- * already exists"). proper-lockfile uses an atomic on-disk lock that holds across
96
- * processes, so only one provisions a given resource at a time while the others
97
- * wait and then find it already done.
83
+ * In-memory locks serialize only within one process, but build tools such as Next.js static
84
+ * generation provision content from parallel worker *processes*, which then race while creating
85
+ * the same git workspace ("cannot mkdir remote.git: File exists", "destination path already
86
+ * exists"). proper-lockfile's atomic on-disk lock holds across processes, so one provisions while
87
+ * the others wait and then find it already done.
98
88
  *
99
- * Returns a release function — always call it in a `finally`.
100
- *
101
- * @param lockTargetDir directory the lock marker lives in (created if missing)
102
- * @param lockName name of the on-disk lock marker, created inside lockTargetDir
103
89
  * @param onCompromised see {@link OnLockCompromised}; defaults to log-and-continue
104
90
  */
105
91
  export async function acquireProvisioningLock(lockTargetDir, lockName, onCompromised) {
106
92
  await fs.mkdir(lockTargetDir, { recursive: true });
107
93
  const lockPath = path.join(lockTargetDir, lockName);
108
- // Generous, jittered retries: several processes may contend for one workspace
109
- // (e.g. Lambda containers cold-starting together against one EFS root), and
110
- // the holder can take several seconds to init + clone/push. `randomize` de-syncs the herd so a waiter isn't
111
- // perpetually colliding on the same tick. `stale` stays modest because proper-
112
- // lockfile auto-refreshes a live holder's lock, so it only expires when a
113
- // process actually dies.
94
+ // Generous, jittered retries: several processes may contend for one workspace (Lambda
95
+ // containers cold-starting together against one EFS root), and the holder can take seconds to
96
+ // init plus clone/push. `randomize` de-syncs the herd so a waiter is not perpetually colliding
97
+ // on the same tick. `stale` stays modest because proper-lockfile auto-refreshes a live holder's
98
+ // lock, so it expires only when a process actually dies.
114
99
  const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true }, onCompromised));
115
100
  return releaseIgnoringAlreadyReleased(release, lockPath);
116
101
  }
117
102
  /**
118
- * Zero-retry variant of {@link acquireProvisioningLock}, for admin actions
119
- * running inside a synchronous request/response cycle (e.g. a Lambda-backed
120
- * API handler). `acquireProvisioningLock`'s ~600-retry budget waits minutes
121
- * for a live provisioner to finish; an admin request must fail fast on
122
- * contention instead (409 immediately) rather than hang the request for that
123
- * long.
103
+ * Zero-retry variant of {@link acquireProvisioningLock}, for admin actions inside a synchronous
104
+ * request/response cycle (a Lambda-backed API handler). `acquireProvisioningLock`'s ~600-retry
105
+ * budget waits minutes for a live provisioner; an admin request must fail fast instead.
124
106
  *
125
- * `stale: 30_000` is unchanged from the patient variant: a genuinely stale
126
- * lock (holder crashed more than 30s ago) is still taken over normally --
127
- * this only removes the RETRY loop for live contention, not the staleness
128
- * recovery a caller depends on (see branch-health.ts's [H1] freshness rail,
129
- * which reads this same lock's mtime to decide whether it is fresh or
130
- * stale before an admin purge/repair proceeds).
107
+ * `stale: 30_000` is unchanged from the patient variant: a genuinely stale lock (holder crashed
108
+ * more than 30s ago) is still taken over normally. Only the RETRY loop for live contention is
109
+ * removed, not the staleness recovery a caller depends on -- see branch-health.ts's [H1]
110
+ * freshness rail, which reads this lock's mtime before an admin purge/repair proceeds.
131
111
  *
132
- * Throws with `err.code === 'ELOCKED'` on contention (a live, non-stale
133
- * holder) -- callers should translate that into a 409.
112
+ * Throws with `err.code === 'ELOCKED'` on contention (a live, non-stale holder) -- callers
113
+ * translate that into a 409.
134
114
  */
135
115
  export async function tryAcquireProvisioningLock(lockTargetDir, lockName, onCompromised) {
136
116
  await fs.mkdir(lockTargetDir, { recursive: true });
@@ -14,9 +14,8 @@ export declare function declaresScheme(url: string): boolean;
14
14
  * Whether `url`, resolved as a URL reference against a fixed sentinel origin, lands on a
15
15
  * DIFFERENT origin than the sentinel WITHOUT itself declaring a scheme.
16
16
  *
17
- * True for a literal protocol-relative reference (`//host`) and for every WHATWG
18
- * backslash-equivalent spelling that a browser resolves the same way (`/\host`, `\\host`,
19
- * `\/host`, and combinations with stripped whitespace/control characters) -- these all read as
17
+ * True for a literal protocol-relative reference (`//host`) and for every backslash-equivalent
18
+ * spelling a browser resolves the same way (see `declaresScheme`) -- these all read as
20
19
  * "site-relative" to a naive string check but actually redefine the authority. False for a
21
20
  * normal site-relative path (`/about`, `docs/guide`) and for a scheme-qualified absolute URL
22
21
  * (`declaresScheme` already identifies those as intentionally off-site on their own).
@@ -25,6 +24,7 @@ export declare function declaresScheme(url: string): boolean;
25
24
  * (e.g. `utils/url-prefix.ts`'s `isAbsoluteUrl`, for a CDN-hosted `ogImage`) should check that
26
25
  * separately and only fall back to this function to catch the spellings that slip past a
27
26
  * `startsWith('//')` check.
27
+ * @internal Exported for tests.
28
28
  */
29
29
  export declare function isImplicitlyOffOrigin(url: string): boolean;
30
30
  /**
@@ -36,7 +36,7 @@ export declare function isImplicitlyOffOrigin(url: string): boolean;
36
36
  * slash/backslash characters: WHATWG URL also strips tabs, newlines, and carriage returns during
37
37
  * parsing (wherever they appear, not just at the edges), so `/\t/evil.com` is exactly as
38
38
  * off-origin as `/\evil.com` despite having no leading backslash-or-slash run for a regex to
39
- * find. Re-parsing gets this right for free instead of re-deriving the quirk a second time.
39
+ * find.
40
40
  *
41
41
  * Useful for a caller that wants to keep emitting SOME value for an implicitly-off-origin input
42
42
  * rather than rejecting it outright (`sanitizeHref` rejects to a fallback instead; see
@@ -102,12 +102,7 @@ export declare function isHttpUrlOrSameOriginPath(value: string, opts?: {
102
102
  * likely to be a paste error or an injection attempt than an intentional
103
103
  * protocol-relative link, so we don't let it through as an absolute
104
104
  * off-site URL. This is enforced by checking whether the input DECLARES a
105
- * scheme, not by matching a `//` prefix: WHATWG URL treats backslash as
106
- * equivalent to slash for special schemes, so `/\evil.com`, `\\evil.com`
107
- * and `\/evil.com` are all protocol-relative in effect. An earlier version
108
- * of this function checked `startsWith('//')` and let all three through as
109
- * `https://evil.com/` -- an open redirect out of the one function whose
110
- * job is to prevent exactly that.
105
+ * scheme, not by matching a `//` prefix -- see `declaresScheme`.
111
106
  * - Fragment-only (`#section`) and query-only (`?q=1`) input resolves
112
107
  * against the sentinel with pathname `/`; we strip that synthetic leading
113
108
  * slash so the result stays a same-page reference (`#section`) instead of
@@ -35,9 +35,8 @@ export function declaresScheme(url) {
35
35
  * Whether `url`, resolved as a URL reference against a fixed sentinel origin, lands on a
36
36
  * DIFFERENT origin than the sentinel WITHOUT itself declaring a scheme.
37
37
  *
38
- * True for a literal protocol-relative reference (`//host`) and for every WHATWG
39
- * backslash-equivalent spelling that a browser resolves the same way (`/\host`, `\\host`,
40
- * `\/host`, and combinations with stripped whitespace/control characters) -- these all read as
38
+ * True for a literal protocol-relative reference (`//host`) and for every backslash-equivalent
39
+ * spelling a browser resolves the same way (see `declaresScheme`) -- these all read as
41
40
  * "site-relative" to a naive string check but actually redefine the authority. False for a
42
41
  * normal site-relative path (`/about`, `docs/guide`) and for a scheme-qualified absolute URL
43
42
  * (`declaresScheme` already identifies those as intentionally off-site on their own).
@@ -46,6 +45,7 @@ export function declaresScheme(url) {
46
45
  * (e.g. `utils/url-prefix.ts`'s `isAbsoluteUrl`, for a CDN-hosted `ogImage`) should check that
47
46
  * separately and only fall back to this function to catch the spellings that slip past a
48
47
  * `startsWith('//')` check.
48
+ * @internal Exported for tests.
49
49
  */
50
50
  export function isImplicitlyOffOrigin(url) {
51
51
  if (declaresScheme(url))
@@ -66,7 +66,7 @@ export function isImplicitlyOffOrigin(url) {
66
66
  * slash/backslash characters: WHATWG URL also strips tabs, newlines, and carriage returns during
67
67
  * parsing (wherever they appear, not just at the edges), so `/\t/evil.com` is exactly as
68
68
  * off-origin as `/\evil.com` despite having no leading backslash-or-slash run for a regex to
69
- * find. Re-parsing gets this right for free instead of re-deriving the quirk a second time.
69
+ * find.
70
70
  *
71
71
  * Useful for a caller that wants to keep emitting SOME value for an implicitly-off-origin input
72
72
  * rather than rejecting it outright (`sanitizeHref` rejects to a fallback instead; see
@@ -197,12 +197,7 @@ export function isHttpUrlOrSameOriginPath(value, opts = {}) {
197
197
  * likely to be a paste error or an injection attempt than an intentional
198
198
  * protocol-relative link, so we don't let it through as an absolute
199
199
  * off-site URL. This is enforced by checking whether the input DECLARES a
200
- * scheme, not by matching a `//` prefix: WHATWG URL treats backslash as
201
- * equivalent to slash for special schemes, so `/\evil.com`, `\\evil.com`
202
- * and `\/evil.com` are all protocol-relative in effect. An earlier version
203
- * of this function checked `startsWith('//')` and let all three through as
204
- * `https://evil.com/` -- an open redirect out of the one function whose
205
- * job is to prevent exactly that.
200
+ * scheme, not by matching a `//` prefix -- see `declaresScheme`.
206
201
  * - Fragment-only (`#section`) and query-only (`?q=1`) input resolves
207
202
  * against the sentinel with pathname `/`; we strip that synthetic leading
208
203
  * slash so the result stays a same-page reference (`#section`) instead of
@@ -232,14 +227,9 @@ export function sanitizeHref(url, fallback = '#') {
232
227
  return fallback;
233
228
  }
234
229
  // Reject protocol-relative references in EVERY spelling, by testing the
235
- // property we actually care about rather than by enumerating syntax.
236
- //
237
- // A string-prefix check on '//' is not sufficient: WHATWG URL treats a
238
- // backslash as equivalent to a forward slash for special schemes, so
239
- // '/\evil.com', '\\evil.com' and '\/evil.com' are all protocol-relative
240
- // in effect and each resolved to https://evil.com/ while sailing past a
241
- // startsWith('//') guard. Tabs and newlines are stripped during parsing
242
- // too, so the set of spellings is not one you can enumerate confidently.
230
+ // property we actually care about rather than by enumerating syntax (see
231
+ // `declaresScheme`: a startsWith('//') guard misses the backslash
232
+ // spellings, and stripped tabs/newlines make the set unenumerable).
243
233
  //
244
234
  // The property that actually distinguishes "the author asked for another
245
235
  // origin" from "the parser inferred one" is whether the input DECLARES a
@@ -1,19 +1,21 @@
1
1
  import type { FieldConfig } from '../config/index.js';
2
2
  /**
3
- * Find the field marked `isTitle: true` in a schema and extract its value from data.
4
- * Recurses into object fields to support nested title fields (e.g., hero.title).
5
- * Returns the string value or undefined if not found or not a string.
3
+ * Value of the field marked `isTitle: true`, or undefined when there is none or it is not a
4
+ * string. Recurses into object fields, so a nested title (`hero.title`) is found.
5
+ * @internal Exported for tests.
6
6
  */
7
7
  export declare function extractTitleFromSchema(fields: readonly FieldConfig[], data: Record<string, unknown>): string | undefined;
8
- /** Convert a slug like "my-cool-page" to "My Cool Page". */
8
+ /**
9
+ * Convert a slug like "my-cool-page" to "My Cool Page".
10
+ * @internal Exported for tests.
11
+ */
9
12
  export declare function humanizeSlug(slug: string): string;
10
13
  /**
11
- * Resolve a display title for an entry using the full fallback chain:
12
- * 1. Schema-marked isTitle field (if schema provided)
13
- * 2. Convention: data.title or data.name
14
- * 3. Entry type label (if provided)
15
- * 4. Humanized slug (if provided)
16
- * 5. "Untitled"
14
+ * Display title for an entry, by fallback chain: schema-marked isTitle field, then `data.title`
15
+ * or `data.name`, then the entry type label, then the humanized slug, then "Untitled".
16
+ *
17
+ * Client-safe — its only dependency on `../config` is type-only — and re-exported from both
18
+ * `canopycms/server` and the root `canopycms` entry, so adopter client code can import it.
17
19
  */
18
20
  export declare function resolveEntryTitle(data: Record<string, unknown>, options?: {
19
21
  schema?: readonly FieldConfig[];
@@ -21,19 +23,14 @@ export declare function resolveEntryTitle(data: Record<string, unknown>, options
21
23
  slug?: string;
22
24
  }): string;
23
25
  /**
24
- * Count the number of fields marked `isTitle: true` in a schema, recursing into objects.
25
- * Skips `list: true` objects since runtime title extraction cannot resolve array values.
26
- * Used for validation — at most one field per schema should be marked.
26
+ * Count fields marked `isTitle: true`, recursing into objects but skipping `list: true` ones,
27
+ * whose array values runtime title extraction cannot resolve. Validation allows at most one.
27
28
  */
28
29
  export declare function countTitleFields(fields: readonly FieldConfig[]): number;
29
30
  /**
30
- * Validate that all isTitle fields in a schema are string type.
31
- * Skips `list: true` objects (those are caught by findTitleFieldsInLists).
32
- * Returns an array of field names that have isTitle on a non-string type.
31
+ * Dotted paths of isTitle fields whose type is not `string`. Skips `list: true` objects —
32
+ * `findTitleFieldsInLists` catches those.
33
33
  */
34
34
  export declare function findInvalidTitleFields(fields: readonly FieldConfig[], parentPath?: string): string[];
35
- /**
36
- * Find isTitle fields inside `list: true` object fields, where they can never resolve.
37
- * Returns dotted paths of such fields.
38
- */
35
+ /** Dotted paths of isTitle fields inside `list: true` objects, where they can never resolve. */
39
36
  export declare function findTitleFieldsInLists(fields: readonly FieldConfig[], parentPath?: string): string[];
@@ -1,11 +1,10 @@
1
- /** Type guard: true if field is an inline group (type === 'group'). */
2
1
  function isGroupField(field) {
3
2
  return field.type === 'group';
4
3
  }
5
4
  /**
6
- * Find the field marked `isTitle: true` in a schema and extract its value from data.
7
- * Recurses into object fields to support nested title fields (e.g., hero.title).
8
- * Returns the string value or undefined if not found or not a string.
5
+ * Value of the field marked `isTitle: true`, or undefined when there is none or it is not a
6
+ * string. Recurses into object fields, so a nested title (`hero.title`) is found.
7
+ * @internal Exported for tests.
9
8
  */
10
9
  export function extractTitleFromSchema(fields, data) {
11
10
  return findTitleValue(fields, data);
@@ -34,7 +33,10 @@ function findTitleValue(fields, data) {
34
33
  }
35
34
  return undefined;
36
35
  }
37
- /** Convert a slug like "my-cool-page" to "My Cool Page". */
36
+ /**
37
+ * Convert a slug like "my-cool-page" to "My Cool Page".
38
+ * @internal Exported for tests.
39
+ */
38
40
  export function humanizeSlug(slug) {
39
41
  return slug
40
42
  .replace(/[-_]/g, ' ')
@@ -42,34 +44,28 @@ export function humanizeSlug(slug) {
42
44
  .trim();
43
45
  }
44
46
  /**
45
- * Resolve a display title for an entry using the full fallback chain:
46
- * 1. Schema-marked isTitle field (if schema provided)
47
- * 2. Convention: data.title or data.name
48
- * 3. Entry type label (if provided)
49
- * 4. Humanized slug (if provided)
50
- * 5. "Untitled"
47
+ * Display title for an entry, by fallback chain: schema-marked isTitle field, then `data.title`
48
+ * or `data.name`, then the entry type label, then the humanized slug, then "Untitled".
49
+ *
50
+ * Client-safe — its only dependency on `../config` is type-only — and re-exported from both
51
+ * `canopycms/server` and the root `canopycms` entry, so adopter client code can import it.
51
52
  */
52
53
  export function resolveEntryTitle(data, options) {
53
- // 1. Schema-marked isTitle field
54
54
  if (options?.schema) {
55
55
  const schemaTitle = extractTitleFromSchema(options.schema, data);
56
56
  if (schemaTitle)
57
57
  return schemaTitle;
58
58
  }
59
- // 2. Convention: data.title or data.name
60
59
  const title = data.title ?? data.name;
61
60
  if (typeof title === 'string')
62
61
  return title;
63
- // 3. Entry type label
64
62
  if (options?.entryTypeLabel)
65
63
  return options.entryTypeLabel;
66
- // 4. Humanized slug
67
64
  return options?.slug ? humanizeSlug(options.slug) : 'Untitled';
68
65
  }
69
66
  /**
70
- * Count the number of fields marked `isTitle: true` in a schema, recursing into objects.
71
- * Skips `list: true` objects since runtime title extraction cannot resolve array values.
72
- * Used for validation — at most one field per schema should be marked.
67
+ * Count fields marked `isTitle: true`, recursing into objects but skipping `list: true` ones,
68
+ * whose array values runtime title extraction cannot resolve. Validation allows at most one.
73
69
  */
74
70
  export function countTitleFields(fields) {
75
71
  let count = 0;
@@ -88,9 +84,8 @@ export function countTitleFields(fields) {
88
84
  return count;
89
85
  }
90
86
  /**
91
- * Validate that all isTitle fields in a schema are string type.
92
- * Skips `list: true` objects (those are caught by findTitleFieldsInLists).
93
- * Returns an array of field names that have isTitle on a non-string type.
87
+ * Dotted paths of isTitle fields whose type is not `string`. Skips `list: true` objects —
88
+ * `findTitleFieldsInLists` catches those.
94
89
  */
95
90
  export function findInvalidTitleFields(fields, parentPath) {
96
91
  const invalid = [];
@@ -110,10 +105,7 @@ export function findInvalidTitleFields(fields, parentPath) {
110
105
  }
111
106
  return invalid;
112
107
  }
113
- /**
114
- * Find isTitle fields inside `list: true` object fields, where they can never resolve.
115
- * Returns dotted paths of such fields.
116
- */
108
+ /** Dotted paths of isTitle fields inside `list: true` objects, where they can never resolve. */
117
109
  export function findTitleFieldsInLists(fields, parentPath) {
118
110
  const found = [];
119
111
  for (const field of fields) {
@@ -124,7 +116,6 @@ export function findTitleFieldsInLists(fields, parentPath) {
124
116
  const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
125
117
  if (field.type === 'object' && 'fields' in field && field.fields) {
126
118
  if (field.list) {
127
- // Any isTitle inside a list object is invalid — collect them
128
119
  found.push(...collectAllTitleFields(field.fields, fieldPath));
129
120
  }
130
121
  else {
@@ -135,7 +126,7 @@ export function findTitleFieldsInLists(fields, parentPath) {
135
126
  }
136
127
  return found;
137
128
  }
138
- /** Collect all isTitle fields recursively (used inside list context). */
129
+ /** Every isTitle field below this point, for the list context where all of them are invalid. */
139
130
  function collectAllTitleFields(fields, parentPath) {
140
131
  const found = [];
141
132
  for (const field of fields) {
@@ -3,56 +3,28 @@ import type { ContentId, Slug } from '../paths/types.js';
3
3
  /**
4
4
  * Parse a Canopy content filename into its `{type}.{slug}.{id}.{ext}` parts.
5
5
  *
6
- * ## Filename grammar
6
+ * The slug may itself contain dots (`getting.started.guide`), so type and ID anchor the split:
7
+ * the ID is the second-to-last dot-separated segment, the slug is everything between the type and
8
+ * it (lowercased), and the extension is stripped. The ID is a 12-character Base58 content ID, and
9
+ * a would-be ID segment that fails `isValidId` rejects the whole parse.
7
10
  *
8
- * Every entry file on disk is named `{type}.{slug}.{id}.{ext}`:
9
- * - `type` — the entry type name (a key in the collection's `entries` config).
10
- * - `slug` — the entry's URL slug. May itself contain dots (e.g. a slug of
11
- * `getting.started.guide`), so the type and ID anchor the split: the ID is
12
- * always the second-to-last dot-separated segment, and the slug is
13
- * everything between the type and the ID. The returned `slug` is
14
- * lowercased.
15
- * - `id` — a 12-character Base58 content ID (`generateId()`/`isValidId()`).
16
- * Base58 excludes the ambiguous characters `0`, `O`, `I`, `l` so IDs are
17
- * unambiguous when read aloud or hand-transcribed. A filename whose
18
- * would-be ID segment doesn't pass `isValidId` is rejected — the whole
19
- * parse returns `null`, even if the rest of the shape looks right.
20
- * - `ext` — the format extension (`.md`, `.mdx`, `.json`, `.yaml`), stripped
21
- * before parsing and not part of the returned result.
11
+ * `filename` must be a bare filename, and **that precondition is not enforced**: the parser splits
12
+ * purely on `.`, so a `/` or `\` is neither rejected nor treated as special and becomes part of
13
+ * whichever segment it falls in (`'foo/bar.slug.<validId>.md'` gives `type: 'foo/bar'`). Strip the
14
+ * directory component yourself (`path.basename`) — every internal caller does. A leading-dot
15
+ * filename is always rejected, matching `extractEntryTypeFromFilename` in `content-id-index.ts`.
22
16
  *
23
- * @param filename - The bare filename (no directory component). **This precondition is
24
- * not enforced.** The parser splits purely on `.`, so a `/` or `\` you pass in is not
25
- * rejected and is not treated as special — it becomes part of whichever segment it
26
- * falls in, most often the `type` segment (e.g. `'foo/bar.slug.<validId>.md'` parses
27
- * to `type: 'foo/bar'`). Strip any directory component yourself (e.g.
28
- * `path.basename(filePath)`) before calling this — every internal caller already does.
29
- * @param entryTypes - When provided, the parsed `type` segment must match one
30
- * of these entry types by name, or the parse is rejected (this is how
31
- * `listCollectionEntries` filters out files that don't belong to the
32
- * collection's configured entry types). Omit this argument to parse
33
- * structurally without validating the type against a known list — useful
34
- * for adopter code that needs to recover `{type, slug, id}` from a
35
- * filename without having a schema/entry-types list on hand (e.g. a
36
- * filesystem walk over content for tooling or diagnostics). Even without
37
- * `entryTypes`, a leading-dot filename (dotfile, editor swap/backup file)
38
- * is always rejected — an empty string is never a legal type, matching the
39
- * `filename.startsWith('.')` guard `extractEntryTypeFromFilename` in
40
- * `content-id-index.ts` already applies.
41
- * @returns `{ type, slug, id }`, or `null` if `filename` doesn't match the
42
- * `{type}.{slug}.{id}.{ext}` shape (too few segments, no extension, a
43
- * leading dot, an invalid ID, or — when `entryTypes` is given — an
44
- * unrecognized type). `id` is validated (`isValidId`) and safe to trust. **`slug` is
45
- * not** — it is the raw dot-joined middle segment(s), lowercased, cast to the branded
46
- * `Slug` type without running `parseSlug`'s validation. A filename with an
47
- * unconventional slug segment (e.g. containing a space, or a dot) still parses and still
48
- * receives the `Slug` brand. Callers that need a validated slug must run the result
49
- * through `parseSlug` themselves; this function's contract is "split the filename
50
- * grammar apart," not "validate every part." `listCollectionEntries` (content-listing.ts)
51
- * is exactly such a caller that does NOT re-validate — it lists the file and computes a
52
- * `urlPath` for it regardless — so a dotted slug listed this way builds and gets
53
- * advertised (sitemap, `generateStaticParams`) but can never be read back through
54
- * `readByUrlPath`, which does run every URL-resolution candidate through `parseSlug`.
55
- * `static/index.ts`'s `assertRoutableSlugs` is the build-time guard that catches this.
17
+ * `entryTypes`, when given, restricts the `type` segment to those entry types — how
18
+ * `listCollectionEntries` filters out files that do not belong to the collection. Omit it to
19
+ * parse structurally, for adopter code recovering `{type, slug, id}` with no schema on hand.
20
+ *
21
+ * `id` is validated and safe to trust. **`slug` is not** — it is the raw dot-joined middle,
22
+ * lowercased and cast to the branded `Slug` without `parseSlug`'s validation, so a slug holding a
23
+ * space or a dot still parses and still carries the brand. Callers needing a validated slug run
24
+ * `parseSlug` themselves. `listCollectionEntries` does NOT, so a dotted slug is listed and
25
+ * advertised (sitemap, `generateStaticParams`) yet can never be read back through `readByUrlPath`,
26
+ * which runs every candidate through `parseSlug`; `static/index.ts`'s `assertRoutableSlugs` is the
27
+ * build-time guard for that.
56
28
  */
57
29
  export declare const parseTypedFilename: (filename: string, entryTypes?: readonly EntryTypeConfig[]) => {
58
30
  type: string;