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.
- package/dist/auth/plugin.d.ts +8 -0
- package/dist/auth/plugin.d.ts.map +1 -1
- package/dist/build-mode.d.ts +15 -5
- package/dist/build-mode.d.ts.map +1 -1
- package/dist/build-mode.js +18 -8
- package/dist/build-mode.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +37 -36
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/template-files/ai-config.ts.template +21 -0
- package/dist/cli/template-files/ai-route.ts.template +10 -0
- package/dist/cli/template-files/canopy.ts.template +24 -0
- package/dist/cli/templates.d.ts +5 -1
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +9 -2
- package/dist/cli/templates.js.map +1 -1
- package/dist/config/schemas/config.d.ts +4 -0
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +2 -0
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content-reader.js +2 -2
- package/dist/content-reader.js.map +1 -1
- package/dist/context.js +5 -5
- package/dist/context.js.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.js +15 -18
- package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
- package/dist/operating-mode/types.d.ts +8 -0
- package/dist/operating-mode/types.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/package.json +5 -4
- package/src/cli/init.ts +43 -38
- package/dist/__integration__/fixtures/content-seeds.d.ts +0 -43
- package/dist/__integration__/fixtures/content-seeds.d.ts.map +0 -1
- package/dist/__integration__/fixtures/content-seeds.js +0 -99
- package/dist/__integration__/fixtures/content-seeds.js.map +0 -1
- package/dist/__integration__/fixtures/schemas.d.ts +0 -12
- package/dist/__integration__/fixtures/schemas.d.ts.map +0 -1
- package/dist/__integration__/fixtures/schemas.js +0 -65
- package/dist/__integration__/fixtures/schemas.js.map +0 -1
- package/dist/__integration__/test-utils/api-client.d.ts +0 -123
- package/dist/__integration__/test-utils/api-client.d.ts.map +0 -1
- package/dist/__integration__/test-utils/api-client.js +0 -118
- package/dist/__integration__/test-utils/api-client.js.map +0 -1
- package/dist/__integration__/test-utils/multi-user.d.ts +0 -25
- package/dist/__integration__/test-utils/multi-user.d.ts.map +0 -1
- package/dist/__integration__/test-utils/multi-user.js +0 -105
- package/dist/__integration__/test-utils/multi-user.js.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.d.ts +0 -25
- package/dist/__integration__/test-utils/test-workspace.d.ts.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.js +0 -102
- package/dist/__integration__/test-utils/test-workspace.js.map +0 -1
- package/dist/editor/BranchManager.stories.d.ts +0 -8
- package/dist/editor/BranchManager.stories.d.ts.map +0 -1
- package/dist/editor/BranchManager.stories.js +0 -74
- package/dist/editor/BranchManager.stories.js.map +0 -1
- package/dist/editor/CanopyEditor.stories.d.ts +0 -7
- package/dist/editor/CanopyEditor.stories.d.ts.map +0 -1
- package/dist/editor/CanopyEditor.stories.js +0 -99
- package/dist/editor/CanopyEditor.stories.js.map +0 -1
- package/dist/editor/CommentsPanel.stories.d.ts +0 -10
- package/dist/editor/CommentsPanel.stories.d.ts.map +0 -1
- package/dist/editor/CommentsPanel.stories.js +0 -175
- package/dist/editor/CommentsPanel.stories.js.map +0 -1
- package/dist/editor/Editor.stories.d.ts +0 -7
- package/dist/editor/Editor.stories.d.ts.map +0 -1
- package/dist/editor/Editor.stories.js +0 -95
- package/dist/editor/Editor.stories.js.map +0 -1
- package/dist/editor/EditorPanes.stories.d.ts +0 -7
- package/dist/editor/EditorPanes.stories.d.ts.map +0 -1
- package/dist/editor/EditorPanes.stories.js +0 -116
- package/dist/editor/EditorPanes.stories.js.map +0 -1
- package/dist/editor/EntryNavigator.stories.d.ts +0 -8
- package/dist/editor/EntryNavigator.stories.d.ts.map +0 -1
- package/dist/editor/EntryNavigator.stories.js +0 -42
- package/dist/editor/EntryNavigator.stories.js.map +0 -1
- package/dist/editor/FormRenderer.stories.d.ts +0 -7
- package/dist/editor/FormRenderer.stories.d.ts.map +0 -1
- package/dist/editor/FormRenderer.stories.js +0 -115
- package/dist/editor/FormRenderer.stories.js.map +0 -1
- package/dist/editor/GroupManager.stories.d.ts +0 -19
- package/dist/editor/GroupManager.stories.d.ts.map +0 -1
- package/dist/editor/GroupManager.stories.js +0 -265
- package/dist/editor/GroupManager.stories.js.map +0 -1
- package/dist/editor/PermissionManager.stories.d.ts +0 -20
- package/dist/editor/PermissionManager.stories.d.ts.map +0 -1
- package/dist/editor/PermissionManager.stories.js +0 -506
- package/dist/editor/PermissionManager.stories.js.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.d.ts +0 -10
- package/dist/editor/comments/FieldWrapper.stories.d.ts.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.js +0 -173
- package/dist/editor/comments/FieldWrapper.stories.js.map +0 -1
- package/dist/editor/fields/BlockField.stories.d.ts +0 -7
- package/dist/editor/fields/BlockField.stories.d.ts.map +0 -1
- package/dist/editor/fields/BlockField.stories.js +0 -50
- package/dist/editor/fields/BlockField.stories.js.map +0 -1
- package/dist/editor/fields/fields.stories.d.ts +0 -8
- package/dist/editor/fields/fields.stories.d.ts.map +0 -1
- package/dist/editor/fields/fields.stories.js +0 -34
- package/dist/editor/fields/fields.stories.js.map +0 -1
- package/dist/test-utils/api-test-helpers.d.ts +0 -238
- package/dist/test-utils/api-test-helpers.d.ts.map +0 -1
- package/dist/test-utils/api-test-helpers.js +0 -347
- package/dist/test-utils/api-test-helpers.js.map +0 -1
- package/dist/test-utils/console-spy.d.ts +0 -56
- package/dist/test-utils/console-spy.d.ts.map +0 -1
- package/dist/test-utils/console-spy.js +0 -81
- package/dist/test-utils/console-spy.js.map +0 -1
- package/dist/test-utils/git-helpers.d.ts +0 -21
- package/dist/test-utils/git-helpers.d.ts.map +0 -1
- package/dist/test-utils/git-helpers.js +0 -23
- package/dist/test-utils/git-helpers.js.map +0 -1
- package/dist/test-utils/index.d.ts +0 -5
- package/dist/test-utils/index.d.ts.map +0 -1
- package/dist/test-utils/index.js +0 -4
- package/dist/test-utils/index.js.map +0 -1
- package/src/__integration__/errors/invalid-content.test.ts +0 -238
- package/src/__integration__/errors/permission-denied.test.ts +0 -220
- package/src/__integration__/fixtures/content-seeds.ts +0 -105
- package/src/__integration__/fixtures/schemas.ts +0 -67
- package/src/__integration__/initialization/prod-sim-init.test.ts +0 -139
- package/src/__integration__/permissions/path-permissions.test.ts +0 -314
- package/src/__integration__/permissions/role-permissions.test.ts +0 -354
- package/src/__integration__/permissions/settings-branch-isolation.test.ts +0 -317
- package/src/__integration__/settings/groups-api.test.ts +0 -403
- package/src/__integration__/test-utils/api-client.ts +0 -167
- package/src/__integration__/test-utils/multi-user.ts +0 -129
- package/src/__integration__/test-utils/test-workspace.ts +0 -130
- package/src/__integration__/user/user-context.test.ts +0 -174
- package/src/__integration__/validation/input-validation.test.ts +0 -166
- package/src/__integration__/workflows/api-editing-workflow.test.ts +0 -244
- package/src/__integration__/workflows/conflict-resolution.test.ts +0 -259
- package/src/__integration__/workflows/editing-workflow.test.ts +0 -205
- package/src/__integration__/workflows/review-workflow.test.ts +0 -260
- package/src/ai/__tests__/build.integration.test.ts +0 -224
- package/src/ai/__tests__/generate.integration.test.ts +0 -495
- package/src/ai/__tests__/handler.integration.test.ts +0 -212
- package/src/ai/__tests__/json-to-markdown.test.ts +0 -553
- package/src/ai/generate.ts +0 -410
- package/src/ai/handler.ts +0 -123
- package/src/ai/index.ts +0 -26
- package/src/ai/json-to-markdown.ts +0 -424
- package/src/ai/resolve-branch.ts +0 -34
- package/src/ai/types.ts +0 -160
- package/src/api/AGENTS.md +0 -81
- package/src/api/__test__/mock-client.ts +0 -404
- package/src/api/assets.test.ts +0 -140
- package/src/api/assets.ts +0 -154
- package/src/api/branch-merge.test.ts +0 -163
- package/src/api/branch-merge.ts +0 -113
- package/src/api/branch-review.test.ts +0 -297
- package/src/api/branch-review.ts +0 -136
- package/src/api/branch-status.test.ts +0 -85
- package/src/api/branch-status.ts +0 -153
- package/src/api/branch-withdraw.test.ts +0 -146
- package/src/api/branch-withdraw.ts +0 -81
- package/src/api/branch-workflow.integration.test.ts +0 -578
- package/src/api/branch.test.ts +0 -620
- package/src/api/branch.ts +0 -492
- package/src/api/client.test.ts +0 -349
- package/src/api/client.ts +0 -506
- package/src/api/comments.test.ts +0 -285
- package/src/api/comments.ts +0 -210
- package/src/api/content.test.ts +0 -345
- package/src/api/content.ts +0 -454
- package/src/api/entries.test.ts +0 -1339
- package/src/api/entries.ts +0 -650
- package/src/api/github-sync.ts +0 -144
- package/src/api/groups.test.ts +0 -1013
- package/src/api/groups.ts +0 -375
- package/src/api/guards.test.ts +0 -533
- package/src/api/guards.ts +0 -271
- package/src/api/index.ts +0 -87
- package/src/api/permissions.test.ts +0 -766
- package/src/api/permissions.ts +0 -334
- package/src/api/reference-options.ts +0 -118
- package/src/api/resolve-references.ts +0 -107
- package/src/api/route-builder.ts +0 -289
- package/src/api/schema.test.ts +0 -840
- package/src/api/schema.ts +0 -936
- package/src/api/security.test.ts +0 -233
- package/src/api/settings-helpers.ts +0 -84
- package/src/api/types.ts +0 -40
- package/src/api/user.test.ts +0 -127
- package/src/api/user.ts +0 -42
- package/src/api/validators.test.ts +0 -275
- package/src/api/validators.ts +0 -176
- package/src/asset-store.test.ts +0 -37
- package/src/asset-store.ts +0 -110
- package/src/auth/cache.ts +0 -7
- package/src/auth/caching-auth-plugin.test.ts +0 -154
- package/src/auth/caching-auth-plugin.ts +0 -109
- package/src/auth/context-helpers.ts +0 -75
- package/src/auth/file-based-auth-cache.test.ts +0 -257
- package/src/auth/file-based-auth-cache.ts +0 -279
- package/src/auth/index.ts +0 -12
- package/src/auth/plugin.ts +0 -51
- package/src/auth/types.ts +0 -38
- package/src/authorization/__tests__/branch.test.ts +0 -260
- package/src/authorization/__tests__/content.test.ts +0 -142
- package/src/authorization/__tests__/path.test.ts +0 -133
- package/src/authorization/__tests__/permissions-loader.test.ts +0 -200
- package/src/authorization/branch.ts +0 -94
- package/src/authorization/content.ts +0 -93
- package/src/authorization/groups/index.ts +0 -11
- package/src/authorization/groups/loader.ts +0 -127
- package/src/authorization/groups/schema.ts +0 -48
- package/src/authorization/helpers.ts +0 -48
- package/src/authorization/index.ts +0 -84
- package/src/authorization/path.ts +0 -112
- package/src/authorization/permissions/index.ts +0 -11
- package/src/authorization/permissions/loader.ts +0 -116
- package/src/authorization/permissions/schema.ts +0 -66
- package/src/authorization/test-utils.ts +0 -15
- package/src/authorization/types.ts +0 -66
- package/src/authorization/validation.test.ts +0 -100
- package/src/authorization/validation.ts +0 -62
- package/src/branch-metadata.test.ts +0 -168
- package/src/branch-metadata.ts +0 -166
- package/src/branch-registry.test.ts +0 -248
- package/src/branch-registry.ts +0 -152
- package/src/branch-schema-cache.test.ts +0 -275
- package/src/branch-schema-cache.ts +0 -189
- package/src/branch-workspace.test.ts +0 -183
- package/src/branch-workspace.ts +0 -124
- package/src/build/generate-ai-content.ts +0 -78
- package/src/build/index.ts +0 -8
- package/src/build-mode.ts +0 -27
- package/src/cli/generate-ai-content.ts +0 -100
- package/src/cli/init.test.ts +0 -240
- package/src/cli/templates/canopy.ts.template +0 -55
- package/src/cli/templates.ts +0 -47
- package/src/client.ts +0 -12
- package/src/comment-store.test.ts +0 -442
- package/src/comment-store.ts +0 -301
- package/src/config/__tests__/config.test.ts +0 -513
- package/src/config/flatten.ts +0 -174
- package/src/config/helpers.ts +0 -167
- package/src/config/index.ts +0 -86
- package/src/config/schemas/collection.ts +0 -67
- package/src/config/schemas/config.ts +0 -77
- package/src/config/schemas/field.ts +0 -108
- package/src/config/schemas/media.ts +0 -27
- package/src/config/schemas/permissions.ts +0 -21
- package/src/config/types.ts +0 -321
- package/src/config/validation.ts +0 -70
- package/src/config-test.ts +0 -65
- package/src/config.ts +0 -11
- package/src/content-id-index.test.ts +0 -512
- package/src/content-id-index.ts +0 -479
- package/src/content-reader.test.ts +0 -478
- package/src/content-reader.ts +0 -214
- package/src/content-store.test.ts +0 -1126
- package/src/content-store.ts +0 -793
- package/src/context.ts +0 -111
- package/src/editor/BranchManager.stories.tsx +0 -80
- package/src/editor/BranchManager.test.tsx +0 -324
- package/src/editor/BranchManager.tsx +0 -461
- package/src/editor/CanopyEditor.stories.tsx +0 -128
- package/src/editor/CanopyEditor.test.tsx +0 -81
- package/src/editor/CanopyEditor.tsx +0 -73
- package/src/editor/CanopyEditorPage.test.tsx +0 -59
- package/src/editor/CanopyEditorPage.tsx +0 -25
- package/src/editor/CommentsPanel.stories.tsx +0 -184
- package/src/editor/CommentsPanel.tsx +0 -338
- package/src/editor/Editor.integration.test.tsx +0 -227
- package/src/editor/Editor.stories.tsx +0 -119
- package/src/editor/Editor.tsx +0 -1221
- package/src/editor/EditorPanes.stories.tsx +0 -256
- package/src/editor/EditorPanes.test.tsx +0 -77
- package/src/editor/EditorPanes.tsx +0 -180
- package/src/editor/EntryNavigator.stories.tsx +0 -65
- package/src/editor/EntryNavigator.test.tsx +0 -598
- package/src/editor/EntryNavigator.tsx +0 -665
- package/src/editor/FormRenderer.stories.tsx +0 -212
- package/src/editor/FormRenderer.test.tsx +0 -194
- package/src/editor/FormRenderer.tsx +0 -432
- package/src/editor/GroupManager.stories.tsx +0 -301
- package/src/editor/GroupManager.test.tsx +0 -682
- package/src/editor/GroupManager.tsx +0 -9
- package/src/editor/PermissionManager.stories.tsx +0 -539
- package/src/editor/PermissionManager.test.tsx +0 -864
- package/src/editor/PermissionManager.tsx +0 -12
- package/src/editor/canopy-path.test.ts +0 -23
- package/src/editor/canopy-path.ts +0 -52
- package/src/editor/client-reference-resolver.ts +0 -118
- package/src/editor/comments/BranchComments.tsx +0 -93
- package/src/editor/comments/EntryComments.tsx +0 -94
- package/src/editor/comments/FieldWrapper.stories.tsx +0 -210
- package/src/editor/comments/FieldWrapper.tsx +0 -129
- package/src/editor/comments/InlineCommentThread.test.tsx +0 -384
- package/src/editor/comments/InlineCommentThread.tsx +0 -246
- package/src/editor/comments/ThreadCarousel.test.tsx +0 -393
- package/src/editor/comments/ThreadCarousel.tsx +0 -525
- package/src/editor/components/ConfirmDeleteModal.tsx +0 -49
- package/src/editor/components/EditorContext.tsx +0 -49
- package/src/editor/components/EditorFooter.tsx +0 -47
- package/src/editor/components/EditorHeader.tsx +0 -492
- package/src/editor/components/EditorSidebar.tsx +0 -193
- package/src/editor/components/EntryCreateModal.tsx +0 -193
- package/src/editor/components/RenameEntryModal.tsx +0 -152
- package/src/editor/components/UserBadge.test.tsx +0 -274
- package/src/editor/components/UserBadge.tsx +0 -240
- package/src/editor/components/index.ts +0 -6
- package/src/editor/context/ApiClientContext.tsx +0 -56
- package/src/editor/context/EditorStateContext.tsx +0 -221
- package/src/editor/context/index.ts +0 -40
- package/src/editor/editor-config.test.ts +0 -385
- package/src/editor/editor-config.ts +0 -94
- package/src/editor/editor-utils.test.ts +0 -772
- package/src/editor/editor-utils.ts +0 -303
- package/src/editor/env.ts +0 -4
- package/src/editor/fields/BlockField.stories.tsx +0 -79
- package/src/editor/fields/BlockField.tsx +0 -267
- package/src/editor/fields/CodeField.tsx +0 -41
- package/src/editor/fields/MarkdownField.tsx +0 -205
- package/src/editor/fields/ObjectField.tsx +0 -71
- package/src/editor/fields/ReferenceField.tsx +0 -138
- package/src/editor/fields/SelectField.tsx +0 -76
- package/src/editor/fields/TextField.tsx +0 -35
- package/src/editor/fields/ToggleField.tsx +0 -37
- package/src/editor/fields/fields.stories.tsx +0 -40
- package/src/editor/group-manager/ExternalGroupsTab.tsx +0 -114
- package/src/editor/group-manager/GroupCard.tsx +0 -102
- package/src/editor/group-manager/GroupForm.tsx +0 -66
- package/src/editor/group-manager/InternalGroupsTab.tsx +0 -147
- package/src/editor/group-manager/MemberList.tsx +0 -184
- package/src/editor/group-manager/hooks/useExternalGroupSearch.ts +0 -63
- package/src/editor/group-manager/hooks/useGroupState.ts +0 -134
- package/src/editor/group-manager/hooks/useUserSearch.ts +0 -84
- package/src/editor/group-manager/index.tsx +0 -210
- package/src/editor/group-manager/types.ts +0 -28
- package/src/editor/hooks/README.md +0 -26
- package/src/editor/hooks/__test__/test-utils.tsx +0 -183
- package/src/editor/hooks/index.ts +0 -23
- package/src/editor/hooks/useBranchActions.test.tsx +0 -267
- package/src/editor/hooks/useBranchActions.tsx +0 -121
- package/src/editor/hooks/useBranchManager.test.tsx +0 -391
- package/src/editor/hooks/useBranchManager.tsx +0 -326
- package/src/editor/hooks/useCommentSystem.test.ts +0 -615
- package/src/editor/hooks/useCommentSystem.ts +0 -347
- package/src/editor/hooks/useDraftManager.test.ts +0 -375
- package/src/editor/hooks/useDraftManager.ts +0 -259
- package/src/editor/hooks/useEditorLayout.test.ts +0 -147
- package/src/editor/hooks/useEditorLayout.ts +0 -67
- package/src/editor/hooks/useEntryManager.test.ts +0 -588
- package/src/editor/hooks/useEntryManager.ts +0 -387
- package/src/editor/hooks/useGroupManager.test.ts +0 -277
- package/src/editor/hooks/useGroupManager.ts +0 -139
- package/src/editor/hooks/usePermissionManager.test.ts +0 -211
- package/src/editor/hooks/usePermissionManager.ts +0 -113
- package/src/editor/hooks/useReferenceResolution.ts +0 -248
- package/src/editor/hooks/useSchemaManager.test.ts +0 -370
- package/src/editor/hooks/useSchemaManager.ts +0 -310
- package/src/editor/hooks/useUserContext.tsx +0 -57
- package/src/editor/hooks/useUserMetadata.test.ts +0 -191
- package/src/editor/hooks/useUserMetadata.ts +0 -71
- package/src/editor/permission-manager/GroupSelector.tsx +0 -73
- package/src/editor/permission-manager/PermissionEditor.tsx +0 -321
- package/src/editor/permission-manager/PermissionLevelBadge.tsx +0 -53
- package/src/editor/permission-manager/PermissionTree.tsx +0 -237
- package/src/editor/permission-manager/UserSelector.tsx +0 -95
- package/src/editor/permission-manager/constants.tsx +0 -18
- package/src/editor/permission-manager/hooks/useGroupsAndUsers.ts +0 -153
- package/src/editor/permission-manager/hooks/usePermissionTree.ts +0 -200
- package/src/editor/permission-manager/index.tsx +0 -294
- package/src/editor/permission-manager/types.ts +0 -58
- package/src/editor/permission-manager/utils.ts +0 -179
- package/src/editor/preview-bridge.test.tsx +0 -50
- package/src/editor/preview-bridge.tsx +0 -294
- package/src/editor/schema-editor/CollectionEditor.test.tsx +0 -238
- package/src/editor/schema-editor/CollectionEditor.tsx +0 -520
- package/src/editor/schema-editor/EntryTypeEditor.test.tsx +0 -215
- package/src/editor/schema-editor/EntryTypeEditor.tsx +0 -367
- package/src/editor/schema-editor/index.ts +0 -19
- package/src/editor/setup-test-dom.ts +0 -10
- package/src/editor/test-setup.ts +0 -33
- package/src/editor/theme.tsx +0 -119
- package/src/editor/utils/env.ts +0 -39
- package/src/entry-schema-registry.test.ts +0 -281
- package/src/entry-schema-registry.ts +0 -121
- package/src/entry-schema.ts +0 -84
- package/src/git-manager.test.ts +0 -552
- package/src/git-manager.ts +0 -667
- package/src/github-service.test.ts +0 -312
- package/src/github-service.ts +0 -295
- package/src/http/handler.test.ts +0 -275
- package/src/http/handler.ts +0 -280
- package/src/http/index.ts +0 -11
- package/src/http/router.ts +0 -164
- package/src/http/types.ts +0 -44
- package/src/id.test.ts +0 -48
- package/src/id.ts +0 -22
- package/src/index.ts +0 -26
- package/src/operating-mode/__tests__/strategies.test.ts +0 -511
- package/src/operating-mode/client-safe-strategy.ts +0 -184
- package/src/operating-mode/client-unsafe-strategy.ts +0 -303
- package/src/operating-mode/client.ts +0 -13
- package/src/operating-mode/index.ts +0 -34
- package/src/operating-mode/types.ts +0 -186
- package/src/paths/__tests__/branch.test.ts +0 -53
- package/src/paths/__tests__/normalize.test.ts +0 -141
- package/src/paths/__tests__/resolve.test.ts +0 -207
- package/src/paths/__tests__/validation.test.ts +0 -61
- package/src/paths/branch.ts +0 -115
- package/src/paths/index.ts +0 -73
- package/src/paths/normalize-server.ts +0 -40
- package/src/paths/normalize.ts +0 -107
- package/src/paths/resolve.ts +0 -61
- package/src/paths/test-utils.ts +0 -37
- package/src/paths/types.ts +0 -68
- package/src/paths/validation.test.ts +0 -480
- package/src/paths/validation.ts +0 -391
- package/src/reference-resolver.test.ts +0 -107
- package/src/reference-resolver.ts +0 -157
- package/src/schema/index.ts +0 -29
- package/src/schema/meta-loader.ts +0 -366
- package/src/schema/resolver.ts +0 -83
- package/src/schema/schema-store-types.ts +0 -56
- package/src/schema/schema-store.test.ts +0 -816
- package/src/schema/schema-store.ts +0 -795
- package/src/schema/types.ts +0 -33
- package/src/schema-meta-loader.test.ts +0 -447
- package/src/server.ts +0 -15
- package/src/services.test.ts +0 -559
- package/src/services.ts +0 -373
- package/src/settings-branch-utils.ts +0 -53
- package/src/settings-workspace.ts +0 -156
- package/src/task-queue/README.md +0 -144
- package/src/task-queue/index.ts +0 -14
- package/src/task-queue/task-queue.test.ts +0 -524
- package/src/task-queue/task-queue.ts +0 -514
- package/src/task-queue/types.ts +0 -41
- package/src/test-utils/api-test-helpers.ts +0 -445
- package/src/test-utils/console-spy.test.ts +0 -14
- package/src/test-utils/console-spy.ts +0 -125
- package/src/test-utils/git-helpers.ts +0 -31
- package/src/test-utils/index.ts +0 -4
- package/src/types.ts +0 -54
- package/src/user.ts +0 -118
- package/src/utils/debug.test.ts +0 -114
- package/src/utils/debug.ts +0 -127
- package/src/utils/error.test.ts +0 -92
- package/src/utils/error.ts +0 -83
- package/src/utils/format.ts +0 -12
- package/src/validation/__tests__/field-traversal.test.ts +0 -263
- package/src/validation/deletion-checker.ts +0 -234
- package/src/validation/field-traversal.ts +0 -146
- package/src/validation/reference-validator.ts +0 -168
- package/src/worker/cms-worker-rebase.test.ts +0 -473
- package/src/worker/cms-worker.ts +0 -777
- package/src/worker/integration.test.ts +0 -289
- package/src/worker/task-queue-config.ts +0 -25
- package/src/worker/task-queue.test.ts +0 -452
- package/src/worker/task-queue.ts +0 -58
- /package/{src/cli/templates → dist/cli/template-files}/Dockerfile.cms.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/canopycms.config.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/deploy-cms.yml.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/edit-page.tsx.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/route.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/schemas.ts.template +0 -0
package/src/http/handler.test.ts
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { createCanopyRequestHandler } from './handler'
|
|
3
|
-
import type { CanopyRequest } from './types'
|
|
4
|
-
import type { AuthPlugin } from '../auth/plugin'
|
|
5
|
-
import { mockConsole } from '../test-utils/console-spy'
|
|
6
|
-
|
|
7
|
-
// Mock the BranchWorkspaceManager to avoid git operations
|
|
8
|
-
vi.mock('../branch-workspace', () => ({
|
|
9
|
-
BranchWorkspaceManager: vi.fn().mockImplementation(() => ({
|
|
10
|
-
openOrCreateBranch: vi.fn().mockResolvedValue({
|
|
11
|
-
branch: {
|
|
12
|
-
name: 'new-branch',
|
|
13
|
-
status: 'editing',
|
|
14
|
-
createdBy: 'test-user',
|
|
15
|
-
createdAt: new Date().toISOString(),
|
|
16
|
-
updatedAt: new Date().toISOString(),
|
|
17
|
-
access: {},
|
|
18
|
-
},
|
|
19
|
-
branchRoot: '/tmp/test',
|
|
20
|
-
baseRoot: '/tmp/base',
|
|
21
|
-
}),
|
|
22
|
-
})),
|
|
23
|
-
loadBranchContext: vi.fn().mockResolvedValue(null),
|
|
24
|
-
}))
|
|
25
|
-
|
|
26
|
-
// Mock the permissions loader to avoid file system operations
|
|
27
|
-
vi.mock('../authorization/permissions', () => ({
|
|
28
|
-
loadPathPermissions: vi.fn().mockResolvedValue([]),
|
|
29
|
-
}))
|
|
30
|
-
|
|
31
|
-
const ADMINS = 'Admins'
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create a mock AuthPlugin for testing.
|
|
35
|
-
*/
|
|
36
|
-
const createMockAuthPlugin = (
|
|
37
|
-
user = {
|
|
38
|
-
type: 'authenticated' as const,
|
|
39
|
-
userId: 'test-user',
|
|
40
|
-
groups: [ADMINS],
|
|
41
|
-
},
|
|
42
|
-
): AuthPlugin => ({
|
|
43
|
-
authenticate: async () => ({
|
|
44
|
-
success: true,
|
|
45
|
-
user: {
|
|
46
|
-
userId: user.userId,
|
|
47
|
-
externalGroups: user.groups,
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
searchUsers: async () => [],
|
|
51
|
-
getUserMetadata: async () => null,
|
|
52
|
-
getGroupMetadata: async () => null,
|
|
53
|
-
listGroups: async () => [],
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Create a mock AuthPlugin that rejects all authentication.
|
|
58
|
-
*/
|
|
59
|
-
const createRejectingAuthPlugin = (error = 'Unauthorized'): AuthPlugin => ({
|
|
60
|
-
authenticate: async () => ({ success: false, error }),
|
|
61
|
-
searchUsers: async () => [],
|
|
62
|
-
getUserMetadata: async () => null,
|
|
63
|
-
getGroupMetadata: async () => null,
|
|
64
|
-
listGroups: async () => [],
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Create a mock CanopyRequest for testing.
|
|
69
|
-
*/
|
|
70
|
-
const createMockRequest = (overrides: Partial<CanopyRequest> = {}): CanopyRequest => ({
|
|
71
|
-
method: 'GET',
|
|
72
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
73
|
-
header: () => null,
|
|
74
|
-
json: async () => undefined,
|
|
75
|
-
...overrides,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Create mock services for testing.
|
|
80
|
-
*/
|
|
81
|
-
const createMockServices = () => ({
|
|
82
|
-
config: {
|
|
83
|
-
schema: [],
|
|
84
|
-
contentRoot: 'content',
|
|
85
|
-
gitBotAuthorName: 'Test Bot',
|
|
86
|
-
gitBotAuthorEmail: 'bot@test.com',
|
|
87
|
-
mode: 'dev' as const,
|
|
88
|
-
},
|
|
89
|
-
checkBranchAccess: vi.fn().mockReturnValue({ allowed: true, reason: '' }),
|
|
90
|
-
checkPathAccess: vi.fn().mockReturnValue({ allowed: true }),
|
|
91
|
-
checkContentAccess: vi.fn().mockReturnValue({ allowed: true, branch: {}, path: {} }),
|
|
92
|
-
pathPermissions: [],
|
|
93
|
-
createGitManagerFor: vi.fn(),
|
|
94
|
-
registry: {
|
|
95
|
-
get: vi.fn().mockResolvedValue(null),
|
|
96
|
-
list: vi.fn().mockResolvedValue([]),
|
|
97
|
-
},
|
|
98
|
-
bootstrapAdminIds: new Set<string>(),
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
describe('createCanopyRequestHandler', () => {
|
|
102
|
-
beforeEach(() => {
|
|
103
|
-
mockConsole()
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('routes requests to handlers and returns response', async () => {
|
|
107
|
-
const services: any = createMockServices()
|
|
108
|
-
const authPlugin = createMockAuthPlugin()
|
|
109
|
-
|
|
110
|
-
const handler = createCanopyRequestHandler({
|
|
111
|
-
services,
|
|
112
|
-
authPlugin,
|
|
113
|
-
getBranchContext: async () => null,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const req = createMockRequest({
|
|
117
|
-
method: 'GET',
|
|
118
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
const response = await handler(req, ['branches'])
|
|
122
|
-
|
|
123
|
-
expect(response.status).toBe(200)
|
|
124
|
-
expect(response.body).toHaveProperty('ok', true)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('returns 404 for unknown routes', async () => {
|
|
128
|
-
const services: any = createMockServices()
|
|
129
|
-
const authPlugin = createMockAuthPlugin()
|
|
130
|
-
|
|
131
|
-
const handler = createCanopyRequestHandler({
|
|
132
|
-
services,
|
|
133
|
-
authPlugin,
|
|
134
|
-
getBranchContext: async () => null,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
const req = createMockRequest()
|
|
138
|
-
const response = await handler(req, ['unknown', 'route'])
|
|
139
|
-
|
|
140
|
-
expect(response.status).toBe(404)
|
|
141
|
-
expect(response.body).toHaveProperty('error', 'Not found')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('returns 401 for unauthenticated requests', async () => {
|
|
145
|
-
const services: any = createMockServices()
|
|
146
|
-
const authPlugin = createRejectingAuthPlugin('No token')
|
|
147
|
-
|
|
148
|
-
const handler = createCanopyRequestHandler({
|
|
149
|
-
services,
|
|
150
|
-
authPlugin,
|
|
151
|
-
getBranchContext: async () => null,
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
const req = createMockRequest()
|
|
155
|
-
const response = await handler(req, ['branches'])
|
|
156
|
-
|
|
157
|
-
expect(response.status).toBe(401)
|
|
158
|
-
expect(response.body).toHaveProperty('error', 'No token')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('handles POST requests with empty body gracefully', async () => {
|
|
162
|
-
const services: any = createMockServices()
|
|
163
|
-
const authPlugin = createMockAuthPlugin()
|
|
164
|
-
|
|
165
|
-
const handler = createCanopyRequestHandler({
|
|
166
|
-
services,
|
|
167
|
-
authPlugin,
|
|
168
|
-
getBranchContext: async () => ({
|
|
169
|
-
baseRoot: '/tmp/base',
|
|
170
|
-
branchRoot: '/tmp/base/test',
|
|
171
|
-
branch: {
|
|
172
|
-
name: 'test',
|
|
173
|
-
status: 'editing',
|
|
174
|
-
access: {},
|
|
175
|
-
createdBy: 'user1',
|
|
176
|
-
createdAt: new Date().toISOString(),
|
|
177
|
-
updatedAt: new Date().toISOString(),
|
|
178
|
-
},
|
|
179
|
-
}),
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
const req = createMockRequest({
|
|
183
|
-
method: 'POST',
|
|
184
|
-
url: 'http://localhost:3000/api/canopycms/test/submit',
|
|
185
|
-
json: async () => {
|
|
186
|
-
throw new SyntaxError('Unexpected end of JSON input')
|
|
187
|
-
},
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
// Should not crash, should handle gracefully
|
|
191
|
-
const response = await handler(req, ['test', 'submit'])
|
|
192
|
-
expect(response).toBeDefined()
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('handles POST requests with valid body', async () => {
|
|
196
|
-
const services: any = createMockServices()
|
|
197
|
-
const authPlugin = createMockAuthPlugin()
|
|
198
|
-
|
|
199
|
-
const handler = createCanopyRequestHandler({
|
|
200
|
-
services,
|
|
201
|
-
authPlugin,
|
|
202
|
-
getBranchContext: async () => null,
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
const req = createMockRequest({
|
|
206
|
-
method: 'POST',
|
|
207
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
208
|
-
json: async () => ({ branch: 'new-branch', title: 'Test Branch' }),
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
const response = await handler(req, ['branches'])
|
|
212
|
-
expect(response.status).toBe(200)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('parses query parameters from URL', async () => {
|
|
216
|
-
const services: any = createMockServices()
|
|
217
|
-
const authPlugin = createMockAuthPlugin()
|
|
218
|
-
|
|
219
|
-
const handler = createCanopyRequestHandler({
|
|
220
|
-
services,
|
|
221
|
-
authPlugin,
|
|
222
|
-
getBranchContext: async () => null,
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
const req = createMockRequest({
|
|
226
|
-
method: 'GET',
|
|
227
|
-
url: 'http://localhost:3000/api/canopycms/users/search?query=test&limit=5',
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
const response = await handler(req, ['users', 'search'])
|
|
231
|
-
// The handler should parse the query params - we're just verifying it doesn't crash
|
|
232
|
-
expect(response).toBeDefined()
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('applies bootstrap admin groups to user', async () => {
|
|
236
|
-
const services: any = createMockServices()
|
|
237
|
-
services.bootstrapAdminIds = new Set(['test-user'])
|
|
238
|
-
|
|
239
|
-
// User without Admins group
|
|
240
|
-
const authPlugin = createMockAuthPlugin({
|
|
241
|
-
type: 'authenticated',
|
|
242
|
-
userId: 'test-user',
|
|
243
|
-
groups: [],
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
const handler = createCanopyRequestHandler({
|
|
247
|
-
services,
|
|
248
|
-
authPlugin,
|
|
249
|
-
getBranchContext: async () => null,
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
const req = createMockRequest()
|
|
253
|
-
const response = await handler(req, ['branches'])
|
|
254
|
-
|
|
255
|
-
// Should succeed because bootstrap admin gets Admins group added
|
|
256
|
-
expect(response.status).toBe(200)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it('throws error when no config or services provided', async () => {
|
|
260
|
-
const authPlugin = createMockAuthPlugin()
|
|
261
|
-
|
|
262
|
-
expect(() =>
|
|
263
|
-
createCanopyRequestHandler({
|
|
264
|
-
authPlugin,
|
|
265
|
-
} as any),
|
|
266
|
-
).not.toThrow() // Factory doesn't throw, handler will throw on first request
|
|
267
|
-
|
|
268
|
-
const handler = createCanopyRequestHandler({
|
|
269
|
-
authPlugin,
|
|
270
|
-
} as any)
|
|
271
|
-
|
|
272
|
-
const req = createMockRequest()
|
|
273
|
-
await expect(handler(req, ['branches'])).rejects.toThrow('config or services is required')
|
|
274
|
-
})
|
|
275
|
-
})
|
package/src/http/handler.ts
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import type { CanopyRequest, CanopyResponse } from './types'
|
|
2
|
-
import { jsonResponse } from './types'
|
|
3
|
-
import { createCanopyRouter } from './router'
|
|
4
|
-
import type { ApiContext, ApiResponse } from '../api/types'
|
|
5
|
-
import type { AuthPlugin } from '../auth/plugin'
|
|
6
|
-
import { createCanopyServices, type CanopyServices } from '../services'
|
|
7
|
-
import type { CanopyConfig } from '../config'
|
|
8
|
-
import type { BranchContext } from '../types'
|
|
9
|
-
import { loadBranchContext, BranchWorkspaceManager } from '../branch-workspace'
|
|
10
|
-
import { authResultToCanopyUser } from '../user'
|
|
11
|
-
import { loadInternalGroups, RESERVED_GROUPS } from '../authorization'
|
|
12
|
-
import { clientOperatingStrategy } from '../operating-mode'
|
|
13
|
-
|
|
14
|
-
let warnedNoAdmins = false
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Options for creating a Canopy request handler.
|
|
18
|
-
* This is framework-agnostic - adapters convert their framework's
|
|
19
|
-
* request/response types to/from CanopyRequest/CanopyResponse.
|
|
20
|
-
*/
|
|
21
|
-
export interface CanopyHandlerOptions {
|
|
22
|
-
services?: CanopyServices
|
|
23
|
-
config?: CanopyConfig
|
|
24
|
-
assetStore?: ApiContext['assetStore']
|
|
25
|
-
getBranchContext?: (branch: string) => Promise<BranchContext | null>
|
|
26
|
-
authPlugin: AuthPlugin
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Build API context from options.
|
|
31
|
-
*/
|
|
32
|
-
const buildContext = async (options: CanopyHandlerOptions): Promise<ApiContext> => {
|
|
33
|
-
const services =
|
|
34
|
-
options.services ?? (options.config ? await createCanopyServices(options.config) : undefined)
|
|
35
|
-
if (!services) {
|
|
36
|
-
throw new Error('CanopyCMS: config or services is required')
|
|
37
|
-
}
|
|
38
|
-
const operatingMode = services.config.mode
|
|
39
|
-
const baseBranch = services.config.defaultBaseBranch ?? 'main'
|
|
40
|
-
const settingsBranch = services.config.settingsBranch ?? 'canopycms-settings'
|
|
41
|
-
|
|
42
|
-
const getBranchContext =
|
|
43
|
-
options.getBranchContext ??
|
|
44
|
-
(async (branch: string, opts?: { loadSchema?: boolean }): Promise<BranchContext | null> => {
|
|
45
|
-
// Try to load existing branch
|
|
46
|
-
const existing = await loadBranchContext({
|
|
47
|
-
branchName: branch,
|
|
48
|
-
mode: operatingMode,
|
|
49
|
-
})
|
|
50
|
-
if (existing) {
|
|
51
|
-
// Optionally load per-branch schema
|
|
52
|
-
if (opts?.loadSchema) {
|
|
53
|
-
const contentRootName = services.config.contentRoot || 'content'
|
|
54
|
-
const cached = await services.branchSchemaCache.getSchema(
|
|
55
|
-
existing.branchRoot,
|
|
56
|
-
services.entrySchemaRegistry,
|
|
57
|
-
contentRootName,
|
|
58
|
-
)
|
|
59
|
-
existing.flatSchema = cached.flatSchema
|
|
60
|
-
}
|
|
61
|
-
return existing
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// In modes that support branching, auto-create system branches if they don't exist
|
|
65
|
-
const shouldAutoCreate =
|
|
66
|
-
clientOperatingStrategy(operatingMode).supportsBranching() &&
|
|
67
|
-
(branch === baseBranch || branch === settingsBranch)
|
|
68
|
-
|
|
69
|
-
if (shouldAutoCreate) {
|
|
70
|
-
const manager = new BranchWorkspaceManager(services.config)
|
|
71
|
-
const context = await manager.openOrCreateBranch({
|
|
72
|
-
branchName: branch,
|
|
73
|
-
mode: operatingMode,
|
|
74
|
-
createdBy: 'canopycms-system',
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Optionally load per-branch schema for auto-created branches
|
|
78
|
-
if (opts?.loadSchema && context) {
|
|
79
|
-
const contentRootName = services.config.contentRoot || 'content'
|
|
80
|
-
const cached = await services.branchSchemaCache.getSchema(
|
|
81
|
-
context.branchRoot,
|
|
82
|
-
services.entrySchemaRegistry,
|
|
83
|
-
contentRootName,
|
|
84
|
-
)
|
|
85
|
-
context.flatSchema = cached.flatSchema
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return context
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
services,
|
|
96
|
-
assetStore: options.assetStore,
|
|
97
|
-
getBranchContext,
|
|
98
|
-
authPlugin: options.authPlugin,
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Parse query parameters from URL.
|
|
104
|
-
*/
|
|
105
|
-
const parseQueryParams = (url: string): Record<string, string> => {
|
|
106
|
-
try {
|
|
107
|
-
const urlObj = new URL(url, 'http://localhost')
|
|
108
|
-
return Object.fromEntries(urlObj.searchParams.entries())
|
|
109
|
-
} catch {
|
|
110
|
-
return {}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Core request handler result type.
|
|
116
|
-
*/
|
|
117
|
-
export type CanopyRequestHandler = (
|
|
118
|
-
req: CanopyRequest,
|
|
119
|
-
pathSegments: string[],
|
|
120
|
-
) => Promise<CanopyResponse<ApiResponse>>
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Create a framework-agnostic Canopy request handler.
|
|
124
|
-
*
|
|
125
|
-
* This is the core handler that processes all Canopy API requests.
|
|
126
|
-
* Framework adapters (Next.js, Express, Hono, etc.) should:
|
|
127
|
-
* 1. Convert their framework's request to CanopyRequest
|
|
128
|
-
* 2. Extract path segments from the URL
|
|
129
|
-
* 3. Call this handler
|
|
130
|
-
* 4. Convert the CanopyResponse to their framework's response
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```ts
|
|
134
|
-
* // In a framework adapter
|
|
135
|
-
* const coreHandler = createCanopyRequestHandler({
|
|
136
|
-
* config: myConfig,
|
|
137
|
-
* authPlugin: myAuthPlugin,
|
|
138
|
-
* })
|
|
139
|
-
*
|
|
140
|
-
* // Framework-specific handler
|
|
141
|
-
* async function handleRequest(frameworkReq) {
|
|
142
|
-
* const canopyReq = convertToCanopyRequest(frameworkReq)
|
|
143
|
-
* const segments = extractPathSegments(frameworkReq)
|
|
144
|
-
* const response = await coreHandler(canopyReq, segments)
|
|
145
|
-
* return convertToFrameworkResponse(response)
|
|
146
|
-
* }
|
|
147
|
-
* ```
|
|
148
|
-
*/
|
|
149
|
-
export function createCanopyRequestHandler(options: CanopyHandlerOptions): CanopyRequestHandler {
|
|
150
|
-
const router = createCanopyRouter()
|
|
151
|
-
|
|
152
|
-
// Build context once at initialization, not per-request
|
|
153
|
-
let apiCtxPromise: Promise<ApiContext> | null = null
|
|
154
|
-
const getContext = () => {
|
|
155
|
-
if (!apiCtxPromise) {
|
|
156
|
-
apiCtxPromise = buildContext(options)
|
|
157
|
-
}
|
|
158
|
-
return apiCtxPromise
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return async (
|
|
162
|
-
req: CanopyRequest,
|
|
163
|
-
pathSegments: string[],
|
|
164
|
-
): Promise<CanopyResponse<ApiResponse>> => {
|
|
165
|
-
// Route matching (fast, do first before async work)
|
|
166
|
-
const match = router.match(req.method, pathSegments)
|
|
167
|
-
if (!match) {
|
|
168
|
-
return jsonResponse({ ok: false, status: 404, error: 'Not found' }, 404)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Get cached context
|
|
172
|
-
const apiCtx = await getContext()
|
|
173
|
-
|
|
174
|
-
// Authenticate and convert to CanopyUser
|
|
175
|
-
const authResult = await options.authPlugin.authenticate(req)
|
|
176
|
-
|
|
177
|
-
// Load internal groups from main branch and merge with user groups
|
|
178
|
-
const baseBranch = apiCtx.services.config.defaultBaseBranch ?? 'main'
|
|
179
|
-
const mainBranchContext = await apiCtx.getBranchContext(baseBranch)
|
|
180
|
-
const operatingMode = apiCtx.services.config.mode
|
|
181
|
-
const internalGroups = mainBranchContext
|
|
182
|
-
? await loadInternalGroups(
|
|
183
|
-
mainBranchContext.branchRoot,
|
|
184
|
-
operatingMode,
|
|
185
|
-
apiCtx.services.bootstrapAdminIds,
|
|
186
|
-
).catch((err: unknown) => {
|
|
187
|
-
console.warn('CanopyCMS: Failed to load internal groups from main branch:', err)
|
|
188
|
-
return []
|
|
189
|
-
})
|
|
190
|
-
: []
|
|
191
|
-
|
|
192
|
-
if (!warnedNoAdmins && Array.isArray(internalGroups)) {
|
|
193
|
-
const adminsGroup = internalGroups.find((g) => g.id === RESERVED_GROUPS.ADMINS)
|
|
194
|
-
const hasAdmins =
|
|
195
|
-
(adminsGroup && adminsGroup.members.length > 0) ||
|
|
196
|
-
apiCtx.services.bootstrapAdminIds.size > 0
|
|
197
|
-
if (!hasAdmins) {
|
|
198
|
-
console.warn(
|
|
199
|
-
'CanopyCMS: No admin users configured. Set CANOPY_BOOTSTRAP_ADMIN_IDS or add members to the Admins group.',
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
warnedNoAdmins = true
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const user = authResultToCanopyUser(
|
|
206
|
-
authResult,
|
|
207
|
-
apiCtx.services.bootstrapAdminIds,
|
|
208
|
-
internalGroups,
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
// API routes require authentication - reject anonymous users
|
|
212
|
-
if (user.type === 'anonymous') {
|
|
213
|
-
return jsonResponse(
|
|
214
|
-
{ ok: false, status: 401, error: authResult.error ?? 'Unauthorized' },
|
|
215
|
-
401,
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Parse query params and merge with route params
|
|
220
|
-
const queryParams = parseQueryParams(req.url)
|
|
221
|
-
const mergedParams = { ...queryParams, ...match.params }
|
|
222
|
-
|
|
223
|
-
// Parse body for non-GET requests
|
|
224
|
-
let body: unknown
|
|
225
|
-
if (req.method !== 'GET') {
|
|
226
|
-
try {
|
|
227
|
-
body = await req.json()
|
|
228
|
-
} catch {
|
|
229
|
-
body = undefined
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Build API request
|
|
234
|
-
const branch =
|
|
235
|
-
(mergedParams as Record<string, string>)?.branch ??
|
|
236
|
-
(body as Record<string, unknown> | undefined)?.branch
|
|
237
|
-
const apiReq = { user, body, branch, query: queryParams }
|
|
238
|
-
|
|
239
|
-
// Validate params and body using the route's validation function (if available)
|
|
240
|
-
if (match.validate) {
|
|
241
|
-
const validationResult = match.validate({ params: mergedParams, body })
|
|
242
|
-
if (!validationResult.ok) {
|
|
243
|
-
return jsonResponse({ ok: false, status: 400, error: validationResult.error }, 400)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Call handler with validated params/body based on what's defined
|
|
247
|
-
const handlerArgs: unknown[] = [apiCtx, apiReq]
|
|
248
|
-
if (validationResult.params !== undefined) {
|
|
249
|
-
handlerArgs.push(validationResult.params)
|
|
250
|
-
}
|
|
251
|
-
if (validationResult.body !== undefined) {
|
|
252
|
-
handlerArgs.push(validationResult.body)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const result = await match.handler(...handlerArgs)
|
|
256
|
-
return jsonResponse(result, result.status)
|
|
257
|
-
} else {
|
|
258
|
-
// Should not happen - all routes should use defineEndpoint now
|
|
259
|
-
// This is here for safety in case any route doesn't have validation
|
|
260
|
-
const result = await match.handler(
|
|
261
|
-
apiCtx as unknown,
|
|
262
|
-
apiReq as unknown,
|
|
263
|
-
mergedParams as unknown,
|
|
264
|
-
)
|
|
265
|
-
return jsonResponse(result, result.status)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Create a handler with pre-built services from config.
|
|
272
|
-
*/
|
|
273
|
-
export async function createCanopyRequestHandlerFromConfig(
|
|
274
|
-
options: { config: CanopyConfig } & Omit<CanopyHandlerOptions, 'services' | 'config'>,
|
|
275
|
-
): Promise<CanopyRequestHandler> {
|
|
276
|
-
return createCanopyRequestHandler({
|
|
277
|
-
...options,
|
|
278
|
-
services: await createCanopyServices(options.config),
|
|
279
|
-
})
|
|
280
|
-
}
|
package/src/http/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// HTTP types
|
|
2
|
-
export type { CanopyRequest, CanopyResponse } from './types'
|
|
3
|
-
export { jsonResponse } from './types'
|
|
4
|
-
|
|
5
|
-
// Router
|
|
6
|
-
export type { CanopyHandler, RouteDefinition, RouteMatch, CanopyRouter } from './router'
|
|
7
|
-
export { createCanopyRouter } from './router'
|
|
8
|
-
|
|
9
|
-
// Core request handler
|
|
10
|
-
export type { CanopyHandlerOptions, CanopyRequestHandler } from './handler'
|
|
11
|
-
export { createCanopyRequestHandler, createCanopyRequestHandlerFromConfig } from './handler'
|