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
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for Groups API endpoints
|
|
3
|
-
*
|
|
4
|
-
* These tests verify that the Groups API works correctly with settings stored
|
|
5
|
-
* in the new settings directory structure across all operating modes.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
9
|
-
import path from 'node:path'
|
|
10
|
-
import fs from 'node:fs/promises'
|
|
11
|
-
|
|
12
|
-
import { createTestWorkspace, type TestWorkspace } from '../test-utils/test-workspace'
|
|
13
|
-
import { BLOG_SCHEMA } from '../fixtures/schemas'
|
|
14
|
-
import { createTestServices } from '../../config-test'
|
|
15
|
-
import { createMockApiContext } from '../../test-utils/api-test-helpers'
|
|
16
|
-
import { RESERVED_GROUPS, type InternalGroup } from '../../authorization'
|
|
17
|
-
import { GROUP_ROUTES } from '../../api/groups'
|
|
18
|
-
import { operatingStrategy } from '../../operating-mode'
|
|
19
|
-
import { mockConsole } from '../../test-utils/console-spy'
|
|
20
|
-
|
|
21
|
-
// Extract handlers
|
|
22
|
-
const getInternalGroups = GROUP_ROUTES.getInternal.handler
|
|
23
|
-
const updateInternalGroups = GROUP_ROUTES.updateInternal.handler
|
|
24
|
-
|
|
25
|
-
describe('Groups API Integration', () => {
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
mockConsole()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
describe('prod-sim mode', () => {
|
|
31
|
-
let workspace: TestWorkspace
|
|
32
|
-
|
|
33
|
-
beforeEach(async () => {
|
|
34
|
-
workspace = await createTestWorkspace({
|
|
35
|
-
schema: BLOG_SCHEMA,
|
|
36
|
-
mode: 'prod-sim',
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
afterEach(async () => {
|
|
41
|
-
await workspace.cleanup()
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('should load groups from settings directory', async () => {
|
|
45
|
-
// Create services
|
|
46
|
-
const services = await createTestServices({
|
|
47
|
-
...workspace.config,
|
|
48
|
-
schema: BLOG_SCHEMA,
|
|
49
|
-
})
|
|
50
|
-
const context = createMockApiContext({ services })
|
|
51
|
-
|
|
52
|
-
// Call the groups API
|
|
53
|
-
const result = await getInternalGroups(context, {
|
|
54
|
-
user: {
|
|
55
|
-
type: 'authenticated',
|
|
56
|
-
userId: 'admin-1',
|
|
57
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
expect(result.ok).toBe(true)
|
|
62
|
-
if (result.ok && result.data) {
|
|
63
|
-
expect(result.data.groups).toBeDefined()
|
|
64
|
-
expect(Array.isArray(result.data.groups)).toBe(true)
|
|
65
|
-
// Should have at least Admins and Reviewers groups
|
|
66
|
-
expect(result.data.groups.length).toBeGreaterThanOrEqual(2)
|
|
67
|
-
|
|
68
|
-
const adminsGroup = result.data.groups.find((g) => g.id === RESERVED_GROUPS.ADMINS)
|
|
69
|
-
expect(adminsGroup).toBeDefined()
|
|
70
|
-
expect(adminsGroup?.name).toBe(RESERVED_GROUPS.ADMINS)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should save groups to settings directory with autogenerated IDs', async () => {
|
|
75
|
-
const services = await createTestServices({
|
|
76
|
-
...workspace.config,
|
|
77
|
-
schema: BLOG_SCHEMA,
|
|
78
|
-
})
|
|
79
|
-
const context = createMockApiContext({ services })
|
|
80
|
-
|
|
81
|
-
// Create new groups (with empty IDs for new groups, server will generate them)
|
|
82
|
-
const newGroups: InternalGroup[] = [
|
|
83
|
-
{
|
|
84
|
-
id: '',
|
|
85
|
-
name: RESERVED_GROUPS.ADMINS,
|
|
86
|
-
description: 'Full access',
|
|
87
|
-
members: ['admin-1'],
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: '',
|
|
91
|
-
name: RESERVED_GROUPS.REVIEWERS,
|
|
92
|
-
description: 'Can review',
|
|
93
|
-
members: [],
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: '', // Empty ID for new group
|
|
97
|
-
name: 'Editors',
|
|
98
|
-
description: 'Can edit content',
|
|
99
|
-
members: ['user-1', 'user-2'],
|
|
100
|
-
},
|
|
101
|
-
]
|
|
102
|
-
|
|
103
|
-
// Update groups
|
|
104
|
-
const result = await updateInternalGroups(
|
|
105
|
-
context,
|
|
106
|
-
{
|
|
107
|
-
user: {
|
|
108
|
-
type: 'authenticated',
|
|
109
|
-
userId: 'admin-1',
|
|
110
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
{ groups: newGroups },
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
expect(result.ok).toBe(true)
|
|
117
|
-
|
|
118
|
-
// Verify file exists at correct path
|
|
119
|
-
const strategy = operatingStrategy('prod-sim')
|
|
120
|
-
const settingsRoot = strategy.getSettingsRoot(workspace.tmpRoot)
|
|
121
|
-
const groupsPath = path.join(settingsRoot, 'groups.json')
|
|
122
|
-
|
|
123
|
-
const fileExists = await fs.access(groupsPath).then(
|
|
124
|
-
() => true,
|
|
125
|
-
() => false,
|
|
126
|
-
)
|
|
127
|
-
expect(fileExists).toBe(true)
|
|
128
|
-
|
|
129
|
-
// Verify file content
|
|
130
|
-
const fileContent = await fs.readFile(groupsPath, 'utf-8')
|
|
131
|
-
const parsed = JSON.parse(fileContent)
|
|
132
|
-
expect(parsed.groups).toBeDefined()
|
|
133
|
-
expect(parsed.groups.length).toBe(3)
|
|
134
|
-
|
|
135
|
-
// Verify reserved groups use their names as IDs
|
|
136
|
-
const adminsGroup = parsed.groups.find(
|
|
137
|
-
(g: InternalGroup) => g.name === RESERVED_GROUPS.ADMINS,
|
|
138
|
-
)
|
|
139
|
-
expect(adminsGroup).toBeDefined()
|
|
140
|
-
expect(adminsGroup.id).toBe(RESERVED_GROUPS.ADMINS)
|
|
141
|
-
|
|
142
|
-
const reviewersGroup = parsed.groups.find(
|
|
143
|
-
(g: InternalGroup) => g.name === RESERVED_GROUPS.REVIEWERS,
|
|
144
|
-
)
|
|
145
|
-
expect(reviewersGroup).toBeDefined()
|
|
146
|
-
expect(reviewersGroup.id).toBe(RESERVED_GROUPS.REVIEWERS)
|
|
147
|
-
|
|
148
|
-
// Verify regular group has generated ID
|
|
149
|
-
const editorsGroup = parsed.groups.find((g: InternalGroup) => g.name === 'Editors')
|
|
150
|
-
expect(editorsGroup).toBeDefined()
|
|
151
|
-
expect(editorsGroup.id).not.toBe('')
|
|
152
|
-
expect(editorsGroup.id.length).toBeGreaterThan(0)
|
|
153
|
-
expect(editorsGroup.members).toEqual(['user-1', 'user-2'])
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('should handle concurrent group loads without race conditions', async () => {
|
|
157
|
-
const services = await createTestServices({
|
|
158
|
-
...workspace.config,
|
|
159
|
-
schema: BLOG_SCHEMA,
|
|
160
|
-
})
|
|
161
|
-
const context = createMockApiContext({ services })
|
|
162
|
-
|
|
163
|
-
// Make 5 concurrent requests
|
|
164
|
-
const promises = Array.from({ length: 5 }, () =>
|
|
165
|
-
getInternalGroups(context, {
|
|
166
|
-
user: {
|
|
167
|
-
type: 'authenticated',
|
|
168
|
-
userId: 'admin-1',
|
|
169
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
170
|
-
},
|
|
171
|
-
}),
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
const results = await Promise.all(promises)
|
|
175
|
-
|
|
176
|
-
// All should succeed
|
|
177
|
-
results.forEach((result) => {
|
|
178
|
-
expect(result.ok).toBe(true)
|
|
179
|
-
if (result.ok && result.data) {
|
|
180
|
-
expect(result.data.groups.length).toBeGreaterThanOrEqual(2)
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('should preserve groups across service restarts', async () => {
|
|
186
|
-
// Create services and save groups
|
|
187
|
-
const services1 = await createTestServices({
|
|
188
|
-
...workspace.config,
|
|
189
|
-
schema: BLOG_SCHEMA,
|
|
190
|
-
})
|
|
191
|
-
const context1 = createMockApiContext({ services: services1 })
|
|
192
|
-
|
|
193
|
-
const customGroups: InternalGroup[] = [
|
|
194
|
-
{
|
|
195
|
-
id: '',
|
|
196
|
-
name: RESERVED_GROUPS.ADMINS,
|
|
197
|
-
description: 'Admins',
|
|
198
|
-
members: ['admin-1'],
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
id: '',
|
|
202
|
-
name: RESERVED_GROUPS.REVIEWERS,
|
|
203
|
-
description: 'Reviewers',
|
|
204
|
-
members: [],
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: '', // Empty ID for new group
|
|
208
|
-
name: 'Custom Group',
|
|
209
|
-
description: 'A custom group',
|
|
210
|
-
members: ['user-1', 'user-2', 'user-3'],
|
|
211
|
-
},
|
|
212
|
-
]
|
|
213
|
-
|
|
214
|
-
await updateInternalGroups(
|
|
215
|
-
context1,
|
|
216
|
-
{
|
|
217
|
-
user: {
|
|
218
|
-
type: 'authenticated',
|
|
219
|
-
userId: 'admin-1',
|
|
220
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
{ groups: customGroups },
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
// Create new services instance (simulating restart)
|
|
227
|
-
const services2 = await createTestServices({
|
|
228
|
-
...workspace.config,
|
|
229
|
-
schema: BLOG_SCHEMA,
|
|
230
|
-
})
|
|
231
|
-
const context2 = createMockApiContext({ services: services2 })
|
|
232
|
-
|
|
233
|
-
// Load groups with new services
|
|
234
|
-
const result = await getInternalGroups(context2, {
|
|
235
|
-
user: {
|
|
236
|
-
type: 'authenticated',
|
|
237
|
-
userId: 'admin-1',
|
|
238
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
239
|
-
},
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
expect(result.ok).toBe(true)
|
|
243
|
-
if (result.ok && result.data) {
|
|
244
|
-
const customGroup = result.data.groups.find((g) => g.name === 'Custom Group')
|
|
245
|
-
expect(customGroup).toBeDefined()
|
|
246
|
-
expect(customGroup?.id).not.toBe('') // Should have generated ID
|
|
247
|
-
expect(customGroup?.members).toEqual(['user-1', 'user-2', 'user-3'])
|
|
248
|
-
}
|
|
249
|
-
})
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
describe('dev mode', () => {
|
|
253
|
-
let workspace: TestWorkspace
|
|
254
|
-
|
|
255
|
-
beforeEach(async () => {
|
|
256
|
-
workspace = await createTestWorkspace({
|
|
257
|
-
schema: BLOG_SCHEMA,
|
|
258
|
-
mode: 'dev',
|
|
259
|
-
})
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
afterEach(async () => {
|
|
263
|
-
await workspace.cleanup()
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('should load groups from dev settings directory', async () => {
|
|
267
|
-
const services = await createTestServices({
|
|
268
|
-
...workspace.config,
|
|
269
|
-
schema: BLOG_SCHEMA,
|
|
270
|
-
})
|
|
271
|
-
const context = createMockApiContext({ services })
|
|
272
|
-
|
|
273
|
-
const result = await getInternalGroups(context, {
|
|
274
|
-
user: {
|
|
275
|
-
type: 'authenticated',
|
|
276
|
-
userId: 'admin-1',
|
|
277
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
278
|
-
},
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
expect(result.ok).toBe(true)
|
|
282
|
-
if (result.ok && result.data) {
|
|
283
|
-
expect(result.data.groups).toBeDefined()
|
|
284
|
-
expect(result.data.groups.length).toBeGreaterThanOrEqual(2)
|
|
285
|
-
}
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
it('should save groups to .canopy-dev/settings directory', async () => {
|
|
289
|
-
const services = await createTestServices({
|
|
290
|
-
...workspace.config,
|
|
291
|
-
schema: BLOG_SCHEMA,
|
|
292
|
-
})
|
|
293
|
-
const context = createMockApiContext({ services })
|
|
294
|
-
|
|
295
|
-
const newGroups: InternalGroup[] = [
|
|
296
|
-
{
|
|
297
|
-
id: '', // Empty ID for new reserved group
|
|
298
|
-
name: RESERVED_GROUPS.ADMINS,
|
|
299
|
-
description: 'Admins',
|
|
300
|
-
members: ['admin-1'],
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
id: '', // Empty ID for new reserved group
|
|
304
|
-
name: RESERVED_GROUPS.REVIEWERS,
|
|
305
|
-
description: 'Reviewers',
|
|
306
|
-
members: [],
|
|
307
|
-
},
|
|
308
|
-
]
|
|
309
|
-
|
|
310
|
-
await updateInternalGroups(
|
|
311
|
-
context,
|
|
312
|
-
{
|
|
313
|
-
user: {
|
|
314
|
-
type: 'authenticated',
|
|
315
|
-
userId: 'admin-1',
|
|
316
|
-
groups: [RESERVED_GROUPS.ADMINS],
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
{ groups: newGroups },
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
// Verify file exists at correct path in dev mode
|
|
323
|
-
const strategy = operatingStrategy('dev')
|
|
324
|
-
const settingsRoot = strategy.getSettingsRoot(workspace.tmpRoot)
|
|
325
|
-
const groupsPath = path.join(settingsRoot, 'groups.json')
|
|
326
|
-
|
|
327
|
-
const fileExists = await fs.access(groupsPath).then(
|
|
328
|
-
() => true,
|
|
329
|
-
() => false,
|
|
330
|
-
)
|
|
331
|
-
expect(fileExists).toBe(true)
|
|
332
|
-
|
|
333
|
-
// Verify reserved groups use their names as IDs
|
|
334
|
-
const fileContent = await fs.readFile(groupsPath, 'utf-8')
|
|
335
|
-
const parsed = JSON.parse(fileContent)
|
|
336
|
-
expect(parsed.groups.length).toBe(2)
|
|
337
|
-
expect(parsed.groups[0].id).toBe(RESERVED_GROUPS.ADMINS)
|
|
338
|
-
expect(parsed.groups[1].id).toBe(RESERVED_GROUPS.REVIEWERS)
|
|
339
|
-
})
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
describe('permissions checking', () => {
|
|
343
|
-
let workspace: TestWorkspace
|
|
344
|
-
|
|
345
|
-
beforeEach(async () => {
|
|
346
|
-
workspace = await createTestWorkspace({
|
|
347
|
-
schema: BLOG_SCHEMA,
|
|
348
|
-
mode: 'prod-sim',
|
|
349
|
-
})
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
afterEach(async () => {
|
|
353
|
-
await workspace.cleanup()
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
it('should deny access to non-admin users for getting groups', async () => {
|
|
357
|
-
const services = await createTestServices({
|
|
358
|
-
...workspace.config,
|
|
359
|
-
schema: BLOG_SCHEMA,
|
|
360
|
-
})
|
|
361
|
-
const context = createMockApiContext({ services })
|
|
362
|
-
|
|
363
|
-
const result = await getInternalGroups(context, {
|
|
364
|
-
user: { type: 'authenticated', userId: 'regular-user', groups: [] },
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
expect(result.ok).toBe(false)
|
|
368
|
-
expect(result.status).toBe(403)
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('should deny access to non-admin users for updating groups', async () => {
|
|
372
|
-
const services = await createTestServices({
|
|
373
|
-
...workspace.config,
|
|
374
|
-
schema: BLOG_SCHEMA,
|
|
375
|
-
})
|
|
376
|
-
const context = createMockApiContext({ services })
|
|
377
|
-
|
|
378
|
-
const newGroups: InternalGroup[] = [
|
|
379
|
-
{
|
|
380
|
-
id: RESERVED_GROUPS.ADMINS,
|
|
381
|
-
name: RESERVED_GROUPS.ADMINS,
|
|
382
|
-
description: 'Admins',
|
|
383
|
-
members: ['admin-1'],
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
id: RESERVED_GROUPS.REVIEWERS,
|
|
387
|
-
name: RESERVED_GROUPS.REVIEWERS,
|
|
388
|
-
description: 'Reviewers',
|
|
389
|
-
members: [],
|
|
390
|
-
},
|
|
391
|
-
]
|
|
392
|
-
|
|
393
|
-
const result = await updateInternalGroups(
|
|
394
|
-
context,
|
|
395
|
-
{ user: { type: 'authenticated', userId: 'regular-user', groups: [] } },
|
|
396
|
-
{ groups: newGroups },
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
expect(result.ok).toBe(false)
|
|
400
|
-
expect(result.status).toBe(403)
|
|
401
|
-
})
|
|
402
|
-
})
|
|
403
|
-
})
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API client helper for integration tests.
|
|
3
|
-
* Creates requests and calls the Canopy HTTP handler.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { CanopyRequest } from '../../http/types'
|
|
7
|
-
import { createCanopyRequestHandler } from '../../http/handler'
|
|
8
|
-
import { createTestServices } from '../../config-test'
|
|
9
|
-
import type { CreateCanopyServicesOptions } from '../../services'
|
|
10
|
-
import type { CanopyConfig, RootCollectionConfig } from '../../config'
|
|
11
|
-
import type { AuthPlugin } from '../../auth/plugin'
|
|
12
|
-
import { CanopyApiClient } from '../../api/client'
|
|
13
|
-
|
|
14
|
-
export interface ApiClientOptions {
|
|
15
|
-
config: CanopyConfig
|
|
16
|
-
authPlugin: AuthPlugin
|
|
17
|
-
/** Pre-resolved schema for tests (bypasses .collection.json loading) */
|
|
18
|
-
schema?: RootCollectionConfig
|
|
19
|
-
/** Entry schema registry for resolving .collection.json references */
|
|
20
|
-
entrySchemaRegistry?: CreateCanopyServicesOptions['entrySchemaRegistry']
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create an API client that makes requests through the HTTP handler.
|
|
25
|
-
* This simulates real API calls without needing a server.
|
|
26
|
-
*
|
|
27
|
-
* The client wraps the production CanopyApiClient with a custom fetch
|
|
28
|
-
* that routes through the handler directly instead of making network requests.
|
|
29
|
-
*/
|
|
30
|
-
export async function createApiClient(options: ApiClientOptions) {
|
|
31
|
-
// Use test services with schema
|
|
32
|
-
const services = await createTestServices(
|
|
33
|
-
{
|
|
34
|
-
...options.config,
|
|
35
|
-
schema: options.schema ?? { collections: [] },
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
entrySchemaRegistry: options.entrySchemaRegistry,
|
|
39
|
-
},
|
|
40
|
-
)
|
|
41
|
-
const handler = createCanopyRequestHandler({
|
|
42
|
-
services,
|
|
43
|
-
authPlugin: options.authPlugin,
|
|
44
|
-
getBranchContext: async (branchName: string, opts?: { loadSchema?: boolean }) => {
|
|
45
|
-
if (!services.registry) {
|
|
46
|
-
throw new Error('Branch registry not available in dev mode')
|
|
47
|
-
}
|
|
48
|
-
const context = await services.registry.get(branchName)
|
|
49
|
-
if (!context) {
|
|
50
|
-
return null
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Load per-branch schema if requested
|
|
54
|
-
if (opts?.loadSchema) {
|
|
55
|
-
const contentRootName = services.config.contentRoot || 'content'
|
|
56
|
-
const cached = await services.branchSchemaCache.getSchema(
|
|
57
|
-
context.branchRoot,
|
|
58
|
-
services.entrySchemaRegistry,
|
|
59
|
-
contentRootName,
|
|
60
|
-
)
|
|
61
|
-
context.flatSchema = cached.flatSchema
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return context
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Custom fetch that routes through the handler
|
|
70
|
-
*/
|
|
71
|
-
const testFetch: typeof fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
72
|
-
// Handle Request objects by extracting URL
|
|
73
|
-
const urlStr =
|
|
74
|
-
typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
|
|
75
|
-
|
|
76
|
-
const path = urlStr.replace(/^http:\/\/[^/]+/, '') // Strip protocol/host
|
|
77
|
-
const segments = path
|
|
78
|
-
.replace(/^\/api\/canopycms\/?/, '')
|
|
79
|
-
.split('/')
|
|
80
|
-
.filter(Boolean)
|
|
81
|
-
|
|
82
|
-
const req: CanopyRequest = {
|
|
83
|
-
method: init?.method ?? 'GET',
|
|
84
|
-
url: urlStr,
|
|
85
|
-
header: (name: string) => {
|
|
86
|
-
if (!init?.headers) return null
|
|
87
|
-
if (init.headers instanceof Headers) {
|
|
88
|
-
return init.headers.get(name)
|
|
89
|
-
}
|
|
90
|
-
return (init.headers as Record<string, string>)[name.toLowerCase()] ?? null
|
|
91
|
-
},
|
|
92
|
-
json: async () => {
|
|
93
|
-
if (!init?.body) return undefined
|
|
94
|
-
if (typeof init.body === 'string') {
|
|
95
|
-
return JSON.parse(init.body)
|
|
96
|
-
}
|
|
97
|
-
return undefined
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const response = await handler(req, segments)
|
|
102
|
-
|
|
103
|
-
// Return a Response-like object
|
|
104
|
-
return {
|
|
105
|
-
status: response.status,
|
|
106
|
-
ok: response.status >= 200 && response.status < 300,
|
|
107
|
-
json: async () => response.body,
|
|
108
|
-
} as Response
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Create production client with test fetch
|
|
112
|
-
const client = new CanopyApiClient({
|
|
113
|
-
baseUrl: '/api/canopycms',
|
|
114
|
-
fetch: testFetch,
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Legacy request function for backward compatibility
|
|
119
|
-
*/
|
|
120
|
-
async function request(
|
|
121
|
-
method: string,
|
|
122
|
-
path: string,
|
|
123
|
-
body?: any,
|
|
124
|
-
headers: Record<string, string> = {},
|
|
125
|
-
) {
|
|
126
|
-
const response = await testFetch(path, {
|
|
127
|
-
method,
|
|
128
|
-
headers,
|
|
129
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
status: response.status,
|
|
134
|
-
ok: response.ok,
|
|
135
|
-
json: async <T = unknown>() => response.json() as Promise<T>,
|
|
136
|
-
body: await response.json(),
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
// Expose all typed client methods
|
|
142
|
-
...client,
|
|
143
|
-
|
|
144
|
-
// Legacy compatibility: keep old method signatures
|
|
145
|
-
get: (path: string, headers?: Record<string, string>) =>
|
|
146
|
-
request('GET', path, undefined, headers),
|
|
147
|
-
|
|
148
|
-
post: (path: string, body?: any, headers?: Record<string, string>) =>
|
|
149
|
-
request('POST', path, body, headers),
|
|
150
|
-
|
|
151
|
-
put: (path: string, body?: any, headers?: Record<string, string>) =>
|
|
152
|
-
request('PUT', path, body, headers),
|
|
153
|
-
|
|
154
|
-
patch: (path: string, body?: any, headers?: Record<string, string>) =>
|
|
155
|
-
request('PATCH', path, body, headers),
|
|
156
|
-
|
|
157
|
-
delete: (path: string, headers?: Record<string, string>) =>
|
|
158
|
-
request('DELETE', path, undefined, headers),
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Access to underlying services for setup/teardown
|
|
162
|
-
*/
|
|
163
|
-
services,
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export type ApiClient = ReturnType<typeof createApiClient>
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { AuthPlugin } from '../../auth/plugin'
|
|
2
|
-
import type { AuthenticatedUser } from '../../user'
|
|
3
|
-
import type { UserSearchResult, GroupMetadata, AuthenticationResult } from '../../auth/types'
|
|
4
|
-
import type { CanopyUserId, CanopyGroupId } from '../../types'
|
|
5
|
-
|
|
6
|
-
export type TestUserRole = 'admin' | 'reviewer' | 'editor'
|
|
7
|
-
|
|
8
|
-
export interface TestUser {
|
|
9
|
-
userId: CanopyUserId
|
|
10
|
-
name: string
|
|
11
|
-
email: string
|
|
12
|
-
role: TestUserRole
|
|
13
|
-
groups: CanopyGroupId[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Predefined test user personas with different permission levels
|
|
18
|
-
*/
|
|
19
|
-
export const TEST_USERS: Record<TestUserRole, TestUser> = {
|
|
20
|
-
admin: {
|
|
21
|
-
userId: 'test-admin',
|
|
22
|
-
name: 'Admin User',
|
|
23
|
-
email: 'admin@test.local',
|
|
24
|
-
role: 'admin',
|
|
25
|
-
groups: ['Admins'], // Reserved group with full access
|
|
26
|
-
},
|
|
27
|
-
reviewer: {
|
|
28
|
-
userId: 'test-reviewer',
|
|
29
|
-
name: 'Reviewer User',
|
|
30
|
-
email: 'reviewer@test.local',
|
|
31
|
-
role: 'reviewer',
|
|
32
|
-
groups: ['Reviewers'], // Reserved group with review access
|
|
33
|
-
},
|
|
34
|
-
editor: {
|
|
35
|
-
userId: 'test-editor',
|
|
36
|
-
name: 'Editor User',
|
|
37
|
-
email: 'editor@test.local',
|
|
38
|
-
role: 'editor',
|
|
39
|
-
groups: ['ContentEditors'], // Custom group with limited access
|
|
40
|
-
},
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Convert test user persona to AuthenticatedUser
|
|
45
|
-
*/
|
|
46
|
-
export function createTestUser(role: TestUserRole): AuthenticatedUser {
|
|
47
|
-
const user = TEST_USERS[role]
|
|
48
|
-
return {
|
|
49
|
-
type: 'authenticated',
|
|
50
|
-
userId: user.userId,
|
|
51
|
-
email: user.email,
|
|
52
|
-
name: user.name,
|
|
53
|
-
groups: user.groups,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Create a mock auth plugin for integration tests.
|
|
59
|
-
* Allows switching between test users to simulate multi-user scenarios.
|
|
60
|
-
*/
|
|
61
|
-
export function createMockAuthPlugin(currentRole: TestUserRole): AuthPlugin {
|
|
62
|
-
return {
|
|
63
|
-
async authenticate(_context: unknown): Promise<AuthenticationResult> {
|
|
64
|
-
const testUser = createTestUser(currentRole)
|
|
65
|
-
return {
|
|
66
|
-
success: true,
|
|
67
|
-
user: {
|
|
68
|
-
userId: testUser.userId,
|
|
69
|
-
email: testUser.email,
|
|
70
|
-
name: testUser.name,
|
|
71
|
-
externalGroups: testUser.groups,
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
async searchUsers(query: string, limit = 10): Promise<UserSearchResult[]> {
|
|
77
|
-
const allUsers = Object.values(TEST_USERS)
|
|
78
|
-
return allUsers
|
|
79
|
-
.filter(
|
|
80
|
-
(u) =>
|
|
81
|
-
u.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
82
|
-
u.email.toLowerCase().includes(query.toLowerCase()),
|
|
83
|
-
)
|
|
84
|
-
.slice(0, limit)
|
|
85
|
-
.map((u) => ({
|
|
86
|
-
id: u.userId,
|
|
87
|
-
name: u.name,
|
|
88
|
-
email: u.email,
|
|
89
|
-
}))
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
async getUserMetadata(userId: CanopyUserId): Promise<UserSearchResult | null> {
|
|
93
|
-
const user = Object.values(TEST_USERS).find((u) => u.userId === userId)
|
|
94
|
-
return user
|
|
95
|
-
? {
|
|
96
|
-
id: user.userId,
|
|
97
|
-
name: user.name,
|
|
98
|
-
email: user.email,
|
|
99
|
-
}
|
|
100
|
-
: null
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
async getGroupMetadata(groupId: CanopyGroupId): Promise<GroupMetadata | null> {
|
|
104
|
-
const groups: Record<string, GroupMetadata> = {
|
|
105
|
-
Admins: { id: 'Admins', name: 'Administrators', memberCount: 1 },
|
|
106
|
-
Reviewers: { id: 'Reviewers', name: 'Reviewers', memberCount: 1 },
|
|
107
|
-
ContentEditors: {
|
|
108
|
-
id: 'ContentEditors',
|
|
109
|
-
name: 'Content Editors',
|
|
110
|
-
memberCount: 1,
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
return groups[groupId] || null
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
async listGroups(limit = 100): Promise<GroupMetadata[]> {
|
|
117
|
-
return [
|
|
118
|
-
{ id: 'Admins', name: 'Administrators', memberCount: 1 },
|
|
119
|
-
{ id: 'Reviewers', name: 'Reviewers', memberCount: 1 },
|
|
120
|
-
{ id: 'ContentEditors', name: 'Content Editors', memberCount: 1 },
|
|
121
|
-
].slice(0, limit)
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
async searchExternalGroups(_query: string) {
|
|
125
|
-
// Return empty for tests - external groups are optional
|
|
126
|
-
return []
|
|
127
|
-
},
|
|
128
|
-
}
|
|
129
|
-
}
|