canopycms 0.0.67-int.90 → 0.0.67
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/README.md +30 -3
- package/dist/ai/generate.js +1 -27
- package/dist/ai/handler.d.ts +0 -4
- package/dist/ai/handler.js +0 -11
- package/dist/ai/index.d.ts +1 -8
- package/dist/ai/index.js +1 -8
- package/dist/ai/json-to-markdown.js +15 -58
- package/dist/ai/resolve-branch.d.ts +2 -7
- package/dist/ai/resolve-branch.js +2 -8
- package/dist/ai/strip-mdx.d.ts +5 -13
- package/dist/ai/strip-mdx.js +5 -17
- package/dist/ai/to-plain-text.d.ts +10 -28
- package/dist/ai/to-plain-text.js +10 -28
- package/dist/ai/transform-components.d.ts +2 -4
- package/dist/ai/transform-components.js +8 -19
- package/dist/ai/types.d.ts +5 -10
- package/dist/api/admin-branch-health.d.ts +4 -12
- package/dist/api/admin-branch-health.js +26 -66
- package/dist/api/admin.d.ts +4 -12
- package/dist/api/admin.js +10 -26
- package/dist/api/assets.d.ts +0 -25
- package/dist/api/assets.js +50 -91
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +0 -2
- package/dist/api/branch-review.d.ts +0 -8
- package/dist/api/branch-review.js +0 -13
- package/dist/api/branch-status.d.ts +0 -3
- package/dist/api/branch-status.js +8 -33
- package/dist/api/branch-withdraw.d.ts +0 -1
- package/dist/api/branch-withdraw.js +6 -9
- package/dist/api/branch.d.ts +7 -12
- package/dist/api/branch.js +7 -44
- package/dist/api/client.d.ts +71 -224
- package/dist/api/client.js +82 -233
- package/dist/api/comments.d.ts +2 -4
- package/dist/api/comments.js +0 -17
- package/dist/api/content.d.ts +0 -6
- package/dist/api/content.js +66 -142
- package/dist/api/entries-constants.d.ts +5 -9
- package/dist/api/entries-constants.js +5 -9
- package/dist/api/entries.d.ts +8 -13
- package/dist/api/entries.js +20 -51
- package/dist/api/github-sync.d.ts +2 -4
- package/dist/api/github-sync.js +4 -8
- package/dist/api/groups.d.ts +3 -0
- package/dist/api/groups.js +8 -21
- package/dist/api/guards.d.ts +4 -16
- package/dist/api/guards.js +3 -22
- package/dist/api/index.d.ts +4 -4
- package/dist/api/permissions.d.ts +2 -0
- package/dist/api/permissions.js +18 -40
- package/dist/api/reference-options.d.ts +0 -1
- package/dist/api/reference-options.js +3 -11
- package/dist/api/request-body-hash.d.ts +12 -23
- package/dist/api/request-body-hash.js +12 -23
- package/dist/api/resolve-references.d.ts +0 -1
- package/dist/api/resolve-references.js +13 -28
- package/dist/api/route-builder.d.ts +3 -7
- package/dist/api/route-builder.js +1 -1
- package/dist/api/routes.d.ts +12 -0
- package/dist/api/routes.js +47 -0
- package/dist/api/schema.d.ts +23 -33
- package/dist/api/schema.js +20 -74
- package/dist/api/settings-helpers.js +4 -5
- package/dist/api/types.d.ts +0 -5
- package/dist/api/user.js +0 -5
- package/dist/api/validators.d.ts +14 -52
- package/dist/api/validators.js +14 -52
- package/dist/assets/asset-prefixes.d.ts +4 -8
- package/dist/assets/asset-prefixes.js +4 -8
- package/dist/assets/asset-src.d.ts +5 -22
- package/dist/assets/asset-src.js +5 -21
- package/dist/assets/factory.d.ts +1 -2
- package/dist/assets/factory.js +1 -2
- package/dist/assets/finalize.d.ts +3 -1
- package/dist/assets/finalize.js +3 -1
- package/dist/assets/index.d.ts +3 -3
- package/dist/assets/index.js +1 -1
- package/dist/assets/keys.d.ts +5 -3
- package/dist/assets/keys.js +6 -3
- package/dist/assets/pipeline.d.ts +3 -2
- package/dist/assets/pipeline.js +22 -56
- package/dist/assets/sharp-loader.d.ts +2 -10
- package/dist/assets/sharp-loader.js +2 -10
- package/dist/assets/store-local.d.ts +1 -2
- package/dist/assets/store-local.js +1 -2
- package/dist/assets/store-s3.d.ts +3 -4
- package/dist/assets/svg-sanitizer.d.ts +6 -14
- package/dist/assets/svg-sanitizer.js +6 -14
- package/dist/assets/transform-directives.d.ts +3 -4
- package/dist/assets/transform-directives.js +1 -1
- package/dist/assets/transform.d.ts +19 -30
- package/dist/assets/transform.js +18 -31
- package/dist/auth/cache.d.ts +2 -3
- package/dist/auth/cache.js +2 -3
- package/dist/auth/caching-auth-plugin.d.ts +11 -22
- package/dist/auth/caching-auth-plugin.js +12 -14
- package/dist/auth/context-helpers.d.ts +6 -20
- package/dist/auth/context-helpers.js +5 -17
- package/dist/auth/file-based-auth-cache.d.ts +10 -21
- package/dist/auth/file-based-auth-cache.js +23 -51
- package/dist/auth/plugin.d.ts +29 -61
- package/dist/auth/plugin.js +9 -13
- package/dist/auth/types.d.ts +11 -20
- package/dist/authorization/branch.d.ts +30 -49
- package/dist/authorization/branch.js +16 -42
- package/dist/authorization/content.d.ts +11 -40
- package/dist/authorization/content.js +13 -45
- package/dist/authorization/groups/index.d.ts +0 -3
- package/dist/authorization/groups/index.js +0 -3
- package/dist/authorization/groups/loader.d.ts +14 -28
- package/dist/authorization/groups/loader.js +14 -38
- package/dist/authorization/groups/schema.d.ts +0 -6
- package/dist/authorization/groups/schema.js +4 -7
- package/dist/authorization/helpers.d.ts +9 -28
- package/dist/authorization/helpers.js +9 -28
- package/dist/authorization/index.d.ts +3 -25
- package/dist/authorization/index.js +3 -35
- package/dist/authorization/path.d.ts +6 -13
- package/dist/authorization/path.js +8 -25
- package/dist/authorization/permissions/index.d.ts +0 -3
- package/dist/authorization/permissions/index.js +0 -3
- package/dist/authorization/permissions/loader.d.ts +14 -27
- package/dist/authorization/permissions/loader.js +14 -32
- package/dist/authorization/permissions/schema.d.ts +1 -6
- package/dist/authorization/permissions/schema.js +6 -13
- package/dist/authorization/protected-branch.d.ts +36 -65
- package/dist/authorization/protected-branch.js +29 -41
- package/dist/authorization/settings-file-store.d.ts +38 -81
- package/dist/authorization/settings-file-store.js +38 -79
- package/dist/authorization/types.d.ts +4 -26
- package/dist/authorization/types.js +0 -6
- package/dist/authorization/validation.d.ts +1 -23
- package/dist/authorization/validation.js +1 -24
- package/dist/branch-health.d.ts +39 -67
- package/dist/branch-health.js +27 -43
- package/dist/branch-metadata-file.d.ts +16 -32
- package/dist/branch-metadata-file.js +13 -26
- package/dist/branch-metadata.d.ts +49 -80
- package/dist/branch-metadata.js +57 -87
- package/dist/branch-registry.d.ts +26 -64
- package/dist/branch-registry.js +44 -93
- package/dist/branch-schema-cache.d.ts +34 -124
- package/dist/branch-schema-cache.js +49 -155
- package/dist/branch-workspace.d.ts +4 -4
- package/dist/branch-workspace.js +15 -19
- package/dist/build/generate-ai-content.d.ts +1 -0
- package/dist/build/generate-ai-content.js +12 -25
- package/dist/build-canopy.d.ts +17 -27
- package/dist/build-canopy.js +16 -23
- package/dist/build-mode.d.ts +25 -27
- package/dist/build-mode.js +25 -29
- package/dist/cli/cli.d.ts +15 -26
- package/dist/cli/cli.js +601 -978
- package/dist/cli/generate-ai-content.js +351 -628
- package/dist/cli/github-app-manifest.d.ts +84 -0
- package/dist/cli/github-app-manifest.js +161 -0
- package/dist/cli/init-github-app.d.ts +110 -444
- package/dist/cli/init-github-app.js +236 -773
- package/dist/cli/init.js +39 -80
- package/dist/cli/migrate.d.ts +13 -10
- package/dist/cli/migrate.js +22 -30
- package/dist/cli/project-detect.d.ts +17 -31
- package/dist/cli/project-detect.js +31 -42
- package/dist/cli/project-root.d.ts +4 -11
- package/dist/cli/project-root.js +4 -11
- package/dist/cli/prompt.d.ts +10 -0
- package/dist/cli/prompt.js +67 -0
- package/dist/cli/sync.d.ts +0 -8
- package/dist/cli/sync.js +8 -28
- package/dist/cli/template-files/middleware.ts.template +15 -29
- package/dist/cli/templates.d.ts +2 -12
- package/dist/cli/templates.js +4 -4
- package/dist/client.d.ts +1 -1
- package/dist/comment-store.d.ts +32 -53
- package/dist/comment-store.js +32 -52
- package/dist/config/flatten.d.ts +0 -3
- package/dist/config/flatten.js +2 -12
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -4
- package/dist/config/schemas/collection.d.ts +0 -8
- package/dist/config/schemas/collection.js +0 -10
- package/dist/config/schemas/config.d.ts +6 -78
- package/dist/config/schemas/config.js +38 -54
- package/dist/config/schemas/field.d.ts +2 -242
- package/dist/config/schemas/field.js +9 -20
- package/dist/config/schemas/media.js +1 -1
- package/dist/config/schemas/permissions.js +0 -2
- package/dist/config/schemas/url.d.ts +5 -6
- package/dist/config/schemas/url.js +5 -6
- package/dist/config/types.d.ts +45 -33
- package/dist/config/types.js +0 -1
- package/dist/config.d.ts +1 -8
- package/dist/config.js +1 -9
- package/dist/content-id-index.d.ts +76 -188
- package/dist/content-id-index.js +79 -230
- package/dist/content-index-generation.d.ts +27 -48
- package/dist/content-index-generation.js +35 -84
- package/dist/content-index-registry.d.ts +15 -24
- package/dist/content-index-registry.js +5 -9
- package/dist/content-listing.d.ts +64 -107
- package/dist/content-listing.js +68 -118
- package/dist/content-reader.d.ts +20 -29
- package/dist/content-reader.js +10 -18
- package/dist/content-store.d.ts +210 -336
- package/dist/content-store.js +413 -657
- package/dist/content-tree.d.ts +44 -86
- package/dist/content-tree.js +26 -54
- package/dist/context.d.ts +67 -94
- package/dist/context.js +53 -72
- package/dist/dev-content-watcher.d.ts +18 -18
- package/dist/dev-content-watcher.js +41 -44
- package/dist/editor/BranchManager.d.ts +3 -6
- package/dist/editor/BranchManager.js +24 -44
- package/dist/editor/CanopyEditor.js +5 -2
- package/dist/editor/CommentsPanel.js +0 -2
- package/dist/editor/Editor.js +19 -76
- package/dist/editor/EditorAuthGate.d.ts +27 -0
- package/dist/editor/EditorAuthGate.js +140 -0
- package/dist/editor/EntryNavigator.js +0 -20
- package/dist/editor/FormRenderer.d.ts +1 -2
- package/dist/editor/FormRenderer.js +8 -23
- package/dist/editor/admin/SystemHealthPanel.js +20 -37
- package/dist/editor/admin/useSystemHealth.d.ts +2 -3
- package/dist/editor/admin/useSystemHealth.js +10 -12
- package/dist/editor/client-reference-resolver.d.ts +2 -5
- package/dist/editor/client-reference-resolver.js +2 -19
- package/dist/editor/comments/BranchComments.js +0 -4
- package/dist/editor/comments/EntryComments.js +0 -4
- package/dist/editor/comments/FieldWrapper.js +0 -4
- package/dist/editor/comments/InlineCommentThread.js +0 -2
- package/dist/editor/comments/ThreadCarousel.d.ts +0 -58
- package/dist/editor/comments/ThreadCarousel.js +0 -12
- package/dist/editor/components/EditorFooter.d.ts +0 -8
- package/dist/editor/components/EditorFooter.js +0 -5
- package/dist/editor/components/EditorHeader.d.ts +0 -31
- package/dist/editor/components/EditorHeader.js +21 -71
- package/dist/editor/components/EditorSidebar.d.ts +0 -19
- package/dist/editor/components/EditorSidebar.js +0 -16
- package/dist/editor/components/EntryCreateModal.d.ts +4 -4
- package/dist/editor/components/EntryCreateModal.js +7 -22
- package/dist/editor/components/RenameEntryModal.js +0 -4
- package/dist/editor/components/UserBadge.js +0 -10
- package/dist/editor/components/index.js +0 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -7
- package/dist/editor/context/ApiClientContext.js +39 -11
- package/dist/editor/context/AssetContext.d.ts +7 -17
- package/dist/editor/context/AssetContext.js +7 -17
- package/dist/editor/context/EditorIdentityContext.d.ts +13 -0
- package/dist/editor/context/EditorIdentityContext.js +7 -0
- package/dist/editor/context/EditorStateContext.d.ts +10 -41
- package/dist/editor/context/EditorStateContext.js +7 -23
- package/dist/editor/context/SWRProvider.d.ts +3 -6
- package/dist/editor/context/index.d.ts +1 -20
- package/dist/editor/context/index.js +1 -20
- package/dist/editor/editor-utils.d.ts +8 -47
- package/dist/editor/editor-utils.js +19 -72
- package/dist/editor/fields/BlockField.d.ts +2 -2
- package/dist/editor/fields/BlockField.js +1 -2
- package/dist/editor/fields/CodeField.d.ts +0 -1
- package/dist/editor/fields/CodeField.js +0 -1
- package/dist/editor/fields/DateTimeField.d.ts +15 -22
- package/dist/editor/fields/DateTimeField.js +17 -26
- package/dist/editor/fields/ImageField.d.ts +2 -2
- package/dist/editor/fields/ImageField.js +8 -14
- package/dist/editor/fields/InlineGroupField.d.ts +0 -1
- package/dist/editor/fields/InlineGroupField.js +0 -1
- package/dist/editor/fields/MarkdownField.d.ts +0 -1
- package/dist/editor/fields/MarkdownField.js +6 -11
- package/dist/editor/fields/MdxImageDialog.d.ts +1 -1
- package/dist/editor/fields/MdxImageDialog.js +0 -1
- package/dist/editor/fields/NumberField.d.ts +9 -19
- package/dist/editor/fields/NumberField.js +11 -22
- package/dist/editor/fields/NumberListField.d.ts +7 -10
- package/dist/editor/fields/NumberListField.js +7 -10
- package/dist/editor/fields/ObjectField.d.ts +5 -8
- package/dist/editor/fields/ObjectField.js +0 -1
- package/dist/editor/fields/ReferenceField.d.ts +1 -1
- package/dist/editor/fields/ReferenceField.js +11 -16
- package/dist/editor/fields/SelectField.d.ts +2 -2
- package/dist/editor/fields/SelectField.js +0 -1
- package/dist/editor/fields/StringListField.d.ts +0 -4
- package/dist/editor/fields/StringListField.js +4 -9
- package/dist/editor/fields/TextField.d.ts +0 -1
- package/dist/editor/fields/TextField.js +0 -1
- package/dist/editor/fields/ToggleField.d.ts +0 -1
- package/dist/editor/fields/ToggleField.js +0 -1
- package/dist/editor/fields/entry-link/EntryLinkContext.js +2 -4
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +0 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.js +0 -4
- package/dist/editor/group-manager/ExternalGroupsTab.d.ts +0 -3
- package/dist/editor/group-manager/ExternalGroupsTab.js +0 -1
- package/dist/editor/group-manager/GroupCard.d.ts +0 -3
- package/dist/editor/group-manager/GroupForm.d.ts +0 -3
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +0 -3
- package/dist/editor/group-manager/InternalGroupsTab.js +0 -2
- package/dist/editor/group-manager/MemberList.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useExternalGroupSearch.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useExternalGroupSearch.js +0 -3
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useGroupState.js +0 -5
- package/dist/editor/group-manager/hooks/useUserSearch.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useUserSearch.js +0 -3
- package/dist/editor/group-manager/index.d.ts +0 -6
- package/dist/editor/group-manager/index.js +0 -10
- package/dist/editor/group-manager/types.d.ts +2 -5
- package/dist/editor/group-manager/types.js +0 -3
- package/dist/editor/hooks/index.d.ts +6 -11
- package/dist/editor/hooks/index.js +6 -12
- package/dist/editor/hooks/useBranchActions.d.ts +0 -14
- package/dist/editor/hooks/useBranchActions.js +0 -22
- package/dist/editor/hooks/useBranchManager.d.ts +2 -48
- package/dist/editor/hooks/useBranchManager.js +4 -64
- package/dist/editor/hooks/useBranchesData.d.ts +7 -6
- package/dist/editor/hooks/useBranchesData.js +7 -6
- package/dist/editor/hooks/useCommentSystem.d.ts +0 -49
- package/dist/editor/hooks/useCommentSystem.js +1 -36
- package/dist/editor/hooks/useDraftManager.d.ts +2 -35
- package/dist/editor/hooks/useDraftManager.js +23 -64
- package/dist/editor/hooks/useEditorLayout.d.ts +0 -20
- package/dist/editor/hooks/useEditorLayout.js +0 -23
- package/dist/editor/hooks/useEntriesData.d.ts +1 -0
- package/dist/editor/hooks/useEntriesData.js +2 -3
- package/dist/editor/hooks/useEntryLinkResolution.d.ts +2 -2
- package/dist/editor/hooks/useEntryLinkResolution.js +2 -2
- package/dist/editor/hooks/useEntryManager.d.ts +2 -27
- package/dist/editor/hooks/useEntryManager.js +29 -78
- package/dist/editor/hooks/useGroupManager.d.ts +0 -21
- package/dist/editor/hooks/useGroupManager.js +0 -21
- package/dist/editor/hooks/usePermissionManager.d.ts +0 -20
- package/dist/editor/hooks/usePermissionManager.js +0 -20
- package/dist/editor/hooks/useReferenceResolution.d.ts +2 -33
- package/dist/editor/hooks/useReferenceResolution.js +1 -48
- package/dist/editor/hooks/useSchemaManager.d.ts +8 -27
- package/dist/editor/hooks/useSchemaManager.js +0 -19
- package/dist/editor/hooks/useUserContext.d.ts +2 -9
- package/dist/editor/hooks/useUserContext.js +10 -10
- package/dist/editor/hooks/useUserMetadata.d.ts +0 -2
- package/dist/editor/hooks/useUserMetadata.js +0 -3
- package/dist/editor/media/AssetCard.d.ts +0 -1
- package/dist/editor/media/AssetCard.js +1 -2
- package/dist/editor/media/CropStep.d.ts +0 -1
- package/dist/editor/media/CropStep.js +0 -1
- package/dist/editor/media/MediaLibrary.d.ts +1 -2
- package/dist/editor/media/MediaLibrary.js +1 -2
- package/dist/editor/media/MediaLibraryBody.d.ts +0 -1
- package/dist/editor/media/MediaLibraryBody.js +0 -1
- package/dist/editor/media/crop-math.d.ts +1 -1
- package/dist/editor/media/crop-math.js +1 -1
- package/dist/editor/permission-manager/GroupSelector.d.ts +0 -3
- package/dist/editor/permission-manager/PermissionEditor.d.ts +0 -4
- package/dist/editor/permission-manager/PermissionEditor.js +0 -1
- package/dist/editor/permission-manager/PermissionLevelBadge.d.ts +0 -7
- package/dist/editor/permission-manager/PermissionLevelBadge.js +0 -4
- package/dist/editor/permission-manager/PermissionTree.d.ts +0 -4
- package/dist/editor/permission-manager/UserSelector.d.ts +0 -3
- package/dist/editor/permission-manager/constants.d.ts +0 -3
- package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts +0 -3
- package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js +0 -10
- package/dist/editor/permission-manager/hooks/usePermissionTree.d.ts +0 -14
- package/dist/editor/permission-manager/hooks/usePermissionTree.js +0 -11
- package/dist/editor/permission-manager/index.d.ts +0 -7
- package/dist/editor/permission-manager/index.js +0 -16
- package/dist/editor/permission-manager/types.d.ts +2 -12
- package/dist/editor/permission-manager/types.js +0 -3
- package/dist/editor/permission-manager/utils.d.ts +0 -28
- package/dist/editor/permission-manager/utils.js +9 -23
- package/dist/editor/preview-bridge.d.ts +5 -1
- package/dist/editor/preview-bridge.js +4 -4
- package/dist/editor/schema-editor/CollectionEditor.d.ts +0 -5
- package/dist/editor/schema-editor/CollectionEditor.js +9 -31
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +0 -6
- package/dist/editor/schema-editor/EntryTypeEditor.js +3 -24
- package/dist/editor/schema-editor/index.d.ts +0 -7
- package/dist/editor/schema-editor/index.js +0 -7
- package/dist/editor/utils/env.d.ts +3 -18
- package/dist/editor/utils/env.js +6 -25
- package/dist/entry-link-resolver.d.ts +13 -35
- package/dist/entry-link-resolver.js +20 -53
- package/dist/entry-schema-registry.d.ts +20 -60
- package/dist/entry-schema-registry.js +20 -69
- package/dist/entry-schema.d.ts +118 -223
- package/dist/entry-schema.js +46 -91
- package/dist/git-manager.d.ts +101 -200
- package/dist/git-manager.js +211 -384
- package/dist/github-service.d.ts +34 -75
- package/dist/github-service.js +37 -91
- package/dist/http/handler.d.ts +6 -37
- package/dist/http/handler.js +65 -120
- package/dist/http/router.d.ts +15 -32
- package/dist/http/router.js +34 -99
- package/dist/http/types.d.ts +15 -40
- package/dist/http/types.js +3 -7
- package/dist/id.d.ts +4 -10
- package/dist/id.js +4 -10
- package/dist/index.js +6 -9
- package/dist/operating-mode/client-safe-strategy.d.ts +7 -14
- package/dist/operating-mode/client-safe-strategy.js +8 -28
- package/dist/operating-mode/client-unsafe-strategy.d.ts +6 -16
- package/dist/operating-mode/client-unsafe-strategy.js +9 -34
- package/dist/operating-mode/client.d.ts +3 -10
- package/dist/operating-mode/client.js +3 -8
- package/dist/operating-mode/deployment-name-fixtures.d.ts +14 -18
- package/dist/operating-mode/deployment-name-fixtures.js +14 -18
- package/dist/operating-mode/deployment-name.d.ts +19 -29
- package/dist/operating-mode/deployment-name.js +29 -47
- package/dist/operating-mode/index.d.ts +4 -25
- package/dist/operating-mode/index.js +9 -31
- package/dist/operating-mode/mode-env.d.ts +32 -54
- package/dist/operating-mode/mode-env.js +36 -58
- package/dist/operating-mode/types.d.ts +24 -72
- package/dist/operating-mode/types.js +1 -7
- package/dist/paths/branch-name.d.ts +30 -40
- package/dist/paths/branch-name.js +30 -40
- package/dist/paths/branch.d.ts +4 -18
- package/dist/paths/branch.js +6 -22
- package/dist/paths/index.d.ts +2 -13
- package/dist/paths/index.js +3 -23
- package/dist/paths/normalize-server.d.ts +3 -15
- package/dist/paths/normalize-server.js +3 -15
- package/dist/paths/normalize.d.ts +13 -33
- package/dist/paths/normalize.js +13 -33
- package/dist/paths/resolve.d.ts +4 -17
- package/dist/paths/resolve.js +4 -22
- package/dist/paths/types.d.ts +12 -34
- package/dist/paths/types.js +5 -7
- package/dist/paths/validation.d.ts +22 -119
- package/dist/paths/validation.js +30 -148
- package/dist/reference-resolver.d.ts +0 -13
- package/dist/reference-resolver.js +0 -18
- package/dist/resolve-canopy-user.d.ts +16 -18
- package/dist/resolve-canopy-user.js +17 -26
- package/dist/resource-generation.d.ts +16 -25
- package/dist/resource-generation.js +34 -97
- package/dist/schema/meta-loader.d.ts +2 -14
- package/dist/schema/meta-loader.js +15 -64
- package/dist/schema/resolver.d.ts +1 -4
- package/dist/schema/resolver.js +1 -6
- package/dist/schema/schema-store-types.d.ts +0 -12
- package/dist/schema/schema-store.d.ts +61 -138
- package/dist/schema/schema-store.js +85 -282
- package/dist/schema/types.d.ts +0 -4
- package/dist/server.d.ts +62 -93
- package/dist/server.js +63 -94
- package/dist/services.d.ts +23 -47
- package/dist/services.js +55 -98
- package/dist/settings-workspace.d.ts +17 -30
- package/dist/settings-workspace.js +51 -81
- package/dist/static/index.d.ts +26 -52
- package/dist/static/index.js +33 -67
- package/dist/{worker/task-queue.d.ts → task-queue/cms-task-queue.d.ts} +7 -9
- package/dist/task-queue/cms-task-queue.js +13 -0
- package/dist/task-queue/task-queue-config.d.ts +3 -0
- package/dist/{worker → task-queue}/task-queue-config.js +1 -6
- package/dist/task-queue/task-queue.d.ts +16 -22
- package/dist/task-queue/task-queue.js +20 -44
- package/dist/task-queue/types.d.ts +0 -1
- package/dist/task-queue/worker-status.d.ts +35 -0
- package/dist/task-queue/worker-status.js +40 -0
- package/dist/types.d.ts +40 -51
- package/dist/url-collision.d.ts +26 -40
- package/dist/url-collision.js +47 -63
- package/dist/url-exclusivity-fixtures.d.ts +15 -20
- package/dist/url-exclusivity-fixtures.js +14 -19
- package/dist/url-path-resolver.d.ts +8 -16
- package/dist/url-path-resolver.js +19 -29
- package/dist/user.d.ts +8 -21
- package/dist/user.js +10 -19
- package/dist/utils/async-mutex.js +4 -6
- package/dist/utils/atomic-write.d.ts +2 -11
- package/dist/utils/atomic-write.js +2 -11
- package/dist/utils/body-field.d.ts +8 -15
- package/dist/utils/body-field.js +8 -15
- package/dist/utils/content-serialize.d.ts +18 -24
- package/dist/utils/content-serialize.js +109 -160
- package/dist/utils/content-write-lock.d.ts +51 -106
- package/dist/utils/content-write-lock.js +66 -111
- package/dist/utils/debug.d.ts +7 -23
- package/dist/utils/debug.js +7 -23
- package/dist/utils/entry-url.d.ts +11 -23
- package/dist/utils/entry-url.js +11 -26
- package/dist/utils/error.d.ts +25 -90
- package/dist/utils/error.js +60 -147
- package/dist/utils/flatten-group-fields.d.ts +5 -7
- package/dist/utils/flatten-group-fields.js +5 -7
- package/dist/utils/format.d.ts +1 -5
- package/dist/utils/format.js +1 -5
- package/dist/utils/fs.d.ts +3 -4
- package/dist/utils/fs.js +3 -4
- package/dist/utils/git.d.ts +52 -83
- package/dist/utils/git.js +79 -125
- package/dist/utils/logger.d.ts +35 -50
- package/dist/utils/logger.js +35 -52
- package/dist/utils/provisioning-lock.d.ts +23 -35
- package/dist/utils/provisioning-lock.js +49 -69
- package/dist/utils/sanitize-href.d.ts +5 -10
- package/dist/utils/sanitize-href.js +8 -18
- package/dist/utils/title-field.d.ts +17 -20
- package/dist/utils/title-field.js +18 -27
- package/dist/utils/typed-filename.d.ts +20 -48
- package/dist/utils/typed-filename.js +25 -61
- package/dist/utils/url-prefix.d.ts +3 -3
- package/dist/utils/url-prefix.js +3 -3
- package/dist/validation/block-structural-keys.d.ts +16 -31
- package/dist/validation/block-structural-keys.js +19 -29
- package/dist/validation/deletion-checker.d.ts +0 -34
- package/dist/validation/deletion-checker.js +1 -43
- package/dist/validation/entry-link-validator.d.ts +6 -13
- package/dist/validation/entry-link-validator.js +4 -13
- package/dist/validation/entry-type-reference-validator.d.ts +13 -18
- package/dist/validation/entry-type-reference-validator.js +13 -18
- package/dist/validation/entry-validator.d.ts +13 -25
- package/dist/validation/entry-validator.js +15 -32
- package/dist/validation/field-traversal.d.ts +3 -30
- package/dist/validation/field-traversal.js +1 -32
- package/dist/validation/reference-validator.d.ts +5 -24
- package/dist/validation/reference-validator.js +5 -30
- package/dist/worker/cms-worker.d.ts +172 -232
- package/dist/worker/cms-worker.js +241 -333
- package/dist/worker/git-sync.d.ts +32 -133
- package/dist/worker/git-sync.js +129 -191
- package/dist/worker/github-auth.d.ts +13 -19
- package/dist/worker/github-auth.js +13 -19
- package/dist/worker/history-rewrite.d.ts +53 -66
- package/dist/worker/history-rewrite.js +54 -60
- package/dist/worker/log.d.ts +24 -41
- package/dist/worker/log.js +25 -42
- package/dist/worker/rebase.d.ts +20 -110
- package/dist/worker/rebase.js +279 -354
- package/dist/worker/task-runner.d.ts +32 -66
- package/dist/worker/task-runner.js +126 -162
- package/dist/worker/worker-context.d.ts +41 -81
- package/package.json +4 -4
- package/dist/cli/template-files/middleware-clerk.ts.template +0 -37
- package/dist/worker/task-queue-config.d.ts +0 -8
- package/dist/worker/task-queue.js +0 -19
- package/dist/worker/worker-status.d.ts +0 -47
- package/dist/worker/worker-status.js +0 -52
package/dist/http/handler.js
CHANGED
|
@@ -9,35 +9,27 @@ import { authResultToCanopyUser } from '../user.js';
|
|
|
9
9
|
import { isAdmin } from '../authorization/index.js';
|
|
10
10
|
import { clientOperatingStrategy, operatingStrategy } from '../operating-mode/index.js';
|
|
11
11
|
import { getErrorMessage, redactCredentials, sanitizeErrorMessage } from '../utils/error.js';
|
|
12
|
-
// canopyLogError, not console.error:
|
|
13
|
-
//
|
|
14
|
-
// utils/logger.ts)
|
|
12
|
+
// canopyLogError, not console.error: this is shared code and not guaranteed to
|
|
13
|
+
// stay out of the worker's runtime import closure, so new log lines here go
|
|
14
|
+
// through the indirection (utils/logger.ts).
|
|
15
15
|
import { canopyLogError } from '../utils/logger.js';
|
|
16
|
-
/**
|
|
17
|
-
* Build API context from options.
|
|
18
|
-
*/
|
|
19
16
|
const buildContext = async (options) => {
|
|
20
17
|
const services = options.services ?? (options.config ? await createCanopyServices(options.config) : undefined);
|
|
21
18
|
if (!services) {
|
|
22
19
|
throw new Error('CanopyCMS: config or services is required');
|
|
23
20
|
}
|
|
24
21
|
const operatingMode = services.config.mode;
|
|
25
|
-
// Derive from the strategy
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// deployment-namespaced settings branch (e.g. canopycms-settings-acme)
|
|
29
|
-
// never matched this check, so `getBranchContext(settingsBranch)` could
|
|
30
|
-
// never auto-create it.
|
|
22
|
+
// Derive from the strategy, which resolves deploymentName; a literal here
|
|
23
|
+
// would not match a deployment-namespaced settings branch (say
|
|
24
|
+
// canopycms-settings-acme), so getBranchContext could never auto-create it.
|
|
31
25
|
const settingsBranch = operatingStrategy(operatingMode).getSettingsBranchName(services.config);
|
|
32
26
|
const getBranchContext = options.getBranchContext ??
|
|
33
27
|
(async (branch, opts) => {
|
|
34
|
-
// Try to load existing branch
|
|
35
28
|
const existing = await loadBranchContext({
|
|
36
29
|
branchName: branch,
|
|
37
30
|
mode: operatingMode,
|
|
38
31
|
});
|
|
39
32
|
if (existing) {
|
|
40
|
-
// Optionally load per-branch schema
|
|
41
33
|
if (opts?.loadSchema) {
|
|
42
34
|
const contentRootName = services.config.contentRoot || 'content';
|
|
43
35
|
const cached = await services.branchSchemaCache.getSchema(existing.branchRoot, services.entrySchemaRegistry, contentRootName);
|
|
@@ -45,9 +37,8 @@ const buildContext = async (options) => {
|
|
|
45
37
|
}
|
|
46
38
|
return existing;
|
|
47
39
|
}
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// refreshActiveBranch() updates are reflected immediately.
|
|
40
|
+
// Read from services.config per-request, not a captured variable, so a
|
|
41
|
+
// refreshActiveBranch() update takes effect immediately.
|
|
51
42
|
const baseBranch = services.config.defaultBaseBranch ?? 'main';
|
|
52
43
|
const activeBranch = services.config.defaultActiveBranch ?? baseBranch;
|
|
53
44
|
const shouldAutoCreate = clientOperatingStrategy(operatingMode).supportsBranching() &&
|
|
@@ -59,7 +50,6 @@ const buildContext = async (options) => {
|
|
|
59
50
|
mode: operatingMode,
|
|
60
51
|
createdBy: 'canopycms-system',
|
|
61
52
|
});
|
|
62
|
-
// Optionally load per-branch schema for auto-created branches
|
|
63
53
|
if (opts?.loadSchema && context) {
|
|
64
54
|
const contentRootName = services.config.contentRoot || 'content';
|
|
65
55
|
const cached = await services.branchSchemaCache.getSchema(context.branchRoot, services.entrySchemaRegistry, contentRootName);
|
|
@@ -76,9 +66,6 @@ const buildContext = async (options) => {
|
|
|
76
66
|
authPlugin: options.authPlugin,
|
|
77
67
|
};
|
|
78
68
|
};
|
|
79
|
-
/**
|
|
80
|
-
* Parse query parameters from URL.
|
|
81
|
-
*/
|
|
82
69
|
const parseQueryParams = (url) => {
|
|
83
70
|
try {
|
|
84
71
|
const urlObj = new URL(url, 'http://localhost');
|
|
@@ -89,31 +76,9 @@ const parseQueryParams = (url) => {
|
|
|
89
76
|
}
|
|
90
77
|
};
|
|
91
78
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* Framework adapters (Next.js, Express, Hono, etc.) should:
|
|
96
|
-
* 1. Convert their framework's request to CanopyRequest
|
|
97
|
-
* 2. Extract path segments from the URL
|
|
98
|
-
* 3. Call this handler
|
|
99
|
-
* 4. Convert the CanopyResponse to their framework's response
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```ts
|
|
103
|
-
* // In a framework adapter
|
|
104
|
-
* const coreHandler = createCanopyRequestHandler({
|
|
105
|
-
* config: myConfig,
|
|
106
|
-
* authPlugin: myAuthPlugin,
|
|
107
|
-
* })
|
|
108
|
-
*
|
|
109
|
-
* // Framework-specific handler
|
|
110
|
-
* async function handleRequest(frameworkReq) {
|
|
111
|
-
* const canopyReq = convertToCanopyRequest(frameworkReq)
|
|
112
|
-
* const segments = extractPathSegments(frameworkReq)
|
|
113
|
-
* const response = await coreHandler(canopyReq, segments)
|
|
114
|
-
* return convertToFrameworkResponse(response)
|
|
115
|
-
* }
|
|
116
|
-
* ```
|
|
79
|
+
* The core handler behind every Canopy API request. A framework adapter
|
|
80
|
+
* (Next.js, Express, Hono) converts its request to a CanopyRequest, extracts
|
|
81
|
+
* the path segments, calls this, and converts the CanopyResponse back.
|
|
117
82
|
*/
|
|
118
83
|
export function createCanopyRequestHandler(options) {
|
|
119
84
|
// Fail closed (SEC-C1): a dev/insecure auth plugin must never serve prod traffic.
|
|
@@ -121,10 +86,9 @@ export function createCanopyRequestHandler(options) {
|
|
|
121
86
|
const mode = options.services?.config.mode ?? options.config?.mode;
|
|
122
87
|
assertAuthPluginAllowedForMode(options.authPlugin, mode);
|
|
123
88
|
const router = createCanopyRouter();
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
// buildContext() instead of replaying the same rejection forever.
|
|
89
|
+
// Built once and reused across requests in the same warm container. On
|
|
90
|
+
// rejection (transient cold start, EFS not yet mounted) the cache is cleared
|
|
91
|
+
// (API-H3) so the NEXT request retries instead of replaying the rejection.
|
|
128
92
|
let apiCtxPromise = null;
|
|
129
93
|
const getContext = () => {
|
|
130
94
|
if (!apiCtxPromise) {
|
|
@@ -135,37 +99,30 @@ export function createCanopyRequestHandler(options) {
|
|
|
135
99
|
}
|
|
136
100
|
return apiCtxPromise;
|
|
137
101
|
};
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
// 500 that breaks the uniform { ok, status, error } contract the editor depends on.
|
|
102
|
+
// Wrapped below by a top-level try/catch (API-C1). Nothing in here is
|
|
103
|
+
// individually try/catched, and some handlers deliberately re-throw
|
|
104
|
+
// unrecognized errors, so without that outer boundary an unhandled throw
|
|
105
|
+
// would escape the adapter as a generic 500 and break the uniform
|
|
106
|
+
// { ok, status, error } contract the editor depends on.
|
|
144
107
|
const handleRequest = async (req, pathSegments) => {
|
|
145
|
-
// Route matching (fast, do first before async work)
|
|
146
108
|
const match = router.match(req.method, pathSegments);
|
|
147
109
|
if (!match) {
|
|
148
110
|
return jsonResponse({ ok: false, status: 404, error: 'Not found' }, 404);
|
|
149
111
|
}
|
|
150
|
-
// Get cached context
|
|
151
112
|
const apiCtx = await getContext();
|
|
152
113
|
// In dev mode, re-check if the developer switched git branches
|
|
153
114
|
await apiCtx.services.refreshActiveBranch();
|
|
154
|
-
// Authenticate and convert to CanopyUser
|
|
155
115
|
const authResult = await options.authPlugin.authenticate(req);
|
|
156
|
-
// API routes require authentication.
|
|
157
|
-
// workspace provisioning below, so they can neither trigger expensive
|
|
158
|
-
// operations nor read provisioning error details.
|
|
116
|
+
// API routes require authentication. Anonymous callers are rejected BEFORE
|
|
117
|
+
// any workspace provisioning below, so they can neither trigger expensive
|
|
118
|
+
// git operations nor read provisioning error details.
|
|
159
119
|
if (!authResult.success || !authResult.user) {
|
|
160
120
|
return jsonResponse({ ok: false, status: 401, error: authResult.error ?? 'Unauthorized' }, 401);
|
|
161
121
|
}
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
// don't return confusing empty results on a cold start, so fail loudly
|
|
167
|
-
// on a real provisioning error rather than let that surprise a later
|
|
168
|
-
// handler.
|
|
122
|
+
// Provision the base/active branch workspace on first request, so the many
|
|
123
|
+
// endpoints that assume it exists (registry reads and the like) don't return
|
|
124
|
+
// confusing empty results on a cold start. A real provisioning error fails
|
|
125
|
+
// loudly here rather than surprising a later handler.
|
|
169
126
|
const baseBranch = apiCtx.services.config.defaultBaseBranch ?? 'main';
|
|
170
127
|
try {
|
|
171
128
|
await apiCtx.getBranchContext(baseBranch);
|
|
@@ -173,16 +130,16 @@ export function createCanopyRequestHandler(options) {
|
|
|
173
130
|
catch (err) {
|
|
174
131
|
const message = getErrorMessage(err);
|
|
175
132
|
if (err instanceof BranchMetadataCorruptError) {
|
|
176
|
-
// Corrupt BASE branch metadata must not take down every endpoint
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
133
|
+
// Corrupt BASE branch metadata must not take down every endpoint,
|
|
134
|
+
// since /admin is how it gets fixed. Keep routing instead: internal
|
|
135
|
+
// groups come from the settings workspace below, independent of this
|
|
136
|
+
// branch's health.
|
|
180
137
|
console.error(`CanopyCMS: Base branch '${baseBranch}' has corrupt metadata; serving without base-branch provisioning until repaired: ${redactCredentials(message)}`);
|
|
181
138
|
}
|
|
182
139
|
else {
|
|
183
|
-
// Full path detail to server logs
|
|
184
|
-
// (authenticated) client. Credentials
|
|
185
|
-
// redacted even from server logs.
|
|
140
|
+
// Full path detail to server logs, sanitized detail to the
|
|
141
|
+
// (authenticated) client. Credentials, which git errors can embed, are
|
|
142
|
+
// redacted even from the server logs.
|
|
186
143
|
console.error(`CanopyCMS: Failed to provision workspace for base branch '${baseBranch}': ${redactCredentials(message)}`);
|
|
187
144
|
return jsonResponse({
|
|
188
145
|
ok: false,
|
|
@@ -191,14 +148,13 @@ export function createCanopyRequestHandler(options) {
|
|
|
191
148
|
}, 503);
|
|
192
149
|
}
|
|
193
150
|
}
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
// fallback would be a silent authorization change.
|
|
151
|
+
// Internal groups are the single source of truth for group-based privilege
|
|
152
|
+
// and MUST come from the settings workspace (the pattern in
|
|
153
|
+
// authorization/content.ts's createContentAccessChecker), never from a
|
|
154
|
+
// content branch clone, which nothing writes groups.json into. A failure
|
|
155
|
+
// throws, mapped to a 503 below, rather than degrading to an empty group
|
|
156
|
+
// list: "no groups" reads as "no privileges", so a silent fallback would be
|
|
157
|
+
// a silent authorization change.
|
|
202
158
|
let user;
|
|
203
159
|
try {
|
|
204
160
|
user = await resolveCanopyUser(authResult, {
|
|
@@ -210,34 +166,32 @@ export function createCanopyRequestHandler(options) {
|
|
|
210
166
|
catch (err) {
|
|
211
167
|
const message = getErrorMessage(err);
|
|
212
168
|
canopyLogError(`CanopyCMS: Failed to resolve internal groups from the settings workspace: ${redactCredentials(message)}`);
|
|
213
|
-
// Same trade as the base-branch degradation above
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// operator with no in-product way to see why anything is down.
|
|
169
|
+
// Same trade as the base-branch degradation above: /admin is the recovery
|
|
170
|
+
// surface for exactly this failure (a renamed settings branch trips
|
|
171
|
+
// assertSettingsWorkspaceIdentity until a human intervenes), so 503ing it
|
|
172
|
+
// too would leave an operator no in-product way to see what is down.
|
|
218
173
|
//
|
|
219
|
-
// Safe because it can only REMOVE privilege, never grant it
|
|
220
|
-
// `authResultToCanopyUser` merges internal groups ADDITIVELY, and
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
// environment and does not touch the settings workspace at all.
|
|
174
|
+
// Safe because it can only REMOVE privilege, never grant it:
|
|
175
|
+
// `authResultToCanopyUser` merges internal groups ADDITIVELY, and path
|
|
176
|
+
// rules select on the user MATCHING a target, never on the user LACKING a
|
|
177
|
+
// group, so dropping them flips allowed -> denied and not the reverse. The
|
|
178
|
+
// one surviving privilege is bootstrap admin, which comes from
|
|
179
|
+
// CANOPY_BOOTSTRAP_ADMIN_IDS and never touches the settings workspace.
|
|
226
180
|
//
|
|
227
|
-
// A non-bootstrap admin still gets the 503, deliberately: the admin
|
|
228
|
-
//
|
|
229
|
-
//
|
|
181
|
+
// A non-bootstrap admin still gets the 503, deliberately: the admin guard
|
|
182
|
+
// would answer them 403, and "settings workspace unavailable" is the more
|
|
183
|
+
// actionable of the two.
|
|
230
184
|
const degradedUser = authResultToCanopyUser(authResult, apiCtx.services.bootstrapAdminIds);
|
|
231
185
|
if (pathSegments[0] === 'admin' && isAdmin(degradedUser.groups)) {
|
|
232
186
|
canopyLogError(`CanopyCMS: Serving ${req.method} /admin for a bootstrap admin with group-based privileges UNRESOLVED (settings workspace unavailable) so the recovery endpoints stay reachable.`);
|
|
233
187
|
user = degradedUser;
|
|
234
188
|
}
|
|
235
189
|
else {
|
|
236
|
-
// Name the settings branch:
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
190
|
+
// Name the settings branch: this usually means a changed deploymentName
|
|
191
|
+
// pointing the deployment at a branch other than the one its workspace
|
|
192
|
+
// was cloned for, and the branch name is the part of that an operator
|
|
193
|
+
// can act on without CloudWatch (paths are redacted from client-facing
|
|
194
|
+
// messages).
|
|
241
195
|
const settingsBranch = operatingStrategy(apiCtx.services.config.mode).getSettingsBranchName(apiCtx.services.config);
|
|
242
196
|
return jsonResponse({
|
|
243
197
|
ok: false,
|
|
@@ -246,16 +200,13 @@ export function createCanopyRequestHandler(options) {
|
|
|
246
200
|
}, 503);
|
|
247
201
|
}
|
|
248
202
|
}
|
|
249
|
-
// API routes require authentication - reject anonymous users
|
|
250
203
|
if (user.type === 'anonymous') {
|
|
251
204
|
return jsonResponse({ ok: false, status: 401, error: authResult.error ?? 'Unauthorized' }, 401);
|
|
252
205
|
}
|
|
253
|
-
// Parse query params and merge with route params
|
|
254
206
|
const queryParams = parseQueryParams(req.url);
|
|
255
207
|
const mergedParams = { ...queryParams, ...match.params };
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
// req.formData() - calling req.json() first would consume it.
|
|
208
|
+
// Multipart routes opt out via bodyFormat so their handler can read the
|
|
209
|
+
// single-use body stream itself: calling req.json() first would consume it.
|
|
259
210
|
let body;
|
|
260
211
|
if (req.method !== 'GET' && match.bodyFormat !== 'multipart') {
|
|
261
212
|
try {
|
|
@@ -265,17 +216,14 @@ export function createCanopyRequestHandler(options) {
|
|
|
265
216
|
body = undefined;
|
|
266
217
|
}
|
|
267
218
|
}
|
|
268
|
-
// Build API request
|
|
269
219
|
const branch = mergedParams?.branch ??
|
|
270
220
|
body?.branch;
|
|
271
221
|
const apiReq = { user, body, branch, query: queryParams, rawRequest: req };
|
|
272
|
-
// Validate params and body using the route's validation function (if available)
|
|
273
222
|
if (match.validate) {
|
|
274
223
|
const validationResult = match.validate({ params: mergedParams, body });
|
|
275
224
|
if (!validationResult.ok) {
|
|
276
225
|
return jsonResponse({ ok: false, status: 400, error: validationResult.error }, 400);
|
|
277
226
|
}
|
|
278
|
-
// Call handler with validated params/body based on what's defined
|
|
279
227
|
const handlerArgs = [apiCtx, apiReq];
|
|
280
228
|
if (validationResult.params !== undefined) {
|
|
281
229
|
handlerArgs.push(validationResult.params);
|
|
@@ -284,16 +232,16 @@ export function createCanopyRequestHandler(options) {
|
|
|
284
232
|
handlerArgs.push(validationResult.body);
|
|
285
233
|
}
|
|
286
234
|
const result = await match.handler(...handlerArgs);
|
|
287
|
-
// Binary routes
|
|
288
|
-
//
|
|
289
|
-
//
|
|
235
|
+
// Binary routes carry their own status and headers and MUST reach the
|
|
236
|
+
// adapter untouched: jsonResponse would serialize raw bytes and drop
|
|
237
|
+
// contentType/contentDisposition.
|
|
290
238
|
if (isCanopyBinaryResponse(result))
|
|
291
239
|
return result;
|
|
292
240
|
return jsonResponse(result, result.status);
|
|
293
241
|
}
|
|
294
242
|
else {
|
|
295
|
-
//
|
|
296
|
-
//
|
|
243
|
+
// Every route should use defineEndpoint; this is the safety net for one
|
|
244
|
+
// that carries no validate function.
|
|
297
245
|
const result = await match.handler(apiCtx, apiReq, mergedParams);
|
|
298
246
|
if (isCanopyBinaryResponse(result))
|
|
299
247
|
return result;
|
|
@@ -312,9 +260,6 @@ export function createCanopyRequestHandler(options) {
|
|
|
312
260
|
}
|
|
313
261
|
};
|
|
314
262
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Create a handler with pre-built services from config.
|
|
317
|
-
*/
|
|
318
263
|
export async function createCanopyRequestHandlerFromConfig(options) {
|
|
319
264
|
return createCanopyRequestHandler({
|
|
320
265
|
...options,
|
package/dist/http/router.d.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import type { ApiResponse } from '../api/types.js';
|
|
2
2
|
import type { CanopyBinaryResponse } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* stream bytes (e.g. asset serving) can be registered in the same route
|
|
9
|
-
* table as ordinary JSON routes; the core handler discriminates on `kind`
|
|
10
|
-
* before deciding whether to wrap the result in a JSON envelope.
|
|
4
|
+
* `any` because handler signatures differ: some take (ctx, req, params), others
|
|
5
|
+
* (ctx, params). The return type includes `CanopyBinaryResponse` so byte-
|
|
6
|
+
* streaming routes share this route table with JSON ones; http/handler.ts
|
|
7
|
+
* discriminates on `kind` before wrapping a result in a JSON envelope.
|
|
11
8
|
*/
|
|
12
9
|
export type CanopyHandler = (...args: any[]) => Promise<ApiResponse<any> | CanopyBinaryResponse>;
|
|
13
|
-
/**
|
|
14
|
-
* Route definition for the Canopy API.
|
|
15
|
-
* Maps HTTP method + path pattern to a handler.
|
|
16
|
-
*/
|
|
10
|
+
/** Route definition for the Canopy API. */
|
|
17
11
|
export interface RouteDefinition {
|
|
18
12
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
19
13
|
pattern: readonly string[];
|
|
@@ -30,43 +24,32 @@ export interface RouteDefinition {
|
|
|
30
24
|
error: string;
|
|
31
25
|
};
|
|
32
26
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `req.formData()` instead, since a body stream can only be consumed once.
|
|
27
|
+
* Opts out of the core handler's eager `req.json()` (see http/handler.ts).
|
|
28
|
+
* Set by routes taking a non-JSON body: the body stream is single-use, so
|
|
29
|
+
* such a handler must read it itself via `req.formData()`.
|
|
37
30
|
*/
|
|
38
31
|
bodyFormat?: 'multipart';
|
|
39
32
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Result of route matching.
|
|
42
|
-
*/
|
|
43
33
|
export interface RouteMatch {
|
|
44
34
|
handler: CanopyHandler;
|
|
45
35
|
params: Record<string, string>;
|
|
46
36
|
validate?: RouteDefinition['validate'];
|
|
47
37
|
bodyFormat?: RouteDefinition['bodyFormat'];
|
|
48
38
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Core router - framework-agnostic route matching.
|
|
51
|
-
*/
|
|
39
|
+
/** Core router: framework-agnostic route matching. */
|
|
52
40
|
export interface CanopyRouter {
|
|
53
|
-
/** All registered routes */
|
|
54
41
|
readonly routes: RouteDefinition[];
|
|
55
|
-
/** Find a matching route for the given method and path segments */
|
|
56
42
|
match(method: string, segments: string[]): RouteMatch | null;
|
|
57
43
|
}
|
|
58
44
|
/**
|
|
59
45
|
* Find the most specific matching route for a method + path.
|
|
60
46
|
*
|
|
61
|
-
* Scans
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
47
|
+
* Scans EVERY route, not just the first structural match, so registration order
|
|
48
|
+
* can never let a broad dynamic route (`:branch`) shadow a narrower,
|
|
49
|
+
* differently-guarded static one (`assets`) registered later. Exported
|
|
50
|
+
* standalone so that precedence rule can be unit-tested against synthetic route
|
|
51
|
+
* tables, independent of the real API surface.
|
|
52
|
+
* @internal Exported for tests.
|
|
67
53
|
*/
|
|
68
54
|
export declare function matchRoute(routes: readonly RouteDefinition[], method: string, segments: string[]): RouteMatch | null;
|
|
69
|
-
/**
|
|
70
|
-
* Create the standard Canopy router with all API routes.
|
|
71
|
-
*/
|
|
72
55
|
export declare function createCanopyRouter(): CanopyRouter;
|
package/dist/http/router.js
CHANGED
|
@@ -1,67 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { WORKFLOW_ROUTES } from '../api/branch-status.js';
|
|
3
|
-
import { COMMENT_ROUTES } from '../api/comments.js';
|
|
4
|
-
import { CONTENT_ROUTES } from '../api/content.js';
|
|
5
|
-
import { REFERENCE_OPTIONS_ROUTES } from '../api/reference-options.js';
|
|
6
|
-
import { RESOLVE_REFERENCES_ROUTES } from '../api/resolve-references.js';
|
|
7
|
-
import { ENTRY_ROUTES } from '../api/entries.js';
|
|
8
|
-
import { ASSET_ROUTES, assetRawRoute } from '../api/assets.js';
|
|
9
|
-
import { PERMISSION_ROUTES } from '../api/permissions.js';
|
|
10
|
-
import { GROUP_ROUTES } from '../api/groups.js';
|
|
11
|
-
import { USER_ROUTES } from '../api/user.js';
|
|
12
|
-
import { SCHEMA_ROUTES } from '../api/schema.js';
|
|
13
|
-
import { ADMIN_ROUTES } from '../api/admin.js';
|
|
1
|
+
import { buildCanopyRoutes } from '../api/routes.js';
|
|
14
2
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
3
|
+
* Match a route pattern against actual path segments: `:param` takes one
|
|
4
|
+
* segment, `...slug` takes the rest.
|
|
17
5
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
...Object.values(BRANCH_ROUTES),
|
|
25
|
-
...Object.values(WORKFLOW_ROUTES),
|
|
26
|
-
...Object.values(COMMENT_ROUTES),
|
|
27
|
-
...Object.values(CONTENT_ROUTES),
|
|
28
|
-
...Object.values(REFERENCE_OPTIONS_ROUTES),
|
|
29
|
-
...Object.values(RESOLVE_REFERENCES_ROUTES),
|
|
30
|
-
...Object.values(ENTRY_ROUTES),
|
|
31
|
-
...Object.values(ASSET_ROUTES),
|
|
32
|
-
assetRawRoute,
|
|
33
|
-
...Object.values(PERMISSION_ROUTES),
|
|
34
|
-
...Object.values(GROUP_ROUTES),
|
|
35
|
-
...Object.values(USER_ROUTES),
|
|
36
|
-
...Object.values(SCHEMA_ROUTES),
|
|
37
|
-
...Object.values(ADMIN_ROUTES),
|
|
38
|
-
].map((route) => ({
|
|
39
|
-
method: route.method,
|
|
40
|
-
pattern: route.pattern,
|
|
41
|
-
handler: route.handler,
|
|
42
|
-
// Include validation function if present (new-style routes from defineEndpoint)
|
|
43
|
-
validate: 'validate' in route ? route.validate : undefined,
|
|
44
|
-
bodyFormat: 'bodyFormat' in route ? route.bodyFormat : undefined,
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Match a route pattern against actual path segments.
|
|
49
|
-
* Supports :param for single-segment params and ...slug for catch-all.
|
|
50
|
-
*
|
|
51
|
-
* Params are returned RAW (undecoded) here on purpose (C5): matching is
|
|
52
|
-
* purely structural (segment counts, static-segment equality) and must never
|
|
53
|
-
* throw. Decoding happens exactly once, uniformly for :param and catch-all
|
|
54
|
-
* alike, in matchRoute() below - after the winning route has been picked, so
|
|
55
|
-
* a malformed `%` escape is handled in a single place instead of aborting
|
|
56
|
-
* mid-match (which previously let a URIError from a bad :param escape to
|
|
57
|
-
* `decodeURIComponent` bubble all the way to http/handler.ts's top-level
|
|
58
|
-
* catch and surface as a 500).
|
|
6
|
+
* Params come back RAW (undecoded) on purpose (C5): matching is purely
|
|
7
|
+
* structural and must NEVER throw. Decoding happens exactly once, uniformly for
|
|
8
|
+
* `:param` and catch-all alike, in matchRoute() below, after the winning route
|
|
9
|
+
* is picked — so a malformed `%` escape is handled in one place instead of
|
|
10
|
+
* aborting mid-match, where the URIError would reach http/handler.ts's
|
|
11
|
+
* top-level catch and surface as a 500 rather than a 400.
|
|
59
12
|
*/
|
|
60
13
|
const matchPattern = (pattern, actual) => {
|
|
61
14
|
const params = {};
|
|
62
15
|
const actualCopy = [...actual];
|
|
63
16
|
for (const part of pattern) {
|
|
64
|
-
// Catch-all: consume remaining segments
|
|
65
17
|
if (part.startsWith('...')) {
|
|
66
18
|
const paramName = part.slice(3); // Remove '...' prefix
|
|
67
19
|
params[paramName] = actualCopy.join('/');
|
|
@@ -72,24 +24,21 @@ const matchPattern = (pattern, actual) => {
|
|
|
72
24
|
if (!next)
|
|
73
25
|
return null;
|
|
74
26
|
if (part.startsWith(':')) {
|
|
75
|
-
//
|
|
27
|
+
// Still raw; see the decode note above.
|
|
76
28
|
params[part.slice(1)] = next;
|
|
77
29
|
}
|
|
78
30
|
else if (part !== next) {
|
|
79
|
-
// Static segment - must match exactly
|
|
80
31
|
return null;
|
|
81
32
|
}
|
|
82
33
|
}
|
|
83
|
-
// If there are leftover segments, no match
|
|
84
34
|
if (actualCopy.length > 0)
|
|
85
35
|
return null;
|
|
86
36
|
return { params };
|
|
87
37
|
};
|
|
88
38
|
/**
|
|
89
|
-
* Synthetic handler for
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* table, and only ever reachable via matchRoute()'s decode step below.
|
|
39
|
+
* Synthetic handler for matched params carrying a malformed `%` escape (C5) —
|
|
40
|
+
* a lone `%`, `%zz`. The request is malformed, so it answers 400. Never
|
|
41
|
+
* registered in the route table; only matchRoute()'s decode step reaches it.
|
|
93
42
|
*/
|
|
94
43
|
const malformedPathHandler = async () => ({
|
|
95
44
|
ok: false,
|
|
@@ -97,18 +46,16 @@ const malformedPathHandler = async () => ({
|
|
|
97
46
|
error: 'Malformed URL-encoded path segment',
|
|
98
47
|
});
|
|
99
48
|
/**
|
|
100
|
-
* Specificity rank of a pattern segment
|
|
101
|
-
*
|
|
102
|
-
* more specific than a `...catchall`.
|
|
49
|
+
* Specificity rank of a pattern segment, higher wins: a literal beats a
|
|
50
|
+
* `:param`, which beats a `...catchall`.
|
|
103
51
|
*/
|
|
104
52
|
const STATIC_RANK = 2;
|
|
105
53
|
const DYNAMIC_RANK = 1;
|
|
106
54
|
const CATCHALL_RANK = 0;
|
|
107
55
|
/**
|
|
108
|
-
* Rank of the pattern segment governing position `index`.
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* effectively "consumes" every subsequent position).
|
|
56
|
+
* Rank of the pattern segment governing position `index`. A catch-all at or
|
|
57
|
+
* before `index` governs every position from there on — it is always the last
|
|
58
|
+
* pattern segment here, so it consumes everything after itself.
|
|
112
59
|
*/
|
|
113
60
|
const segmentRankAt = (pattern, index) => {
|
|
114
61
|
for (let i = 0; i <= index && i < pattern.length; i++) {
|
|
@@ -120,13 +67,10 @@ const segmentRankAt = (pattern, index) => {
|
|
|
120
67
|
return pattern[index].startsWith(':') ? DYNAMIC_RANK : STATIC_RANK;
|
|
121
68
|
};
|
|
122
69
|
/**
|
|
123
|
-
* Compare two
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* `b` is more specific than `a`, and 0 if they are tied (in which case the
|
|
128
|
-
* caller should keep whichever route it already picked, so registration
|
|
129
|
-
* order acts as the final, deterministic tiebreaker).
|
|
70
|
+
* Compare two patterns that both matched, position by position: negative when
|
|
71
|
+
* `a` is more specific, positive when `b` is, 0 when tied. On a tie the caller
|
|
72
|
+
* keeps the route it already picked, so registration order is the final,
|
|
73
|
+
* deterministic tiebreaker.
|
|
130
74
|
*/
|
|
131
75
|
const compareSpecificity = (a, b) => {
|
|
132
76
|
const maxLen = Math.max(a.length, b.length);
|
|
@@ -145,12 +89,12 @@ const compareSpecificity = (a, b) => {
|
|
|
145
89
|
/**
|
|
146
90
|
* Find the most specific matching route for a method + path.
|
|
147
91
|
*
|
|
148
|
-
* Scans
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
92
|
+
* Scans EVERY route, not just the first structural match, so registration order
|
|
93
|
+
* can never let a broad dynamic route (`:branch`) shadow a narrower,
|
|
94
|
+
* differently-guarded static one (`assets`) registered later. Exported
|
|
95
|
+
* standalone so that precedence rule can be unit-tested against synthetic route
|
|
96
|
+
* tables, independent of the real API surface.
|
|
97
|
+
* @internal Exported for tests.
|
|
154
98
|
*/
|
|
155
99
|
export function matchRoute(routes, method, segments) {
|
|
156
100
|
const upperMethod = method.toUpperCase();
|
|
@@ -161,24 +105,18 @@ export function matchRoute(routes, method, segments) {
|
|
|
161
105
|
const match = matchPattern(route.pattern, segments);
|
|
162
106
|
if (!match)
|
|
163
107
|
continue;
|
|
164
|
-
// Among all routes that match this request, pick the most specific one
|
|
165
|
-
// (static segments beat :params, which beat ...catchalls).
|
|
166
108
|
if (!best || compareSpecificity(route.pattern, best.route.pattern) < 0) {
|
|
167
109
|
best = { route, params: match.params };
|
|
168
110
|
}
|
|
169
111
|
}
|
|
170
112
|
if (!best)
|
|
171
113
|
return null;
|
|
172
|
-
// Decode every matched param exactly once, here
|
|
173
|
-
// catch-all
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// (Zod's logicalPathSchema/branchNameSchema
|
|
177
|
-
//
|
|
178
|
-
// parseLogicalPath call) still run their own traversal validation against
|
|
179
|
-
// this same final decoded value - decoding centrally here does not skip
|
|
180
|
-
// that, it just removes the redundant second decode that used to run
|
|
181
|
-
// alongside it in a few handlers.
|
|
114
|
+
// Decode every matched param exactly once, here, uniformly for :param and
|
|
115
|
+
// catch-all alike (C5). A malformed escape MUST produce a 400, not an
|
|
116
|
+
// uncaught URIError reaching http/handler.ts's top-level catch as a 500.
|
|
117
|
+
// Decoding centrally does not replace downstream validation: each route's
|
|
118
|
+
// `validate` (Zod's logicalPathSchema/branchNameSchema) and any handler-local
|
|
119
|
+
// re-check still run their own traversal checks against this decoded value.
|
|
182
120
|
let decodedParams;
|
|
183
121
|
try {
|
|
184
122
|
decodedParams = Object.fromEntries(Object.entries(best.params).map(([key, value]) => [key, decodeURIComponent(value)]));
|
|
@@ -193,9 +131,6 @@ export function matchRoute(routes, method, segments) {
|
|
|
193
131
|
bodyFormat: best.route.bodyFormat,
|
|
194
132
|
};
|
|
195
133
|
}
|
|
196
|
-
/**
|
|
197
|
-
* Create the standard Canopy router with all API routes.
|
|
198
|
-
*/
|
|
199
134
|
export function createCanopyRouter() {
|
|
200
135
|
const routes = buildCanopyRoutes();
|
|
201
136
|
return {
|