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
@@ -12,16 +12,11 @@ const MTIME_CHECK_DEBOUNCE_MS = 1000;
12
12
  /** resource-generation.ts resource key for the schema cache's marker. */
13
13
  export const SCHEMA_GENERATION_RESOURCE = 'schema';
14
14
  /**
15
- * In dev mode, check whether any .collection.json file under dir has been
16
- * modified more recently than cachedAt. Returns true if stale.
15
+ * True when a .collection.json under `dir` is newer than `cachedAt`.
17
16
  *
18
- * Uses a single recursive readdir to find all .collection.json files,
19
- * then stats only those files.
20
- *
21
- * This is a dev-only convenience for out-of-band hand edits made directly to
22
- * .collection.json files outside the CMS (which don't go through SchemaOps
23
- * and therefore never bump the generation marker below). It is not needed for
24
- * prod correctness: every mutation path that matters in prod bumps the marker.
17
+ * Dev-only, and dev-only on purpose: it catches hand edits made outside the
18
+ * CMS, which bypass SchemaOps and so never bump the marker. Every prod mutation
19
+ * path does bump it, so the marker alone is sufficient there.
25
20
  */
26
21
  async function isStaleByMtime(dir, cachedAt) {
27
22
  let entries;
@@ -48,75 +43,18 @@ async function isStaleByMtime(dir, cachedAt) {
48
43
  return false;
49
44
  }
50
45
  /**
51
- * Manages per-branch schema caching with lazy loading and automatic
52
- * invalidation.
53
- *
54
- * ## Cross-process freshness (resource-generation.ts marker protocol)
55
- *
56
- * Several warm Lambda containers plus the EC2 worker can share one
57
- * branch-clone root over EFS, each with its own copy of
58
- * `schema-cache.json`. There is no shared memory and no cross-host file
59
- * watching, so freshness is coordinated via the generic on-disk generation
60
- * marker in resource-generation.ts: every cached snapshot embeds the marker
61
- * token it was resolved against, and every read cheaply re-checks the live
62
- * marker before trusting the cache. See that module's doc comment for the
63
- * full protocol and residual staleness windows (A/B/C/E).
64
- *
65
- * BranchSchemaCache is the "durable snapshot consumer" case called out there:
66
- * a resolve whose scan is served from stale NFS dentry/attribute caches can
67
- * record a FRESH token over STALE data (window E), and because the result is
68
- * written to `schema-cache.json`, that staleness becomes durable and shared
69
- * with every other host that reads the marker - not just one process's
70
- * memory. `loadFromCacheOrResolve` mitigates this the same way
71
- * BranchRegistry does:
46
+ * Per-branch schema cache: a file at {branchRoot}/.canopy-meta/schema-cache.json
47
+ * with no in-memory layer, so it stays coherent across Lambda invocations.
72
48
  *
73
- * - The marker is captured strictly BEFORE `resolveFresh()` runs, and the
74
- * captured token (not one read after resolving) is what gets embedded in
75
- * the persisted snapshot. A bump landing mid-resolve therefore leaves the
76
- * embedded token older than the live marker, forcing a re-resolve on the
77
- * next read instead of silently resurrecting stale schema.
78
- * - A snapshot is only persisted when the marker read itself succeeded
79
- * (`{ ok: true }`). If the read fails for a reason other than "never
80
- * bumped", we cannot attribute a token to this resolve, and stamping the
81
- * snapshot with an unattributable token would make it indistinguishable
82
- * from a correctly-attributed one to every future reader. The fresh result
83
- * is still served to this caller; it just isn't written durably.
84
- * - invalidate() bumps the marker with `mustSucceed: true`: an explicit
85
- * invalidation (e.g. after a schema mutation) must not silently fail and
86
- * leave every reader confidently stale with no bounding backstop.
87
- * invalidate() itself does NOT eager-regenerate - it isn't given the
88
- * entrySchemaRegistry/contentRootName that resolveSchema needs. The
89
- * eager re-resolve (window-E mitigation, mirroring BranchRegistry's
90
- * eager regen) lives one level up in SchemaOps.invalidateSchemaCache()
91
- * (schema/schema-store.ts), which has those arguments and calls
92
- * {@link resolveAndPersist} (NOT getSchema()) right after invalidating -
93
- * so every SchemaOps mutation re-resolves on the mutating host via a scan
94
- * that is GUARANTEED to run, not merely likely to run. getSchema() would
95
- * be the wrong call here: its cache-read fast path can short-circuit
96
- * through a snapshot a DIFFERENT concurrent host just wrote (see
97
- * resolveAndPersist()'s doc comment for the exact race), silently
98
- * skipping the one scan this eager re-resolve exists to guarantee. (The
99
- * editor's follow-up schema GET is a separate Lambda invocation with no
100
- * container affinity, so it could not serve this purpose either.)
101
- * Callers of invalidate() that bypass SchemaOps (api/schema.ts's explicit
102
- * invalidate endpoint; the bulk-mutation bump in
103
- * invalidateBranchContentCaches) accept the lazy next-read regen.
104
- *
105
- * This is also why prod needs no mtime walk: the dev-only mtime check below
106
- * exists solely to catch hand edits to .collection.json made outside the CMS
107
- * (which bypass SchemaOps and therefore never bump the marker). Every
108
- * mutation path that matters in prod - SchemaOps writes, git working-tree
109
- * rewrites (checkout/merge/rebase/sync/migrate) - bumps the marker via
110
- * SchemaOps.invalidateSchemaCache() or the combined
111
- * invalidateBranchContentCaches() helper in content-index-generation.ts, so
112
- * the marker alone is a sufficient prod backstop.
113
- *
114
- * Caching Strategy:
115
- * - File-based cache at {branchRoot}/.canopy-meta/schema-cache.json (no
116
- * in-memory layer - intentional: matches prod behavior and keeps cache
117
- * coherent across Lambda invocations)
118
- * - Invalidation: invalidate() bumps the cross-process generation marker;
119
- * every reader sharing this branchRoot re-resolves at its next access.
49
+ * Freshness follows the generation-marker protocol owned by
50
+ * resource-generation.ts, and this is one of that protocol's durable-snapshot
51
+ * consumers. Its eager re-resolve lives one level up, in
52
+ * `SchemaOps.invalidateSchemaCache()` (schema/schema-store.ts), because that is
53
+ * where the entrySchemaRegistry/contentRootName arguments {@link invalidate}
54
+ * lacks are available; it calls {@link resolveAndPersist}, never getSchema().
55
+ * Callers that bypass SchemaOps (api/schema.ts's invalidate endpoint, and the
56
+ * bulk git-op bump in invalidateBranchContentCaches) accept the lazy
57
+ * next-read regen instead.
120
58
  */
121
59
  export class BranchSchemaCache {
122
60
  constructor(mode = 'prod') {
@@ -127,42 +65,28 @@ export class BranchSchemaCache {
127
65
  /**
128
66
  * Whether to skip the on-disk cache for this branchRoot.
129
67
  *
130
- * branchRoot equals the project root (process.cwd()) only in the synthetic
131
- * contexts used by static deployments and build phases — real branch roots are
132
- * always nested under the workspace (.canopy-dev/content-branches/<name> in dev,
133
- * <workspaceRoot>/content-branches/<name> in prod). Never write .canopy-meta/ at
134
- * the project root, regardless of which entrypoint produced the cwd branchRoot.
68
+ * Never write `.canopy-meta/` at the project root, whichever entrypoint
69
+ * produced the cwd branchRoot. branchRoot equals process.cwd() only in the
70
+ * synthetic contexts static deployments and build phases use; a real branch
71
+ * root is always nested under the workspace.
135
72
  */
136
73
  skipDiskCache(branchRoot) {
137
74
  return isBuildMode() || path.resolve(branchRoot) === path.resolve(process.cwd());
138
75
  }
139
- /**
140
- * Get schema for a branch (loads from cache or resolves fresh).
141
- *
142
- * @param branchRoot - Root directory of the branch (e.g., .canopy-dev/content-branches/main)
143
- * @param entrySchemaRegistry - Map of schema names to field definitions
144
- * @param contentRootName - Name of content directory (e.g., "content") from config
145
- * @returns Resolved schema tree and flattened schema
146
- */
76
+ /** Get schema for a branch, from the cache when fresh, else resolved fresh. */
147
77
  async getSchema(branchRoot, entrySchemaRegistry, contentRootName = 'content') {
148
78
  return this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName);
149
79
  }
150
80
  /**
151
- * Resolve the schema from disk. Wrapped in a protected method (rather than
152
- * calling the imported resolveSchema directly) so tests can subclass and
153
- * override with a deferred/blocking implementation to simulate cross-process
81
+ * Resolve the schema from disk. Protected rather than a direct resolveSchema
82
+ * call so tests can subclass and block it to simulate cross-process
154
83
  * interleavings — mirrors BranchRegistry's scanBranchDirectories() hook.
155
84
  */
156
85
  async resolveFresh(contentRoot, entrySchemaRegistry) {
157
86
  return resolveSchema(contentRoot, entrySchemaRegistry);
158
87
  }
159
- /**
160
- * Load schema from cache or resolve fresh if cache is missing or stale.
161
- */
162
88
  async loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName) {
163
89
  const contentRoot = path.join(branchRoot, contentRootName);
164
- // In static/build mode, branchRoot is process.cwd() (the project root).
165
- // Skip disk cache to avoid creating .canopy-meta/ at the project root.
166
90
  const skipDiskCache = this.skipDiskCache(branchRoot);
167
91
  if (!skipDiskCache) {
168
92
  const cacheDir = path.join(branchRoot, '.canopy-meta');
@@ -176,16 +100,13 @@ export class BranchSchemaCache {
176
100
  // Cache doesn't exist or can't be read
177
101
  cacheData = null;
178
102
  }
179
- // Strict version check: a truthy-only check would accept a persisted
180
- // pre-marker snapshot (no `generation` field) left on EFS after a
181
- // rolling deploy, and `cacheData.generation` would then be `undefined`
182
- // rather than a real token or explicit `null`, breaking the freshness
183
- // comparison below.
103
+ // Strict version check, not truthiness: a snapshot from an older version
104
+ // left on EFS by a rolling deploy has no `generation` field, and an
105
+ // `undefined` token breaks the freshness comparison below.
184
106
  if (cacheData && cacheData.version === SCHEMA_CACHE_VERSION) {
185
107
  const read = await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
186
108
  if (isGenerationCurrent(cacheData.generation, read)) {
187
- // In dev mode, also check file mtimes so direct schema edits (outside the CMS) are picked up.
188
- // Debounce: skip the walk if we checked this contentRoot within the last second.
109
+ // Dev also walks mtimes, debounced, to catch edits made outside the CMS.
189
110
  const now = Date.now();
190
111
  const lastCheck = this.lastMtimeCheck.get(contentRoot) ?? 0;
191
112
  if (this.devMode &&
@@ -209,8 +130,6 @@ export class BranchSchemaCache {
209
130
  cacheData = null;
210
131
  }
211
132
  }
212
- // Cache miss, stale, or build mode - resolve fresh (and persist, subject
213
- // to the same skip-persist rule) via the shared resolve half.
214
133
  return this.resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, {
215
134
  skipDiskCache,
216
135
  });
@@ -224,10 +143,9 @@ export class BranchSchemaCache {
224
143
  async resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, options) {
225
144
  const { skipDiskCache } = options;
226
145
  const contentRoot = path.join(branchRoot, contentRootName);
227
- // Capture the marker strictly BEFORE resolving: a bump landing mid-resolve
228
- // then differs from the token recorded below, forcing a re-resolve on the
229
- // next read instead of silently persisting a snapshot that embeds a fresh
230
- // token over pre-mutation data.
146
+ // Capture the marker strictly BEFORE resolving, so a bump landing
147
+ // mid-resolve differs from the token persisted below and forces a
148
+ // re-resolve on the next read.
231
149
  const read = skipDiskCache
232
150
  ? null
233
151
  : await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
@@ -250,9 +168,8 @@ export class BranchSchemaCache {
250
168
  if (!skipDiskCache) {
251
169
  const cacheDir = path.join(branchRoot, '.canopy-meta');
252
170
  const cachePath = path.join(cacheDir, 'schema-cache.json');
253
- // Opportunistic cleanup of the legacy .stale marker file from the old
254
- // rename-based invalidation scheme (e.g. a process upgraded mid-flight
255
- // may find one left over from before the deploy). Not load-bearing.
171
+ // Opportunistic cleanup of the retired .stale marker file, which a
172
+ // mid-flight deploy can leave behind. Not load-bearing.
256
173
  await fs.unlink(path.join(cacheDir, 'schema-cache.stale')).catch(() => { });
257
174
  if (read && read.ok) {
258
175
  const newCache = {
@@ -262,10 +179,8 @@ export class BranchSchemaCache {
262
179
  cachedAt: new Date().toISOString(),
263
180
  generation: read.token,
264
181
  };
265
- // Atomic write: write to temp file, then rename. Clean up the temp
266
- // file on a failed rename (matches every other atomic write in the
267
- // codebase, e.g. utils/atomic-write.ts) so a transient rename error
268
- // doesn't leak a stray `.tmp` file into `.canopy-meta/` forever.
182
+ // Temp file then rename, with the temp file unlinked on a failed
183
+ // rename so a transient error leaves no stray `.tmp` in `.canopy-meta/`.
269
184
  await fs.mkdir(cacheDir, { recursive: true });
270
185
  const tmpPath = path.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
271
186
  await fs.writeFile(tmpPath, JSON.stringify(newCache, null, 2), 'utf-8');
@@ -277,34 +192,21 @@ export class BranchSchemaCache {
277
192
  throw err;
278
193
  }
279
194
  }
280
- // else: the marker couldn't be read for a reason other than "never
281
- // bumped" - we cannot attribute a token to this resolve, and stamping
282
- // the snapshot with an unattributable token would make it
283
- // indistinguishable from a correctly-attributed one to every future
284
- // reader. Serve the fresh result without persisting it; the next read
285
- // retries the marker read and, on success, resolves and persists
286
- // normally.
195
+ // else: the marker read failed for a reason other than "never bumped",
196
+ // so no token can be attributed to this resolve, and a snapshot stamped
197
+ // with an unattributable one would look correctly attributed to every
198
+ // future reader. Serve the fresh result without persisting it.
287
199
  }
288
200
  return { schema: result.schema, flatSchema };
289
201
  }
290
202
  /**
291
- * Resolve the schema fresh from disk and persist it, SKIPPING the cache
292
- * read entirely -- unlike {@link getSchema}, which can short-circuit
293
- * through a cache HIT. That distinction matters for eager re-resolve after
294
- * invalidation (see {@link invalidate}'s doc comment and
295
- * `SchemaOps.invalidateSchemaCache()` in schema/schema-store.ts, the sole
296
- * intended caller): the whole point of an eager re-resolve is to guarantee
297
- * ONE scan that is coherent with the mutation this host just made. A plain
298
- * `getSchema()` call right after `invalidate()` is NOT that guarantee --
299
- * `getSchema()`'s cache-read fast path (`loadFromCacheOrResolve`) can still
300
- * return a snapshot written by a DIFFERENT, concurrent host: if that
301
- * foreign host's own eager re-resolve raced this one and embedded the
302
- * (now-current) marker token over ITS OWN stale-NFS-cache scan (the
303
- * window-E case this class's doc comment describes), this host's
304
- * `getSchema()` would happily accept that foreign snapshot as "current"
305
- * and skip the one scan this call was specifically trying to guarantee.
306
- * `resolveAndPersist()` never reads the cache file at all, so it cannot be
307
- * short-circuited that way.
203
+ * Resolve fresh from disk and persist, SKIPPING the cache read entirely.
204
+ * That is what makes it, and not {@link getSchema}, the right call for the
205
+ * eager re-resolve in `SchemaOps.invalidateSchemaCache()` (its sole intended
206
+ * caller): an eager re-resolve exists to guarantee ONE scan coherent with the
207
+ * mutation this host just made, and getSchema()'s cache-read fast path can
208
+ * instead return a snapshot a concurrent host wrote — possibly one embedding
209
+ * the now-current token over ITS own stale-NFS scan — skipping that scan.
308
210
  */
309
211
  async resolveAndPersist(branchRoot, entrySchemaRegistry, contentRootName = 'content') {
310
212
  const skipDiskCache = this.skipDiskCache(branchRoot);
@@ -313,20 +215,12 @@ export class BranchSchemaCache {
313
215
  });
314
216
  }
315
217
  /**
316
- * Invalidate cache for a branch by bumping the cross-process generation
317
- * marker (resource-generation.ts). Every process sharing this branchRoot
318
- * will re-resolve at its next read.
319
- *
320
- * The bump must succeed: a swallowed failure here would leave the schema
321
- * cache stale indefinitely with no bounding backstop (unlike
322
- * BranchRegistry, there is no get-miss backstop for a resolved schema).
323
- *
324
- * No eager regeneration here (unlike BranchRegistry.invalidate()): this
325
- * method isn't given the entrySchemaRegistry/contentRootName resolveSchema
326
- * needs. See the class doc comment for why the mutating request's own
327
- * follow-up schema read closes the same window-E gap instead.
218
+ * Invalidate a branch's cache by bumping the marker; every process sharing
219
+ * this branchRoot re-resolves at its next read. The bump must succeed —
220
+ * swallowing that failure leaves the schema cache stale indefinitely, and
221
+ * unlike BranchRegistry there is no get-miss backstop for a resolved schema.
328
222
  *
329
- * @param branchRoot - Root directory of the branch
223
+ * No eager re-resolve here; it lives in SchemaOps (see the class doc).
330
224
  */
331
225
  async invalidate(branchRoot) {
332
226
  if (this.skipDiskCache(branchRoot))
@@ -23,12 +23,12 @@ export declare class BranchWorkspaceManager {
23
23
  }
24
24
  export { loadBranchContext } from './branch-metadata.js';
25
25
  /**
26
- * Load an existing branch context, or create the workspace if it doesn't exist yet.
26
+ * Load an existing branch context, provisioning the workspace if there is none.
27
27
  *
28
28
  * When content is read from the checkout (`readsFromCheckout`: a static
29
- * deployment, or any build) this skips every git and branch-workspace
30
- * operation and returns a synthetic context rooted at the current working
31
- * directory. `branchName` is echoed back on that context but selects nothing.
29
+ * deployment, or any build) this skips every git and branch-workspace operation
30
+ * and returns a synthetic context rooted at the current working directory,
31
+ * where `branchName` is echoed back but selects nothing.
32
32
  */
33
33
  export declare function loadOrCreateBranchContext(options: {
34
34
  config: CanopyConfig;
@@ -20,23 +20,21 @@ export class BranchWorkspaceManager {
20
20
  }
21
21
  async ensureGitWorkspace(options) {
22
22
  return log.timed('workspace', 'ensureGitWorkspace', async () => {
23
- // Serialize access per branch workspace to prevent race conditions
23
+ // One initialization per branch workspace per process.
24
24
  const existingLock = workspaceInitLocks.get(options.branchRoot);
25
25
  if (existingLock) {
26
26
  await existingLock;
27
27
  return;
28
28
  }
29
- // Create new lock promise
30
29
  const lockPromise = (async () => {
31
- // The in-memory lock above only serializes within one process. Separate
32
- // processes can provision the same branch workspace at once -- several
33
- // Lambda containers sharing one EFS workspace root in prod, or the dev
34
- // server beside a content-reading script run outside a build in dev --
35
- // and would otherwise both clone into it ("destination path already
36
- // exists"), so guard the workspace init with a cross-process lock too.
37
- // initializeWorkspace is idempotent, so the waiter simply finds the
38
- // workspace already cloned. (A build's content reads never get here:
39
- // loadOrCreateBranchContext returns the checkout before provisioning.)
30
+ // The in-memory lock above only serializes one process, and separate
31
+ // processes can provision the same workspace at once (several Lambda
32
+ // containers on one EFS root; the dev server beside a content-reading
33
+ // script), which without a cross-process lock means both cloning into
34
+ // it: "destination path already exists". initializeWorkspace is
35
+ // idempotent, so the waiter finds the workspace already cloned. A
36
+ // build's content reads never get here — loadOrCreateBranchContext
37
+ // returns the checkout before provisioning.
40
38
  let releaseLock;
41
39
  try {
42
40
  log.debug('workspace', 'Ensuring git workspace', {
@@ -44,7 +42,6 @@ export class BranchWorkspaceManager {
44
42
  mode: options.mode,
45
43
  });
46
44
  releaseLock = await acquireProvisioningLock(path.dirname(options.branchRoot), `.${path.basename(options.branchRoot)}.init.lock`);
47
- // Delegate git initialization to GitManager
48
45
  await GitManager.initializeWorkspace({
49
46
  workspacePath: options.branchRoot,
50
47
  branchName: options.branchName,
@@ -74,9 +71,7 @@ export class BranchWorkspaceManager {
74
71
  workspaceInitLocks.delete(options.branchRoot);
75
72
  }
76
73
  })();
77
- // Store the lock promise
78
74
  workspaceInitLocks.set(options.branchRoot, lockPromise);
79
- // Wait for initialization to complete
80
75
  await lockPromise;
81
76
  });
82
77
  }
@@ -103,7 +98,8 @@ export class BranchWorkspaceManager {
103
98
  baseBranch,
104
99
  remoteUrl,
105
100
  });
106
- // save() handles both creation and updates, preserving existing values and invalidating registry
101
+ // save() covers creation and update, keeping existing values and
102
+ // invalidating the registry.
107
103
  const metadata = getBranchMetadataFileManager(branchRoot, baseRoot);
108
104
  const meta = await metadata.save({
109
105
  branch: {
@@ -124,12 +120,12 @@ export class BranchWorkspaceManager {
124
120
  }
125
121
  export { loadBranchContext } from './branch-metadata.js';
126
122
  /**
127
- * Load an existing branch context, or create the workspace if it doesn't exist yet.
123
+ * Load an existing branch context, provisioning the workspace if there is none.
128
124
  *
129
125
  * When content is read from the checkout (`readsFromCheckout`: a static
130
- * deployment, or any build) this skips every git and branch-workspace
131
- * operation and returns a synthetic context rooted at the current working
132
- * directory. `branchName` is echoed back on that context but selects nothing.
126
+ * deployment, or any build) this skips every git and branch-workspace operation
127
+ * and returns a synthetic context rooted at the current working directory,
128
+ * where `branchName` is echoed back but selects nothing.
133
129
  */
134
130
  export async function loadOrCreateBranchContext(options) {
135
131
  // Static deployments and builds read content directly from the checkout — no git ops
@@ -16,6 +16,7 @@ import type { AIContentConfig } from '../ai/types.js';
16
16
  * consumers. Overloading it with a build-time file list would change a contract adopters read.
17
17
  *
18
18
  * Dot-prefixed so it sorts out of the way and reads as machine bookkeeping rather than content.
19
+ * @internal Exported for tests.
19
20
  */
20
21
  export declare const GENERATED_RECORD_FILENAME = ".canopy-generated.json";
21
22
  export interface GenerateAIContentFilesOptions {
@@ -22,6 +22,7 @@ import { assertBuildEntriesValid } from '../static/index.js';
22
22
  * consumers. Overloading it with a build-time file list would change a contract adopters read.
23
23
  *
24
24
  * Dot-prefixed so it sorts out of the way and reads as machine bookkeeping rather than content.
25
+ * @internal Exported for tests.
25
26
  */
26
27
  export const GENERATED_RECORD_FILENAME = '.canopy-generated.json';
27
28
  /** Read the previous run's record. A missing, unreadable or malformed record means "know nothing". */
@@ -102,30 +103,20 @@ function unpinnedConsequence(buildId) {
102
103
  /**
103
104
  * Resolve the manifest's build stamp from the environment.
104
105
  *
105
- * This lives at the BUILD boundary, not inside `generateAIContent`, because the same generator
106
- * also serves the runtime route — where a live clock is the correct answer and a
107
- * `SOURCE_DATE_EPOCH` that happens to be exported in a server environment must not freeze a
108
- * response's timestamp.
106
+ * Lives at the BUILD boundary, not inside `generateAIContent`: the same generator also serves
107
+ * the runtime route, where a live clock is correct and a `SOURCE_DATE_EPOCH` exported in a
108
+ * server environment must not freeze a response's timestamp.
109
109
  *
110
- * `SOURCE_DATE_EPOCH` is the Reproducible Builds convention (decimal seconds since the epoch),
111
- * kept under its standard name so a harness that already exports it for tar/gzip/rpm gets this
112
- * for free. `CANOPY_BUILD_ID` is ours: Next has no such variable of its own.
110
+ * `SOURCE_DATE_EPOCH` is the Reproducible Builds convention (decimal seconds since the epoch).
111
+ * `CANOPY_BUILD_ID` is validated against the same rule as `SAFE_BUILD_ID` above, since the
112
+ * manifest's `buildId` and Next's build id must name the SAME artifact.
113
113
  *
114
- * That id is validated here against a rule justified by Next's `_next/static/<id>/` layout, even
115
- * though this module is framework-agnostic. Deliberate: the variable's whole purpose is that the
116
- * manifest's `buildId` and the framework's build id name the SAME artifact, so a value one reader
117
- * would reject is not useful to the other. The cost is that a non-Next adopter cannot use, say, an
118
- * ISO timestamp as a build id — revisit this rule, in both copies, when a second framework lands.
114
+ * Both variables warn and are ignored when set but unusable, rather than failing the build —
115
+ * same stance as `readGeneratedRecord` above; "unset" is a deliberate opt-out and warns nothing.
119
116
  *
120
- * Every rejection warns and is ignored rather than failing the build — same stance as
121
- * `readGeneratedRecord` above. Both variables treat "set but unusable" as a broken pipeline
122
- * (`SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)` with a failing command) and say so, while
123
- * "unset" is a deliberate opt-out and says nothing.
124
- *
125
- * Note what ignoring a bad `SOURCE_DATE_EPOCH` means when a build id IS set: `generatedAt` stays
126
- * undefined, so `generated` is omitted rather than falling back to a live clock. A bad value must
127
- * not resurrect a field the adopter's configuration says is meaningless — which is also why that
128
- * case warns rather than failing silently, since the field simply disappears.
117
+ * When `SOURCE_DATE_EPOCH` is bad and a build id IS set, `generatedAt` stays undefined so
118
+ * `generated` is omitted rather than falling back to a live clock — a bad value must not
119
+ * resurrect a field the adopter's configuration says is meaningless.
129
120
  */
130
121
  function resolveBuildStamp() {
131
122
  // Trimmed and shape-checked with the same rule `withCanopy` applies, so that when an adopter
@@ -182,9 +173,7 @@ function resolveBuildStamp() {
182
173
  export async function generateAIContentFiles(options) {
183
174
  const { config, entrySchemaRegistry, outputDir, aiConfig, _testFlatSchema } = options;
184
175
  const contentRootName = config.contentRoot || 'content';
185
- // Resolve branch root
186
176
  const branchRoot = await resolveBranchRoot(config);
187
- // Load schema
188
177
  let flatSchema;
189
178
  if (_testFlatSchema) {
190
179
  flatSchema = _testFlatSchema;
@@ -199,7 +188,6 @@ export async function generateAIContentFiles(options) {
199
188
  // command, not something `next dev` runs incidentally.
200
189
  const entriesForValidation = await listEntries(branchRoot, flatSchema, contentRootName);
201
190
  assertBuildEntriesValid(entriesForValidation, 'AI content generation');
202
- // Create store and generate
203
191
  const store = new ContentStore(branchRoot, flatSchema, { contentRootName });
204
192
  const result = await generateAIContent({
205
193
  store,
@@ -209,7 +197,6 @@ export async function generateAIContentFiles(options) {
209
197
  entryLinkUrl: config.entryLinkUrl,
210
198
  ...resolveBuildStamp(),
211
199
  });
212
- // Write files to disk
213
200
  const absoluteOutputDir = path.resolve(outputDir) + path.sep;
214
201
  /** Resolve an output-relative path, refusing anything that escapes the output directory. */
215
202
  const resolveOutputPath = (filePath) => {
@@ -2,41 +2,31 @@ import type { CanopyConfig } from './config/index.js';
2
2
  import type { EntrySchemaRegistry } from './schema/types.js';
3
3
  import { type CanopyBuildContext } from './context.js';
4
4
  export interface CreateBuildCanopyOptions {
5
- /**
6
- * Entry schema registry for resolving `.collection.json` references.
7
- * Same shape as `createCanopyServices`'s option of the same name.
8
- */
5
+ /** Entry schema registry for `.collection.json` references; `createCanopyServices`'s shape. */
9
6
  entrySchemaRegistry?: EntrySchemaRegistry;
10
7
  }
11
8
  /**
12
- * One-call factory for a **build/admin** Canopy context — for standalone
13
- * scripts that run entirely outside a Next.js request or build phase (index
14
- * builders, content audits, codegen, ad hoc reports).
9
+ * One-call factory for a **build/admin** Canopy context, for standalone scripts
10
+ * that run entirely outside a Next.js request or build phase (index builders,
11
+ * content audits, codegen, ad hoc reports).
15
12
  *
16
- * **This is a build/admin context: it reads the filesystem directly as a
17
- * synthetic admin user (`STATIC_DEPLOY_USER`) and bypasses ALL branch and
18
- * path ACLs.** There is no per-request auth to enforce because there is no
19
- * request. Do not reach for this from request-handling code (an API route,
20
- * a server component rendering for a specific visitor, anything with a real
21
- * user on the other end) — there, use `createNextCanopyContext(...)`'s
22
- * `getCanopy()` (or its phase-selecting `read`/`readByUrlPath`), which
23
- * enforce branch/path permissions per user. This factory mirrors the same
24
- * `createCanopyServices` + `createCanopyContext` + `STATIC_DEPLOY_USER` boot
25
- * sequence that `createNextCanopyContext`'s own `getCanopyForBuild()` uses
26
- * internally, minus the Next.js pieces (no `next/headers`, no React
27
- * `cache()`, no request-time misuse guard — there is no request phase for
28
- * it to guard against) — so it works in a plain script run with `tsx`/`node`.
13
+ * **Bypasses ALL branch and path ACLs: it reads the filesystem directly as the
14
+ * synthetic admin user `STATIC_DEPLOY_USER`.** That is safe only because there
15
+ * is no request, and so no per-request auth to enforce. Never reach for it from
16
+ * request-handling code (an API route, a server component rendering for a
17
+ * specific visitor); there, use `createNextCanopyContext(...)`'s `getCanopy()`
18
+ * or its phase-selecting `read`/`readByUrlPath`, which enforce branch and path
19
+ * permissions per user.
29
20
  *
30
- * Trivially stubbable by design: the boot sequence is one function call
31
- * taking a plain config object and returning a plain context, so a script
32
- * that calls it can be imported and exercised from a test with the config
33
- * (or this function itself) swapped out. Contrast with a hand-rolled
34
- * top-level-`await` boot block, which can never be imported by a test at
35
- * all — that's the concrete problem this factory removes.
21
+ * Mirrors the `createCanopyServices` + `createCanopyContext` +
22
+ * `STATIC_DEPLOY_USER` boot sequence of `createNextCanopyContext`'s own
23
+ * `getCanopyForBuild()`, minus the Next.js pieces (no `next/headers`, no React
24
+ * `cache()`, no request-time misuse guard — there is no request phase to guard
25
+ * against), so it runs in a plain `tsx`/`node` script and can be stubbed from a
26
+ * test the way a top-level-`await` boot block cannot.
36
27
  *
37
28
  * @example
38
29
  * ```ts
39
- * // scripts/build-search-index.ts
40
30
  * import { createBuildCanopy } from 'canopycms/server'
41
31
  * import config from '../canopycms.config.js'
42
32
  * import { entrySchemaRegistry } from '../src/schemas.js'
@@ -2,34 +2,27 @@ import { createCanopyServices } from './services.js';
2
2
  import { createCanopyContext } from './context.js';
3
3
  import { STATIC_DEPLOY_USER } from './build-mode.js';
4
4
  /**
5
- * One-call factory for a **build/admin** Canopy context — for standalone
6
- * scripts that run entirely outside a Next.js request or build phase (index
7
- * builders, content audits, codegen, ad hoc reports).
5
+ * One-call factory for a **build/admin** Canopy context, for standalone scripts
6
+ * that run entirely outside a Next.js request or build phase (index builders,
7
+ * content audits, codegen, ad hoc reports).
8
8
  *
9
- * **This is a build/admin context: it reads the filesystem directly as a
10
- * synthetic admin user (`STATIC_DEPLOY_USER`) and bypasses ALL branch and
11
- * path ACLs.** There is no per-request auth to enforce because there is no
12
- * request. Do not reach for this from request-handling code (an API route,
13
- * a server component rendering for a specific visitor, anything with a real
14
- * user on the other end) — there, use `createNextCanopyContext(...)`'s
15
- * `getCanopy()` (or its phase-selecting `read`/`readByUrlPath`), which
16
- * enforce branch/path permissions per user. This factory mirrors the same
17
- * `createCanopyServices` + `createCanopyContext` + `STATIC_DEPLOY_USER` boot
18
- * sequence that `createNextCanopyContext`'s own `getCanopyForBuild()` uses
19
- * internally, minus the Next.js pieces (no `next/headers`, no React
20
- * `cache()`, no request-time misuse guard — there is no request phase for
21
- * it to guard against) — so it works in a plain script run with `tsx`/`node`.
9
+ * **Bypasses ALL branch and path ACLs: it reads the filesystem directly as the
10
+ * synthetic admin user `STATIC_DEPLOY_USER`.** That is safe only because there
11
+ * is no request, and so no per-request auth to enforce. Never reach for it from
12
+ * request-handling code (an API route, a server component rendering for a
13
+ * specific visitor); there, use `createNextCanopyContext(...)`'s `getCanopy()`
14
+ * or its phase-selecting `read`/`readByUrlPath`, which enforce branch and path
15
+ * permissions per user.
22
16
  *
23
- * Trivially stubbable by design: the boot sequence is one function call
24
- * taking a plain config object and returning a plain context, so a script
25
- * that calls it can be imported and exercised from a test with the config
26
- * (or this function itself) swapped out. Contrast with a hand-rolled
27
- * top-level-`await` boot block, which can never be imported by a test at
28
- * all — that's the concrete problem this factory removes.
17
+ * Mirrors the `createCanopyServices` + `createCanopyContext` +
18
+ * `STATIC_DEPLOY_USER` boot sequence of `createNextCanopyContext`'s own
19
+ * `getCanopyForBuild()`, minus the Next.js pieces (no `next/headers`, no React
20
+ * `cache()`, no request-time misuse guard — there is no request phase to guard
21
+ * against), so it runs in a plain `tsx`/`node` script and can be stubbed from a
22
+ * test the way a top-level-`await` boot block cannot.
29
23
  *
30
24
  * @example
31
25
  * ```ts
32
- * // scripts/build-search-index.ts
33
26
  * import { createBuildCanopy } from 'canopycms/server'
34
27
  * import config from '../canopycms.config.js'
35
28
  * import { entrySchemaRegistry } from '../src/schemas.js'