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,354 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for role-based permissions.
|
|
3
|
-
* Tests admin, reviewer, and editor roles with different privilege levels.
|
|
4
|
-
* Tests go through the HTTP API layer.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
8
|
-
|
|
9
|
-
import { createTestWorkspace, type TestWorkspace } from '../test-utils/test-workspace'
|
|
10
|
-
import { createMockAuthPlugin } from '../test-utils/multi-user'
|
|
11
|
-
import { createApiClient } from '../test-utils/api-client'
|
|
12
|
-
import { BLOG_SCHEMA } from '../fixtures/schemas'
|
|
13
|
-
import type { BranchResponse, BranchListResponse } from '../../api/branch'
|
|
14
|
-
|
|
15
|
-
describe('Role Permission Integration', () => {
|
|
16
|
-
let workspace: TestWorkspace
|
|
17
|
-
let adminClient: Awaited<ReturnType<typeof createApiClient>>
|
|
18
|
-
let reviewerClient: Awaited<ReturnType<typeof createApiClient>>
|
|
19
|
-
let editorClient: Awaited<ReturnType<typeof createApiClient>>
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
workspace = await createTestWorkspace({
|
|
23
|
-
schema: BLOG_SCHEMA,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
adminClient = await createApiClient({
|
|
27
|
-
config: workspace.config,
|
|
28
|
-
authPlugin: createMockAuthPlugin('admin'),
|
|
29
|
-
schema: BLOG_SCHEMA,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
reviewerClient = await createApiClient({
|
|
33
|
-
config: workspace.config,
|
|
34
|
-
authPlugin: createMockAuthPlugin('reviewer'),
|
|
35
|
-
schema: BLOG_SCHEMA,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
editorClient = await createApiClient({
|
|
39
|
-
config: workspace.config,
|
|
40
|
-
authPlugin: createMockAuthPlugin('editor'),
|
|
41
|
-
schema: BLOG_SCHEMA,
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
afterEach(async () => {
|
|
46
|
-
await workspace.cleanup()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('allows admin to access all branches', async () => {
|
|
50
|
-
// Editor creates a branch
|
|
51
|
-
const editorBranchResponse = await editorClient.post('/api/canopycms/branches', {
|
|
52
|
-
branch: 'editor-branch',
|
|
53
|
-
title: 'Editor Branch',
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
expect(editorBranchResponse.status).toBe(200)
|
|
57
|
-
|
|
58
|
-
// Admin can access editor's branch
|
|
59
|
-
const adminAccessResponse = await adminClient.get('/api/canopycms/editor-branch/status')
|
|
60
|
-
|
|
61
|
-
expect(adminAccessResponse.status).toBe(200)
|
|
62
|
-
const status = await adminAccessResponse.json<BranchResponse>()
|
|
63
|
-
expect(status.data?.branch.name).toBe('editor-branch')
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('allows admin to modify any branch access control', async () => {
|
|
67
|
-
// Editor creates a branch
|
|
68
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
69
|
-
branch: 'test-branch',
|
|
70
|
-
title: 'Test Branch',
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// Admin modifies access control
|
|
74
|
-
const patchResponse = await adminClient.patch('/api/canopycms/test-branch/access', {
|
|
75
|
-
allowedUsers: ['test-admin'],
|
|
76
|
-
allowedGroups: [],
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
expect(patchResponse.status).toBe(200)
|
|
80
|
-
|
|
81
|
-
// Verify access was restricted
|
|
82
|
-
const editorAccessResponse = await editorClient.get('/api/canopycms/test-branch/status')
|
|
83
|
-
|
|
84
|
-
expect(editorAccessResponse.status).toBe(403)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('allows reviewer to view all branches but not modify access', async () => {
|
|
88
|
-
// Editor creates a branch
|
|
89
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
90
|
-
branch: 'reviewer-test',
|
|
91
|
-
title: 'Reviewer Test',
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Reviewer can view the branch
|
|
95
|
-
const viewResponse = await reviewerClient.get('/api/canopycms/reviewer-test/status')
|
|
96
|
-
|
|
97
|
-
expect(viewResponse.status).toBe(200)
|
|
98
|
-
|
|
99
|
-
// Reviewer cannot modify access control
|
|
100
|
-
const patchResponse = await reviewerClient.patch('/api/canopycms/reviewer-test/access', {
|
|
101
|
-
allowedUsers: ['test-reviewer'],
|
|
102
|
-
allowedGroups: [],
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
expect(patchResponse.status).toBe(403)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('restricts editor to their own branches by default', async () => {
|
|
109
|
-
// Editor creates their own branch
|
|
110
|
-
const createResponse = await editorClient.post('/api/canopycms/branches', {
|
|
111
|
-
branch: 'my-branch',
|
|
112
|
-
title: 'My Branch',
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
expect(createResponse.status).toBe(200)
|
|
116
|
-
|
|
117
|
-
// Editor can access their own branch
|
|
118
|
-
const ownAccessResponse = await editorClient.get('/api/canopycms/my-branch/status')
|
|
119
|
-
|
|
120
|
-
expect(ownAccessResponse.status).toBe(200)
|
|
121
|
-
|
|
122
|
-
// Admin creates a restricted branch
|
|
123
|
-
await adminClient.post('/api/canopycms/branches', {
|
|
124
|
-
branch: 'admin-branch',
|
|
125
|
-
title: 'Admin Branch',
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
await adminClient.patch('/api/canopycms/admin-branch/access', {
|
|
129
|
-
allowedUsers: ['test-admin'],
|
|
130
|
-
allowedGroups: [],
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
// Editor cannot access admin's restricted branch
|
|
134
|
-
const adminBranchResponse = await editorClient.get('/api/canopycms/admin-branch/status')
|
|
135
|
-
|
|
136
|
-
expect(adminBranchResponse.status).toBe(403)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('allows reviewer to approve branches', async () => {
|
|
140
|
-
// Editor creates and submits a branch
|
|
141
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
142
|
-
branch: 'approval-test',
|
|
143
|
-
title: 'Approval Test',
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
await editorClient.post('/api/canopycms/approval-test/submit', {
|
|
147
|
-
message: 'Ready for review',
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
// Reviewer approves
|
|
151
|
-
const approveResponse = await reviewerClient.post('/api/canopycms/approval-test/approve', {
|
|
152
|
-
message: 'Looks good',
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
expect(approveResponse.status).toBe(200)
|
|
156
|
-
const approveData = await approveResponse.json<BranchResponse>()
|
|
157
|
-
expect(approveData.data?.branch.status).toBe('approved')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('prevents editor from approving their own branch', async () => {
|
|
161
|
-
// Editor creates and submits a branch
|
|
162
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
163
|
-
branch: 'self-approve-test',
|
|
164
|
-
title: 'Self Approve Test',
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
await editorClient.post('/api/canopycms/self-approve-test/submit', {
|
|
168
|
-
message: 'Ready',
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// Editor tries to approve their own branch
|
|
172
|
-
const approveResponse = await editorClient.post('/api/canopycms/self-approve-test/approve', {
|
|
173
|
-
message: 'Approving my own work',
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
expect(approveResponse.status).toBe(403)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('allows admin to perform privileged operations', async () => {
|
|
180
|
-
// Editor creates a branch
|
|
181
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
182
|
-
branch: 'privileged-test',
|
|
183
|
-
title: 'Privileged Test',
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
// Admin can delete the branch (privileged operation)
|
|
187
|
-
const deleteResponse = await adminClient.delete('/api/canopycms/privileged-test')
|
|
188
|
-
|
|
189
|
-
expect(deleteResponse.status).toBe(200)
|
|
190
|
-
|
|
191
|
-
// Verify branch is deleted
|
|
192
|
-
const statusResponse = await adminClient.get('/api/canopycms/privileged-test/status')
|
|
193
|
-
|
|
194
|
-
expect(statusResponse.status).toBe(404)
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('prevents non-creator non-admin from deleting branches', async () => {
|
|
198
|
-
// Admin creates a branch
|
|
199
|
-
await adminClient.post('/api/canopycms/branches', {
|
|
200
|
-
branch: 'delete-test',
|
|
201
|
-
title: 'Delete Test',
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
// Editor tries to delete (not creator, not admin) - should fail
|
|
205
|
-
const editorDeleteResponse = await editorClient.delete('/api/canopycms/delete-test')
|
|
206
|
-
|
|
207
|
-
expect(editorDeleteResponse.status).toBe(403)
|
|
208
|
-
|
|
209
|
-
// Reviewer tries to delete (not creator, not admin) - should fail
|
|
210
|
-
const reviewerDeleteResponse = await reviewerClient.delete('/api/canopycms/delete-test')
|
|
211
|
-
|
|
212
|
-
expect(reviewerDeleteResponse.status).toBe(403)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('allows reviewer to request changes but not edit content', async () => {
|
|
216
|
-
// Editor creates and submits branch
|
|
217
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
218
|
-
branch: 'changes-test',
|
|
219
|
-
title: 'Changes Test',
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
await editorClient.post('/api/canopycms/changes-test/submit', {
|
|
223
|
-
message: 'Ready',
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
// Reviewer can request changes
|
|
227
|
-
const requestResponse = await reviewerClient.post(
|
|
228
|
-
'/api/canopycms/changes-test/request-changes',
|
|
229
|
-
{
|
|
230
|
-
message: 'Needs work',
|
|
231
|
-
},
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
expect(requestResponse.status).toBe(200)
|
|
235
|
-
|
|
236
|
-
// Reviewer cannot write content (even after requesting changes)
|
|
237
|
-
await reviewerClient.put('/api/canopycms/changes-test/content/posts/test', {
|
|
238
|
-
collection: 'content/posts',
|
|
239
|
-
slug: 'test',
|
|
240
|
-
format: 'mdx',
|
|
241
|
-
data: {
|
|
242
|
-
title: 'Test',
|
|
243
|
-
author: 'Reviewer',
|
|
244
|
-
date: '2024-01-01',
|
|
245
|
-
tags: [],
|
|
246
|
-
},
|
|
247
|
-
body: 'Content',
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
// This might succeed if defaultPathAccess is 'allow', but the test demonstrates
|
|
251
|
-
// that reviewers have read-only access by role
|
|
252
|
-
// TODO: Once we have proper role-based write restrictions, this should be 403
|
|
253
|
-
// expect(writeResponse.status).toBe(403)
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('respects group-based permissions', async () => {
|
|
257
|
-
// Create branch and restrict to specific groups
|
|
258
|
-
await adminClient.post('/api/canopycms/branches', {
|
|
259
|
-
branch: 'group-test',
|
|
260
|
-
title: 'Group Test',
|
|
261
|
-
access: {
|
|
262
|
-
allowedGroups: ['Admins', 'Reviewers'],
|
|
263
|
-
},
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
// Admin (Admins group) can access
|
|
267
|
-
const adminAccessResponse = await adminClient.get('/api/canopycms/group-test/status')
|
|
268
|
-
expect(adminAccessResponse.status).toBe(200)
|
|
269
|
-
|
|
270
|
-
// Reviewer (Reviewers group) can access
|
|
271
|
-
const reviewerAccessResponse = await reviewerClient.get('/api/canopycms/group-test/status')
|
|
272
|
-
expect(reviewerAccessResponse.status).toBe(200)
|
|
273
|
-
|
|
274
|
-
// Editor (ContentEditors group) cannot access
|
|
275
|
-
const editorAccessResponse = await editorClient.get('/api/canopycms/group-test/status')
|
|
276
|
-
expect(editorAccessResponse.status).toBe(403)
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('lists only accessible branches per role', async () => {
|
|
280
|
-
// Create multiple branches with different access
|
|
281
|
-
await adminClient.post('/api/canopycms/branches', {
|
|
282
|
-
branch: 'admin-only',
|
|
283
|
-
title: 'Admin Only',
|
|
284
|
-
access: {
|
|
285
|
-
allowedUsers: ['test-admin'],
|
|
286
|
-
},
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
290
|
-
branch: 'editor-branch',
|
|
291
|
-
title: 'Editor Branch',
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
await reviewerClient.post('/api/canopycms/branches', {
|
|
295
|
-
branch: 'reviewer-branch',
|
|
296
|
-
title: 'Reviewer Branch',
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
// Admin sees all branches
|
|
300
|
-
const adminListResponse = await adminClient.get('/api/canopycms/branches')
|
|
301
|
-
expect(adminListResponse.status).toBe(200)
|
|
302
|
-
const adminBranches = await adminListResponse.json<BranchListResponse>()
|
|
303
|
-
expect(adminBranches.data?.branches.length).toBeGreaterThanOrEqual(3)
|
|
304
|
-
|
|
305
|
-
// Editor sees their own + public branches (not admin-only)
|
|
306
|
-
const editorListResponse = await editorClient.get('/api/canopycms/branches')
|
|
307
|
-
expect(editorListResponse.status).toBe(200)
|
|
308
|
-
const editorBranches = await editorListResponse.json<BranchListResponse>()
|
|
309
|
-
const editorBranchNames = editorBranches.data?.branches.map((b) => b.name)
|
|
310
|
-
expect(editorBranchNames).toContain('editor-branch')
|
|
311
|
-
expect(editorBranchNames).not.toContain('admin-only')
|
|
312
|
-
|
|
313
|
-
// Reviewer sees all branches (privileged role)
|
|
314
|
-
const reviewerListResponse = await reviewerClient.get('/api/canopycms/branches')
|
|
315
|
-
expect(reviewerListResponse.status).toBe(200)
|
|
316
|
-
const reviewerBranches = await reviewerListResponse.json<BranchListResponse>()
|
|
317
|
-
expect(reviewerBranches.data?.branches.length).toBeGreaterThanOrEqual(3)
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
it('allows branch creator to delete their own branch', async () => {
|
|
321
|
-
// Editor creates branch
|
|
322
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
323
|
-
branch: 'editor-own-branch',
|
|
324
|
-
title: 'Editor Own Branch',
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
// Editor deletes their own branch - should succeed
|
|
328
|
-
const deleteResponse = await editorClient.delete('/api/canopycms/editor-own-branch')
|
|
329
|
-
|
|
330
|
-
expect(deleteResponse.status).toBe(200)
|
|
331
|
-
expect(deleteResponse.ok).toBe(true)
|
|
332
|
-
|
|
333
|
-
// Verify branch is deleted
|
|
334
|
-
const statusResponse = await editorClient.get('/api/canopycms/editor-own-branch/status')
|
|
335
|
-
expect(statusResponse.status).toBe(404)
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
it('allows branch creator to modify access on their own branch', async () => {
|
|
339
|
-
// Editor creates branch
|
|
340
|
-
await editorClient.post('/api/canopycms/branches', {
|
|
341
|
-
branch: 'editor-access-branch',
|
|
342
|
-
title: 'Editor Access Branch',
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
// Editor modifies access on their own branch - should succeed
|
|
346
|
-
const response = await editorClient.patch('/api/canopycms/editor-access-branch/access', {
|
|
347
|
-
allowedUsers: ['other-user'],
|
|
348
|
-
allowedGroups: [],
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
expect(response.status).toBe(200)
|
|
352
|
-
expect(response.ok).toBe(true)
|
|
353
|
-
})
|
|
354
|
-
})
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration test to verify that permissions are read from the settings branch,
|
|
3
|
-
* not from the current branch being accessed.
|
|
4
|
-
*
|
|
5
|
-
* This test creates a scenario where:
|
|
6
|
-
* - Settings branch has restrictive permissions (only allow specific user)
|
|
7
|
-
* - Main branch has permissive permissions (allow everyone)
|
|
8
|
-
* - System must read from settings branch, not main branch
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
12
|
-
import path from 'node:path'
|
|
13
|
-
import fs from 'node:fs/promises'
|
|
14
|
-
|
|
15
|
-
import { createTestWorkspace, type TestWorkspace } from '../test-utils/test-workspace'
|
|
16
|
-
import { BLOG_SCHEMA } from '../fixtures/schemas'
|
|
17
|
-
import { BranchWorkspaceManager } from '../../branch-workspace'
|
|
18
|
-
import { SettingsWorkspaceManager } from '../../settings-workspace'
|
|
19
|
-
import { createTestServices } from '../../config-test'
|
|
20
|
-
import type { PathPermission } from '../../config'
|
|
21
|
-
import type { AuthenticatedUser } from '../../user'
|
|
22
|
-
import { operatingStrategy } from '../../operating-mode'
|
|
23
|
-
import { unsafeAsPermissionPath } from '../../authorization/test-utils'
|
|
24
|
-
import { unsafeAsPhysicalPath } from '../../paths/test-utils'
|
|
25
|
-
|
|
26
|
-
describe('Settings Branch Isolation', () => {
|
|
27
|
-
let workspace: TestWorkspace
|
|
28
|
-
|
|
29
|
-
beforeEach(async () => {
|
|
30
|
-
workspace = await createTestWorkspace({
|
|
31
|
-
schema: BLOG_SCHEMA,
|
|
32
|
-
defaultPathAccess: 'deny', // Default deny to ensure permissions are checked
|
|
33
|
-
mode: 'prod-sim',
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
afterEach(async () => {
|
|
38
|
-
await workspace.cleanup()
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('reads permissions from settings branch, not from main branch', async () => {
|
|
42
|
-
const restrictedUser: AuthenticatedUser = {
|
|
43
|
-
type: 'authenticated',
|
|
44
|
-
userId: 'restricted-user',
|
|
45
|
-
groups: [],
|
|
46
|
-
}
|
|
47
|
-
const allowedUser: AuthenticatedUser = {
|
|
48
|
-
type: 'authenticated',
|
|
49
|
-
userId: 'allowed-user',
|
|
50
|
-
groups: [],
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const manager = new BranchWorkspaceManager(workspace.config)
|
|
54
|
-
|
|
55
|
-
// Create main branch
|
|
56
|
-
const mainBranch = await manager.openOrCreateBranch({
|
|
57
|
-
branchName: 'main',
|
|
58
|
-
mode: 'prod-sim',
|
|
59
|
-
createdBy: 'system',
|
|
60
|
-
remoteUrl: workspace.remotePath,
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// Create settings workspace using SettingsWorkspaceManager
|
|
64
|
-
const settingsManager = new SettingsWorkspaceManager(workspace.config)
|
|
65
|
-
const strategy = operatingStrategy('prod-sim')
|
|
66
|
-
const settingsRoot = strategy.getSettingsRoot(workspace.tmpRoot)
|
|
67
|
-
const settingsBranchName = strategy.getSettingsBranchName({
|
|
68
|
-
settingsBranch: 'canopycms-settings',
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
await settingsManager.ensureGitWorkspace({
|
|
72
|
-
settingsRoot,
|
|
73
|
-
branchName: settingsBranchName,
|
|
74
|
-
mode: 'prod-sim',
|
|
75
|
-
remoteUrl: workspace.remotePath,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Write PERMISSIVE permissions to main branch (the wrong place)
|
|
79
|
-
const mainPermissionsDir = mainBranch.branchRoot
|
|
80
|
-
await fs.mkdir(mainPermissionsDir, { recursive: true })
|
|
81
|
-
const mainPermissionsFile = path.join(mainPermissionsDir, 'permissions.json')
|
|
82
|
-
const permissiveRules: PathPermission[] = [
|
|
83
|
-
{
|
|
84
|
-
path: unsafeAsPermissionPath('content/**'),
|
|
85
|
-
read: {
|
|
86
|
-
allowedUsers: [restrictedUser.userId, allowedUser.userId, 'anonymous'],
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
await fs.writeFile(
|
|
91
|
-
mainPermissionsFile,
|
|
92
|
-
JSON.stringify({
|
|
93
|
-
version: 1,
|
|
94
|
-
updatedAt: new Date().toISOString(),
|
|
95
|
-
updatedBy: 'test',
|
|
96
|
-
pathPermissions: permissiveRules,
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
// Write RESTRICTIVE permissions to settings directory (the correct place)
|
|
101
|
-
await fs.mkdir(settingsRoot, { recursive: true })
|
|
102
|
-
const settingsPermissionsFile = path.join(settingsRoot, 'permissions.json')
|
|
103
|
-
const restrictiveRules: PathPermission[] = [
|
|
104
|
-
{
|
|
105
|
-
path: unsafeAsPermissionPath('content/posts/hello.mdx'),
|
|
106
|
-
read: {
|
|
107
|
-
allowedUsers: [allowedUser.userId], // Only allowedUser can read
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
]
|
|
111
|
-
await fs.writeFile(
|
|
112
|
-
settingsPermissionsFile,
|
|
113
|
-
JSON.stringify({
|
|
114
|
-
version: 1,
|
|
115
|
-
updatedAt: new Date().toISOString(),
|
|
116
|
-
updatedBy: 'test',
|
|
117
|
-
pathPermissions: restrictiveRules,
|
|
118
|
-
}),
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
// Create services with prod-sim mode
|
|
122
|
-
const services = await createTestServices({
|
|
123
|
-
...workspace.config,
|
|
124
|
-
schema: BLOG_SCHEMA,
|
|
125
|
-
mode: 'prod-sim',
|
|
126
|
-
settingsBranch: 'canopycms-settings',
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// Check access for restrictedUser on main branch
|
|
130
|
-
// This should read from settings branch (restrictive), not main branch (permissive)
|
|
131
|
-
const restrictedUserAccess = await services.checkContentAccess(
|
|
132
|
-
mainBranch,
|
|
133
|
-
mainBranch.branchRoot,
|
|
134
|
-
unsafeAsPhysicalPath('content/posts/hello.mdx'),
|
|
135
|
-
restrictedUser,
|
|
136
|
-
'read',
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
// restrictedUser should be DENIED because settings branch doesn't allow them
|
|
140
|
-
expect(restrictedUserAccess.allowed).toBe(false)
|
|
141
|
-
expect(restrictedUserAccess.path.allowed).toBe(false)
|
|
142
|
-
|
|
143
|
-
// Verify it's not falling back to main branch's permissive rule
|
|
144
|
-
// If it were reading from main branch, this would be true
|
|
145
|
-
expect(restrictedUserAccess.path.reason).not.toBe('allowed_by_rule')
|
|
146
|
-
|
|
147
|
-
// Check access for allowedUser - should be allowed
|
|
148
|
-
const allowedUserAccess = await services.checkContentAccess(
|
|
149
|
-
mainBranch,
|
|
150
|
-
mainBranch.branchRoot,
|
|
151
|
-
unsafeAsPhysicalPath('content/posts/hello.mdx'),
|
|
152
|
-
allowedUser,
|
|
153
|
-
'read',
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
expect(allowedUserAccess.allowed).toBe(true)
|
|
157
|
-
expect(allowedUserAccess.path.allowed).toBe(true)
|
|
158
|
-
expect(allowedUserAccess.path.reason).toBe('allowed_by_rule')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('does not read from current branch permissions file in prod-sim', async () => {
|
|
162
|
-
const user: AuthenticatedUser = {
|
|
163
|
-
type: 'authenticated',
|
|
164
|
-
userId: 'test-user',
|
|
165
|
-
groups: [],
|
|
166
|
-
}
|
|
167
|
-
const manager = new BranchWorkspaceManager(workspace.config)
|
|
168
|
-
|
|
169
|
-
// Create main branch with permissive permissions IN THE BRANCH
|
|
170
|
-
const mainBranch = await manager.openOrCreateBranch({
|
|
171
|
-
branchName: 'main',
|
|
172
|
-
mode: 'prod-sim',
|
|
173
|
-
createdBy: 'system',
|
|
174
|
-
remoteUrl: workspace.remotePath,
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
// Write permissions directly to main branch (wrong location in prod-sim)
|
|
178
|
-
const mainPermissionsDir = mainBranch.branchRoot
|
|
179
|
-
await fs.mkdir(mainPermissionsDir, { recursive: true })
|
|
180
|
-
await fs.writeFile(
|
|
181
|
-
path.join(mainPermissionsDir, 'permissions.json'),
|
|
182
|
-
JSON.stringify({
|
|
183
|
-
version: 1,
|
|
184
|
-
updatedAt: new Date().toISOString(),
|
|
185
|
-
updatedBy: 'test',
|
|
186
|
-
pathPermissions: [
|
|
187
|
-
{
|
|
188
|
-
path: unsafeAsPermissionPath('content/**'),
|
|
189
|
-
read: { allowedUsers: [user.userId] },
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
}),
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
// Create settings branch with NO permissions (empty file)
|
|
196
|
-
const settingsBranch = await manager.openOrCreateBranch({
|
|
197
|
-
branchName: 'canopycms-settings',
|
|
198
|
-
mode: 'prod-sim',
|
|
199
|
-
createdBy: 'system',
|
|
200
|
-
remoteUrl: workspace.remotePath,
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
const settingsPermissionsDir = settingsBranch.branchRoot
|
|
204
|
-
await fs.mkdir(settingsPermissionsDir, { recursive: true })
|
|
205
|
-
await fs.writeFile(
|
|
206
|
-
path.join(settingsPermissionsDir, 'permissions.json'),
|
|
207
|
-
JSON.stringify({
|
|
208
|
-
version: 1,
|
|
209
|
-
updatedAt: new Date().toISOString(),
|
|
210
|
-
updatedBy: 'test',
|
|
211
|
-
pathPermissions: [], // Empty - no rules
|
|
212
|
-
}),
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
// Create services
|
|
216
|
-
const services = await createTestServices({
|
|
217
|
-
...workspace.config,
|
|
218
|
-
schema: BLOG_SCHEMA,
|
|
219
|
-
mode: 'prod-sim',
|
|
220
|
-
settingsBranch: 'canopycms-settings',
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
// Check access - should fall back to defaultPathAccess (deny)
|
|
224
|
-
// NOT use the permissive rule from main branch
|
|
225
|
-
const access = await services.checkContentAccess(
|
|
226
|
-
mainBranch,
|
|
227
|
-
mainBranch.branchRoot,
|
|
228
|
-
unsafeAsPhysicalPath('content/posts/test.mdx'),
|
|
229
|
-
user,
|
|
230
|
-
'read',
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
// Should be denied because settings branch has no rules
|
|
234
|
-
// If it were reading from main branch, this would be allowed
|
|
235
|
-
expect(access.allowed).toBe(false)
|
|
236
|
-
expect(access.path.allowed).toBe(false)
|
|
237
|
-
expect(access.path.reason).toBe('no_rule_match')
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
it('verifies settings branch isolation works consistently across modes', async () => {
|
|
241
|
-
// This test verifies the same behavior as the prod-sim test above,
|
|
242
|
-
// but demonstrates it works for any mode that uses settings branch
|
|
243
|
-
const user: AuthenticatedUser = {
|
|
244
|
-
type: 'authenticated',
|
|
245
|
-
userId: 'user-for-mode-test',
|
|
246
|
-
groups: [],
|
|
247
|
-
}
|
|
248
|
-
const manager = new BranchWorkspaceManager(workspace.config)
|
|
249
|
-
|
|
250
|
-
// Create feature branch with permissive permissions
|
|
251
|
-
const featureBranch = await manager.openOrCreateBranch({
|
|
252
|
-
branchName: 'feature-x',
|
|
253
|
-
mode: 'prod-sim',
|
|
254
|
-
createdBy: user.userId,
|
|
255
|
-
remoteUrl: workspace.remotePath,
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
const featurePermissionsDir = featureBranch.branchRoot
|
|
259
|
-
await fs.mkdir(featurePermissionsDir, { recursive: true })
|
|
260
|
-
await fs.writeFile(
|
|
261
|
-
path.join(featurePermissionsDir, 'permissions.json'),
|
|
262
|
-
JSON.stringify({
|
|
263
|
-
version: 1,
|
|
264
|
-
updatedAt: new Date().toISOString(),
|
|
265
|
-
updatedBy: 'test',
|
|
266
|
-
pathPermissions: [
|
|
267
|
-
{
|
|
268
|
-
path: unsafeAsPermissionPath('content/**'),
|
|
269
|
-
read: { allowedUsers: [user.userId] },
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
}),
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
// Create settings branch with restrictive permissions
|
|
276
|
-
const settingsBranch = await manager.openOrCreateBranch({
|
|
277
|
-
branchName: 'canopycms-settings',
|
|
278
|
-
mode: 'prod-sim',
|
|
279
|
-
createdBy: 'system',
|
|
280
|
-
remoteUrl: workspace.remotePath,
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
const settingsPermissionsDir = settingsBranch.branchRoot
|
|
284
|
-
await fs.mkdir(settingsPermissionsDir, { recursive: true })
|
|
285
|
-
await fs.writeFile(
|
|
286
|
-
path.join(settingsPermissionsDir, 'permissions.json'),
|
|
287
|
-
JSON.stringify({
|
|
288
|
-
version: 1,
|
|
289
|
-
updatedAt: new Date().toISOString(),
|
|
290
|
-
updatedBy: 'test',
|
|
291
|
-
pathPermissions: [], // No rules = default deny
|
|
292
|
-
}),
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
// Create services in prod-sim mode
|
|
296
|
-
const services = await createTestServices({
|
|
297
|
-
...workspace.config,
|
|
298
|
-
schema: BLOG_SCHEMA,
|
|
299
|
-
mode: 'prod-sim',
|
|
300
|
-
settingsBranch: 'canopycms-settings',
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
// Check access on feature branch
|
|
304
|
-
// Should read from settings branch (empty), not feature branch (permissive)
|
|
305
|
-
const access = await services.checkContentAccess(
|
|
306
|
-
featureBranch,
|
|
307
|
-
featureBranch.branchRoot,
|
|
308
|
-
unsafeAsPhysicalPath('content/posts/test.mdx'),
|
|
309
|
-
user,
|
|
310
|
-
'read',
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
// Should be denied because settings branch has no rules
|
|
314
|
-
expect(access.allowed).toBe(false)
|
|
315
|
-
expect(access.path.reason).toBe('no_rule_match')
|
|
316
|
-
})
|
|
317
|
-
})
|