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,391 +0,0 @@
1
- /**
2
- * Path validation utilities.
3
- *
4
- * Security-focused validation for content paths and slugs.
5
- */
6
-
7
- import { normalizeFilesystemPath, hasTraversalSequence } from './normalize'
8
- import type {
9
- LogicalPath,
10
- PhysicalPath,
11
- ContentId,
12
- BranchName,
13
- EntrySlug,
14
- CollectionSlug,
15
- } from './types'
16
-
17
- /**
18
- * Base58 alphabet used for content IDs (excludes ambiguous: 0, O, I, l)
19
- */
20
- const BASE58_PATTERN = '[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]'
21
-
22
- /**
23
- * Pattern matching exactly a 12-character content ID
24
- */
25
- const CONTENT_ID_PATTERN = new RegExp(`^${BASE58_PATTERN}{12}$`)
26
-
27
- /**
28
- * Pattern matching a physical path segment with embedded ID.
29
- * Matches patterns like:
30
- * - `post.my-slug.abc123def456.json` (entry)
31
- * - `posts.abc123def456` (collection directory)
32
- */
33
- const PHYSICAL_SEGMENT_PATTERN = new RegExp(`\\.${BASE58_PATTERN}{12}(?:\\.[a-z]+)?$`)
34
-
35
- /**
36
- * Validate a content path for security.
37
- *
38
- * @param path - The path to validate
39
- * @param rootPath - The root directory (for traversal check)
40
- * @returns Validation result
41
- */
42
- export function validateContentPath(
43
- path: string,
44
- rootPath: string,
45
- ): { valid: boolean; error?: string } {
46
- const normalized = normalizeFilesystemPath(path)
47
-
48
- // Check for traversal sequences
49
- if (hasTraversalSequence(normalized)) {
50
- return { valid: false, error: 'Path contains traversal sequence' }
51
- }
52
-
53
- // Check path doesn't escape root
54
- const normalizedRoot = normalizeFilesystemPath(rootPath)
55
- if (!normalized.startsWith(normalizedRoot) && normalized !== normalizedRoot) {
56
- // Allow paths that are relative within the root
57
- const normalizedPath = `${normalizedRoot}/${normalized}`
58
- if (hasTraversalSequence(normalizedPath)) {
59
- return { valid: false, error: 'Path escapes root directory' }
60
- }
61
- }
62
-
63
- return { valid: true }
64
- }
65
-
66
- /**
67
- * Validate a collection path.
68
- *
69
- * @param collectionPath - The collection path to validate
70
- * @returns true if valid, false otherwise
71
- */
72
- export function isValidCollectionPath(collectionPath: string): boolean {
73
- if (!collectionPath || collectionPath.length === 0) {
74
- return false
75
- }
76
-
77
- // Normalize and check for traversal
78
- const normalized = normalizeFilesystemPath(collectionPath)
79
- if (hasTraversalSequence(normalized)) {
80
- return false
81
- }
82
-
83
- // Collection paths should only contain alphanumeric, hyphens, underscores, and forward slashes
84
- const validPattern = /^[a-zA-Z0-9_/-]+$/
85
- return validPattern.test(normalized)
86
- }
87
-
88
- /**
89
- * Sanitize a string for use in paths by removing dangerous characters.
90
- *
91
- * @param input - The string to sanitize
92
- * @returns Sanitized string safe for path use
93
- */
94
- export function sanitizeForPath(input: string): string {
95
- return input
96
- .replace(/[<>:"|?*\\]/g, '') // Remove invalid filesystem chars
97
- .replace(/\.{2,}/g, '.') // Collapse multiple dots
98
- .replace(/^\./, '') // Remove leading dot
99
- .trim()
100
- }
101
-
102
- /**
103
- * Check if a path segment contains an embedded content ID.
104
- *
105
- * Physical paths have segments with embedded 12-char IDs:
106
- * - `post.my-slug.abc123def456.json` (entry file)
107
- * - `posts.abc123def456` (collection directory)
108
- *
109
- * @param segment - A single path segment (no slashes)
110
- * @returns true if segment contains embedded ID pattern
111
- */
112
- export function hasEmbeddedContentId(segment: string): boolean {
113
- return PHYSICAL_SEGMENT_PATTERN.test(segment)
114
- }
115
-
116
- /**
117
- * Check if a path appears to be a physical path (contains embedded content IDs).
118
- *
119
- * Physical paths have the format:
120
- * - `content/posts.abc123/post.hello.def456.json`
121
- *
122
- * Logical paths do not have embedded IDs:
123
- * - `content/posts/hello` or `posts/hello`
124
- *
125
- * @param path - The path to check
126
- * @returns true if any segment contains an embedded content ID
127
- */
128
- export function looksLikePhysicalPath(path: string): boolean {
129
- const segments = path.split('/')
130
- return segments.some(hasEmbeddedContentId)
131
- }
132
-
133
- /**
134
- * Check if a path appears to be a logical path (no embedded content IDs).
135
- *
136
- * @param path - The path to check
137
- * @returns true if no segments contain embedded content IDs
138
- */
139
- export function looksLikeLogicalPath(path: string): boolean {
140
- return !looksLikePhysicalPath(path)
141
- }
142
-
143
- /**
144
- * Validate and cast a string to LogicalPath.
145
- *
146
- * Use this at API boundaries to validate incoming path strings
147
- * and cast them to the branded LogicalPath type.
148
- *
149
- * @param path - The path string to validate
150
- * @returns Object with success flag and either the typed path or an error
151
- *
152
- * @example
153
- * ```ts
154
- * const result = parseLogicalPath(params.collectionPath)
155
- * if (!result.ok) {
156
- * return { ok: false, status: 400, error: result.error }
157
- * }
158
- * const collectionPath: LogicalPath = result.path
159
- * ```
160
- */
161
- export function parseLogicalPath(
162
- path: string,
163
- ): { ok: true; path: LogicalPath } | { ok: false; error: string } {
164
- // Basic validation
165
- if (!path || typeof path !== 'string') {
166
- return { ok: false, error: 'Path is required' }
167
- }
168
-
169
- // Normalize backslashes to forward slashes (consistent with parsePermissionPath)
170
- const normalized = path.replace(/\\/g, '/')
171
-
172
- // Security check
173
- if (hasTraversalSequence(normalized)) {
174
- return { ok: false, error: 'Path contains traversal sequence' }
175
- }
176
-
177
- // Check it's not a physical path
178
- if (looksLikePhysicalPath(normalized)) {
179
- return {
180
- ok: false,
181
- error:
182
- 'Path appears to be a physical path (contains embedded content ID). Expected a logical path.',
183
- }
184
- }
185
-
186
- return { ok: true, path: normalized as LogicalPath }
187
- }
188
-
189
- /**
190
- * Validate and cast a string to PhysicalPath.
191
- *
192
- * Use this at API boundaries to validate incoming path strings
193
- * and cast them to the branded PhysicalPath type.
194
- *
195
- * @param path - The path string to validate
196
- * @returns Object with success flag and either the typed path or an error
197
- */
198
- export function parsePhysicalPath(
199
- path: string,
200
- ): { ok: true; path: PhysicalPath } | { ok: false; error: string } {
201
- // Basic validation
202
- if (!path || typeof path !== 'string') {
203
- return { ok: false, error: 'Path is required' }
204
- }
205
-
206
- // Normalize backslashes to forward slashes (consistent with parseLogicalPath)
207
- const normalized = path.replace(/\\/g, '/')
208
-
209
- // Security check
210
- if (hasTraversalSequence(normalized)) {
211
- return { ok: false, error: 'Path contains traversal sequence' }
212
- }
213
-
214
- // Check it looks like a physical path
215
- if (!looksLikePhysicalPath(normalized)) {
216
- return {
217
- ok: false,
218
- error:
219
- 'Path appears to be a logical path (no embedded content ID). Expected a physical path.',
220
- }
221
- }
222
-
223
- return { ok: true, path: normalized as PhysicalPath }
224
- }
225
-
226
- /**
227
- * Check if a string is a valid 12-character content ID.
228
- *
229
- * @param id - The string to check
230
- * @returns true if valid Base58 12-char ID
231
- */
232
- export function isValidContentId(id: string): boolean {
233
- return CONTENT_ID_PATTERN.test(id)
234
- }
235
-
236
- /**
237
- * Parse and validate a ContentId from a string.
238
- * Validates Base58 format and 12-character length.
239
- *
240
- * @param id - The string to validate
241
- * @returns Object with success flag and either the typed ID or an error
242
- *
243
- * @example
244
- * ```ts
245
- * const result = parseContentId(fileId)
246
- * if (!result.ok) {
247
- * throw new Error(result.error)
248
- * }
249
- * const contentId: ContentId = result.id
250
- * ```
251
- */
252
- export function parseContentId(
253
- id: string,
254
- ): { ok: true; id: ContentId } | { ok: false; error: string } {
255
- if (!id || typeof id !== 'string') {
256
- return { ok: false, error: 'Content ID is required' }
257
- }
258
-
259
- if (!isValidContentId(id)) {
260
- return {
261
- ok: false,
262
- error: `Invalid content ID format (expected 12 Base58 characters, got: ${id})`,
263
- }
264
- }
265
-
266
- return { ok: true, id: id as ContentId }
267
- }
268
-
269
- /**
270
- * Parse and validate a BranchName.
271
- * Checks git branch naming rules.
272
- *
273
- * @param name - The branch name to validate
274
- * @returns Object with success flag and either the typed name or an error
275
- *
276
- * @example
277
- * ```ts
278
- * const result = parseBranchName(params.branch)
279
- * if (!result.ok) {
280
- * return { ok: false, status: 400, error: result.error }
281
- * }
282
- * const branchName: BranchName = result.name
283
- * ```
284
- */
285
- export function parseBranchName(
286
- name: string,
287
- ): { ok: true; name: BranchName } | { ok: false; error: string } {
288
- if (!name || typeof name !== 'string') {
289
- return { ok: false, error: 'Branch name is required' }
290
- }
291
-
292
- // Length limit (branch names become directory names)
293
- if (name.length > 250) {
294
- return { ok: false, error: 'Branch name too long (max 250 characters)' }
295
- }
296
-
297
- // Git branch name rules
298
- if (name.includes('..')) {
299
- return { ok: false, error: 'Branch name cannot contain ".."' }
300
- }
301
-
302
- if (name.startsWith('/') || name.endsWith('/') || name.includes('//')) {
303
- return { ok: false, error: 'Invalid branch name format (invalid slashes)' }
304
- }
305
-
306
- if (name.includes(' ')) {
307
- return { ok: false, error: 'Branch name cannot contain spaces' }
308
- }
309
-
310
- // Additional git restrictions
311
- if (name.startsWith('.') || name.endsWith('.')) {
312
- return { ok: false, error: 'Branch name cannot start or end with a dot' }
313
- }
314
-
315
- if (name.includes('@{')) {
316
- return { ok: false, error: 'Branch name cannot contain "@{"' }
317
- }
318
-
319
- // Git-forbidden characters: ~ ^ : ? * [ \ and control chars
320
- // eslint-disable-next-line no-control-regex -- intentional: git forbids control characters in branch names
321
- if (/[~^:?*[\\\x00-\x1f\x7f]/.test(name)) {
322
- return { ok: false, error: 'Branch name contains invalid characters' }
323
- }
324
-
325
- if (name.endsWith('.lock')) {
326
- return { ok: false, error: 'Branch name cannot end with ".lock"' }
327
- }
328
-
329
- return { ok: true, name: name as BranchName }
330
- }
331
-
332
- /**
333
- * Parse and validate a slug (collection or entry).
334
- * Validates format and length constraints.
335
- *
336
- * @param slug - The slug to validate
337
- * @param type - Whether this is a collection or entry slug
338
- * @returns Object with success flag and either the typed slug or an error
339
- *
340
- * @example
341
- * ```ts
342
- * const result = parseSlug(params.slug, 'entry')
343
- * if (!result.ok) {
344
- * return { ok: false, status: 400, error: result.error }
345
- * }
346
- * const entrySlug: EntrySlug = result.slug
347
- * ```
348
- */
349
- export function parseSlug(
350
- slug: string,
351
- type: 'collection' | 'entry',
352
- ): { ok: true; slug: CollectionSlug | EntrySlug } | { ok: false; error: string } {
353
- if (!slug || typeof slug !== 'string') {
354
- return {
355
- ok: false,
356
- error: `${type === 'collection' ? 'Collection' : 'Entry'} slug is required`,
357
- }
358
- }
359
-
360
- // Check length (filesystem path safety)
361
- if (slug.length > 64) {
362
- return { ok: false, error: 'Slug too long (max 64 characters)' }
363
- }
364
-
365
- // Check for path separators and traversal
366
- if (slug.includes('/') || slug.includes('\\')) {
367
- return {
368
- ok: false,
369
- error: 'Slug cannot contain path separators',
370
- }
371
- }
372
-
373
- if (slug === '.' || slug === '..') {
374
- return {
375
- ok: false,
376
- error: 'Slug cannot be a traversal sequence',
377
- }
378
- }
379
-
380
- // Validation from ContentStore.renameEntry
381
- if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
382
- return {
383
- ok: false,
384
- error:
385
- 'Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens',
386
- }
387
- }
388
-
389
- // Cast to appropriate branded type
390
- return { ok: true, slug: slug as CollectionSlug | EntrySlug }
391
- }
@@ -1,107 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import os from 'node:os'
3
- import path from 'node:path'
4
-
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
-
7
- import type { CanopyConfig } from './config'
8
- import { flattenSchema } from './config'
9
- import { ContentIdIndex } from './content-id-index'
10
- import { ContentStore } from './content-store'
11
- import { ReferenceResolver } from './reference-resolver'
12
- import { unsafeAsLogicalPath } from './paths/test-utils'
13
-
14
- describe('ReferenceResolver', () => {
15
- let tempDir: string
16
- let store: ContentStore
17
- let idIndex: ContentIdIndex
18
- let resolver: ReferenceResolver
19
-
20
- beforeEach(async () => {
21
- // Create temp directory with content structure
22
- tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'canopy-test-'))
23
- await fs.mkdir(path.join(tempDir, 'content', 'authors'), {
24
- recursive: true,
25
- })
26
-
27
- // Create test author files with embedded IDs: {type}.{slug}.{id}.{ext}
28
- const aliceId = 'aXice123ABC4' // 12 chars, valid Base58
29
- const bobId = 'bob456XYZ789' // 12 chars, valid Base58
30
-
31
- await fs.writeFile(
32
- path.join(tempDir, 'content', 'authors', `author.alice.${aliceId}.json`),
33
- JSON.stringify({ slug: 'alice', name: 'Alice' }),
34
- )
35
- await fs.writeFile(
36
- path.join(tempDir, 'content', 'authors', `author.bob.${bobId}.json`),
37
- JSON.stringify({ slug: 'bob', name: 'Bob' }),
38
- )
39
-
40
- // Initialize store and index
41
- const schema = {
42
- collections: [
43
- {
44
- name: 'authors',
45
- path: 'authors',
46
- entries: [
47
- {
48
- name: 'author',
49
- format: 'json' as const,
50
- schema: [{ name: 'name', type: 'string' as const, label: 'Name' }],
51
- },
52
- ],
53
- },
54
- ],
55
- } as const
56
-
57
- const config: CanopyConfig = {
58
- contentRoot: 'content',
59
- gitBotAuthorName: 'Test Bot',
60
- gitBotAuthorEmail: 'test@example.com',
61
- mode: 'prod',
62
- }
63
-
64
- store = new ContentStore(tempDir, flattenSchema(schema, config.contentRoot))
65
- idIndex = await store.idIndex()
66
- resolver = new ReferenceResolver(store, idIndex)
67
- })
68
-
69
- afterEach(async () => {
70
- // Clean up temp directory
71
- await fs.rm(tempDir, { recursive: true, force: true })
72
- })
73
-
74
- describe('loadReferenceOptions', () => {
75
- it('loads options when collection path does not include content/ prefix', async () => {
76
- // REGRESSION TEST: ID index stores "content/authors" but schema specifies "authors"
77
- // This ensures the collection path normalization works correctly
78
- const options = await resolver.loadReferenceOptions([unsafeAsLogicalPath('authors')], 'name')
79
-
80
- expect(options).toHaveLength(2)
81
- const labels = options.map((o) => o.label).sort()
82
- expect(labels).toEqual(['Alice', 'Bob'])
83
- expect(options.every((o) => o.id && o.collection)).toBe(true)
84
- })
85
-
86
- it('loads options when collection path includes content/ prefix', async () => {
87
- // REGRESSION TEST: Should also work if "content/authors" is explicitly specified
88
- const options = await resolver.loadReferenceOptions(
89
- [unsafeAsLogicalPath('content/authors')],
90
- 'name',
91
- )
92
-
93
- expect(options).toHaveLength(2)
94
- const labels = options.map((o) => o.label).sort()
95
- expect(labels).toEqual(['Alice', 'Bob'])
96
- })
97
-
98
- it('returns empty array for non-existent collection', async () => {
99
- const options = await resolver.loadReferenceOptions(
100
- [unsafeAsLogicalPath('nonexistent')],
101
- 'name',
102
- )
103
-
104
- expect(options).toHaveLength(0)
105
- })
106
- })
107
- })
@@ -1,157 +0,0 @@
1
- import path from 'node:path'
2
-
3
- import type { ContentStore } from './content-store'
4
- import type { ContentIdIndex } from './content-id-index'
5
- import { extractSlugFromFilename } from './content-id-index'
6
- import type { LogicalPath, PhysicalPath, EntrySlug } from './paths'
7
-
8
- export interface ResolvedReference {
9
- id: string
10
- exists: boolean
11
- displayValue: string
12
- collection?: LogicalPath
13
- slug?: EntrySlug
14
- }
15
-
16
- export interface ReferenceOption {
17
- id: string
18
- label: string
19
- collection: string
20
- }
21
-
22
- /**
23
- * ReferenceResolver resolves content IDs to display values for reference fields.
24
- *
25
- * This class provides utilities for:
26
- * - Resolving a single ID to its display value (e.g., title)
27
- * - Loading all available options for a reference field
28
- * - Filtering options by collection constraints
29
- * - Searching options by display value
30
- */
31
- export class ReferenceResolver {
32
- constructor(
33
- private store: ContentStore,
34
- private idIndex: ContentIdIndex,
35
- ) {}
36
-
37
- /**
38
- * Resolve a content ID to a display value.
39
- * Returns null if the ID doesn't exist or points to a collection.
40
- *
41
- * @param id - The content ID to resolve
42
- * @param displayField - The field to use for display value (default: 'title')
43
- */
44
- async resolve(id: string, displayField = 'title'): Promise<ResolvedReference | null> {
45
- const location = this.idIndex.findById(id)
46
-
47
- if (!location || location.type !== 'entry') {
48
- return {
49
- id,
50
- exists: false,
51
- displayValue: id, // Fallback to showing the ID itself
52
- }
53
- }
54
-
55
- try {
56
- const doc = await this.store.read(location.collection!, location.slug!)
57
- const displayValue = String(doc.data[displayField] || doc.data.title || location.slug)
58
-
59
- return {
60
- id,
61
- exists: true,
62
- displayValue,
63
- collection: location.collection,
64
- slug: location.slug,
65
- }
66
- } catch (error) {
67
- console.error('Failed to resolve reference:', { id, error })
68
- return {
69
- id,
70
- exists: false,
71
- displayValue: id,
72
- }
73
- }
74
- }
75
-
76
- /**
77
- * Load all available reference options for a reference field.
78
- *
79
- * This method scans the specified collections and returns options
80
- * suitable for a dropdown/select field.
81
- *
82
- * @param collections - Collection paths to search (e.g., ['posts', 'docs'])
83
- * @param displayField - Field to use for option labels (default: 'title')
84
- * @param search - Optional search string to filter options
85
- */
86
- async loadReferenceOptions(
87
- collections: LogicalPath[],
88
- displayField = 'title',
89
- search?: string,
90
- ): Promise<ReferenceOption[]> {
91
- const options: ReferenceOption[] = []
92
-
93
- for (const collectionPath of collections) {
94
- // Get all entries in this collection
95
- const entries = await this.listEntriesInCollection(collectionPath)
96
-
97
- for (const entry of entries) {
98
- const id = this.idIndex.findByPath(entry.relativePath)
99
- if (!id) continue
100
-
101
- try {
102
- // The slug from the index may include the entry type prefix for new-format files
103
- // (e.g., "author.alice" instead of just "alice"). We need to strip the type prefix
104
- // before passing to store.read() to avoid double-prefixing.
105
- // Use extractSlugFromFilename to properly extract just the slug part.
106
- const filename = path.basename(entry.relativePath)
107
- const normalizedSlug = extractSlugFromFilename(filename)
108
-
109
- const doc = await this.store.read(entry.collection, normalizedSlug as EntrySlug)
110
- const label = String(doc.data[displayField] || doc.data.title || normalizedSlug)
111
-
112
- // Apply search filter if provided
113
- if (search && !label.toLowerCase().includes(search.toLowerCase())) {
114
- continue
115
- }
116
-
117
- options.push({
118
- id,
119
- label,
120
- collection: entry.collection,
121
- })
122
- } catch (error) {
123
- // Skip entries that can't be read
124
- console.error('Failed to read entry for reference options:', {
125
- collection: entry.collection,
126
- slug: entry.slug,
127
- error,
128
- })
129
- continue
130
- }
131
- }
132
- }
133
-
134
- return options.sort((a, b) => a.label.localeCompare(b.label))
135
- }
136
-
137
- /**
138
- * Helper to list all entries in a collection.
139
- */
140
- private async listEntriesInCollection(collectionPath: LogicalPath): Promise<
141
- Array<{
142
- relativePath: PhysicalPath
143
- collection: LogicalPath
144
- slug: EntrySlug
145
- }>
146
- > {
147
- return this.store.listCollectionEntries(collectionPath)
148
- }
149
-
150
- /**
151
- * Resolve multiple IDs at once.
152
- * Useful for displaying lists of referenced items.
153
- */
154
- async resolveMany(ids: string[], displayField = 'title'): Promise<(ResolvedReference | null)[]> {
155
- return Promise.all(ids.map((id) => this.resolve(id, displayField)))
156
- }
157
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * Schema Module
3
- *
4
- * This module provides schema loading and resolution for CanopyCMS.
5
- * Schema structure is defined in .collection.json files (single source of truth)
6
- * while field schemas are defined in an entry schema registry for reusability.
7
- *
8
- * @example
9
- * ```ts
10
- * import { resolveSchema } from 'canopycms/schema'
11
- *
12
- * const { schema, sources } = await resolveSchema(contentRoot, entrySchemaRegistry)
13
- * ```
14
- */
15
-
16
- // Types
17
- export type { EntrySchemaRegistry, SchemaSourceInfo, SchemaResolutionResult } from './types'
18
-
19
- // Meta loader (low-level API)
20
- export {
21
- loadCollectionMetaFiles,
22
- resolveCollectionReferences,
23
- watchCollectionMetaFiles,
24
- type CollectionMeta,
25
- type RootCollectionMeta,
26
- } from './meta-loader'
27
-
28
- // Resolver (high-level API)
29
- export { resolveSchema, hasSchemaFiles, isValidSchema } from './resolver'