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,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Groups file loader
|
|
3
|
-
*
|
|
4
|
-
* Handles loading and saving internal groups from the filesystem.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { promises as fs } from 'node:fs'
|
|
8
|
-
import { join } from 'node:path'
|
|
9
|
-
import type { CanopyUserId } from '../../types'
|
|
10
|
-
import { GroupsFileSchema, type InternalGroup, type GroupsFile } from './schema'
|
|
11
|
-
import type { OperatingMode } from '../../operating-mode'
|
|
12
|
-
import { operatingStrategy } from '../../operating-mode'
|
|
13
|
-
import { RESERVED_GROUPS } from '../helpers'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get the appropriate groups file path based on mode
|
|
17
|
-
*/
|
|
18
|
-
function getGroupsFilePath(branchRoot: string, mode: OperatingMode): string {
|
|
19
|
-
return operatingStrategy(mode).getGroupsFilePath(branchRoot)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Load full groups file (for version checking)
|
|
24
|
-
* Returns null if file doesn't exist.
|
|
25
|
-
*/
|
|
26
|
-
export async function loadGroupsFile(
|
|
27
|
-
branchRoot: string,
|
|
28
|
-
mode: OperatingMode,
|
|
29
|
-
): Promise<GroupsFile | null> {
|
|
30
|
-
const groupsPath = getGroupsFilePath(branchRoot, mode)
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const content = await fs.readFile(groupsPath, 'utf-8')
|
|
34
|
-
const parsed = JSON.parse(content)
|
|
35
|
-
const validated = GroupsFileSchema.parse(parsed)
|
|
36
|
-
return validated
|
|
37
|
-
} catch (error) {
|
|
38
|
-
// File doesn't exist
|
|
39
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
throw error
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Load internal groups from .canopycms/groups.json (or .local.json in dev mode)
|
|
48
|
-
* Ensures Admins and Reviewers groups always exist, adding them dynamically if not present.
|
|
49
|
-
* If Admins group exists in file, merges with bootstrap admin IDs.
|
|
50
|
-
*/
|
|
51
|
-
export async function loadInternalGroups(
|
|
52
|
-
branchRoot: string,
|
|
53
|
-
mode: OperatingMode,
|
|
54
|
-
bootstrapAdminIds: Set<string> = new Set(),
|
|
55
|
-
): Promise<InternalGroup[]> {
|
|
56
|
-
const file = await loadGroupsFile(branchRoot, mode)
|
|
57
|
-
const fileGroups = file?.groups ?? []
|
|
58
|
-
|
|
59
|
-
// Find existing Admins and Reviewers groups
|
|
60
|
-
let adminsGroup = fileGroups.find((g) => g.id === RESERVED_GROUPS.ADMINS)
|
|
61
|
-
let reviewersGroup = fileGroups.find((g) => g.id === RESERVED_GROUPS.REVIEWERS)
|
|
62
|
-
|
|
63
|
-
// Ensure Admins group exists and includes bootstrap admins
|
|
64
|
-
if (adminsGroup) {
|
|
65
|
-
// Merge bootstrap admin IDs with existing members
|
|
66
|
-
const allAdmins = new Set([...adminsGroup.members, ...bootstrapAdminIds])
|
|
67
|
-
adminsGroup = {
|
|
68
|
-
...adminsGroup,
|
|
69
|
-
members: Array.from(allAdmins),
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// Create Admins group with bootstrap admins
|
|
73
|
-
adminsGroup = {
|
|
74
|
-
id: RESERVED_GROUPS.ADMINS,
|
|
75
|
-
name: RESERVED_GROUPS.ADMINS,
|
|
76
|
-
description: 'Full access to all CMS operations',
|
|
77
|
-
members: Array.from(bootstrapAdminIds),
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Ensure Reviewers group exists
|
|
82
|
-
if (!reviewersGroup) {
|
|
83
|
-
reviewersGroup = {
|
|
84
|
-
id: RESERVED_GROUPS.REVIEWERS,
|
|
85
|
-
name: RESERVED_GROUPS.REVIEWERS,
|
|
86
|
-
description: 'Can review branches, request changes, approve PRs',
|
|
87
|
-
members: [],
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Return all groups: reserved groups first, then other groups
|
|
92
|
-
const otherGroups = fileGroups.filter(
|
|
93
|
-
(g) => g.id !== RESERVED_GROUPS.ADMINS && g.id !== RESERVED_GROUPS.REVIEWERS,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
return [adminsGroup, reviewersGroup, ...otherGroups]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Save internal groups to .canopycms/groups.json (or .local.json in dev mode)
|
|
101
|
-
*/
|
|
102
|
-
export async function saveInternalGroups(
|
|
103
|
-
branchRoot: string,
|
|
104
|
-
groups: InternalGroup[],
|
|
105
|
-
updatedBy: CanopyUserId,
|
|
106
|
-
mode: OperatingMode,
|
|
107
|
-
contentVersion?: number,
|
|
108
|
-
): Promise<void> {
|
|
109
|
-
const groupsPath = getGroupsFilePath(branchRoot, mode)
|
|
110
|
-
const groupsDir = join(groupsPath, '..')
|
|
111
|
-
|
|
112
|
-
// Ensure parent directory exists (e.g., .canopy-meta or .canopycms)
|
|
113
|
-
await fs.mkdir(groupsDir, { recursive: true })
|
|
114
|
-
|
|
115
|
-
const groupsFile: GroupsFile = {
|
|
116
|
-
version: 1,
|
|
117
|
-
contentVersion: contentVersion ?? 1,
|
|
118
|
-
updatedAt: new Date().toISOString(),
|
|
119
|
-
updatedBy,
|
|
120
|
-
groups,
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Validate before writing
|
|
124
|
-
GroupsFileSchema.parse(groupsFile)
|
|
125
|
-
|
|
126
|
-
await fs.writeFile(groupsPath, JSON.stringify(groupsFile, null, 2), 'utf-8')
|
|
127
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schema for groups.json file
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { z } from 'zod'
|
|
6
|
-
import type { CanopyUserId, CanopyGroupId } from '../../types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Schema for .canopycms/groups.json
|
|
10
|
-
*/
|
|
11
|
-
export const GroupsFileSchema = z.object({
|
|
12
|
-
version: z.literal(1),
|
|
13
|
-
contentVersion: z.number().optional(), // For optimistic locking
|
|
14
|
-
updatedAt: z.string().datetime(),
|
|
15
|
-
updatedBy: z.string() as z.ZodType<CanopyUserId>,
|
|
16
|
-
groups: z.array(
|
|
17
|
-
z.object({
|
|
18
|
-
id: z.string() as z.ZodType<CanopyGroupId>,
|
|
19
|
-
name: z.string().min(1),
|
|
20
|
-
description: z.string().optional(),
|
|
21
|
-
members: z.array(z.string() as z.ZodType<CanopyUserId>),
|
|
22
|
-
}),
|
|
23
|
-
),
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
export type GroupsFile = z.infer<typeof GroupsFileSchema>
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Internal group representation
|
|
30
|
-
*/
|
|
31
|
-
export interface InternalGroup {
|
|
32
|
-
id: CanopyGroupId
|
|
33
|
-
name: string
|
|
34
|
-
description?: string
|
|
35
|
-
members: CanopyUserId[]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Default groups file
|
|
40
|
-
*/
|
|
41
|
-
export function createDefaultGroupsFile(userId: CanopyUserId): GroupsFile {
|
|
42
|
-
return {
|
|
43
|
-
version: 1,
|
|
44
|
-
updatedAt: new Date().toISOString(),
|
|
45
|
-
updatedBy: userId,
|
|
46
|
-
groups: [],
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorization helper functions
|
|
3
|
-
*
|
|
4
|
-
* Simple utilities for checking user roles and permissions.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Reserved groups for CanopyCMS permission system.
|
|
9
|
-
*
|
|
10
|
-
* These groups have special meaning and cannot be deleted or renamed.
|
|
11
|
-
* - Admins: Full access to all CMS operations
|
|
12
|
-
* - Reviewers: Can review branches, request changes, approve PRs
|
|
13
|
-
*/
|
|
14
|
-
export const RESERVED_GROUPS = {
|
|
15
|
-
ADMINS: 'Admins',
|
|
16
|
-
REVIEWERS: 'Reviewers',
|
|
17
|
-
} as const
|
|
18
|
-
|
|
19
|
-
export type ReservedGroupId = (typeof RESERVED_GROUPS)[keyof typeof RESERVED_GROUPS]
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Check if a group ID is a reserved group
|
|
23
|
-
*/
|
|
24
|
-
export function isReservedGroup(groupId: string): groupId is ReservedGroupId {
|
|
25
|
-
return Object.values(RESERVED_GROUPS).includes(groupId as ReservedGroupId)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Check if user is in the Admins group
|
|
30
|
-
*/
|
|
31
|
-
export function isAdmin(groups: readonly string[] | undefined): boolean {
|
|
32
|
-
return groups?.includes(RESERVED_GROUPS.ADMINS) ?? false
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Check if user is in the Reviewers group (or is an Admin, since Admins can do everything)
|
|
37
|
-
*/
|
|
38
|
-
export function isReviewer(groups: readonly string[] | undefined): boolean {
|
|
39
|
-
return isAdmin(groups) || (groups?.includes(RESERVED_GROUPS.REVIEWERS) ?? false)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Check if user has privileged access (Admin or Reviewer)
|
|
44
|
-
* Used for operations that require elevated permissions but not full admin
|
|
45
|
-
*/
|
|
46
|
-
export function isPrivileged(groups: readonly string[] | undefined): boolean {
|
|
47
|
-
return isReviewer(groups)
|
|
48
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorization module for CanopyCMS
|
|
3
|
-
*
|
|
4
|
-
* This module provides a unified API for checking user access to branches and content.
|
|
5
|
-
*
|
|
6
|
-
* ## Quick Start
|
|
7
|
-
*
|
|
8
|
-
* For most use cases, use `checkContentAccess` which handles both branch and path permissions:
|
|
9
|
-
*
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { checkContentAccess } from './authorization'
|
|
12
|
-
*
|
|
13
|
-
* const result = await checkContentAccess(deps, context, branchRoot, 'content/posts/post.mdx', user, 'edit')
|
|
14
|
-
* if (result.allowed) {
|
|
15
|
-
* // User can edit the file
|
|
16
|
-
* }
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* ## Module Structure
|
|
20
|
-
*
|
|
21
|
-
* - `content.ts` - Combined branch + path access (main entry point)
|
|
22
|
-
* - `branch.ts` - Branch-level access control
|
|
23
|
-
* - `path.ts` - Path-level permissions
|
|
24
|
-
* - `helpers.ts` - Utility functions (isAdmin, isReviewer, etc.)
|
|
25
|
-
* - `permissions/` - Permissions file schema and loader
|
|
26
|
-
* - `groups/` - Groups file schema and loader
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
// Types
|
|
30
|
-
export type {
|
|
31
|
-
BranchAccessResult,
|
|
32
|
-
PathPermissionResult,
|
|
33
|
-
ContentAccessResult,
|
|
34
|
-
ContentAccessDeps,
|
|
35
|
-
PermissionPath,
|
|
36
|
-
} from './types'
|
|
37
|
-
|
|
38
|
-
// Validation
|
|
39
|
-
export { parsePermissionPath } from './validation'
|
|
40
|
-
|
|
41
|
-
// Main content access (recommended for most cases)
|
|
42
|
-
export { checkContentAccess, createCheckContentAccess } from './content'
|
|
43
|
-
|
|
44
|
-
// Branch-level access
|
|
45
|
-
export {
|
|
46
|
-
checkBranchAccessWithDefault,
|
|
47
|
-
createCheckBranchAccess,
|
|
48
|
-
canPerformWorkflowAction,
|
|
49
|
-
} from './branch'
|
|
50
|
-
|
|
51
|
-
// Path-level access
|
|
52
|
-
export { checkPathAccess, createCheckPathAccess } from './path'
|
|
53
|
-
|
|
54
|
-
// Helper functions
|
|
55
|
-
export {
|
|
56
|
-
RESERVED_GROUPS,
|
|
57
|
-
type ReservedGroupId,
|
|
58
|
-
isReservedGroup,
|
|
59
|
-
isAdmin,
|
|
60
|
-
isReviewer,
|
|
61
|
-
isPrivileged,
|
|
62
|
-
} from './helpers'
|
|
63
|
-
|
|
64
|
-
// Permissions file handling
|
|
65
|
-
export {
|
|
66
|
-
PermissionsFileSchema,
|
|
67
|
-
createDefaultPermissionsFile,
|
|
68
|
-
type PermissionsFile,
|
|
69
|
-
loadPermissionsFile,
|
|
70
|
-
loadPathPermissions,
|
|
71
|
-
savePathPermissions,
|
|
72
|
-
ensurePermissionsFile,
|
|
73
|
-
} from './permissions'
|
|
74
|
-
|
|
75
|
-
// Groups file handling
|
|
76
|
-
export {
|
|
77
|
-
GroupsFileSchema,
|
|
78
|
-
createDefaultGroupsFile,
|
|
79
|
-
type GroupsFile,
|
|
80
|
-
type InternalGroup,
|
|
81
|
-
loadGroupsFile,
|
|
82
|
-
loadInternalGroups,
|
|
83
|
-
saveInternalGroups,
|
|
84
|
-
} from './groups'
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path-level authorization
|
|
3
|
-
*
|
|
4
|
-
* Handles checking if a user can access a specific path based on permission rules.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import path from 'node:path'
|
|
8
|
-
|
|
9
|
-
import { minimatch } from 'minimatch'
|
|
10
|
-
|
|
11
|
-
import type {
|
|
12
|
-
PathPermission,
|
|
13
|
-
DefaultPathAccess,
|
|
14
|
-
PermissionLevel,
|
|
15
|
-
PermissionTarget,
|
|
16
|
-
} from '../config'
|
|
17
|
-
import { isAdmin } from './helpers'
|
|
18
|
-
import type { CanopyUser } from '../user'
|
|
19
|
-
import type { PathPermissionResult } from './types'
|
|
20
|
-
import type { PhysicalPath } from '../paths/types'
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Normalize a path for consistent matching
|
|
24
|
-
*/
|
|
25
|
-
function normalize(p: PhysicalPath): PhysicalPath {
|
|
26
|
-
const normalized = (p as string).split(path.sep).join('/')
|
|
27
|
-
return normalized.replace(/^\.?\/*/, '') as PhysicalPath
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Check if a path matches a permission rule
|
|
32
|
-
*/
|
|
33
|
-
function matchesRule(rule: PathPermission, relativePath: PhysicalPath): boolean {
|
|
34
|
-
return minimatch(relativePath as string, rule.path, { dot: true })
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Check if user matches a permission target
|
|
39
|
-
*/
|
|
40
|
-
function isAllowedByTarget(target: PermissionTarget, user: CanopyUser): boolean {
|
|
41
|
-
const hasUserConstraint = !!target.allowedUsers?.length
|
|
42
|
-
const hasGroupConstraint = !!target.allowedGroups?.length
|
|
43
|
-
|
|
44
|
-
// No constraints means allowed (rule applies to everyone)
|
|
45
|
-
if (!hasUserConstraint && !hasGroupConstraint) {
|
|
46
|
-
return true
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const matchesUser = hasUserConstraint && target.allowedUsers?.includes(user.userId)
|
|
50
|
-
const matchesGroup =
|
|
51
|
-
hasGroupConstraint && user.groups?.some((gid) => target.allowedGroups?.includes(gid))
|
|
52
|
-
|
|
53
|
-
return Boolean(matchesUser || matchesGroup)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Evaluate access for a relative path against config-defined rules.
|
|
58
|
-
* Uses defaultAccess when no rule matches. First matching rule wins.
|
|
59
|
-
*/
|
|
60
|
-
export function checkPathAccess({
|
|
61
|
-
rules,
|
|
62
|
-
relativePath,
|
|
63
|
-
user,
|
|
64
|
-
defaultAccess,
|
|
65
|
-
level,
|
|
66
|
-
}: {
|
|
67
|
-
rules: PathPermission[]
|
|
68
|
-
relativePath: PhysicalPath
|
|
69
|
-
user: CanopyUser
|
|
70
|
-
defaultAccess: DefaultPathAccess
|
|
71
|
-
level: PermissionLevel
|
|
72
|
-
}): PathPermissionResult {
|
|
73
|
-
const normalizedPath = normalize(relativePath)
|
|
74
|
-
|
|
75
|
-
// Only Admins bypass all path permissions
|
|
76
|
-
if (isAdmin(user.groups)) {
|
|
77
|
-
return { allowed: true, reason: 'admin' }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const rule of rules) {
|
|
81
|
-
if (!matchesRule(rule, normalizedPath)) {
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Get the permission target for this level
|
|
86
|
-
const target = rule[level]
|
|
87
|
-
if (!target) {
|
|
88
|
-
// No permissions defined for this level on this rule, continue to next rule
|
|
89
|
-
continue
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const allowed = isAllowedByTarget(target, user)
|
|
93
|
-
return {
|
|
94
|
-
allowed,
|
|
95
|
-
matchedRule: rule,
|
|
96
|
-
reason: allowed ? 'allowed_by_rule' : 'denied_by_rule',
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return { allowed: defaultAccess === 'allow', reason: 'no_rule_match' }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Factory to bind rules and defaultAccess once.
|
|
105
|
-
*/
|
|
106
|
-
export function createCheckPathAccess(rules: PathPermission[], defaultAccess: DefaultPathAccess) {
|
|
107
|
-
return (input: {
|
|
108
|
-
relativePath: PhysicalPath
|
|
109
|
-
user: CanopyUser
|
|
110
|
-
level: PermissionLevel
|
|
111
|
-
}): PathPermissionResult => checkPathAccess({ ...input, rules, defaultAccess })
|
|
112
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permissions module exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { PermissionsFileSchema, createDefaultPermissionsFile, type PermissionsFile } from './schema'
|
|
6
|
-
export {
|
|
7
|
-
loadPermissionsFile,
|
|
8
|
-
loadPathPermissions,
|
|
9
|
-
savePathPermissions,
|
|
10
|
-
ensurePermissionsFile,
|
|
11
|
-
} from './loader'
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permissions file loader
|
|
3
|
-
*
|
|
4
|
-
* Handles loading and saving path permissions from the filesystem.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'node:fs/promises'
|
|
8
|
-
import path from 'node:path'
|
|
9
|
-
import type { PathPermission } from '../../config'
|
|
10
|
-
import type { PermissionsFile } from './schema'
|
|
11
|
-
import { PermissionsFileSchema } from './schema'
|
|
12
|
-
import type { OperatingMode } from '../../operating-mode'
|
|
13
|
-
import { operatingStrategy } from '../../operating-mode'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get the appropriate permissions file path based on mode
|
|
17
|
-
*/
|
|
18
|
-
function getPermissionsFilePath(repoRoot: string, mode: OperatingMode): string {
|
|
19
|
-
return operatingStrategy(mode).getPermissionsFilePath(repoRoot)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Load full permissions file (for version checking)
|
|
24
|
-
* Returns null if file doesn't exist.
|
|
25
|
-
*
|
|
26
|
-
* @param repoRoot - Repository root directory
|
|
27
|
-
* @param mode - Operating mode (determines file path)
|
|
28
|
-
*/
|
|
29
|
-
export async function loadPermissionsFile(
|
|
30
|
-
repoRoot: string,
|
|
31
|
-
mode: OperatingMode,
|
|
32
|
-
): Promise<PermissionsFile | null> {
|
|
33
|
-
const permissionsPath = getPermissionsFilePath(repoRoot, mode)
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const fileContent = await fs.readFile(permissionsPath, 'utf-8')
|
|
37
|
-
const parsed = JSON.parse(fileContent)
|
|
38
|
-
const validated = PermissionsFileSchema.parse(parsed)
|
|
39
|
-
return validated
|
|
40
|
-
} catch (error) {
|
|
41
|
-
// File doesn't exist
|
|
42
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Parse/validation error - this is more serious
|
|
47
|
-
console.error('CanopyCMS: Failed to parse permissions file', error)
|
|
48
|
-
throw new Error(
|
|
49
|
-
`Invalid permissions file: ${error instanceof Error ? error.message : 'unknown error'}`,
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Load path permissions from .canopycms/permissions.json (or .local.json in dev mode)
|
|
56
|
-
* Returns empty array if file doesn't exist (no restrictions).
|
|
57
|
-
*
|
|
58
|
-
* @param repoRoot - Repository root directory
|
|
59
|
-
* @param mode - Operating mode (determines file path)
|
|
60
|
-
*/
|
|
61
|
-
export async function loadPathPermissions(
|
|
62
|
-
repoRoot: string,
|
|
63
|
-
mode: OperatingMode,
|
|
64
|
-
): Promise<PathPermission[]> {
|
|
65
|
-
const file = await loadPermissionsFile(repoRoot, mode)
|
|
66
|
-
return file?.pathPermissions ?? []
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Save path permissions to .canopycms/permissions.json (or .local.json in dev mode)
|
|
71
|
-
*/
|
|
72
|
-
export async function savePathPermissions(
|
|
73
|
-
repoRoot: string,
|
|
74
|
-
permissions: PathPermission[],
|
|
75
|
-
updatedBy: string,
|
|
76
|
-
mode: OperatingMode,
|
|
77
|
-
contentVersion?: number,
|
|
78
|
-
): Promise<void> {
|
|
79
|
-
const permissionsPath = getPermissionsFilePath(repoRoot, mode)
|
|
80
|
-
const canopyDir = path.dirname(permissionsPath)
|
|
81
|
-
|
|
82
|
-
// Ensure .canopycms directory exists
|
|
83
|
-
await fs.mkdir(canopyDir, { recursive: true })
|
|
84
|
-
|
|
85
|
-
const permissionsFile: PermissionsFile = {
|
|
86
|
-
version: 1,
|
|
87
|
-
contentVersion: contentVersion ?? 1,
|
|
88
|
-
updatedAt: new Date().toISOString(),
|
|
89
|
-
updatedBy,
|
|
90
|
-
pathPermissions: permissions,
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Validate before writing
|
|
94
|
-
PermissionsFileSchema.parse(permissionsFile)
|
|
95
|
-
|
|
96
|
-
await fs.writeFile(permissionsPath, JSON.stringify(permissionsFile, null, 2), 'utf-8')
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Initialize permissions file if it doesn't exist
|
|
101
|
-
*/
|
|
102
|
-
export async function ensurePermissionsFile(
|
|
103
|
-
repoRoot: string,
|
|
104
|
-
userId: string,
|
|
105
|
-
mode: OperatingMode,
|
|
106
|
-
): Promise<void> {
|
|
107
|
-
const permissionsPath = getPermissionsFilePath(repoRoot, mode)
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
await fs.access(permissionsPath)
|
|
111
|
-
// File exists, nothing to do
|
|
112
|
-
} catch {
|
|
113
|
-
// File doesn't exist, create default
|
|
114
|
-
await savePathPermissions(repoRoot, [], userId, mode)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schema for permissions.json file
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { z } from 'zod'
|
|
6
|
-
import type { CanopyUserId, CanopyGroupId } from '../../types'
|
|
7
|
-
import { parsePermissionPath } from '../validation'
|
|
8
|
-
import type { PermissionPath } from '../types'
|
|
9
|
-
|
|
10
|
-
const permissionTargetSchema = z.object({
|
|
11
|
-
allowedUsers: z.array(z.string() as z.ZodType<CanopyUserId>).optional(),
|
|
12
|
-
allowedGroups: z.array(z.string() as z.ZodType<CanopyGroupId>).optional(),
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Zod schema for PermissionPath - validates and prevents path traversal attacks.
|
|
17
|
-
*/
|
|
18
|
-
const permissionPathSchema = z
|
|
19
|
-
.string()
|
|
20
|
-
.min(1)
|
|
21
|
-
.transform((val, ctx) => {
|
|
22
|
-
const result = parsePermissionPath(val)
|
|
23
|
-
if (!result.ok) {
|
|
24
|
-
ctx.addIssue({
|
|
25
|
-
code: z.ZodIssueCode.custom,
|
|
26
|
-
message: result.error,
|
|
27
|
-
})
|
|
28
|
-
return z.NEVER
|
|
29
|
-
}
|
|
30
|
-
return result.path
|
|
31
|
-
}) as unknown as z.ZodType<PermissionPath>
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Schema for .canopycms/permissions.json
|
|
35
|
-
*
|
|
36
|
-
* SECURITY: Permission paths are validated to prevent path traversal attacks.
|
|
37
|
-
* Any path containing '..' or other traversal sequences will be rejected.
|
|
38
|
-
*/
|
|
39
|
-
export const PermissionsFileSchema = z.object({
|
|
40
|
-
version: z.literal(1),
|
|
41
|
-
contentVersion: z.number().optional(), // For optimistic locking
|
|
42
|
-
updatedAt: z.string().datetime(),
|
|
43
|
-
updatedBy: z.string() as z.ZodType<CanopyUserId>,
|
|
44
|
-
pathPermissions: z.array(
|
|
45
|
-
z.object({
|
|
46
|
-
path: permissionPathSchema,
|
|
47
|
-
read: permissionTargetSchema.optional(),
|
|
48
|
-
edit: permissionTargetSchema.optional(),
|
|
49
|
-
review: permissionTargetSchema.optional(),
|
|
50
|
-
}),
|
|
51
|
-
),
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
export type PermissionsFile = z.infer<typeof PermissionsFileSchema>
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Default permissions file
|
|
58
|
-
*/
|
|
59
|
-
export function createDefaultPermissionsFile(userId: CanopyUserId): PermissionsFile {
|
|
60
|
-
return {
|
|
61
|
-
version: 1,
|
|
62
|
-
updatedAt: new Date().toISOString(),
|
|
63
|
-
updatedBy: userId,
|
|
64
|
-
pathPermissions: [],
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test utilities for authorization types.
|
|
3
|
-
*
|
|
4
|
-
* These are unsafe casts with NO validation. Import only from test files.
|
|
5
|
-
* For production code, use parsePermissionPath() instead.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* // In test files only:
|
|
9
|
-
* import { unsafeAsPermissionPath } from '../authorization/test-utils'
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { PermissionPath } from './types'
|
|
13
|
-
|
|
14
|
-
/** Test-only: cast a string to PermissionPath without validation. */
|
|
15
|
-
export const unsafeAsPermissionPath = (path: string): PermissionPath => path as PermissionPath
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorization types for CanopyCMS
|
|
3
|
-
*
|
|
4
|
-
* This module exports all types related to authorization including
|
|
5
|
-
* branch access, path permissions, and content access results.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { CanopyUserId, CanopyGroupId } from '../types'
|
|
9
|
-
|
|
10
|
-
// Re-export types from other modules for convenience
|
|
11
|
-
export type { CanopyUserId, CanopyGroupId }
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A path used in permission rules.
|
|
15
|
-
* SECURITY CRITICAL: Always validated to prevent path traversal.
|
|
16
|
-
* Example: "content/posts" or "content/settings/config"
|
|
17
|
-
*/
|
|
18
|
-
export type PermissionPath = string & { readonly __brand: 'PermissionPath' }
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Result of checking branch-level access
|
|
22
|
-
*/
|
|
23
|
-
export interface BranchAccessResult {
|
|
24
|
-
allowed: boolean
|
|
25
|
-
reason: 'privileged' | 'allowed_by_acl' | 'denied_by_acl' | 'no_acl'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Result of checking path-level permissions
|
|
30
|
-
*/
|
|
31
|
-
export interface PathPermissionResult {
|
|
32
|
-
allowed: boolean
|
|
33
|
-
matchedRule?: import('../config').PathPermission
|
|
34
|
-
reason?: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Combined result of checking both branch and path access
|
|
39
|
-
*/
|
|
40
|
-
export interface ContentAccessResult {
|
|
41
|
-
allowed: boolean
|
|
42
|
-
branch: BranchAccessResult
|
|
43
|
-
path: PathPermissionResult
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Dependencies for content access checking
|
|
48
|
-
*/
|
|
49
|
-
export interface ContentAccessDeps {
|
|
50
|
-
checkBranchAccess: (
|
|
51
|
-
context: import('../types').BranchContext,
|
|
52
|
-
user: import('../user').CanopyUser,
|
|
53
|
-
) => BranchAccessResult
|
|
54
|
-
loadPathPermissions: (
|
|
55
|
-
branchRoot: string,
|
|
56
|
-
mode: import('../operating-mode').OperatingMode,
|
|
57
|
-
) => Promise<import('../config').PathPermission[]>
|
|
58
|
-
defaultPathAccess: import('../config').DefaultPathAccess
|
|
59
|
-
mode: import('../operating-mode').OperatingMode
|
|
60
|
-
/**
|
|
61
|
-
* Get the settings branch root path for loading centralized permissions.
|
|
62
|
-
* Only used in prod/prod-sim modes.
|
|
63
|
-
* Must throw if settings branch cannot be loaded.
|
|
64
|
-
*/
|
|
65
|
-
getSettingsBranchRoot?: () => Promise<string>
|
|
66
|
-
}
|