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,141 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
normalizeFilesystemPath,
|
|
4
|
-
normalizeCollectionPath,
|
|
5
|
-
hasTraversalSequence,
|
|
6
|
-
createLogicalPath,
|
|
7
|
-
createPhysicalPath,
|
|
8
|
-
joinPath,
|
|
9
|
-
} from '../normalize'
|
|
10
|
-
import { validateAndNormalizePath } from '../normalize-server'
|
|
11
|
-
|
|
12
|
-
describe('normalizeFilesystemPath', () => {
|
|
13
|
-
it('converts backslashes to forward slashes', () => {
|
|
14
|
-
expect(normalizeFilesystemPath('content\\posts\\my-post')).toBe('content/posts/my-post')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('removes empty segments from double slashes', () => {
|
|
18
|
-
expect(normalizeFilesystemPath('content//posts///my-post')).toBe('content/posts/my-post')
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('removes leading and trailing slashes', () => {
|
|
22
|
-
expect(normalizeFilesystemPath('/content/posts/')).toBe('content/posts')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('handles mixed separators', () => {
|
|
26
|
-
expect(normalizeFilesystemPath('content\\\\posts//my-post')).toBe('content/posts/my-post')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('returns empty string for empty input', () => {
|
|
30
|
-
expect(normalizeFilesystemPath('')).toBe('')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('returns empty string for only slashes', () => {
|
|
34
|
-
expect(normalizeFilesystemPath('///')).toBe('')
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('normalizeCollectionPath', () => {
|
|
39
|
-
it('removes content/ prefix', () => {
|
|
40
|
-
expect(normalizeCollectionPath('content/posts')).toBe('posts')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('removes content/ prefix from nested paths', () => {
|
|
44
|
-
expect(normalizeCollectionPath('content/docs/api')).toBe('docs/api')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('leaves paths without content/ prefix unchanged', () => {
|
|
48
|
-
expect(normalizeCollectionPath('posts')).toBe('posts')
|
|
49
|
-
expect(normalizeCollectionPath('docs/api')).toBe('docs/api')
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('normalizes slashes before stripping prefix', () => {
|
|
53
|
-
expect(normalizeCollectionPath('content\\blog\\posts')).toBe('blog/posts')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('handles custom content root', () => {
|
|
57
|
-
expect(normalizeCollectionPath('src/posts', 'src')).toBe('posts')
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('validateAndNormalizePath', () => {
|
|
62
|
-
it('returns valid result for path within root', () => {
|
|
63
|
-
const result = validateAndNormalizePath('/root/content', '/root/content/posts/my-post')
|
|
64
|
-
expect(result.valid).toBe(true)
|
|
65
|
-
expect(result.normalizedPath).toBe('posts/my-post')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('returns valid for root itself', () => {
|
|
69
|
-
const result = validateAndNormalizePath('/root/content', '/root/content')
|
|
70
|
-
expect(result.valid).toBe(true)
|
|
71
|
-
expect(result.normalizedPath).toBe('')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('returns invalid for path traversal attempt', () => {
|
|
75
|
-
const result = validateAndNormalizePath('/root/content', '/root/evil')
|
|
76
|
-
expect(result.valid).toBe(false)
|
|
77
|
-
expect(result.error).toBe('Path traversal detected')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('returns invalid for parent directory escape', () => {
|
|
81
|
-
const result = validateAndNormalizePath('/root/content', '/root/content/../evil')
|
|
82
|
-
expect(result.valid).toBe(false)
|
|
83
|
-
expect(result.error).toBe('Path traversal detected')
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
describe('hasTraversalSequence', () => {
|
|
88
|
-
it('detects double dots', () => {
|
|
89
|
-
expect(hasTraversalSequence('../evil')).toBe(true)
|
|
90
|
-
expect(hasTraversalSequence('content/../posts')).toBe(true)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('returns false for safe paths', () => {
|
|
94
|
-
expect(hasTraversalSequence('content/posts')).toBe(false)
|
|
95
|
-
expect(hasTraversalSequence('file.name.txt')).toBe(false)
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
describe('createLogicalPath', () => {
|
|
100
|
-
it('joins segments with forward slashes', () => {
|
|
101
|
-
expect(createLogicalPath('content', 'posts', 'my-post')).toBe('content/posts/my-post')
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('filters empty segments', () => {
|
|
105
|
-
expect(createLogicalPath('content', '', 'posts')).toBe('content/posts')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('normalizes each segment', () => {
|
|
109
|
-
expect(createLogicalPath('content\\posts', 'my-post')).toBe('content/posts/my-post')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('throws on traversal sequence', () => {
|
|
113
|
-
expect(() => createLogicalPath('content', '..', 'evil')).toThrow(
|
|
114
|
-
'Invalid path: contains traversal sequence',
|
|
115
|
-
)
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
describe('createPhysicalPath', () => {
|
|
120
|
-
it('joins segments with forward slashes', () => {
|
|
121
|
-
expect(createPhysicalPath('content', 'posts', 'my-post.ABC123.mdx')).toBe(
|
|
122
|
-
'content/posts/my-post.ABC123.mdx',
|
|
123
|
-
)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('throws on traversal sequence', () => {
|
|
127
|
-
expect(() => createPhysicalPath('content', '../evil')).toThrow(
|
|
128
|
-
'Invalid path: contains traversal sequence',
|
|
129
|
-
)
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
describe('joinPath', () => {
|
|
134
|
-
it('joins segments without validation', () => {
|
|
135
|
-
expect(joinPath('content', 'posts', 'my-post')).toBe('content/posts/my-post')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('normalizes each segment', () => {
|
|
139
|
-
expect(joinPath('content\\posts', '', 'my-post')).toBe('content/posts/my-post')
|
|
140
|
-
})
|
|
141
|
-
})
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { resolveLogicalPath } from '../resolve'
|
|
3
|
-
import { unsafeAsLogicalPath } from '../test-utils'
|
|
4
|
-
import type { FlatSchemaItem } from '../../config'
|
|
5
|
-
|
|
6
|
-
describe('resolveLogicalPath', () => {
|
|
7
|
-
const createMockSchemaItems = (): FlatSchemaItem[] => [
|
|
8
|
-
{
|
|
9
|
-
type: 'collection',
|
|
10
|
-
logicalPath: unsafeAsLogicalPath('content/authors'),
|
|
11
|
-
name: 'authors',
|
|
12
|
-
label: 'Authors',
|
|
13
|
-
} as FlatSchemaItem,
|
|
14
|
-
{
|
|
15
|
-
type: 'collection',
|
|
16
|
-
logicalPath: unsafeAsLogicalPath('content/posts'),
|
|
17
|
-
name: 'posts',
|
|
18
|
-
label: 'Posts',
|
|
19
|
-
} as FlatSchemaItem,
|
|
20
|
-
{
|
|
21
|
-
type: 'collection',
|
|
22
|
-
logicalPath: unsafeAsLogicalPath('content/docs'),
|
|
23
|
-
name: 'docs',
|
|
24
|
-
label: 'Docs',
|
|
25
|
-
} as FlatSchemaItem,
|
|
26
|
-
{
|
|
27
|
-
type: 'collection',
|
|
28
|
-
logicalPath: unsafeAsLogicalPath('content/docs/api'),
|
|
29
|
-
name: 'api',
|
|
30
|
-
label: 'API',
|
|
31
|
-
} as FlatSchemaItem,
|
|
32
|
-
{
|
|
33
|
-
type: 'collection',
|
|
34
|
-
logicalPath: unsafeAsLogicalPath('content/post'),
|
|
35
|
-
name: 'post',
|
|
36
|
-
label: 'Post (singular)',
|
|
37
|
-
} as FlatSchemaItem,
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
describe('basic path matching', () => {
|
|
41
|
-
it('should match physical path to logical path with ID suffix', () => {
|
|
42
|
-
const schemaItems = createMockSchemaItems()
|
|
43
|
-
const result = resolveLogicalPath('content/authors.q52DCVPuH4ga', schemaItems)
|
|
44
|
-
expect(result).toBe('content/authors')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should match exact path without ID suffix', () => {
|
|
48
|
-
const schemaItems = createMockSchemaItems()
|
|
49
|
-
const result = resolveLogicalPath('content/authors', schemaItems)
|
|
50
|
-
expect(result).toBe('content/authors')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('should return physical path when no match found', () => {
|
|
54
|
-
const schemaItems = createMockSchemaItems()
|
|
55
|
-
const result = resolveLogicalPath('content/unknown.ABC123', schemaItems)
|
|
56
|
-
expect(result).toBe('content/unknown.ABC123')
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('nested collections', () => {
|
|
61
|
-
it('should match nested collection with IDs at all levels', () => {
|
|
62
|
-
const schemaItems = createMockSchemaItems()
|
|
63
|
-
const result = resolveLogicalPath('content/docs.bChqT78gcaLd/api.meiuwxTSo7UN', schemaItems)
|
|
64
|
-
expect(result).toBe('content/docs/api')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('should match nested collection with ID only on parent', () => {
|
|
68
|
-
const schemaItems = createMockSchemaItems()
|
|
69
|
-
const result = resolveLogicalPath('content/docs.bChqT78gcaLd/api', schemaItems)
|
|
70
|
-
expect(result).toBe('content/docs/api')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('should match nested collection with ID only on child', () => {
|
|
74
|
-
const schemaItems = createMockSchemaItems()
|
|
75
|
-
const result = resolveLogicalPath('content/docs/api.meiuwxTSo7UN', schemaItems)
|
|
76
|
-
expect(result).toBe('content/docs/api')
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('should not match if segment count differs', () => {
|
|
80
|
-
const schemaItems = createMockSchemaItems()
|
|
81
|
-
const result = resolveLogicalPath('content/docs.ABC/api.DEF/extra.GHI', schemaItems)
|
|
82
|
-
expect(result).toBe('content/docs.ABC/api.DEF/extra.GHI')
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
describe('edge cases with similar names', () => {
|
|
87
|
-
it('should not match collection with similar prefix (post vs posts)', () => {
|
|
88
|
-
const schemaItems = createMockSchemaItems()
|
|
89
|
-
// "posts-archive" does NOT match "posts" because there's no dot after "posts"
|
|
90
|
-
const result = resolveLogicalPath('content/posts-archive.ID123', schemaItems)
|
|
91
|
-
expect(result).toBe('content/posts-archive.ID123')
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should match collection with exact name followed by dot', () => {
|
|
95
|
-
const schemaItems = createMockSchemaItems()
|
|
96
|
-
const result = resolveLogicalPath('content/posts.ID123', schemaItems)
|
|
97
|
-
expect(result).toBe('content/posts')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should distinguish between "post" and "posts"', () => {
|
|
101
|
-
const schemaItems = createMockSchemaItems()
|
|
102
|
-
|
|
103
|
-
// "post.ID" should match "post", not "posts"
|
|
104
|
-
const result1 = resolveLogicalPath('content/post.ABC123', schemaItems)
|
|
105
|
-
expect(result1).toBe('content/post')
|
|
106
|
-
|
|
107
|
-
// "posts.ID" should match "posts", not "post"
|
|
108
|
-
const result2 = resolveLogicalPath('content/posts.XYZ789', schemaItems)
|
|
109
|
-
expect(result2).toBe('content/posts')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('should not match if physical segment is shorter than logical', () => {
|
|
113
|
-
const schemaItems = createMockSchemaItems()
|
|
114
|
-
// "pos.ID" should not match "post" or "posts"
|
|
115
|
-
const result = resolveLogicalPath('content/pos.ABC', schemaItems)
|
|
116
|
-
expect(result).toBe('content/pos.ABC')
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('deeply nested collections', () => {
|
|
121
|
-
it('should handle 5+ levels of nesting', () => {
|
|
122
|
-
const deepSchemaItems: FlatSchemaItem[] = [
|
|
123
|
-
{
|
|
124
|
-
type: 'collection',
|
|
125
|
-
logicalPath: unsafeAsLogicalPath('content/level1/level2/level3/level4/level5'),
|
|
126
|
-
name: 'level5',
|
|
127
|
-
label: 'Level 5',
|
|
128
|
-
} as FlatSchemaItem,
|
|
129
|
-
]
|
|
130
|
-
|
|
131
|
-
const result = resolveLogicalPath(
|
|
132
|
-
'content/level1.A/level2.B/level3.C/level4.D/level5.E',
|
|
133
|
-
deepSchemaItems,
|
|
134
|
-
)
|
|
135
|
-
expect(result).toBe('content/level1/level2/level3/level4/level5')
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
describe('special characters and dots in names', () => {
|
|
140
|
-
it('should handle collection names with dots in logical path', () => {
|
|
141
|
-
const schemaItems: FlatSchemaItem[] = [
|
|
142
|
-
{
|
|
143
|
-
type: 'collection',
|
|
144
|
-
logicalPath: unsafeAsLogicalPath('content/v1.0'),
|
|
145
|
-
name: 'v1.0',
|
|
146
|
-
label: 'Version 1.0',
|
|
147
|
-
} as FlatSchemaItem,
|
|
148
|
-
]
|
|
149
|
-
|
|
150
|
-
// This is a tricky case: the logical name itself has a dot
|
|
151
|
-
// Physical path would be "v1.0.ID123", so it has TWO dots
|
|
152
|
-
const result = resolveLogicalPath('content/v1.0.ABC123', schemaItems)
|
|
153
|
-
expect(result).toBe('content/v1.0')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('should not match if physical segment has multiple dots but wrong prefix', () => {
|
|
157
|
-
const schemaItems = createMockSchemaItems()
|
|
158
|
-
// "post.extra.ID" has a dot but doesn't match "post.ID" pattern
|
|
159
|
-
const result = resolveLogicalPath('content/post.extra.ID', schemaItems)
|
|
160
|
-
// This will match because "post.extra.ID" starts with "post."
|
|
161
|
-
expect(result).toBe('content/post')
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
describe('empty and root paths', () => {
|
|
166
|
-
it('should handle empty schema items', () => {
|
|
167
|
-
const result = resolveLogicalPath('content/authors.ID', [])
|
|
168
|
-
expect(result).toBe('content/authors.ID')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('should handle root-level collection', () => {
|
|
172
|
-
const schemaItems: FlatSchemaItem[] = [
|
|
173
|
-
{
|
|
174
|
-
type: 'collection',
|
|
175
|
-
logicalPath: unsafeAsLogicalPath('content'),
|
|
176
|
-
name: 'content',
|
|
177
|
-
label: 'Content',
|
|
178
|
-
} as FlatSchemaItem,
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
const result = resolveLogicalPath('content', schemaItems)
|
|
182
|
-
expect(result).toBe('content')
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
describe('non-collection schema items', () => {
|
|
187
|
-
it('should skip entry-type schema items', () => {
|
|
188
|
-
const schemaItems: FlatSchemaItem[] = [
|
|
189
|
-
{
|
|
190
|
-
type: 'entry-type',
|
|
191
|
-
logicalPath: unsafeAsLogicalPath('content/posts/article'),
|
|
192
|
-
name: 'article',
|
|
193
|
-
label: 'Article',
|
|
194
|
-
} as FlatSchemaItem,
|
|
195
|
-
{
|
|
196
|
-
type: 'collection',
|
|
197
|
-
logicalPath: unsafeAsLogicalPath('content/posts'),
|
|
198
|
-
name: 'posts',
|
|
199
|
-
label: 'Posts',
|
|
200
|
-
} as FlatSchemaItem,
|
|
201
|
-
]
|
|
202
|
-
|
|
203
|
-
const result = resolveLogicalPath('content/posts.ID123', schemaItems)
|
|
204
|
-
expect(result).toBe('content/posts')
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
})
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { validateContentPath, isValidCollectionPath, sanitizeForPath } from '../validation'
|
|
3
|
-
|
|
4
|
-
describe('validateContentPath', () => {
|
|
5
|
-
it('returns valid for safe paths', () => {
|
|
6
|
-
const result = validateContentPath('content/posts/my-post.mdx', '/root')
|
|
7
|
-
expect(result.valid).toBe(true)
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('returns invalid for traversal attempts', () => {
|
|
11
|
-
const result = validateContentPath('../../../etc/passwd', '/root')
|
|
12
|
-
expect(result.valid).toBe(false)
|
|
13
|
-
expect(result.error).toContain('traversal')
|
|
14
|
-
})
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
describe('isValidCollectionPath', () => {
|
|
18
|
-
it('accepts valid collection paths', () => {
|
|
19
|
-
expect(isValidCollectionPath('posts')).toBe(true)
|
|
20
|
-
expect(isValidCollectionPath('docs/api')).toBe(true)
|
|
21
|
-
expect(isValidCollectionPath('blog-posts')).toBe(true)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('rejects collection paths with traversal', () => {
|
|
25
|
-
expect(isValidCollectionPath('../evil')).toBe(false)
|
|
26
|
-
expect(isValidCollectionPath('posts/../evil')).toBe(false)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('rejects empty collection paths', () => {
|
|
30
|
-
expect(isValidCollectionPath('')).toBe(false)
|
|
31
|
-
})
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
describe('sanitizeForPath', () => {
|
|
35
|
-
it('removes invalid filesystem characters', () => {
|
|
36
|
-
expect(sanitizeForPath('file<name>:test')).toBe('filenametest')
|
|
37
|
-
expect(sanitizeForPath('file|name?test')).toBe('filenametest')
|
|
38
|
-
expect(sanitizeForPath('file*name"test')).toBe('filenametest')
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('removes backslashes', () => {
|
|
42
|
-
expect(sanitizeForPath('path\\to\\file')).toBe('pathtofile')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('collapses multiple dots', () => {
|
|
46
|
-
expect(sanitizeForPath('hello...world')).toBe('hello.world')
|
|
47
|
-
expect(sanitizeForPath('a..b..c')).toBe('a.b.c')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('removes leading dots', () => {
|
|
51
|
-
expect(sanitizeForPath('.hidden')).toBe('hidden')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('trims whitespace', () => {
|
|
55
|
-
expect(sanitizeForPath(' hello ')).toBe('hello')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('preserves valid characters', () => {
|
|
59
|
-
expect(sanitizeForPath('hello-world_123')).toBe('hello-world_123')
|
|
60
|
-
})
|
|
61
|
-
})
|
package/src/paths/branch.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Branch path resolution utilities.
|
|
3
|
-
*
|
|
4
|
-
* Handles resolving branch names to workspace directories.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'node:fs/promises'
|
|
8
|
-
import path from 'node:path'
|
|
9
|
-
|
|
10
|
-
import type { BranchContext } from '../types'
|
|
11
|
-
import { OperatingMode, operatingStrategy } from '../operating-mode'
|
|
12
|
-
import type { SanitizedBranchName } from './types'
|
|
13
|
-
|
|
14
|
-
export interface BranchPathOptions {
|
|
15
|
-
mode: OperatingMode
|
|
16
|
-
branchName: string
|
|
17
|
-
basePathOverride?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface BranchPathResult {
|
|
21
|
-
branchRoot: string
|
|
22
|
-
baseRoot: string
|
|
23
|
-
branchName: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class BranchPathError extends Error {}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Sanitize a branch name for use in filesystem paths.
|
|
30
|
-
* - Replaces invalid characters with hyphens
|
|
31
|
-
* - Collapses multiple hyphens
|
|
32
|
-
* - Trims leading/trailing dots
|
|
33
|
-
*/
|
|
34
|
-
export function sanitizeBranchName(branchName: string): SanitizedBranchName {
|
|
35
|
-
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
36
|
-
const squashed = replaced.replace(/-+/g, '-')
|
|
37
|
-
const trimmedDots = squashed.replace(/^\.+/, '').replace(/\.+$/, '')
|
|
38
|
-
return (trimmedDots || 'branch') as SanitizedBranchName
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const resolveContentBranchesRoot = (mode: OperatingMode, override?: string): string => {
|
|
42
|
-
return operatingStrategy(mode).getContentBranchesRoot(override)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Resolve branch name to workspace paths.
|
|
47
|
-
* Validates for path traversal attacks.
|
|
48
|
-
*/
|
|
49
|
-
export function resolveBranchPath(options: BranchPathOptions): BranchPathResult {
|
|
50
|
-
if (options.branchName.includes('..')) {
|
|
51
|
-
throw new BranchPathError('Branch name cannot contain traversal segments')
|
|
52
|
-
}
|
|
53
|
-
const safeBranch = sanitizeBranchName(options.branchName)
|
|
54
|
-
const strategy = operatingStrategy(options.mode)
|
|
55
|
-
const baseRoot = resolveContentBranchesRoot(options.mode, options.basePathOverride)
|
|
56
|
-
const normalizedBase = path.resolve(baseRoot)
|
|
57
|
-
const baseWithSep = normalizedBase.endsWith(path.sep)
|
|
58
|
-
? normalizedBase
|
|
59
|
-
: `${normalizedBase}${path.sep}`
|
|
60
|
-
const branchRoot = strategy.getContentBranchRoot(safeBranch, options.basePathOverride)
|
|
61
|
-
|
|
62
|
-
const withinBase = (target: string) => {
|
|
63
|
-
const resolved = path.resolve(target)
|
|
64
|
-
return resolved === normalizedBase || resolved.startsWith(baseWithSep)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!withinBase(branchRoot)) {
|
|
68
|
-
throw new BranchPathError('Branch path resolves outside the base root')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { branchRoot, baseRoot: normalizedBase, branchName: safeBranch }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Ensure the branch workspace directory exists.
|
|
76
|
-
*/
|
|
77
|
-
export async function ensureBranchRoot(options: BranchPathOptions): Promise<BranchPathResult> {
|
|
78
|
-
const result = resolveBranchPath(options)
|
|
79
|
-
await fs.mkdir(result.branchRoot, { recursive: true })
|
|
80
|
-
return result
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get the default base directory for branch workspaces.
|
|
85
|
-
*/
|
|
86
|
-
export function getDefaultBranchBase(mode: OperatingMode, override?: string): string {
|
|
87
|
-
return resolveContentBranchesRoot(mode, override)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Resolve branch paths from a branch context.
|
|
92
|
-
*/
|
|
93
|
-
export function resolveBranchPaths(
|
|
94
|
-
branchContext: BranchContext,
|
|
95
|
-
mode: OperatingMode,
|
|
96
|
-
basePathOverride?: string,
|
|
97
|
-
): BranchPathResult {
|
|
98
|
-
if (branchContext.branchRoot || branchContext.baseRoot) {
|
|
99
|
-
const baseRoot = path.resolve(
|
|
100
|
-
branchContext.baseRoot ?? resolveContentBranchesRoot(mode, basePathOverride),
|
|
101
|
-
)
|
|
102
|
-
const branchRoot = path.resolve(branchContext.branchRoot ?? baseRoot)
|
|
103
|
-
return {
|
|
104
|
-
branchRoot,
|
|
105
|
-
baseRoot,
|
|
106
|
-
branchName: sanitizeBranchName(branchContext.branch.name),
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return resolveBranchPath({
|
|
111
|
-
mode,
|
|
112
|
-
branchName: branchContext.branch.name,
|
|
113
|
-
basePathOverride,
|
|
114
|
-
})
|
|
115
|
-
}
|
package/src/paths/index.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path utilities for CanopyCMS
|
|
3
|
-
*
|
|
4
|
-
* This module consolidates path handling utilities that were previously
|
|
5
|
-
* scattered across multiple files:
|
|
6
|
-
* - normalize.ts: Path normalization and creation
|
|
7
|
-
* - validation.ts: Security validation for paths
|
|
8
|
-
* - branch.ts: Branch workspace path resolution
|
|
9
|
-
* - types.ts: Branded types for type safety
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* ```ts
|
|
13
|
-
* import { normalizeFilesystemPath, createLogicalPath, parseSlug } from '../paths'
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// Types
|
|
18
|
-
export type {
|
|
19
|
-
LogicalPath,
|
|
20
|
-
PhysicalPath,
|
|
21
|
-
SanitizedBranchName,
|
|
22
|
-
BranchName,
|
|
23
|
-
ContentId,
|
|
24
|
-
CollectionSlug,
|
|
25
|
-
EntrySlug,
|
|
26
|
-
PathValidationResult,
|
|
27
|
-
} from './types'
|
|
28
|
-
|
|
29
|
-
// Normalization utilities (client-safe)
|
|
30
|
-
export {
|
|
31
|
-
normalizeFilesystemPath,
|
|
32
|
-
normalizeCollectionPath,
|
|
33
|
-
hasTraversalSequence,
|
|
34
|
-
createLogicalPath,
|
|
35
|
-
createPhysicalPath,
|
|
36
|
-
joinPath,
|
|
37
|
-
} from './normalize'
|
|
38
|
-
|
|
39
|
-
// Normalization utilities (server-only, requires Node.js path module)
|
|
40
|
-
export { validateAndNormalizePath } from './normalize-server'
|
|
41
|
-
|
|
42
|
-
// Validation utilities
|
|
43
|
-
export {
|
|
44
|
-
validateContentPath,
|
|
45
|
-
isValidCollectionPath,
|
|
46
|
-
sanitizeForPath,
|
|
47
|
-
// Path type detection and parsing
|
|
48
|
-
hasEmbeddedContentId,
|
|
49
|
-
looksLikePhysicalPath,
|
|
50
|
-
looksLikeLogicalPath,
|
|
51
|
-
parseLogicalPath,
|
|
52
|
-
parsePhysicalPath,
|
|
53
|
-
isValidContentId,
|
|
54
|
-
// Branded type validation and parsing
|
|
55
|
-
parseContentId,
|
|
56
|
-
parseBranchName,
|
|
57
|
-
parseSlug,
|
|
58
|
-
} from './validation'
|
|
59
|
-
|
|
60
|
-
// Path resolution utilities
|
|
61
|
-
export { resolveLogicalPath } from './resolve'
|
|
62
|
-
|
|
63
|
-
// Branch path utilities
|
|
64
|
-
export {
|
|
65
|
-
resolveBranchPath,
|
|
66
|
-
ensureBranchRoot,
|
|
67
|
-
getDefaultBranchBase,
|
|
68
|
-
resolveBranchPaths,
|
|
69
|
-
sanitizeBranchName,
|
|
70
|
-
BranchPathError,
|
|
71
|
-
type BranchPathOptions,
|
|
72
|
-
type BranchPathResult,
|
|
73
|
-
} from './branch'
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-only path normalization utilities.
|
|
3
|
-
*
|
|
4
|
-
* These functions depend on Node.js 'path' module and cannot be used in client code.
|
|
5
|
-
* Client-safe functions are in normalize.ts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { sep, resolve, relative } from 'node:path'
|
|
9
|
-
import type { PathValidationResult } from './types'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Validate and normalize a path relative to a root directory.
|
|
13
|
-
* Checks for path traversal attacks.
|
|
14
|
-
*
|
|
15
|
-
* NOTE: This function uses Node.js path module and is server-only.
|
|
16
|
-
* For client code, use the pure functions in normalize.ts.
|
|
17
|
-
*
|
|
18
|
-
* @param root - The root directory
|
|
19
|
-
* @param target - The target path to validate
|
|
20
|
-
* @returns Validation result with normalized relative path if valid
|
|
21
|
-
*/
|
|
22
|
-
export function validateAndNormalizePath(root: string, target: string): PathValidationResult {
|
|
23
|
-
const resolvedRoot = resolve(root)
|
|
24
|
-
const withSep = resolvedRoot.endsWith(sep) ? resolvedRoot : `${resolvedRoot}${sep}`
|
|
25
|
-
const resolvedTarget = resolve(target)
|
|
26
|
-
|
|
27
|
-
if (!resolvedTarget.startsWith(withSep) && resolvedTarget !== resolvedRoot) {
|
|
28
|
-
return {
|
|
29
|
-
valid: false,
|
|
30
|
-
error: 'Path traversal detected',
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const relativePath = relative(resolvedRoot, resolvedTarget).split(sep).join('/')
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
valid: true,
|
|
38
|
-
normalizedPath: relativePath,
|
|
39
|
-
}
|
|
40
|
-
}
|