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
|
@@ -6,19 +6,15 @@ import { canopyLogWarn } from './logger.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Shared option set for both provisioning-lock variants.
|
|
8
8
|
*
|
|
9
|
-
* **The lock is anchored on the lock file's own path, not its directory.**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* key identical to the on-disk lock identity, so two live locks can never
|
|
19
|
-
* share a key. `realpath: false` because the anchor path is the lock marker
|
|
20
|
-
* itself and does not exist before we create it (realpath would ENOENT).
|
|
21
|
-
* See docs/concurrency.md ("Anchor path matters").
|
|
9
|
+
* **The lock is anchored on the lock file's own path, not its directory.** proper-lockfile keys
|
|
10
|
+
* its module-level `locks{}` bookkeeping (refresh timer, release fn) by the TARGET path passed
|
|
11
|
+
* to `lock()`, not by `lockfilePath`, so passing a shared parent makes every branch under one
|
|
12
|
+
* root alias a single registry entry: releasing one tears down the other's refresh timer, fails
|
|
13
|
+
* its release with `ERELEASED`, leaks its lock directory until `stale` expires, and leaves an
|
|
14
|
+
* orphaned timer to `stat` a deleted path and raise `ECOMPROMISED`. Anchoring on the marker
|
|
15
|
+
* makes the registry key identical to the on-disk lock identity, so two live locks can never
|
|
16
|
+
* share one. `realpath: false` because that anchor path does not exist before we create it
|
|
17
|
+
* (realpath would ENOENT). See docs/concurrency.md ("Anchor path matters").
|
|
22
18
|
*/
|
|
23
19
|
function provisioningLockOptions(lockPath, retries, onCompromised) {
|
|
24
20
|
return {
|
|
@@ -26,30 +22,27 @@ function provisioningLockOptions(lockPath, retries, onCompromised) {
|
|
|
26
22
|
realpath: false,
|
|
27
23
|
retries,
|
|
28
24
|
stale: 30_000,
|
|
29
|
-
// proper-lockfile invokes this from inside its refresh timer, so ANY throw
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// vitest's `onConsoleLog` turns any console write into a throw.
|
|
25
|
+
// proper-lockfile invokes this from inside its refresh timer, so ANY throw escaping here is
|
|
26
|
+
// an uncaught exception that kills the process. Call sites are told not to throw (see
|
|
27
|
+
// OnLockCompromised); this makes it structural rather than a convention, and also covers the
|
|
28
|
+
// LOGGER throwing -- under `CI=true`, vitest's `onConsoleLog` turns a console write into a
|
|
29
|
+
// throw.
|
|
35
30
|
onCompromised: (err) => {
|
|
36
31
|
try {
|
|
37
32
|
if (onCompromised) {
|
|
38
33
|
onCompromised(err);
|
|
39
34
|
return;
|
|
40
35
|
}
|
|
41
|
-
// Default: log and let the holder finish. proper-lockfile's own default
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// holders may now be live": it must be visible without CANOPYCMS_DEBUG.
|
|
36
|
+
// Default: log and let the holder finish. proper-lockfile's own default rethrows from
|
|
37
|
+
// the refresh timer, which protects nothing -- by the time a compromise is reported the
|
|
38
|
+
// mutual exclusion is already gone. `canopyLogWarn`, not the debug logger, because "two
|
|
39
|
+
// holders may now be live" must be visible without CANOPYCMS_DEBUG.
|
|
46
40
|
canopyLogWarn(`[canopy] Provisioning lock compromised mid-hold for ${lockPath}:`, getErrorMessage(err));
|
|
47
41
|
}
|
|
48
42
|
catch {
|
|
49
|
-
// Last resort: a raw stderr write goes around both a replaced logger
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// to throw out of a refresh timer.
|
|
43
|
+
// Last resort: a raw stderr write goes around both a replaced logger and vitest's console
|
|
44
|
+
// interception, so the compromise still leaves a trace. Guarded in turn, because nothing
|
|
45
|
+
// here is allowed to throw out of a refresh timer.
|
|
53
46
|
try {
|
|
54
47
|
process.stderr.write(`[canopy] lock compromised for ${lockPath}; its handler or logger threw\n`);
|
|
55
48
|
}
|
|
@@ -61,12 +54,11 @@ function provisioningLockOptions(lockPath, retries, onCompromised) {
|
|
|
61
54
|
};
|
|
62
55
|
}
|
|
63
56
|
/**
|
|
64
|
-
* proper-lockfile's release rejects with `ERELEASED` once the lock has been
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* that
|
|
68
|
-
*
|
|
69
|
-
* let every other release error propagate to the caller that asked for it.
|
|
57
|
+
* proper-lockfile's release rejects with `ERELEASED` once the lock has been marked compromised
|
|
58
|
+
* (`setLockAsCompromised` sets `released = true` before the caller gets to release). Callers hold
|
|
59
|
+
* this in a `finally`, so letting it escape converts a COMPLETED operation into a spurious
|
|
60
|
+
* failure. Nothing is left to release in that state, so swallow just that code and let every
|
|
61
|
+
* other release error propagate.
|
|
70
62
|
*/
|
|
71
63
|
function releaseIgnoringAlreadyReleased(release, lockPath) {
|
|
72
64
|
return async () => {
|
|
@@ -75,9 +67,8 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
|
|
|
75
67
|
}
|
|
76
68
|
catch (err) {
|
|
77
69
|
if (isNodeError(err) && err.code === 'ERELEASED') {
|
|
78
|
-
// Deliberately does not name a cause: ERELEASED is raised both by a
|
|
79
|
-
//
|
|
80
|
-
// from here the two are indistinguishable.
|
|
70
|
+
// Deliberately does not name a cause: ERELEASED is raised both by a lock compromised
|
|
71
|
+
// mid-hold and by a plain double-release bug, indistinguishable from here.
|
|
81
72
|
canopyLogWarn(`[canopy] Lock at ${lockPath} was already released (compromised mid-hold, or released twice); nothing to release`);
|
|
82
73
|
return;
|
|
83
74
|
}
|
|
@@ -86,51 +77,40 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
|
|
|
86
77
|
};
|
|
87
78
|
}
|
|
88
79
|
/**
|
|
89
|
-
* Acquire a cross-process filesystem lock for content provisioning.
|
|
80
|
+
* Acquire a cross-process filesystem lock for content provisioning. Returns a release function —
|
|
81
|
+
* always call it in a `finally`.
|
|
90
82
|
*
|
|
91
|
-
* In-memory locks
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* processes, so only one provisions a given resource at a time while the others
|
|
97
|
-
* wait and then find it already done.
|
|
83
|
+
* In-memory locks serialize only within one process, but build tools such as Next.js static
|
|
84
|
+
* generation provision content from parallel worker *processes*, which then race while creating
|
|
85
|
+
* the same git workspace ("cannot mkdir remote.git: File exists", "destination path already
|
|
86
|
+
* exists"). proper-lockfile's atomic on-disk lock holds across processes, so one provisions while
|
|
87
|
+
* the others wait and then find it already done.
|
|
98
88
|
*
|
|
99
|
-
* Returns a release function — always call it in a `finally`.
|
|
100
|
-
*
|
|
101
|
-
* @param lockTargetDir directory the lock marker lives in (created if missing)
|
|
102
|
-
* @param lockName name of the on-disk lock marker, created inside lockTargetDir
|
|
103
89
|
* @param onCompromised see {@link OnLockCompromised}; defaults to log-and-continue
|
|
104
90
|
*/
|
|
105
91
|
export async function acquireProvisioningLock(lockTargetDir, lockName, onCompromised) {
|
|
106
92
|
await fs.mkdir(lockTargetDir, { recursive: true });
|
|
107
93
|
const lockPath = path.join(lockTargetDir, lockName);
|
|
108
|
-
// Generous, jittered retries: several processes may contend for one workspace
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// process actually dies.
|
|
94
|
+
// Generous, jittered retries: several processes may contend for one workspace (Lambda
|
|
95
|
+
// containers cold-starting together against one EFS root), and the holder can take seconds to
|
|
96
|
+
// init plus clone/push. `randomize` de-syncs the herd so a waiter is not perpetually colliding
|
|
97
|
+
// on the same tick. `stale` stays modest because proper-lockfile auto-refreshes a live holder's
|
|
98
|
+
// lock, so it expires only when a process actually dies.
|
|
114
99
|
const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true }, onCompromised));
|
|
115
100
|
return releaseIgnoringAlreadyReleased(release, lockPath);
|
|
116
101
|
}
|
|
117
102
|
/**
|
|
118
|
-
* Zero-retry variant of {@link acquireProvisioningLock}, for admin actions
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* for a live provisioner to finish; an admin request must fail fast on
|
|
122
|
-
* contention instead (409 immediately) rather than hang the request for that
|
|
123
|
-
* long.
|
|
103
|
+
* Zero-retry variant of {@link acquireProvisioningLock}, for admin actions inside a synchronous
|
|
104
|
+
* request/response cycle (a Lambda-backed API handler). `acquireProvisioningLock`'s ~600-retry
|
|
105
|
+
* budget waits minutes for a live provisioner; an admin request must fail fast instead.
|
|
124
106
|
*
|
|
125
|
-
* `stale: 30_000` is unchanged from the patient variant: a genuinely stale
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* which reads this same lock's mtime to decide whether it is fresh or
|
|
130
|
-
* stale before an admin purge/repair proceeds).
|
|
107
|
+
* `stale: 30_000` is unchanged from the patient variant: a genuinely stale lock (holder crashed
|
|
108
|
+
* more than 30s ago) is still taken over normally. Only the RETRY loop for live contention is
|
|
109
|
+
* removed, not the staleness recovery a caller depends on -- see branch-health.ts's [H1]
|
|
110
|
+
* freshness rail, which reads this lock's mtime before an admin purge/repair proceeds.
|
|
131
111
|
*
|
|
132
|
-
* Throws with `err.code === 'ELOCKED'` on contention (a live, non-stale
|
|
133
|
-
*
|
|
112
|
+
* Throws with `err.code === 'ELOCKED'` on contention (a live, non-stale holder) -- callers
|
|
113
|
+
* translate that into a 409.
|
|
134
114
|
*/
|
|
135
115
|
export async function tryAcquireProvisioningLock(lockTargetDir, lockName, onCompromised) {
|
|
136
116
|
await fs.mkdir(lockTargetDir, { recursive: true });
|
|
@@ -14,9 +14,8 @@ export declare function declaresScheme(url: string): boolean;
|
|
|
14
14
|
* Whether `url`, resolved as a URL reference against a fixed sentinel origin, lands on a
|
|
15
15
|
* DIFFERENT origin than the sentinel WITHOUT itself declaring a scheme.
|
|
16
16
|
*
|
|
17
|
-
* True for a literal protocol-relative reference (`//host`) and for every
|
|
18
|
-
*
|
|
19
|
-
* `\/host`, and combinations with stripped whitespace/control characters) -- these all read as
|
|
17
|
+
* True for a literal protocol-relative reference (`//host`) and for every backslash-equivalent
|
|
18
|
+
* spelling a browser resolves the same way (see `declaresScheme`) -- these all read as
|
|
20
19
|
* "site-relative" to a naive string check but actually redefine the authority. False for a
|
|
21
20
|
* normal site-relative path (`/about`, `docs/guide`) and for a scheme-qualified absolute URL
|
|
22
21
|
* (`declaresScheme` already identifies those as intentionally off-site on their own).
|
|
@@ -25,6 +24,7 @@ export declare function declaresScheme(url: string): boolean;
|
|
|
25
24
|
* (e.g. `utils/url-prefix.ts`'s `isAbsoluteUrl`, for a CDN-hosted `ogImage`) should check that
|
|
26
25
|
* separately and only fall back to this function to catch the spellings that slip past a
|
|
27
26
|
* `startsWith('//')` check.
|
|
27
|
+
* @internal Exported for tests.
|
|
28
28
|
*/
|
|
29
29
|
export declare function isImplicitlyOffOrigin(url: string): boolean;
|
|
30
30
|
/**
|
|
@@ -36,7 +36,7 @@ export declare function isImplicitlyOffOrigin(url: string): boolean;
|
|
|
36
36
|
* slash/backslash characters: WHATWG URL also strips tabs, newlines, and carriage returns during
|
|
37
37
|
* parsing (wherever they appear, not just at the edges), so `/\t/evil.com` is exactly as
|
|
38
38
|
* off-origin as `/\evil.com` despite having no leading backslash-or-slash run for a regex to
|
|
39
|
-
* find.
|
|
39
|
+
* find.
|
|
40
40
|
*
|
|
41
41
|
* Useful for a caller that wants to keep emitting SOME value for an implicitly-off-origin input
|
|
42
42
|
* rather than rejecting it outright (`sanitizeHref` rejects to a fallback instead; see
|
|
@@ -102,12 +102,7 @@ export declare function isHttpUrlOrSameOriginPath(value: string, opts?: {
|
|
|
102
102
|
* likely to be a paste error or an injection attempt than an intentional
|
|
103
103
|
* protocol-relative link, so we don't let it through as an absolute
|
|
104
104
|
* off-site URL. This is enforced by checking whether the input DECLARES a
|
|
105
|
-
* scheme, not by matching a `//` prefix
|
|
106
|
-
* equivalent to slash for special schemes, so `/\evil.com`, `\\evil.com`
|
|
107
|
-
* and `\/evil.com` are all protocol-relative in effect. An earlier version
|
|
108
|
-
* of this function checked `startsWith('//')` and let all three through as
|
|
109
|
-
* `https://evil.com/` -- an open redirect out of the one function whose
|
|
110
|
-
* job is to prevent exactly that.
|
|
105
|
+
* scheme, not by matching a `//` prefix -- see `declaresScheme`.
|
|
111
106
|
* - Fragment-only (`#section`) and query-only (`?q=1`) input resolves
|
|
112
107
|
* against the sentinel with pathname `/`; we strip that synthetic leading
|
|
113
108
|
* slash so the result stays a same-page reference (`#section`) instead of
|
|
@@ -35,9 +35,8 @@ export function declaresScheme(url) {
|
|
|
35
35
|
* Whether `url`, resolved as a URL reference against a fixed sentinel origin, lands on a
|
|
36
36
|
* DIFFERENT origin than the sentinel WITHOUT itself declaring a scheme.
|
|
37
37
|
*
|
|
38
|
-
* True for a literal protocol-relative reference (`//host`) and for every
|
|
39
|
-
*
|
|
40
|
-
* `\/host`, and combinations with stripped whitespace/control characters) -- these all read as
|
|
38
|
+
* True for a literal protocol-relative reference (`//host`) and for every backslash-equivalent
|
|
39
|
+
* spelling a browser resolves the same way (see `declaresScheme`) -- these all read as
|
|
41
40
|
* "site-relative" to a naive string check but actually redefine the authority. False for a
|
|
42
41
|
* normal site-relative path (`/about`, `docs/guide`) and for a scheme-qualified absolute URL
|
|
43
42
|
* (`declaresScheme` already identifies those as intentionally off-site on their own).
|
|
@@ -46,6 +45,7 @@ export function declaresScheme(url) {
|
|
|
46
45
|
* (e.g. `utils/url-prefix.ts`'s `isAbsoluteUrl`, for a CDN-hosted `ogImage`) should check that
|
|
47
46
|
* separately and only fall back to this function to catch the spellings that slip past a
|
|
48
47
|
* `startsWith('//')` check.
|
|
48
|
+
* @internal Exported for tests.
|
|
49
49
|
*/
|
|
50
50
|
export function isImplicitlyOffOrigin(url) {
|
|
51
51
|
if (declaresScheme(url))
|
|
@@ -66,7 +66,7 @@ export function isImplicitlyOffOrigin(url) {
|
|
|
66
66
|
* slash/backslash characters: WHATWG URL also strips tabs, newlines, and carriage returns during
|
|
67
67
|
* parsing (wherever they appear, not just at the edges), so `/\t/evil.com` is exactly as
|
|
68
68
|
* off-origin as `/\evil.com` despite having no leading backslash-or-slash run for a regex to
|
|
69
|
-
* find.
|
|
69
|
+
* find.
|
|
70
70
|
*
|
|
71
71
|
* Useful for a caller that wants to keep emitting SOME value for an implicitly-off-origin input
|
|
72
72
|
* rather than rejecting it outright (`sanitizeHref` rejects to a fallback instead; see
|
|
@@ -197,12 +197,7 @@ export function isHttpUrlOrSameOriginPath(value, opts = {}) {
|
|
|
197
197
|
* likely to be a paste error or an injection attempt than an intentional
|
|
198
198
|
* protocol-relative link, so we don't let it through as an absolute
|
|
199
199
|
* off-site URL. This is enforced by checking whether the input DECLARES a
|
|
200
|
-
* scheme, not by matching a `//` prefix
|
|
201
|
-
* equivalent to slash for special schemes, so `/\evil.com`, `\\evil.com`
|
|
202
|
-
* and `\/evil.com` are all protocol-relative in effect. An earlier version
|
|
203
|
-
* of this function checked `startsWith('//')` and let all three through as
|
|
204
|
-
* `https://evil.com/` -- an open redirect out of the one function whose
|
|
205
|
-
* job is to prevent exactly that.
|
|
200
|
+
* scheme, not by matching a `//` prefix -- see `declaresScheme`.
|
|
206
201
|
* - Fragment-only (`#section`) and query-only (`?q=1`) input resolves
|
|
207
202
|
* against the sentinel with pathname `/`; we strip that synthetic leading
|
|
208
203
|
* slash so the result stays a same-page reference (`#section`) instead of
|
|
@@ -232,14 +227,9 @@ export function sanitizeHref(url, fallback = '#') {
|
|
|
232
227
|
return fallback;
|
|
233
228
|
}
|
|
234
229
|
// Reject protocol-relative references in EVERY spelling, by testing the
|
|
235
|
-
// property we actually care about rather than by enumerating syntax
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
// backslash as equivalent to a forward slash for special schemes, so
|
|
239
|
-
// '/\evil.com', '\\evil.com' and '\/evil.com' are all protocol-relative
|
|
240
|
-
// in effect and each resolved to https://evil.com/ while sailing past a
|
|
241
|
-
// startsWith('//') guard. Tabs and newlines are stripped during parsing
|
|
242
|
-
// too, so the set of spellings is not one you can enumerate confidently.
|
|
230
|
+
// property we actually care about rather than by enumerating syntax (see
|
|
231
|
+
// `declaresScheme`: a startsWith('//') guard misses the backslash
|
|
232
|
+
// spellings, and stripped tabs/newlines make the set unenumerable).
|
|
243
233
|
//
|
|
244
234
|
// The property that actually distinguishes "the author asked for another
|
|
245
235
|
// origin" from "the parser inferred one" is whether the input DECLARES a
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import type { FieldConfig } from '../config/index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Recurses into object fields
|
|
5
|
-
*
|
|
3
|
+
* Value of the field marked `isTitle: true`, or undefined when there is none or it is not a
|
|
4
|
+
* string. Recurses into object fields, so a nested title (`hero.title`) is found.
|
|
5
|
+
* @internal Exported for tests.
|
|
6
6
|
*/
|
|
7
7
|
export declare function extractTitleFromSchema(fields: readonly FieldConfig[], data: Record<string, unknown>): string | undefined;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Convert a slug like "my-cool-page" to "My Cool Page".
|
|
10
|
+
* @internal Exported for tests.
|
|
11
|
+
*/
|
|
9
12
|
export declare function humanizeSlug(slug: string): string;
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 5. "Untitled"
|
|
14
|
+
* Display title for an entry, by fallback chain: schema-marked isTitle field, then `data.title`
|
|
15
|
+
* or `data.name`, then the entry type label, then the humanized slug, then "Untitled".
|
|
16
|
+
*
|
|
17
|
+
* Client-safe — its only dependency on `../config` is type-only — and re-exported from both
|
|
18
|
+
* `canopycms/server` and the root `canopycms` entry, so adopter client code can import it.
|
|
17
19
|
*/
|
|
18
20
|
export declare function resolveEntryTitle(data: Record<string, unknown>, options?: {
|
|
19
21
|
schema?: readonly FieldConfig[];
|
|
@@ -21,19 +23,14 @@ export declare function resolveEntryTitle(data: Record<string, unknown>, options
|
|
|
21
23
|
slug?: string;
|
|
22
24
|
}): string;
|
|
23
25
|
/**
|
|
24
|
-
* Count
|
|
25
|
-
*
|
|
26
|
-
* Used for validation — at most one field per schema should be marked.
|
|
26
|
+
* Count fields marked `isTitle: true`, recursing into objects but skipping `list: true` ones,
|
|
27
|
+
* whose array values runtime title extraction cannot resolve. Validation allows at most one.
|
|
27
28
|
*/
|
|
28
29
|
export declare function countTitleFields(fields: readonly FieldConfig[]): number;
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* Returns an array of field names that have isTitle on a non-string type.
|
|
31
|
+
* Dotted paths of isTitle fields whose type is not `string`. Skips `list: true` objects —
|
|
32
|
+
* `findTitleFieldsInLists` catches those.
|
|
33
33
|
*/
|
|
34
34
|
export declare function findInvalidTitleFields(fields: readonly FieldConfig[], parentPath?: string): string[];
|
|
35
|
-
/**
|
|
36
|
-
* Find isTitle fields inside `list: true` object fields, where they can never resolve.
|
|
37
|
-
* Returns dotted paths of such fields.
|
|
38
|
-
*/
|
|
35
|
+
/** Dotted paths of isTitle fields inside `list: true` objects, where they can never resolve. */
|
|
39
36
|
export declare function findTitleFieldsInLists(fields: readonly FieldConfig[], parentPath?: string): string[];
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
/** Type guard: true if field is an inline group (type === 'group'). */
|
|
2
1
|
function isGroupField(field) {
|
|
3
2
|
return field.type === 'group';
|
|
4
3
|
}
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Recurses into object fields
|
|
8
|
-
*
|
|
5
|
+
* Value of the field marked `isTitle: true`, or undefined when there is none or it is not a
|
|
6
|
+
* string. Recurses into object fields, so a nested title (`hero.title`) is found.
|
|
7
|
+
* @internal Exported for tests.
|
|
9
8
|
*/
|
|
10
9
|
export function extractTitleFromSchema(fields, data) {
|
|
11
10
|
return findTitleValue(fields, data);
|
|
@@ -34,7 +33,10 @@ function findTitleValue(fields, data) {
|
|
|
34
33
|
}
|
|
35
34
|
return undefined;
|
|
36
35
|
}
|
|
37
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Convert a slug like "my-cool-page" to "My Cool Page".
|
|
38
|
+
* @internal Exported for tests.
|
|
39
|
+
*/
|
|
38
40
|
export function humanizeSlug(slug) {
|
|
39
41
|
return slug
|
|
40
42
|
.replace(/[-_]/g, ' ')
|
|
@@ -42,34 +44,28 @@ export function humanizeSlug(slug) {
|
|
|
42
44
|
.trim();
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* 5. "Untitled"
|
|
47
|
+
* Display title for an entry, by fallback chain: schema-marked isTitle field, then `data.title`
|
|
48
|
+
* or `data.name`, then the entry type label, then the humanized slug, then "Untitled".
|
|
49
|
+
*
|
|
50
|
+
* Client-safe — its only dependency on `../config` is type-only — and re-exported from both
|
|
51
|
+
* `canopycms/server` and the root `canopycms` entry, so adopter client code can import it.
|
|
51
52
|
*/
|
|
52
53
|
export function resolveEntryTitle(data, options) {
|
|
53
|
-
// 1. Schema-marked isTitle field
|
|
54
54
|
if (options?.schema) {
|
|
55
55
|
const schemaTitle = extractTitleFromSchema(options.schema, data);
|
|
56
56
|
if (schemaTitle)
|
|
57
57
|
return schemaTitle;
|
|
58
58
|
}
|
|
59
|
-
// 2. Convention: data.title or data.name
|
|
60
59
|
const title = data.title ?? data.name;
|
|
61
60
|
if (typeof title === 'string')
|
|
62
61
|
return title;
|
|
63
|
-
// 3. Entry type label
|
|
64
62
|
if (options?.entryTypeLabel)
|
|
65
63
|
return options.entryTypeLabel;
|
|
66
|
-
// 4. Humanized slug
|
|
67
64
|
return options?.slug ? humanizeSlug(options.slug) : 'Untitled';
|
|
68
65
|
}
|
|
69
66
|
/**
|
|
70
|
-
* Count
|
|
71
|
-
*
|
|
72
|
-
* Used for validation — at most one field per schema should be marked.
|
|
67
|
+
* Count fields marked `isTitle: true`, recursing into objects but skipping `list: true` ones,
|
|
68
|
+
* whose array values runtime title extraction cannot resolve. Validation allows at most one.
|
|
73
69
|
*/
|
|
74
70
|
export function countTitleFields(fields) {
|
|
75
71
|
let count = 0;
|
|
@@ -88,9 +84,8 @@ export function countTitleFields(fields) {
|
|
|
88
84
|
return count;
|
|
89
85
|
}
|
|
90
86
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* Returns an array of field names that have isTitle on a non-string type.
|
|
87
|
+
* Dotted paths of isTitle fields whose type is not `string`. Skips `list: true` objects —
|
|
88
|
+
* `findTitleFieldsInLists` catches those.
|
|
94
89
|
*/
|
|
95
90
|
export function findInvalidTitleFields(fields, parentPath) {
|
|
96
91
|
const invalid = [];
|
|
@@ -110,10 +105,7 @@ export function findInvalidTitleFields(fields, parentPath) {
|
|
|
110
105
|
}
|
|
111
106
|
return invalid;
|
|
112
107
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Find isTitle fields inside `list: true` object fields, where they can never resolve.
|
|
115
|
-
* Returns dotted paths of such fields.
|
|
116
|
-
*/
|
|
108
|
+
/** Dotted paths of isTitle fields inside `list: true` objects, where they can never resolve. */
|
|
117
109
|
export function findTitleFieldsInLists(fields, parentPath) {
|
|
118
110
|
const found = [];
|
|
119
111
|
for (const field of fields) {
|
|
@@ -124,7 +116,6 @@ export function findTitleFieldsInLists(fields, parentPath) {
|
|
|
124
116
|
const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
|
|
125
117
|
if (field.type === 'object' && 'fields' in field && field.fields) {
|
|
126
118
|
if (field.list) {
|
|
127
|
-
// Any isTitle inside a list object is invalid — collect them
|
|
128
119
|
found.push(...collectAllTitleFields(field.fields, fieldPath));
|
|
129
120
|
}
|
|
130
121
|
else {
|
|
@@ -135,7 +126,7 @@ export function findTitleFieldsInLists(fields, parentPath) {
|
|
|
135
126
|
}
|
|
136
127
|
return found;
|
|
137
128
|
}
|
|
138
|
-
/**
|
|
129
|
+
/** Every isTitle field below this point, for the list context where all of them are invalid. */
|
|
139
130
|
function collectAllTitleFields(fields, parentPath) {
|
|
140
131
|
const found = [];
|
|
141
132
|
for (const field of fields) {
|
|
@@ -3,56 +3,28 @@ import type { ContentId, Slug } from '../paths/types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse a Canopy content filename into its `{type}.{slug}.{id}.{ext}` parts.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* The slug may itself contain dots (`getting.started.guide`), so type and ID anchor the split:
|
|
7
|
+
* the ID is the second-to-last dot-separated segment, the slug is everything between the type and
|
|
8
|
+
* it (lowercased), and the extension is stripped. The ID is a 12-character Base58 content ID, and
|
|
9
|
+
* a would-be ID segment that fails `isValidId` rejects the whole parse.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* everything between the type and the ID. The returned `slug` is
|
|
14
|
-
* lowercased.
|
|
15
|
-
* - `id` — a 12-character Base58 content ID (`generateId()`/`isValidId()`).
|
|
16
|
-
* Base58 excludes the ambiguous characters `0`, `O`, `I`, `l` so IDs are
|
|
17
|
-
* unambiguous when read aloud or hand-transcribed. A filename whose
|
|
18
|
-
* would-be ID segment doesn't pass `isValidId` is rejected — the whole
|
|
19
|
-
* parse returns `null`, even if the rest of the shape looks right.
|
|
20
|
-
* - `ext` — the format extension (`.md`, `.mdx`, `.json`, `.yaml`), stripped
|
|
21
|
-
* before parsing and not part of the returned result.
|
|
11
|
+
* `filename` must be a bare filename, and **that precondition is not enforced**: the parser splits
|
|
12
|
+
* purely on `.`, so a `/` or `\` is neither rejected nor treated as special and becomes part of
|
|
13
|
+
* whichever segment it falls in (`'foo/bar.slug.<validId>.md'` gives `type: 'foo/bar'`). Strip the
|
|
14
|
+
* directory component yourself (`path.basename`) — every internal caller does. A leading-dot
|
|
15
|
+
* filename is always rejected, matching `extractEntryTypeFromFilename` in `content-id-index.ts`.
|
|
22
16
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* for adopter code that needs to recover `{type, slug, id}` from a
|
|
35
|
-
* filename without having a schema/entry-types list on hand (e.g. a
|
|
36
|
-
* filesystem walk over content for tooling or diagnostics). Even without
|
|
37
|
-
* `entryTypes`, a leading-dot filename (dotfile, editor swap/backup file)
|
|
38
|
-
* is always rejected — an empty string is never a legal type, matching the
|
|
39
|
-
* `filename.startsWith('.')` guard `extractEntryTypeFromFilename` in
|
|
40
|
-
* `content-id-index.ts` already applies.
|
|
41
|
-
* @returns `{ type, slug, id }`, or `null` if `filename` doesn't match the
|
|
42
|
-
* `{type}.{slug}.{id}.{ext}` shape (too few segments, no extension, a
|
|
43
|
-
* leading dot, an invalid ID, or — when `entryTypes` is given — an
|
|
44
|
-
* unrecognized type). `id` is validated (`isValidId`) and safe to trust. **`slug` is
|
|
45
|
-
* not** — it is the raw dot-joined middle segment(s), lowercased, cast to the branded
|
|
46
|
-
* `Slug` type without running `parseSlug`'s validation. A filename with an
|
|
47
|
-
* unconventional slug segment (e.g. containing a space, or a dot) still parses and still
|
|
48
|
-
* receives the `Slug` brand. Callers that need a validated slug must run the result
|
|
49
|
-
* through `parseSlug` themselves; this function's contract is "split the filename
|
|
50
|
-
* grammar apart," not "validate every part." `listCollectionEntries` (content-listing.ts)
|
|
51
|
-
* is exactly such a caller that does NOT re-validate — it lists the file and computes a
|
|
52
|
-
* `urlPath` for it regardless — so a dotted slug listed this way builds and gets
|
|
53
|
-
* advertised (sitemap, `generateStaticParams`) but can never be read back through
|
|
54
|
-
* `readByUrlPath`, which does run every URL-resolution candidate through `parseSlug`.
|
|
55
|
-
* `static/index.ts`'s `assertRoutableSlugs` is the build-time guard that catches this.
|
|
17
|
+
* `entryTypes`, when given, restricts the `type` segment to those entry types — how
|
|
18
|
+
* `listCollectionEntries` filters out files that do not belong to the collection. Omit it to
|
|
19
|
+
* parse structurally, for adopter code recovering `{type, slug, id}` with no schema on hand.
|
|
20
|
+
*
|
|
21
|
+
* `id` is validated and safe to trust. **`slug` is not** — it is the raw dot-joined middle,
|
|
22
|
+
* lowercased and cast to the branded `Slug` without `parseSlug`'s validation, so a slug holding a
|
|
23
|
+
* space or a dot still parses and still carries the brand. Callers needing a validated slug run
|
|
24
|
+
* `parseSlug` themselves. `listCollectionEntries` does NOT, so a dotted slug is listed and
|
|
25
|
+
* advertised (sitemap, `generateStaticParams`) yet can never be read back through `readByUrlPath`,
|
|
26
|
+
* which runs every candidate through `parseSlug`; `static/index.ts`'s `assertRoutableSlugs` is the
|
|
27
|
+
* build-time guard for that.
|
|
56
28
|
*/
|
|
57
29
|
export declare const parseTypedFilename: (filename: string, entryTypes?: readonly EntryTypeConfig[]) => {
|
|
58
30
|
type: string;
|