canopycms 0.0.0 → 0.0.2

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 (467) hide show
  1. package/dist/auth/plugin.d.ts +8 -0
  2. package/dist/auth/plugin.d.ts.map +1 -1
  3. package/dist/build-mode.d.ts +15 -5
  4. package/dist/build-mode.d.ts.map +1 -1
  5. package/dist/build-mode.js +18 -8
  6. package/dist/build-mode.js.map +1 -1
  7. package/dist/cli/init.d.ts +2 -2
  8. package/dist/cli/init.d.ts.map +1 -1
  9. package/dist/cli/init.js +37 -36
  10. package/dist/cli/init.js.map +1 -1
  11. package/dist/cli/template-files/ai-config.ts.template +21 -0
  12. package/dist/cli/template-files/ai-route.ts.template +10 -0
  13. package/dist/cli/template-files/canopy.ts.template +24 -0
  14. package/dist/cli/templates.d.ts +5 -1
  15. package/dist/cli/templates.d.ts.map +1 -1
  16. package/dist/cli/templates.js +9 -2
  17. package/dist/cli/templates.js.map +1 -1
  18. package/dist/config/schemas/config.d.ts +4 -0
  19. package/dist/config/schemas/config.d.ts.map +1 -1
  20. package/dist/config/schemas/config.js +2 -0
  21. package/dist/config/schemas/config.js.map +1 -1
  22. package/dist/config/types.d.ts +5 -0
  23. package/dist/config/types.d.ts.map +1 -1
  24. package/dist/content-reader.js +2 -2
  25. package/dist/content-reader.js.map +1 -1
  26. package/dist/context.js +5 -5
  27. package/dist/context.js.map +1 -1
  28. package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
  29. package/dist/operating-mode/client-unsafe-strategy.js +15 -18
  30. package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
  31. package/dist/operating-mode/types.d.ts +8 -0
  32. package/dist/operating-mode/types.d.ts.map +1 -1
  33. package/dist/server.d.ts +2 -0
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +2 -0
  36. package/dist/server.js.map +1 -1
  37. package/package.json +5 -4
  38. package/src/cli/init.ts +43 -38
  39. package/dist/__integration__/fixtures/content-seeds.d.ts +0 -43
  40. package/dist/__integration__/fixtures/content-seeds.d.ts.map +0 -1
  41. package/dist/__integration__/fixtures/content-seeds.js +0 -99
  42. package/dist/__integration__/fixtures/content-seeds.js.map +0 -1
  43. package/dist/__integration__/fixtures/schemas.d.ts +0 -12
  44. package/dist/__integration__/fixtures/schemas.d.ts.map +0 -1
  45. package/dist/__integration__/fixtures/schemas.js +0 -65
  46. package/dist/__integration__/fixtures/schemas.js.map +0 -1
  47. package/dist/__integration__/test-utils/api-client.d.ts +0 -123
  48. package/dist/__integration__/test-utils/api-client.d.ts.map +0 -1
  49. package/dist/__integration__/test-utils/api-client.js +0 -118
  50. package/dist/__integration__/test-utils/api-client.js.map +0 -1
  51. package/dist/__integration__/test-utils/multi-user.d.ts +0 -25
  52. package/dist/__integration__/test-utils/multi-user.d.ts.map +0 -1
  53. package/dist/__integration__/test-utils/multi-user.js +0 -105
  54. package/dist/__integration__/test-utils/multi-user.js.map +0 -1
  55. package/dist/__integration__/test-utils/test-workspace.d.ts +0 -25
  56. package/dist/__integration__/test-utils/test-workspace.d.ts.map +0 -1
  57. package/dist/__integration__/test-utils/test-workspace.js +0 -102
  58. package/dist/__integration__/test-utils/test-workspace.js.map +0 -1
  59. package/dist/editor/BranchManager.stories.d.ts +0 -8
  60. package/dist/editor/BranchManager.stories.d.ts.map +0 -1
  61. package/dist/editor/BranchManager.stories.js +0 -74
  62. package/dist/editor/BranchManager.stories.js.map +0 -1
  63. package/dist/editor/CanopyEditor.stories.d.ts +0 -7
  64. package/dist/editor/CanopyEditor.stories.d.ts.map +0 -1
  65. package/dist/editor/CanopyEditor.stories.js +0 -99
  66. package/dist/editor/CanopyEditor.stories.js.map +0 -1
  67. package/dist/editor/CommentsPanel.stories.d.ts +0 -10
  68. package/dist/editor/CommentsPanel.stories.d.ts.map +0 -1
  69. package/dist/editor/CommentsPanel.stories.js +0 -175
  70. package/dist/editor/CommentsPanel.stories.js.map +0 -1
  71. package/dist/editor/Editor.stories.d.ts +0 -7
  72. package/dist/editor/Editor.stories.d.ts.map +0 -1
  73. package/dist/editor/Editor.stories.js +0 -95
  74. package/dist/editor/Editor.stories.js.map +0 -1
  75. package/dist/editor/EditorPanes.stories.d.ts +0 -7
  76. package/dist/editor/EditorPanes.stories.d.ts.map +0 -1
  77. package/dist/editor/EditorPanes.stories.js +0 -116
  78. package/dist/editor/EditorPanes.stories.js.map +0 -1
  79. package/dist/editor/EntryNavigator.stories.d.ts +0 -8
  80. package/dist/editor/EntryNavigator.stories.d.ts.map +0 -1
  81. package/dist/editor/EntryNavigator.stories.js +0 -42
  82. package/dist/editor/EntryNavigator.stories.js.map +0 -1
  83. package/dist/editor/FormRenderer.stories.d.ts +0 -7
  84. package/dist/editor/FormRenderer.stories.d.ts.map +0 -1
  85. package/dist/editor/FormRenderer.stories.js +0 -115
  86. package/dist/editor/FormRenderer.stories.js.map +0 -1
  87. package/dist/editor/GroupManager.stories.d.ts +0 -19
  88. package/dist/editor/GroupManager.stories.d.ts.map +0 -1
  89. package/dist/editor/GroupManager.stories.js +0 -265
  90. package/dist/editor/GroupManager.stories.js.map +0 -1
  91. package/dist/editor/PermissionManager.stories.d.ts +0 -20
  92. package/dist/editor/PermissionManager.stories.d.ts.map +0 -1
  93. package/dist/editor/PermissionManager.stories.js +0 -506
  94. package/dist/editor/PermissionManager.stories.js.map +0 -1
  95. package/dist/editor/comments/FieldWrapper.stories.d.ts +0 -10
  96. package/dist/editor/comments/FieldWrapper.stories.d.ts.map +0 -1
  97. package/dist/editor/comments/FieldWrapper.stories.js +0 -173
  98. package/dist/editor/comments/FieldWrapper.stories.js.map +0 -1
  99. package/dist/editor/fields/BlockField.stories.d.ts +0 -7
  100. package/dist/editor/fields/BlockField.stories.d.ts.map +0 -1
  101. package/dist/editor/fields/BlockField.stories.js +0 -50
  102. package/dist/editor/fields/BlockField.stories.js.map +0 -1
  103. package/dist/editor/fields/fields.stories.d.ts +0 -8
  104. package/dist/editor/fields/fields.stories.d.ts.map +0 -1
  105. package/dist/editor/fields/fields.stories.js +0 -34
  106. package/dist/editor/fields/fields.stories.js.map +0 -1
  107. package/dist/test-utils/api-test-helpers.d.ts +0 -238
  108. package/dist/test-utils/api-test-helpers.d.ts.map +0 -1
  109. package/dist/test-utils/api-test-helpers.js +0 -347
  110. package/dist/test-utils/api-test-helpers.js.map +0 -1
  111. package/dist/test-utils/console-spy.d.ts +0 -56
  112. package/dist/test-utils/console-spy.d.ts.map +0 -1
  113. package/dist/test-utils/console-spy.js +0 -81
  114. package/dist/test-utils/console-spy.js.map +0 -1
  115. package/dist/test-utils/git-helpers.d.ts +0 -21
  116. package/dist/test-utils/git-helpers.d.ts.map +0 -1
  117. package/dist/test-utils/git-helpers.js +0 -23
  118. package/dist/test-utils/git-helpers.js.map +0 -1
  119. package/dist/test-utils/index.d.ts +0 -5
  120. package/dist/test-utils/index.d.ts.map +0 -1
  121. package/dist/test-utils/index.js +0 -4
  122. package/dist/test-utils/index.js.map +0 -1
  123. package/src/__integration__/errors/invalid-content.test.ts +0 -238
  124. package/src/__integration__/errors/permission-denied.test.ts +0 -220
  125. package/src/__integration__/fixtures/content-seeds.ts +0 -105
  126. package/src/__integration__/fixtures/schemas.ts +0 -67
  127. package/src/__integration__/initialization/prod-sim-init.test.ts +0 -139
  128. package/src/__integration__/permissions/path-permissions.test.ts +0 -314
  129. package/src/__integration__/permissions/role-permissions.test.ts +0 -354
  130. package/src/__integration__/permissions/settings-branch-isolation.test.ts +0 -317
  131. package/src/__integration__/settings/groups-api.test.ts +0 -403
  132. package/src/__integration__/test-utils/api-client.ts +0 -167
  133. package/src/__integration__/test-utils/multi-user.ts +0 -129
  134. package/src/__integration__/test-utils/test-workspace.ts +0 -130
  135. package/src/__integration__/user/user-context.test.ts +0 -174
  136. package/src/__integration__/validation/input-validation.test.ts +0 -166
  137. package/src/__integration__/workflows/api-editing-workflow.test.ts +0 -244
  138. package/src/__integration__/workflows/conflict-resolution.test.ts +0 -259
  139. package/src/__integration__/workflows/editing-workflow.test.ts +0 -205
  140. package/src/__integration__/workflows/review-workflow.test.ts +0 -260
  141. package/src/ai/__tests__/build.integration.test.ts +0 -224
  142. package/src/ai/__tests__/generate.integration.test.ts +0 -495
  143. package/src/ai/__tests__/handler.integration.test.ts +0 -212
  144. package/src/ai/__tests__/json-to-markdown.test.ts +0 -553
  145. package/src/ai/generate.ts +0 -410
  146. package/src/ai/handler.ts +0 -123
  147. package/src/ai/index.ts +0 -26
  148. package/src/ai/json-to-markdown.ts +0 -424
  149. package/src/ai/resolve-branch.ts +0 -34
  150. package/src/ai/types.ts +0 -160
  151. package/src/api/AGENTS.md +0 -81
  152. package/src/api/__test__/mock-client.ts +0 -404
  153. package/src/api/assets.test.ts +0 -140
  154. package/src/api/assets.ts +0 -154
  155. package/src/api/branch-merge.test.ts +0 -163
  156. package/src/api/branch-merge.ts +0 -113
  157. package/src/api/branch-review.test.ts +0 -297
  158. package/src/api/branch-review.ts +0 -136
  159. package/src/api/branch-status.test.ts +0 -85
  160. package/src/api/branch-status.ts +0 -153
  161. package/src/api/branch-withdraw.test.ts +0 -146
  162. package/src/api/branch-withdraw.ts +0 -81
  163. package/src/api/branch-workflow.integration.test.ts +0 -578
  164. package/src/api/branch.test.ts +0 -620
  165. package/src/api/branch.ts +0 -492
  166. package/src/api/client.test.ts +0 -349
  167. package/src/api/client.ts +0 -506
  168. package/src/api/comments.test.ts +0 -285
  169. package/src/api/comments.ts +0 -210
  170. package/src/api/content.test.ts +0 -345
  171. package/src/api/content.ts +0 -454
  172. package/src/api/entries.test.ts +0 -1339
  173. package/src/api/entries.ts +0 -650
  174. package/src/api/github-sync.ts +0 -144
  175. package/src/api/groups.test.ts +0 -1013
  176. package/src/api/groups.ts +0 -375
  177. package/src/api/guards.test.ts +0 -533
  178. package/src/api/guards.ts +0 -271
  179. package/src/api/index.ts +0 -87
  180. package/src/api/permissions.test.ts +0 -766
  181. package/src/api/permissions.ts +0 -334
  182. package/src/api/reference-options.ts +0 -118
  183. package/src/api/resolve-references.ts +0 -107
  184. package/src/api/route-builder.ts +0 -289
  185. package/src/api/schema.test.ts +0 -840
  186. package/src/api/schema.ts +0 -936
  187. package/src/api/security.test.ts +0 -233
  188. package/src/api/settings-helpers.ts +0 -84
  189. package/src/api/types.ts +0 -40
  190. package/src/api/user.test.ts +0 -127
  191. package/src/api/user.ts +0 -42
  192. package/src/api/validators.test.ts +0 -275
  193. package/src/api/validators.ts +0 -176
  194. package/src/asset-store.test.ts +0 -37
  195. package/src/asset-store.ts +0 -110
  196. package/src/auth/cache.ts +0 -7
  197. package/src/auth/caching-auth-plugin.test.ts +0 -154
  198. package/src/auth/caching-auth-plugin.ts +0 -109
  199. package/src/auth/context-helpers.ts +0 -75
  200. package/src/auth/file-based-auth-cache.test.ts +0 -257
  201. package/src/auth/file-based-auth-cache.ts +0 -279
  202. package/src/auth/index.ts +0 -12
  203. package/src/auth/plugin.ts +0 -51
  204. package/src/auth/types.ts +0 -38
  205. package/src/authorization/__tests__/branch.test.ts +0 -260
  206. package/src/authorization/__tests__/content.test.ts +0 -142
  207. package/src/authorization/__tests__/path.test.ts +0 -133
  208. package/src/authorization/__tests__/permissions-loader.test.ts +0 -200
  209. package/src/authorization/branch.ts +0 -94
  210. package/src/authorization/content.ts +0 -93
  211. package/src/authorization/groups/index.ts +0 -11
  212. package/src/authorization/groups/loader.ts +0 -127
  213. package/src/authorization/groups/schema.ts +0 -48
  214. package/src/authorization/helpers.ts +0 -48
  215. package/src/authorization/index.ts +0 -84
  216. package/src/authorization/path.ts +0 -112
  217. package/src/authorization/permissions/index.ts +0 -11
  218. package/src/authorization/permissions/loader.ts +0 -116
  219. package/src/authorization/permissions/schema.ts +0 -66
  220. package/src/authorization/test-utils.ts +0 -15
  221. package/src/authorization/types.ts +0 -66
  222. package/src/authorization/validation.test.ts +0 -100
  223. package/src/authorization/validation.ts +0 -62
  224. package/src/branch-metadata.test.ts +0 -168
  225. package/src/branch-metadata.ts +0 -166
  226. package/src/branch-registry.test.ts +0 -248
  227. package/src/branch-registry.ts +0 -152
  228. package/src/branch-schema-cache.test.ts +0 -275
  229. package/src/branch-schema-cache.ts +0 -189
  230. package/src/branch-workspace.test.ts +0 -183
  231. package/src/branch-workspace.ts +0 -124
  232. package/src/build/generate-ai-content.ts +0 -78
  233. package/src/build/index.ts +0 -8
  234. package/src/build-mode.ts +0 -27
  235. package/src/cli/generate-ai-content.ts +0 -100
  236. package/src/cli/init.test.ts +0 -240
  237. package/src/cli/templates/canopy.ts.template +0 -55
  238. package/src/cli/templates.ts +0 -47
  239. package/src/client.ts +0 -12
  240. package/src/comment-store.test.ts +0 -442
  241. package/src/comment-store.ts +0 -301
  242. package/src/config/__tests__/config.test.ts +0 -513
  243. package/src/config/flatten.ts +0 -174
  244. package/src/config/helpers.ts +0 -167
  245. package/src/config/index.ts +0 -86
  246. package/src/config/schemas/collection.ts +0 -67
  247. package/src/config/schemas/config.ts +0 -77
  248. package/src/config/schemas/field.ts +0 -108
  249. package/src/config/schemas/media.ts +0 -27
  250. package/src/config/schemas/permissions.ts +0 -21
  251. package/src/config/types.ts +0 -321
  252. package/src/config/validation.ts +0 -70
  253. package/src/config-test.ts +0 -65
  254. package/src/config.ts +0 -11
  255. package/src/content-id-index.test.ts +0 -512
  256. package/src/content-id-index.ts +0 -479
  257. package/src/content-reader.test.ts +0 -478
  258. package/src/content-reader.ts +0 -214
  259. package/src/content-store.test.ts +0 -1126
  260. package/src/content-store.ts +0 -793
  261. package/src/context.ts +0 -111
  262. package/src/editor/BranchManager.stories.tsx +0 -80
  263. package/src/editor/BranchManager.test.tsx +0 -324
  264. package/src/editor/BranchManager.tsx +0 -461
  265. package/src/editor/CanopyEditor.stories.tsx +0 -128
  266. package/src/editor/CanopyEditor.test.tsx +0 -81
  267. package/src/editor/CanopyEditor.tsx +0 -73
  268. package/src/editor/CanopyEditorPage.test.tsx +0 -59
  269. package/src/editor/CanopyEditorPage.tsx +0 -25
  270. package/src/editor/CommentsPanel.stories.tsx +0 -184
  271. package/src/editor/CommentsPanel.tsx +0 -338
  272. package/src/editor/Editor.integration.test.tsx +0 -227
  273. package/src/editor/Editor.stories.tsx +0 -119
  274. package/src/editor/Editor.tsx +0 -1221
  275. package/src/editor/EditorPanes.stories.tsx +0 -256
  276. package/src/editor/EditorPanes.test.tsx +0 -77
  277. package/src/editor/EditorPanes.tsx +0 -180
  278. package/src/editor/EntryNavigator.stories.tsx +0 -65
  279. package/src/editor/EntryNavigator.test.tsx +0 -598
  280. package/src/editor/EntryNavigator.tsx +0 -665
  281. package/src/editor/FormRenderer.stories.tsx +0 -212
  282. package/src/editor/FormRenderer.test.tsx +0 -194
  283. package/src/editor/FormRenderer.tsx +0 -432
  284. package/src/editor/GroupManager.stories.tsx +0 -301
  285. package/src/editor/GroupManager.test.tsx +0 -682
  286. package/src/editor/GroupManager.tsx +0 -9
  287. package/src/editor/PermissionManager.stories.tsx +0 -539
  288. package/src/editor/PermissionManager.test.tsx +0 -864
  289. package/src/editor/PermissionManager.tsx +0 -12
  290. package/src/editor/canopy-path.test.ts +0 -23
  291. package/src/editor/canopy-path.ts +0 -52
  292. package/src/editor/client-reference-resolver.ts +0 -118
  293. package/src/editor/comments/BranchComments.tsx +0 -93
  294. package/src/editor/comments/EntryComments.tsx +0 -94
  295. package/src/editor/comments/FieldWrapper.stories.tsx +0 -210
  296. package/src/editor/comments/FieldWrapper.tsx +0 -129
  297. package/src/editor/comments/InlineCommentThread.test.tsx +0 -384
  298. package/src/editor/comments/InlineCommentThread.tsx +0 -246
  299. package/src/editor/comments/ThreadCarousel.test.tsx +0 -393
  300. package/src/editor/comments/ThreadCarousel.tsx +0 -525
  301. package/src/editor/components/ConfirmDeleteModal.tsx +0 -49
  302. package/src/editor/components/EditorContext.tsx +0 -49
  303. package/src/editor/components/EditorFooter.tsx +0 -47
  304. package/src/editor/components/EditorHeader.tsx +0 -492
  305. package/src/editor/components/EditorSidebar.tsx +0 -193
  306. package/src/editor/components/EntryCreateModal.tsx +0 -193
  307. package/src/editor/components/RenameEntryModal.tsx +0 -152
  308. package/src/editor/components/UserBadge.test.tsx +0 -274
  309. package/src/editor/components/UserBadge.tsx +0 -240
  310. package/src/editor/components/index.ts +0 -6
  311. package/src/editor/context/ApiClientContext.tsx +0 -56
  312. package/src/editor/context/EditorStateContext.tsx +0 -221
  313. package/src/editor/context/index.ts +0 -40
  314. package/src/editor/editor-config.test.ts +0 -385
  315. package/src/editor/editor-config.ts +0 -94
  316. package/src/editor/editor-utils.test.ts +0 -772
  317. package/src/editor/editor-utils.ts +0 -303
  318. package/src/editor/env.ts +0 -4
  319. package/src/editor/fields/BlockField.stories.tsx +0 -79
  320. package/src/editor/fields/BlockField.tsx +0 -267
  321. package/src/editor/fields/CodeField.tsx +0 -41
  322. package/src/editor/fields/MarkdownField.tsx +0 -205
  323. package/src/editor/fields/ObjectField.tsx +0 -71
  324. package/src/editor/fields/ReferenceField.tsx +0 -138
  325. package/src/editor/fields/SelectField.tsx +0 -76
  326. package/src/editor/fields/TextField.tsx +0 -35
  327. package/src/editor/fields/ToggleField.tsx +0 -37
  328. package/src/editor/fields/fields.stories.tsx +0 -40
  329. package/src/editor/group-manager/ExternalGroupsTab.tsx +0 -114
  330. package/src/editor/group-manager/GroupCard.tsx +0 -102
  331. package/src/editor/group-manager/GroupForm.tsx +0 -66
  332. package/src/editor/group-manager/InternalGroupsTab.tsx +0 -147
  333. package/src/editor/group-manager/MemberList.tsx +0 -184
  334. package/src/editor/group-manager/hooks/useExternalGroupSearch.ts +0 -63
  335. package/src/editor/group-manager/hooks/useGroupState.ts +0 -134
  336. package/src/editor/group-manager/hooks/useUserSearch.ts +0 -84
  337. package/src/editor/group-manager/index.tsx +0 -210
  338. package/src/editor/group-manager/types.ts +0 -28
  339. package/src/editor/hooks/README.md +0 -26
  340. package/src/editor/hooks/__test__/test-utils.tsx +0 -183
  341. package/src/editor/hooks/index.ts +0 -23
  342. package/src/editor/hooks/useBranchActions.test.tsx +0 -267
  343. package/src/editor/hooks/useBranchActions.tsx +0 -121
  344. package/src/editor/hooks/useBranchManager.test.tsx +0 -391
  345. package/src/editor/hooks/useBranchManager.tsx +0 -326
  346. package/src/editor/hooks/useCommentSystem.test.ts +0 -615
  347. package/src/editor/hooks/useCommentSystem.ts +0 -347
  348. package/src/editor/hooks/useDraftManager.test.ts +0 -375
  349. package/src/editor/hooks/useDraftManager.ts +0 -259
  350. package/src/editor/hooks/useEditorLayout.test.ts +0 -147
  351. package/src/editor/hooks/useEditorLayout.ts +0 -67
  352. package/src/editor/hooks/useEntryManager.test.ts +0 -588
  353. package/src/editor/hooks/useEntryManager.ts +0 -387
  354. package/src/editor/hooks/useGroupManager.test.ts +0 -277
  355. package/src/editor/hooks/useGroupManager.ts +0 -139
  356. package/src/editor/hooks/usePermissionManager.test.ts +0 -211
  357. package/src/editor/hooks/usePermissionManager.ts +0 -113
  358. package/src/editor/hooks/useReferenceResolution.ts +0 -248
  359. package/src/editor/hooks/useSchemaManager.test.ts +0 -370
  360. package/src/editor/hooks/useSchemaManager.ts +0 -310
  361. package/src/editor/hooks/useUserContext.tsx +0 -57
  362. package/src/editor/hooks/useUserMetadata.test.ts +0 -191
  363. package/src/editor/hooks/useUserMetadata.ts +0 -71
  364. package/src/editor/permission-manager/GroupSelector.tsx +0 -73
  365. package/src/editor/permission-manager/PermissionEditor.tsx +0 -321
  366. package/src/editor/permission-manager/PermissionLevelBadge.tsx +0 -53
  367. package/src/editor/permission-manager/PermissionTree.tsx +0 -237
  368. package/src/editor/permission-manager/UserSelector.tsx +0 -95
  369. package/src/editor/permission-manager/constants.tsx +0 -18
  370. package/src/editor/permission-manager/hooks/useGroupsAndUsers.ts +0 -153
  371. package/src/editor/permission-manager/hooks/usePermissionTree.ts +0 -200
  372. package/src/editor/permission-manager/index.tsx +0 -294
  373. package/src/editor/permission-manager/types.ts +0 -58
  374. package/src/editor/permission-manager/utils.ts +0 -179
  375. package/src/editor/preview-bridge.test.tsx +0 -50
  376. package/src/editor/preview-bridge.tsx +0 -294
  377. package/src/editor/schema-editor/CollectionEditor.test.tsx +0 -238
  378. package/src/editor/schema-editor/CollectionEditor.tsx +0 -520
  379. package/src/editor/schema-editor/EntryTypeEditor.test.tsx +0 -215
  380. package/src/editor/schema-editor/EntryTypeEditor.tsx +0 -367
  381. package/src/editor/schema-editor/index.ts +0 -19
  382. package/src/editor/setup-test-dom.ts +0 -10
  383. package/src/editor/test-setup.ts +0 -33
  384. package/src/editor/theme.tsx +0 -119
  385. package/src/editor/utils/env.ts +0 -39
  386. package/src/entry-schema-registry.test.ts +0 -281
  387. package/src/entry-schema-registry.ts +0 -121
  388. package/src/entry-schema.ts +0 -84
  389. package/src/git-manager.test.ts +0 -552
  390. package/src/git-manager.ts +0 -667
  391. package/src/github-service.test.ts +0 -312
  392. package/src/github-service.ts +0 -295
  393. package/src/http/handler.test.ts +0 -275
  394. package/src/http/handler.ts +0 -280
  395. package/src/http/index.ts +0 -11
  396. package/src/http/router.ts +0 -164
  397. package/src/http/types.ts +0 -44
  398. package/src/id.test.ts +0 -48
  399. package/src/id.ts +0 -22
  400. package/src/index.ts +0 -26
  401. package/src/operating-mode/__tests__/strategies.test.ts +0 -511
  402. package/src/operating-mode/client-safe-strategy.ts +0 -184
  403. package/src/operating-mode/client-unsafe-strategy.ts +0 -303
  404. package/src/operating-mode/client.ts +0 -13
  405. package/src/operating-mode/index.ts +0 -34
  406. package/src/operating-mode/types.ts +0 -186
  407. package/src/paths/__tests__/branch.test.ts +0 -53
  408. package/src/paths/__tests__/normalize.test.ts +0 -141
  409. package/src/paths/__tests__/resolve.test.ts +0 -207
  410. package/src/paths/__tests__/validation.test.ts +0 -61
  411. package/src/paths/branch.ts +0 -115
  412. package/src/paths/index.ts +0 -73
  413. package/src/paths/normalize-server.ts +0 -40
  414. package/src/paths/normalize.ts +0 -107
  415. package/src/paths/resolve.ts +0 -61
  416. package/src/paths/test-utils.ts +0 -37
  417. package/src/paths/types.ts +0 -68
  418. package/src/paths/validation.test.ts +0 -480
  419. package/src/paths/validation.ts +0 -391
  420. package/src/reference-resolver.test.ts +0 -107
  421. package/src/reference-resolver.ts +0 -157
  422. package/src/schema/index.ts +0 -29
  423. package/src/schema/meta-loader.ts +0 -366
  424. package/src/schema/resolver.ts +0 -83
  425. package/src/schema/schema-store-types.ts +0 -56
  426. package/src/schema/schema-store.test.ts +0 -816
  427. package/src/schema/schema-store.ts +0 -795
  428. package/src/schema/types.ts +0 -33
  429. package/src/schema-meta-loader.test.ts +0 -447
  430. package/src/server.ts +0 -15
  431. package/src/services.test.ts +0 -559
  432. package/src/services.ts +0 -373
  433. package/src/settings-branch-utils.ts +0 -53
  434. package/src/settings-workspace.ts +0 -156
  435. package/src/task-queue/README.md +0 -144
  436. package/src/task-queue/index.ts +0 -14
  437. package/src/task-queue/task-queue.test.ts +0 -524
  438. package/src/task-queue/task-queue.ts +0 -514
  439. package/src/task-queue/types.ts +0 -41
  440. package/src/test-utils/api-test-helpers.ts +0 -445
  441. package/src/test-utils/console-spy.test.ts +0 -14
  442. package/src/test-utils/console-spy.ts +0 -125
  443. package/src/test-utils/git-helpers.ts +0 -31
  444. package/src/test-utils/index.ts +0 -4
  445. package/src/types.ts +0 -54
  446. package/src/user.ts +0 -118
  447. package/src/utils/debug.test.ts +0 -114
  448. package/src/utils/debug.ts +0 -127
  449. package/src/utils/error.test.ts +0 -92
  450. package/src/utils/error.ts +0 -83
  451. package/src/utils/format.ts +0 -12
  452. package/src/validation/__tests__/field-traversal.test.ts +0 -263
  453. package/src/validation/deletion-checker.ts +0 -234
  454. package/src/validation/field-traversal.ts +0 -146
  455. package/src/validation/reference-validator.ts +0 -168
  456. package/src/worker/cms-worker-rebase.test.ts +0 -473
  457. package/src/worker/cms-worker.ts +0 -777
  458. package/src/worker/integration.test.ts +0 -289
  459. package/src/worker/task-queue-config.ts +0 -25
  460. package/src/worker/task-queue.test.ts +0 -452
  461. package/src/worker/task-queue.ts +0 -58
  462. /package/{src/cli/templates → dist/cli/template-files}/Dockerfile.cms.template +0 -0
  463. /package/{src/cli/templates → dist/cli/template-files}/canopycms.config.ts.template +0 -0
  464. /package/{src/cli/templates → dist/cli/template-files}/deploy-cms.yml.template +0 -0
  465. /package/{src/cli/templates → dist/cli/template-files}/edit-page.tsx.template +0 -0
  466. /package/{src/cli/templates → dist/cli/template-files}/route.ts.template +0 -0
  467. /package/{src/cli/templates → dist/cli/template-files}/schemas.ts.template +0 -0
@@ -1,795 +0,0 @@
1
- /**
2
- * Schema Store - handles reading and writing .collection.json files.
3
- *
4
- * This module provides CRUD operations for collection schema metadata:
5
- * - Create/update/delete collections
6
- * - Add/update/remove entry types
7
- * - Update ordering of items within collections
8
- *
9
- * All mutations are branch-specific (like content edits).
10
- */
11
-
12
- import { promises as fs } from 'node:fs'
13
- import path from 'node:path'
14
- import { z } from 'zod'
15
-
16
- import type { ContentFormat } from '../config'
17
- import type { EntrySchemaRegistry } from './types'
18
- import { resolveCollectionPath } from '../content-id-index'
19
- import { generateId, isValidId } from '../id'
20
- import { createLogicalPath, validateAndNormalizePath } from '../paths'
21
- import type { LogicalPath, ContentId } from '../paths/types'
22
- import type { CanopyServices } from '../services'
23
-
24
- // Re-export types from client-safe module
25
- export type {
26
- CreateCollectionInput,
27
- CreateEntryTypeInput,
28
- UpdateCollectionInput,
29
- UpdateEntryTypeInput,
30
- } from './schema-store-types'
31
-
32
- // Import types for internal use
33
- import type {
34
- CreateCollectionInput,
35
- CreateEntryTypeInput,
36
- UpdateCollectionInput,
37
- UpdateEntryTypeInput,
38
- } from './schema-store-types'
39
-
40
- /**
41
- * Raw collection meta as stored in .collection.json
42
- */
43
- interface CollectionMetaFile {
44
- name: string
45
- label?: string
46
- entries?: Array<{
47
- name: string
48
- label?: string
49
- format: ContentFormat
50
- schema: string
51
- default?: boolean
52
- maxItems?: number
53
- }>
54
- order?: string[]
55
- }
56
-
57
- /**
58
- * Raw root collection meta as stored in content/.collection.json
59
- */
60
- interface RootCollectionMetaFile {
61
- label?: string
62
- entries?: Array<{
63
- name: string
64
- label?: string
65
- format: ContentFormat
66
- schema: string
67
- default?: boolean
68
- maxItems?: number
69
- }>
70
- order?: string[]
71
- }
72
-
73
- // ============================================================================
74
- // Zod Schemas for Validation
75
- // ============================================================================
76
-
77
- /** Max length for names and slugs (filesystem path safety) */
78
- const MAX_NAME_LENGTH = 64
79
- /** Max length for labels */
80
- const MAX_LABEL_LENGTH = 128
81
-
82
- const entryTypeInputSchema = z.object({
83
- name: z.string().min(1).max(MAX_NAME_LENGTH),
84
- label: z.string().max(MAX_LABEL_LENGTH).optional(),
85
- format: z.enum(['md', 'mdx', 'json']),
86
- schema: z.string().min(1),
87
- default: z.boolean().optional(),
88
- maxItems: z.number().int().positive().optional(),
89
- })
90
-
91
- const createCollectionInputSchema = z.object({
92
- name: z.string().min(1).max(MAX_NAME_LENGTH),
93
- label: z.string().max(MAX_LABEL_LENGTH).optional(),
94
- parentPath: z.string().optional(),
95
- entries: z.array(entryTypeInputSchema).min(1, 'Collection must have at least one entry type'),
96
- })
97
-
98
- const updateCollectionInputSchema = z.object({
99
- name: z.string().min(1).max(MAX_NAME_LENGTH).optional(),
100
- label: z.string().max(MAX_LABEL_LENGTH).optional(),
101
- slug: z.string().min(1).max(MAX_NAME_LENGTH).optional(), // Directory name (e.g., "posts" in "posts.{id}/")
102
- order: z.array(z.string()).optional(),
103
- })
104
-
105
- const updateEntryTypeInputSchema = z.object({
106
- label: z.string().max(MAX_LABEL_LENGTH).optional(),
107
- format: z.enum(['md', 'mdx', 'json']).optional(),
108
- schema: z.string().min(1).optional(),
109
- default: z.boolean().optional(),
110
- maxItems: z.number().int().positive().optional(),
111
- })
112
-
113
- // ============================================================================
114
- // SchemaOps Class
115
- // ============================================================================
116
-
117
- export class SchemaOps {
118
- constructor(
119
- private readonly contentRoot: string,
120
- private readonly entrySchemaRegistry: EntrySchemaRegistry,
121
- private readonly services?: CanopyServices,
122
- ) {}
123
-
124
- // --------------------------------------------------------------------------
125
- // Cache Invalidation
126
- // --------------------------------------------------------------------------
127
-
128
- /**
129
- * Invalidate schema cache for this branch after mutations.
130
- * This marks the cache as stale so the next schema load will regenerate it.
131
- */
132
- private async invalidateSchemaCache(): Promise<void> {
133
- if (this.services) {
134
- // Get branchRoot from contentRoot (parent directory)
135
- const branchRoot = path.dirname(this.contentRoot)
136
- await this.services.branchSchemaCache.invalidate(branchRoot)
137
- }
138
- }
139
-
140
- // --------------------------------------------------------------------------
141
- // Validation Helpers
142
- // --------------------------------------------------------------------------
143
-
144
- /**
145
- * Validate that a schema reference exists in the registry
146
- */
147
- validateSchemaReference(schemaKey: string): boolean {
148
- return schemaKey in this.entrySchemaRegistry
149
- }
150
-
151
- /**
152
- * Validate all schema references in entry types
153
- */
154
- private validateEntryTypeSchemas(entryTypes: CreateEntryTypeInput[]): {
155
- valid: boolean
156
- error?: string
157
- } {
158
- for (const entryType of entryTypes) {
159
- if (!this.validateSchemaReference(entryType.schema)) {
160
- const available = Object.keys(this.entrySchemaRegistry).join(', ')
161
- return {
162
- valid: false,
163
- error: `Schema reference "${entryType.schema}" not found. Available: ${available}`,
164
- }
165
- }
166
- }
167
- return { valid: true }
168
- }
169
-
170
- /**
171
- * Validate path to prevent traversal attacks
172
- */
173
- private validatePath(targetPath: string): {
174
- valid: boolean
175
- normalizedPath?: string
176
- error?: string
177
- } {
178
- const result = validateAndNormalizePath(this.contentRoot, targetPath)
179
- if (!result.valid) {
180
- return { valid: false, error: result.error || 'Invalid path' }
181
- }
182
- return { valid: true, normalizedPath: result.normalizedPath }
183
- }
184
-
185
- // --------------------------------------------------------------------------
186
- // Read Operations
187
- // --------------------------------------------------------------------------
188
-
189
- /**
190
- * Read a collection's .collection.json file
191
- */
192
- async readCollectionMeta(collectionPath: LogicalPath): Promise<CollectionMetaFile | null> {
193
- // Resolve logical path to physical path with embedded IDs
194
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
195
- if (!physicalPath) {
196
- return null
197
- }
198
-
199
- const metaPath = path.join(physicalPath, '.collection.json')
200
- try {
201
- const content = await fs.readFile(metaPath, 'utf-8')
202
- return JSON.parse(content) as CollectionMetaFile
203
- } catch (err) {
204
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
205
- return null
206
- }
207
- throw err
208
- }
209
- }
210
-
211
- /**
212
- * Read root collection meta (content/.collection.json)
213
- */
214
- async readRootCollectionMeta(): Promise<RootCollectionMetaFile | null> {
215
- const metaPath = path.join(this.contentRoot, '.collection.json')
216
- try {
217
- const content = await fs.readFile(metaPath, 'utf-8')
218
- return JSON.parse(content) as RootCollectionMetaFile
219
- } catch (err) {
220
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
221
- return null
222
- }
223
- throw err
224
- }
225
- }
226
-
227
- /**
228
- * Check if a collection is empty (has no content files or child collections)
229
- */
230
- async isCollectionEmpty(collectionPath: LogicalPath): Promise<boolean> {
231
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
232
- if (!physicalPath) {
233
- // Collection doesn't exist, consider it empty
234
- return true
235
- }
236
-
237
- try {
238
- const entries = await fs.readdir(physicalPath, { withFileTypes: true })
239
- for (const entry of entries) {
240
- // Content files mean not empty
241
- if (entry.isFile() && entry.name !== '.collection.json') {
242
- return false
243
- }
244
- // Child collection directories mean not empty
245
- if (entry.isDirectory()) {
246
- try {
247
- await fs.access(path.join(physicalPath, entry.name, '.collection.json'))
248
- return false
249
- } catch {
250
- // Not a collection directory, ignore
251
- }
252
- }
253
- }
254
- return true
255
- } catch (err) {
256
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
257
- return true
258
- }
259
- throw err
260
- }
261
- }
262
-
263
- // --------------------------------------------------------------------------
264
- // Write Operations
265
- // --------------------------------------------------------------------------
266
-
267
- /**
268
- * Write a collection's .collection.json file
269
- */
270
- private async writeCollectionMeta(physicalPath: string, meta: CollectionMetaFile): Promise<void> {
271
- const metaPath = path.join(physicalPath, '.collection.json')
272
- const content = JSON.stringify(meta, null, 2) + '\n'
273
- await fs.writeFile(metaPath, content, 'utf-8')
274
- }
275
-
276
- /**
277
- * Write root collection meta
278
- */
279
- private async writeRootCollectionMeta(meta: RootCollectionMetaFile): Promise<void> {
280
- const metaPath = path.join(this.contentRoot, '.collection.json')
281
- const content = JSON.stringify(meta, null, 2) + '\n'
282
- await fs.writeFile(metaPath, content, 'utf-8')
283
- }
284
-
285
- // --------------------------------------------------------------------------
286
- // Collection Operations
287
- // --------------------------------------------------------------------------
288
-
289
- /**
290
- * Create a new collection
291
- */
292
- async createCollection(
293
- input: CreateCollectionInput,
294
- ): Promise<{ collectionPath: LogicalPath; contentId: ContentId }> {
295
- // Validate input
296
- const parseResult = createCollectionInputSchema.safeParse(input)
297
- if (!parseResult.success) {
298
- throw new Error(`Invalid input: ${parseResult.error.message}`)
299
- }
300
-
301
- // Validate schema references
302
- const schemaValidation = this.validateEntryTypeSchemas(input.entries)
303
- if (!schemaValidation.valid) {
304
- throw new Error(schemaValidation.error)
305
- }
306
-
307
- // Determine parent directory
308
- let parentPhysicalPath: string
309
- if (input.parentPath) {
310
- const resolved = await resolveCollectionPath(this.contentRoot, input.parentPath)
311
- if (!resolved) {
312
- throw new Error(`Parent collection not found: ${input.parentPath}`)
313
- }
314
- parentPhysicalPath = resolved
315
- } else {
316
- parentPhysicalPath = this.contentRoot
317
- }
318
-
319
- // Generate embedded ID for new collection
320
- const contentId = generateId()
321
- const dirName = `${input.name}.${contentId}`
322
- const physicalPath = path.join(parentPhysicalPath, dirName)
323
-
324
- // Create directory
325
- await fs.mkdir(physicalPath, { recursive: true })
326
-
327
- // Build collection meta with empty order array (required for ordering support)
328
- const meta: CollectionMetaFile = {
329
- name: input.name,
330
- label: input.label,
331
- entries: input.entries.map((et) => ({
332
- name: et.name,
333
- label: et.label,
334
- format: et.format,
335
- schema: et.schema,
336
- default: et.default,
337
- maxItems: et.maxItems,
338
- })),
339
- order: [], // Initialize with empty order array
340
- }
341
-
342
- // Write .collection.json
343
- await this.writeCollectionMeta(physicalPath, meta)
344
-
345
- // Add new collection's contentId to parent's order array
346
- // For root-level collections (empty parentPath), we don't update parent order
347
- const parentLogicalPath = input.parentPath
348
- ? createLogicalPath(input.parentPath)
349
- : createLogicalPath('')
350
- const parentMeta = input.parentPath ? await this.readCollectionMeta(parentLogicalPath) : null
351
- if (parentMeta) {
352
- // Initialize parent's order array if it doesn't exist
353
- const existingOrder = parentMeta.order ?? []
354
- parentMeta.order = [...existingOrder, contentId]
355
- await this.writeCollectionMeta(parentPhysicalPath, parentMeta)
356
- }
357
-
358
- // Build logical path
359
- const logicalPath = input.parentPath
360
- ? createLogicalPath(`${input.parentPath}/${input.name}`)
361
- : createLogicalPath(input.name)
362
-
363
- // Invalidate schema cache after mutation
364
- await this.invalidateSchemaCache()
365
-
366
- return { collectionPath: logicalPath, contentId }
367
- }
368
-
369
- /**
370
- * Update a collection's metadata
371
- */
372
- async updateCollection(
373
- collectionPath: LogicalPath,
374
- updates: UpdateCollectionInput,
375
- ): Promise<void> {
376
- // Validate input
377
- const parseResult = updateCollectionInputSchema.safeParse(updates)
378
- if (!parseResult.success) {
379
- throw new Error(`Invalid input: ${parseResult.error.message}`)
380
- }
381
-
382
- // Check if this is the root collection (path equals contentRoot basename, e.g., "content")
383
- const contentRootName = path.basename(this.contentRoot)
384
- if (collectionPath === contentRootName) {
385
- // Update root collection meta
386
- let meta = await this.readRootCollectionMeta()
387
- if (!meta) {
388
- meta = {}
389
- }
390
- // Root only supports label and order updates (no name)
391
- if (updates.label !== undefined) {
392
- meta.label = updates.label
393
- }
394
- if (updates.order !== undefined) {
395
- meta.order = updates.order
396
- }
397
- await this.writeRootCollectionMeta(meta)
398
- // Invalidate schema cache after mutation
399
- await this.invalidateSchemaCache()
400
- return
401
- }
402
-
403
- // Strip contentRoot prefix to get relative path for regular collection
404
- // E.g., "content/posts" -> "posts"
405
- const relativePath = collectionPath.startsWith(`${contentRootName}/`)
406
- ? collectionPath.slice(contentRootName.length + 1)
407
- : collectionPath
408
-
409
- // Resolve path for regular collection
410
- const physicalPath = await resolveCollectionPath(
411
- this.contentRoot,
412
- createLogicalPath(relativePath),
413
- )
414
- if (!physicalPath) {
415
- throw new Error(`Collection not found: ${collectionPath}`)
416
- }
417
-
418
- // Read existing meta
419
- const meta = await this.readCollectionMeta(relativePath as LogicalPath)
420
- if (!meta) {
421
- throw new Error(`Collection meta not found: ${collectionPath}`)
422
- }
423
-
424
- // Handle slug change (directory rename) if provided
425
- let finalPhysicalPath = physicalPath
426
- if (updates.slug !== undefined) {
427
- // Extract current slug and ID from physical path
428
- // Format: /path/to/{slug}.{12-char-id}
429
- const dirName = path.basename(physicalPath)
430
- const parts = dirName.split('.')
431
-
432
- if (parts.length !== 2 || !isValidId(parts[1])) {
433
- throw new Error(`Invalid collection directory format: ${dirName}`)
434
- }
435
-
436
- const currentSlug = parts[0]
437
- const contentId = parts[1]
438
-
439
- // Only rename if slug is actually different
440
- if (updates.slug !== currentSlug) {
441
- // Validate new slug (alphanumeric + hyphens, lowercase)
442
- if (!/^[a-z][a-z0-9-]*$/.test(updates.slug)) {
443
- throw new Error(
444
- 'Slug must start with a letter and contain only lowercase letters, numbers, and hyphens',
445
- )
446
- }
447
-
448
- // Build new path with new slug + same ID
449
- const parentDir = path.dirname(physicalPath)
450
- const newDirName = `${updates.slug}.${contentId}`
451
- const newPhysicalPath = path.join(parentDir, newDirName)
452
-
453
- // Check if any collection with this slug already exists
454
- // Need to check for any directory matching {slug}.{any-id}
455
- try {
456
- const entries = await fs.readdir(parentDir, { withFileTypes: true })
457
- for (const entry of entries) {
458
- if (entry.isDirectory() && entry.name.startsWith(`${updates.slug}.`)) {
459
- const parts = entry.name.split('.')
460
- if (parts.length === 2 && isValidId(parts[1])) {
461
- throw new Error(`Collection with slug "${updates.slug}" already exists`)
462
- }
463
- }
464
- }
465
- } catch (err) {
466
- // Re-throw "already exists" errors
467
- if ((err as Error).message.includes('already exists')) {
468
- throw err
469
- }
470
- // Ignore other errors (e.g., ENOENT if parent dir doesn't exist somehow)
471
- }
472
-
473
- // Atomically rename the directory
474
- await fs.rename(physicalPath, newPhysicalPath)
475
- finalPhysicalPath = newPhysicalPath
476
-
477
- // Note: Content ID index will rebuild lazily on next access
478
- }
479
- }
480
-
481
- // Apply metadata updates
482
- if (updates.name !== undefined) {
483
- meta.name = updates.name
484
- }
485
- if (updates.label !== undefined) {
486
- meta.label = updates.label
487
- }
488
- if (updates.order !== undefined) {
489
- meta.order = updates.order
490
- }
491
-
492
- // Write back to the (potentially renamed) path
493
- await this.writeCollectionMeta(finalPhysicalPath, meta)
494
-
495
- // Invalidate schema cache after mutation
496
- await this.invalidateSchemaCache()
497
- }
498
-
499
- /**
500
- * Delete a collection (must be empty)
501
- */
502
- async deleteCollection(collectionPath: LogicalPath): Promise<void> {
503
- // Check if empty
504
- const isEmpty = await this.isCollectionEmpty(collectionPath)
505
- if (!isEmpty) {
506
- throw new Error('Collection must be empty before deletion. Delete all entries first.')
507
- }
508
-
509
- // Resolve path
510
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
511
- if (!physicalPath) {
512
- throw new Error(`Collection not found: ${collectionPath}`)
513
- }
514
-
515
- // Delete the directory (including .collection.json)
516
- await fs.rm(physicalPath, { recursive: true })
517
-
518
- // Invalidate schema cache after mutation
519
- await this.invalidateSchemaCache()
520
- }
521
-
522
- // --------------------------------------------------------------------------
523
- // Entry Type Operations
524
- // --------------------------------------------------------------------------
525
-
526
- /**
527
- * Add an entry type to a collection
528
- */
529
- async addEntryType(collectionPath: LogicalPath, entryType: CreateEntryTypeInput): Promise<void> {
530
- // Validate input
531
- const parseResult = entryTypeInputSchema.safeParse(entryType)
532
- if (!parseResult.success) {
533
- throw new Error(`Invalid input: ${parseResult.error.message}`)
534
- }
535
-
536
- // Validate schema reference
537
- if (!this.validateSchemaReference(entryType.schema)) {
538
- const available = Object.keys(this.entrySchemaRegistry).join(', ')
539
- throw new Error(`Schema reference "${entryType.schema}" not found. Available: ${available}`)
540
- }
541
-
542
- // Resolve path
543
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
544
- if (!physicalPath) {
545
- throw new Error(`Collection not found: ${collectionPath}`)
546
- }
547
-
548
- // Read existing meta
549
- const meta = await this.readCollectionMeta(collectionPath)
550
- if (!meta) {
551
- throw new Error(`Collection meta not found: ${collectionPath}`)
552
- }
553
-
554
- // Check for duplicate name
555
- if (meta.entries?.some((et) => et.name === entryType.name)) {
556
- throw new Error(`Entry type "${entryType.name}" already exists in this collection`)
557
- }
558
-
559
- // Add entry type
560
- meta.entries = meta.entries || []
561
- meta.entries.push({
562
- name: entryType.name,
563
- label: entryType.label,
564
- format: entryType.format,
565
- schema: entryType.schema,
566
- default: entryType.default,
567
- maxItems: entryType.maxItems,
568
- })
569
-
570
- // Write back
571
- await this.writeCollectionMeta(physicalPath, meta)
572
-
573
- // Invalidate schema cache after mutation
574
- await this.invalidateSchemaCache()
575
- }
576
-
577
- /**
578
- * Update an entry type in a collection
579
- */
580
- async updateEntryType(
581
- collectionPath: LogicalPath,
582
- entryTypeName: string,
583
- updates: UpdateEntryTypeInput,
584
- ): Promise<void> {
585
- // Validate input
586
- const parseResult = updateEntryTypeInputSchema.safeParse(updates)
587
- if (!parseResult.success) {
588
- throw new Error(`Invalid input: ${parseResult.error.message}`)
589
- }
590
-
591
- // Validate schema reference if provided
592
- if (updates.schema && !this.validateSchemaReference(updates.schema)) {
593
- const available = Object.keys(this.entrySchemaRegistry).join(', ')
594
- throw new Error(`Schema reference "${updates.schema}" not found. Available: ${available}`)
595
- }
596
-
597
- // Resolve path
598
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
599
- if (!physicalPath) {
600
- throw new Error(`Collection not found: ${collectionPath}`)
601
- }
602
-
603
- // Read existing meta
604
- const meta = await this.readCollectionMeta(collectionPath)
605
- if (!meta) {
606
- throw new Error(`Collection meta not found: ${collectionPath}`)
607
- }
608
-
609
- // Find entry type
610
- const entryType = meta.entries?.find((et) => et.name === entryTypeName)
611
- if (!entryType) {
612
- throw new Error(`Entry type "${entryTypeName}" not found in collection`)
613
- }
614
-
615
- // Apply updates
616
- if (updates.label !== undefined) {
617
- entryType.label = updates.label
618
- }
619
- if (updates.format !== undefined) {
620
- entryType.format = updates.format
621
- }
622
- if (updates.schema !== undefined) {
623
- entryType.schema = updates.schema
624
- }
625
- if (updates.default !== undefined) {
626
- entryType.default = updates.default
627
- }
628
- if (updates.maxItems !== undefined) {
629
- entryType.maxItems = updates.maxItems
630
- }
631
-
632
- // Write back
633
- await this.writeCollectionMeta(physicalPath, meta)
634
-
635
- // Invalidate schema cache after mutation
636
- await this.invalidateSchemaCache()
637
- }
638
-
639
- /**
640
- * Remove an entry type from a collection
641
- */
642
- async removeEntryType(collectionPath: LogicalPath, entryTypeName: string): Promise<void> {
643
- // Resolve path
644
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
645
- if (!physicalPath) {
646
- throw new Error(`Collection not found: ${collectionPath}`)
647
- }
648
-
649
- // Read existing meta
650
- const meta = await this.readCollectionMeta(collectionPath)
651
- if (!meta) {
652
- throw new Error(`Collection meta not found: ${collectionPath}`)
653
- }
654
-
655
- // Check entry type exists
656
- const index = meta.entries?.findIndex((et) => et.name === entryTypeName) ?? -1
657
- if (index === -1) {
658
- throw new Error(`Entry type "${entryTypeName}" not found in collection`)
659
- }
660
-
661
- // Ensure at least one entry type remains
662
- if (meta.entries!.length === 1) {
663
- throw new Error(
664
- 'Cannot remove last entry type. Collection must have at least one entry type.',
665
- )
666
- }
667
-
668
- // Check for entries still using this type
669
- const usageCount = await this.countEntriesUsingType(collectionPath, entryTypeName)
670
- if (usageCount > 0) {
671
- throw new Error(
672
- `Cannot remove entry type "${entryTypeName}": ${usageCount} ${usageCount === 1 ? 'entry still uses' : 'entries still use'} it. ` +
673
- 'Delete or migrate those entries first.',
674
- )
675
- }
676
-
677
- // Remove entry type
678
- meta.entries!.splice(index, 1)
679
-
680
- // Write back
681
- await this.writeCollectionMeta(physicalPath, meta)
682
-
683
- // Invalidate schema cache after mutation
684
- await this.invalidateSchemaCache()
685
- }
686
-
687
- // --------------------------------------------------------------------------
688
- // Usage Counting
689
- // --------------------------------------------------------------------------
690
-
691
- /**
692
- * Count the number of entries using a specific entry type in a collection.
693
- * This is used to prevent breaking changes to entry types that have existing content.
694
- *
695
- * @param collectionPath - Logical path to the collection (e.g., "content/posts")
696
- * @param entryTypeName - Name of the entry type to count
697
- * @returns Number of entries using this entry type
698
- *
699
- * @example
700
- * ```ts
701
- * const count = await store.countEntriesUsingType('content/posts', 'post')
702
- * if (count > 0) {
703
- * // Cannot modify schema/format
704
- * }
705
- * ```
706
- */
707
- async countEntriesUsingType(collectionPath: LogicalPath, entryTypeName: string): Promise<number> {
708
- // Resolve collection physical path
709
- const physicalPath = await resolveCollectionPath(this.contentRoot, collectionPath)
710
- if (!physicalPath) {
711
- // Collection doesn't exist yet - return 0
712
- return 0
713
- }
714
-
715
- try {
716
- // Read directory entries
717
- const entries = await fs.readdir(physicalPath, { withFileTypes: true })
718
-
719
- // Count files matching pattern: {entryTypeName}.{slug}.{id}.{ext}
720
- let count = 0
721
- for (const entry of entries) {
722
- // Skip directories and hidden files
723
- if (entry.isDirectory() || entry.name.startsWith('.')) {
724
- continue
725
- }
726
-
727
- // Parse filename: type.slug.id.ext
728
- const parts = entry.name.split('.')
729
-
730
- // Need at least 4 parts: type, slug, id, ext
731
- if (parts.length < 4) {
732
- continue
733
- }
734
-
735
- // Check if first part matches entry type name
736
- if (parts[0] !== entryTypeName) {
737
- continue
738
- }
739
-
740
- // Check if second-to-last part is a valid 12-char ID
741
- const candidateId = parts[parts.length - 2]
742
- if (isValidId(candidateId)) {
743
- count++
744
- }
745
- }
746
-
747
- return count
748
- } catch (err) {
749
- // Directory might not exist yet
750
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
751
- return 0
752
- }
753
- throw err
754
- }
755
- }
756
-
757
- // --------------------------------------------------------------------------
758
- // Order Operations
759
- // --------------------------------------------------------------------------
760
-
761
- /**
762
- * Update the order of items in a collection
763
- */
764
- async updateOrder(collectionPath: LogicalPath, order: string[]): Promise<void> {
765
- // Check if this is the root collection (path equals contentRoot basename, e.g., "content")
766
- const contentRootName = path.basename(this.contentRoot)
767
- if (collectionPath === contentRootName) {
768
- // Update root collection meta
769
- let meta = await this.readRootCollectionMeta()
770
- if (!meta) {
771
- meta = {}
772
- }
773
- meta.order = order
774
- await this.writeRootCollectionMeta(meta)
775
- // Invalidate schema cache after mutation
776
- await this.invalidateSchemaCache()
777
- return
778
- }
779
-
780
- // Update regular collection (handles contentRoot prefix stripping internally)
781
- // Note: updateCollection already invalidates cache, so no need to do it again
782
- await this.updateCollection(collectionPath, { order })
783
- }
784
- }
785
-
786
- // ============================================================================
787
- // Exports
788
- // ============================================================================
789
-
790
- export {
791
- createCollectionInputSchema,
792
- updateCollectionInputSchema,
793
- entryTypeInputSchema,
794
- updateEntryTypeInputSchema,
795
- }