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
|
@@ -1,60 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [SYNC-C1] Cross-host mutual exclusion between the worker's rebase loop and
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Production runs a Lambda (API) and an EC2 worker over one EFS filesystem
|
|
6
|
-
* `
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* ## Why the lock marker lives under `{branchRoot}/.canopy-meta`
|
|
31
|
-
*
|
|
32
|
-
* proper-lockfile keys its module-level `locks{}` bookkeeping (refresh timer,
|
|
33
|
-
* release function) by the **target path** passed to `lock()`, NOT by
|
|
34
|
-
* `lockfilePath`. `provisioning-lock.ts` now anchors every lock on its own
|
|
35
|
-
* marker path, so the registry key equals the on-disk lock identity and two
|
|
36
|
-
* live locks can no longer share a key -- aliasing is structurally impossible
|
|
37
|
-
* rather than merely avoided by convention. Keeping the content lock under
|
|
38
|
-
* `{branchRoot}/.canopy-meta` still buys:
|
|
39
|
-
*
|
|
40
|
-
* - a per-branch marker, so one branch's content lock is independent of every
|
|
41
|
-
* other branch's and of that branch's provisioning lock; and
|
|
42
|
-
* - no deadlock potential: the two locks are never both required, and the only
|
|
43
|
-
* possible acquisition order (provision, then write) is consistent.
|
|
44
|
-
*
|
|
45
|
-
* `.canopy-meta/` is git-excluded in every branch clone (`ensureGitExclude`),
|
|
46
|
-
* so the lock directory can never dirty the working tree or be swept into
|
|
47
|
-
* `git add .` at publish time.
|
|
48
|
-
*
|
|
49
|
-
* ## Honest caveat (do not oversell this)
|
|
50
|
-
*
|
|
51
|
-
* `proper-lockfile` decides a holder is dead by reading the lock directory's
|
|
52
|
-
* mtime with `fs.stat`, which on EFS is served through the NFS attribute
|
|
53
|
-
* cache. A live holder refreshes every `stale/2`, but a waiter can read a
|
|
54
|
-
* cached mtime and conclude the lock is stale when it is not, taking it over.
|
|
55
|
-
* The failure mode of a bad takeover is exactly today's behavior (two
|
|
56
|
-
* unsynchronized writers), so this is a strict improvement -- not a proof of
|
|
57
|
-
* mutual exclusion. See docs/concurrency.md.
|
|
2
|
+
* [SYNC-C1] Cross-host mutual exclusion between the worker's rebase loop and content writes
|
|
3
|
+
* against the same branch working tree. docs/concurrency.md has the layering.
|
|
4
|
+
*
|
|
5
|
+
* Production runs a Lambda (API) and an EC2 worker over one EFS filesystem, so `ContentStore`'s
|
|
6
|
+
* in-process mutex (`utils/async-mutex`) does not cover the worker's rebase (`rebaseOneBranch` in
|
|
7
|
+
* worker/rebase.ts) rewriting the same tree. A write landing after `git rebase` started is
|
|
8
|
+
* destroyed either way -- `git checkout --theirs` overwrites it and the rebase then SUCCEEDS
|
|
9
|
+
* silently, or `git rebase --abort` hard-resets the tree -- and the editor already got its 200.
|
|
10
|
+
*
|
|
11
|
+
* **Asymmetric by design.** The worker retries every branch on its next sync cycle (~5 minutes)
|
|
12
|
+
* while the editor is a person waiting on a save, so the worker yields and the writer waits:
|
|
13
|
+
*
|
|
14
|
+
* - Worker: {@link tryAcquireContentWriteLock} -- zero retries, skips the branch this cycle.
|
|
15
|
+
* - Writers: {@link withContentWriteLock} -- a short bounded wait, then
|
|
16
|
+
* {@link ContentWriteLockBusyError}, which `ContentStore` maps to `BranchSyncingError` and the
|
|
17
|
+
* API to a 409.
|
|
18
|
+
*
|
|
19
|
+
* **Reads never take this lock.** An extra EFS round-trip on every read is not an acceptable
|
|
20
|
+
* price, and a read racing a rebase gets an older or newer file, never a destroyed one.
|
|
21
|
+
*
|
|
22
|
+
* The marker lives under `{branchRoot}/.canopy-meta` and the lock anchors on that marker path like
|
|
23
|
+
* every other lock (see provisioning-lock.ts), so it can never alias the branch's provisioning
|
|
24
|
+
* lock; the two are never both required and the only possible order (provision, then write) is
|
|
25
|
+
* consistent, so they cannot deadlock. `.canopy-meta/` is git-excluded in every branch clone
|
|
26
|
+
* (`ensureGitExclude`), so the lock directory cannot dirty the tree or be swept into `git add .`.
|
|
27
|
+
*
|
|
28
|
+
* Mutual exclusion is not proven: on EFS a stale cached mtime lets a waiter take over a live lock,
|
|
29
|
+
* leaving two unsynchronized writers -- the unlocked behaviour, so still a strict improvement.
|
|
58
30
|
*/
|
|
59
31
|
import path from 'node:path';
|
|
60
32
|
import { getErrorMessage, isNodeError } from './error.js';
|
|
@@ -63,42 +35,33 @@ import { tryAcquireProvisioningLock } from './provisioning-lock.js';
|
|
|
63
35
|
/** Directory the lock marker lives in, relative to the branch root. */
|
|
64
36
|
const META_DIR = '.canopy-meta';
|
|
65
37
|
/** On-disk name of the lock marker (a directory, created by mkdir). */
|
|
66
|
-
|
|
38
|
+
const CONTENT_WRITE_LOCK_NAME = 'content-write.lock';
|
|
67
39
|
/**
|
|
68
40
|
* Default bounded wait for a content write.
|
|
69
41
|
*
|
|
70
|
-
* Deliberately short. A rebase holds this lock for a fetch
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* where in-process serialization used to be per-entry -- bounded on Lambda
|
|
82
|
-
* (one invocation per container), but real under `next dev` and build-time
|
|
83
|
-
* provisioning across worker processes. The case to watch is a write whose
|
|
84
|
-
* in-lock path triggers a full `idIndex()` rescan of a large tree over EFS:
|
|
85
|
-
* that can plausibly exceed this budget and start 409ing unrelated saves.
|
|
86
|
-
* Making it configurable for prod tuning is tracked in
|
|
42
|
+
* Deliberately short. A rebase holds this lock for a fetch, a replay and N conflict rounds of git
|
|
43
|
+
* subprocesses on EFS -- longer than an interactive save can absorb -- so the wait is not sized to
|
|
44
|
+
* outlast one. It absorbs lock handoff and other writers' short holds, and turns everything longer
|
|
45
|
+
* into a fast, explicit "retry" rather than a request hanging toward the Lambda timeout; the
|
|
46
|
+
* worker revisits the branch next cycle, so the blocked state is transient.
|
|
47
|
+
*
|
|
48
|
+
* It is ALSO the writer-vs-writer budget: the lock is per-branch-root, so every write to one
|
|
49
|
+
* branch serializes behind it -- bounded on Lambda (one invocation per container), but real under
|
|
50
|
+
* `next dev` and build-time provisioning across worker processes. The case to watch is a write
|
|
51
|
+
* whose in-lock path triggers a full `idIndex()` rescan of a large tree over EFS, which can exceed
|
|
52
|
+
* this budget and start 409ing unrelated saves. Making it configurable is tracked in
|
|
87
53
|
* .claude/future-tasks/content-write-lock-tuning-and-granularity.md.
|
|
88
54
|
*/
|
|
89
55
|
export const DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2000;
|
|
90
56
|
/**
|
|
91
|
-
* Thrown when the bounded wait expires with the branch's content lock still
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* may not be happening. `api/content.ts` routes this error ahead of the
|
|
100
|
-
* generic conflict specifically so the editor sees this wording, which makes
|
|
101
|
-
* the wording load-bearing rather than cosmetic.
|
|
57
|
+
* Thrown when the bounded wait expires with the branch's content lock still held. Retriable --
|
|
58
|
+
* callers translate it into a 409 with a message that says so.
|
|
59
|
+
*
|
|
60
|
+
* The message says "syncing OR another save" because BOTH produce it: the lock is taken by
|
|
61
|
+
* `write`/`delete`/`renameEntry` and the admin repair-content-duplicates action as well as by the
|
|
62
|
+
* worker's rebase, so naming only the rebase would claim more than is known. `api/content.ts`
|
|
63
|
+
* routes this error ahead of the generic conflict so the editor sees this wording, making it
|
|
64
|
+
* load-bearing rather than cosmetic.
|
|
102
65
|
*/
|
|
103
66
|
export class ContentWriteLockBusyError extends Error {
|
|
104
67
|
constructor(message = 'This branch is busy (syncing with the base branch, or another save is in flight); the change was not saved. Try again in a moment.') {
|
|
@@ -115,11 +78,8 @@ const sleep = (ms) => new Promise((resolve) => {
|
|
|
115
78
|
setTimeout(resolve, ms);
|
|
116
79
|
});
|
|
117
80
|
/**
|
|
118
|
-
* Acquire the branch's content-write lock WITHOUT waiting
|
|
119
|
-
*
|
|
120
|
-
* Throws an error with `code === 'ELOCKED'` when a live holder has it. Used by
|
|
121
|
-
* the worker's rebase loop (which skips the branch and retries next cycle) and
|
|
122
|
-
* by tests standing in for a writer.
|
|
81
|
+
* Acquire the branch's content-write lock WITHOUT waiting, for the worker's rebase loop, which
|
|
82
|
+
* skips the branch and retries next cycle. Throws with `code === 'ELOCKED'` on a live holder.
|
|
123
83
|
*/
|
|
124
84
|
export function tryAcquireContentWriteLock(branchRoot, onCompromised) {
|
|
125
85
|
return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised);
|
|
@@ -127,14 +87,13 @@ export function tryAcquireContentWriteLock(branchRoot, onCompromised) {
|
|
|
127
87
|
/**
|
|
128
88
|
* Acquire the branch's content-write lock with a short bounded wait.
|
|
129
89
|
*
|
|
130
|
-
* Retries ONLY on genuine contention (`ELOCKED`), the same discipline
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* the branch directory was deleted out from under the caller.
|
|
90
|
+
* Retries ONLY on genuine contention (`ELOCKED`), the same discipline `withOccFileLock` uses:
|
|
91
|
+
* proper-lockfile's own retry loop retries blindly on any error, burning the whole budget
|
|
92
|
+
* re-hitting e.g. ENOENT after the branch directory was deleted under the caller.
|
|
134
93
|
*
|
|
135
94
|
* @throws ContentWriteLockBusyError when the budget expires under contention.
|
|
136
95
|
*/
|
|
137
|
-
|
|
96
|
+
async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, onCompromised) {
|
|
138
97
|
const startedAt = Date.now();
|
|
139
98
|
for (let attempt = 0;; attempt++) {
|
|
140
99
|
try {
|
|
@@ -146,23 +105,20 @@ export async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTE
|
|
|
146
105
|
const remaining = waitMs - (Date.now() - startedAt);
|
|
147
106
|
if (remaining <= 0)
|
|
148
107
|
throw new ContentWriteLockBusyError();
|
|
149
|
-
// Exponential backoff with jitter, capped so a long budget still polls
|
|
150
|
-
//
|
|
108
|
+
// Exponential backoff with jitter, capped so a long budget still polls often enough to
|
|
109
|
+
// pick the lock up promptly when the holder releases.
|
|
151
110
|
const base = Math.min(40 * 2 ** attempt, 250);
|
|
152
111
|
await sleep(Math.min(base * (0.5 + Math.random()), remaining));
|
|
153
112
|
}
|
|
154
113
|
}
|
|
155
114
|
}
|
|
156
|
-
/**
|
|
157
|
-
*
|
|
158
|
-
* `finally` so a throw can never strand it.
|
|
159
|
-
*/
|
|
115
|
+
/** Run `fn` holding the branch's content-write lock, releasing in a `finally` so a throw cannot
|
|
116
|
+
* strand it. */
|
|
160
117
|
export async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS) {
|
|
161
|
-
// A compromise means this write may
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
// to (409) and let the caller re-save against the settled tree.
|
|
118
|
+
// A compromise means this write may not have been exclusive -- the worker's rebase, or another
|
|
119
|
+
// writer, could have been running against the same tree. Reporting success would silently
|
|
120
|
+
// accept a possibly-raced write, so surface the same retriable "branch busy" error (409) that
|
|
121
|
+
// contention maps to and let the caller re-save against the settled tree.
|
|
166
122
|
let compromised = false;
|
|
167
123
|
const release = await acquireContentWriteLock(branchRoot, waitMs, (err) => {
|
|
168
124
|
compromised = true;
|
|
@@ -176,11 +132,10 @@ export async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONT
|
|
|
176
132
|
await release();
|
|
177
133
|
}
|
|
178
134
|
if (compromised) {
|
|
179
|
-
// Deliberately NOT the default "was not saved" message: `fn()` completed,
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
// caller's own landed write as a phantom editor collision.
|
|
135
|
+
// Deliberately NOT the default "was not saved" message: `fn()` completed, so the write is on
|
|
136
|
+
// disk and only the proof of exclusivity is lost. "Reload, then decide" rather than "retry",
|
|
137
|
+
// which would resend a now-stale expectedVersion and bounce off the caller's own landed write
|
|
138
|
+
// as a phantom editor collision.
|
|
184
139
|
throw new ContentWriteLockBusyError('This branch was being synced while your change was written, so the change may or may not have been recorded. Reload the entry to see the current state before saving again.');
|
|
185
140
|
}
|
|
186
141
|
return result;
|
package/dist/utils/debug.d.ts
CHANGED
|
@@ -15,40 +15,24 @@ export declare class DebugLogger {
|
|
|
15
15
|
constructor(options?: DebugOptions);
|
|
16
16
|
private shouldLog;
|
|
17
17
|
/**
|
|
18
|
-
* The ISO-8601 timestamp leads the line BARE
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* leading `[` meant every debug line was silently folded into the previous
|
|
24
|
-
* event instead of starting its own. The bracketed `[prefix:category]` and
|
|
25
|
-
* `[level]` fields still follow, so the human-readable shape is unchanged
|
|
26
|
-
* apart from those two characters.
|
|
18
|
+
* The ISO-8601 timestamp leads the line BARE, never bracketed. `CANOPYCMS_DEBUG=true` can
|
|
19
|
+
* be set on the EC2 worker, whose stdout appends to /var/log/canopy-worker/worker.log, and
|
|
20
|
+
* the CloudWatch agent's `multi_line_start_pattern` matches a timestamp at the START of a
|
|
21
|
+
* line (see worker/log.ts's INVARIANT and cms-service.ts's agent config): any leading
|
|
22
|
+
* character folds the line into the previous event instead of starting its own.
|
|
27
23
|
*/
|
|
28
24
|
private formatMessage;
|
|
29
25
|
debug(category: string, message: string, data?: unknown): void;
|
|
30
26
|
info(category: string, message: string, data?: unknown): void;
|
|
31
27
|
warn(category: string, message: string, data?: unknown): void;
|
|
32
28
|
error(category: string, message: string, data?: unknown): void;
|
|
33
|
-
/**
|
|
34
|
-
* Start timing an operation
|
|
35
|
-
*/
|
|
36
29
|
time(label: string): void;
|
|
37
|
-
/**
|
|
38
|
-
* End timing an operation and log the duration
|
|
39
|
-
*/
|
|
40
30
|
timeEnd(category: string, label: string): number | undefined;
|
|
41
|
-
/**
|
|
42
|
-
* Wrap an async function with automatic timing
|
|
43
|
-
*/
|
|
44
31
|
timed<T>(category: string, label: string, fn: () => Promise<T>): Promise<T>;
|
|
45
32
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Create a debug logger instance
|
|
48
|
-
*/
|
|
49
33
|
export declare function createDebugLogger(options?: DebugOptions): DebugLogger;
|
|
50
34
|
/**
|
|
51
|
-
* Default logger for test infrastructure
|
|
52
|
-
*
|
|
35
|
+
* Default logger for E2E test infrastructure.
|
|
36
|
+
* @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md.
|
|
53
37
|
*/
|
|
54
38
|
export declare const testLogger: DebugLogger;
|
package/dist/utils/debug.js
CHANGED
|
@@ -18,15 +18,11 @@ export class DebugLogger {
|
|
|
18
18
|
return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* The ISO-8601 timestamp leads the line BARE
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* leading `[` meant every debug line was silently folded into the previous
|
|
27
|
-
* event instead of starting its own. The bracketed `[prefix:category]` and
|
|
28
|
-
* `[level]` fields still follow, so the human-readable shape is unchanged
|
|
29
|
-
* apart from those two characters.
|
|
21
|
+
* The ISO-8601 timestamp leads the line BARE, never bracketed. `CANOPYCMS_DEBUG=true` can
|
|
22
|
+
* be set on the EC2 worker, whose stdout appends to /var/log/canopy-worker/worker.log, and
|
|
23
|
+
* the CloudWatch agent's `multi_line_start_pattern` matches a timestamp at the START of a
|
|
24
|
+
* line (see worker/log.ts's INVARIANT and cms-service.ts's agent config): any leading
|
|
25
|
+
* character folds the line into the previous event instead of starting its own.
|
|
30
26
|
*/
|
|
31
27
|
formatMessage(level, category, message) {
|
|
32
28
|
const timestamp = new Date().toISOString();
|
|
@@ -59,15 +55,9 @@ export class DebugLogger {
|
|
|
59
55
|
throw new Error(errorMsg);
|
|
60
56
|
}
|
|
61
57
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Start timing an operation
|
|
64
|
-
*/
|
|
65
58
|
time(label) {
|
|
66
59
|
this.timers.set(label, Date.now());
|
|
67
60
|
}
|
|
68
|
-
/**
|
|
69
|
-
* End timing an operation and log the duration
|
|
70
|
-
*/
|
|
71
61
|
timeEnd(category, label) {
|
|
72
62
|
const start = this.timers.get(label);
|
|
73
63
|
if (start === undefined) {
|
|
@@ -79,9 +69,6 @@ export class DebugLogger {
|
|
|
79
69
|
this.debug(category, `${label} completed`, { durationMs: duration });
|
|
80
70
|
return duration;
|
|
81
71
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Wrap an async function with automatic timing
|
|
84
|
-
*/
|
|
85
72
|
async timed(category, label, fn) {
|
|
86
73
|
this.time(label);
|
|
87
74
|
try {
|
|
@@ -92,15 +79,12 @@ export class DebugLogger {
|
|
|
92
79
|
}
|
|
93
80
|
}
|
|
94
81
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Create a debug logger instance
|
|
97
|
-
*/
|
|
98
82
|
export function createDebugLogger(options) {
|
|
99
83
|
return new DebugLogger(options);
|
|
100
84
|
}
|
|
101
85
|
/**
|
|
102
|
-
* Default logger for test infrastructure
|
|
103
|
-
*
|
|
86
|
+
* Default logger for E2E test infrastructure.
|
|
87
|
+
* @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md.
|
|
104
88
|
*/
|
|
105
89
|
export const testLogger = createDebugLogger({
|
|
106
90
|
enabled: process.env.E2E_DEBUG === 'true',
|
|
@@ -1,33 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared entry URL computation
|
|
3
|
-
*
|
|
4
|
-
* This module has NO server-only dependencies (no node:fs, etc.)
|
|
5
|
-
* so it can be safely imported into browser bundles.
|
|
2
|
+
* Shared entry URL computation, used by server and client code alike. No server-only
|
|
3
|
+
* dependencies, so it is safe to import into browser bundles.
|
|
6
4
|
*/
|
|
7
5
|
/**
|
|
8
|
-
* Is this slug the collection-index slug?
|
|
6
|
+
* Is this slug the collection-index slug? The shared home for that decision — every forward and
|
|
7
|
+
* reverse URL rule routes through it (see utils/AGENTS.md), and it is re-exported from
|
|
8
|
+
* `canopycms/server` for adopters.
|
|
9
9
|
*
|
|
10
|
-
* The
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* entries for a single-segment route. Re-exported from `canopycms/server` for adopters. (`content-tree.ts`'s `indexEntry` lookup still does its own
|
|
15
|
-
* `slug === 'index'`; it is fed slugs already lowercased by the listing, so it is safe rather
|
|
16
|
-
* than shared.) Slug matching is case-insensitive throughout
|
|
17
|
-
* CanopyCMS (`parseSlug` lowercases, and `ContentStore` resolves slugs by a lowercased
|
|
18
|
-
* directory scan), so this compares lowercased — a bare `slug === 'index'` is correct only
|
|
19
|
-
* for callers whose input was already normalized, and silently wrong for the ones handling
|
|
20
|
-
* raw URL segments or on-disk names. `resolveUrlPathCandidates` was exactly that case: its
|
|
21
|
-
* strict compare left `/x/Index` resolving the index entry after `/x/index` stopped.
|
|
10
|
+
* The compare is lowercased because slug matching is case-insensitive throughout CanopyCMS
|
|
11
|
+
* (`parseSlug` lowercases; `ContentStore` resolves slugs by a lowercased directory scan): a bare
|
|
12
|
+
* `slug === 'index'` is correct only where the input is already normalized, and silently wrong
|
|
13
|
+
* for callers handling raw URL segments or on-disk names.
|
|
22
14
|
*/
|
|
23
15
|
export declare function isIndexSlug(slug: string | undefined): boolean;
|
|
24
16
|
/**
|
|
25
|
-
* Compute a URL path from an entry's collection path and slug
|
|
26
|
-
*
|
|
27
|
-
* Logic:
|
|
28
|
-
* - Strip the contentRoot prefix (e.g., "content/") from the collection path
|
|
29
|
-
* - Append the slug (unless it's "index", which collapses to the parent path)
|
|
30
|
-
* - Always returns a path starting with "/"
|
|
17
|
+
* Compute a URL path from an entry's collection path and slug: strip the `contentRoot` prefix,
|
|
18
|
+
* append the slug unless it is an index slug, lowercase. Always starts with `/`.
|
|
31
19
|
*
|
|
32
20
|
* Examples:
|
|
33
21
|
* ("content/posts", "hello-world", "content") => "/posts/hello-world"
|
package/dist/utils/entry-url.js
CHANGED
|
@@ -1,36 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared entry URL computation
|
|
3
|
-
*
|
|
4
|
-
* This module has NO server-only dependencies (no node:fs, etc.)
|
|
5
|
-
* so it can be safely imported into browser bundles.
|
|
2
|
+
* Shared entry URL computation, used by server and client code alike. No server-only
|
|
3
|
+
* dependencies, so it is safe to import into browser bundles.
|
|
6
4
|
*/
|
|
7
5
|
import { trimSlashes } from '../paths/normalize.js';
|
|
8
6
|
/**
|
|
9
|
-
* Is this slug the collection-index slug?
|
|
7
|
+
* Is this slug the collection-index slug? The shared home for that decision — every forward and
|
|
8
|
+
* reverse URL rule routes through it (see utils/AGENTS.md), and it is re-exported from
|
|
9
|
+
* `canopycms/server` for adopters.
|
|
10
10
|
*
|
|
11
|
-
* The
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* entries for a single-segment route. Re-exported from `canopycms/server` for adopters. (`content-tree.ts`'s `indexEntry` lookup still does its own
|
|
16
|
-
* `slug === 'index'`; it is fed slugs already lowercased by the listing, so it is safe rather
|
|
17
|
-
* than shared.) Slug matching is case-insensitive throughout
|
|
18
|
-
* CanopyCMS (`parseSlug` lowercases, and `ContentStore` resolves slugs by a lowercased
|
|
19
|
-
* directory scan), so this compares lowercased — a bare `slug === 'index'` is correct only
|
|
20
|
-
* for callers whose input was already normalized, and silently wrong for the ones handling
|
|
21
|
-
* raw URL segments or on-disk names. `resolveUrlPathCandidates` was exactly that case: its
|
|
22
|
-
* strict compare left `/x/Index` resolving the index entry after `/x/index` stopped.
|
|
11
|
+
* The compare is lowercased because slug matching is case-insensitive throughout CanopyCMS
|
|
12
|
+
* (`parseSlug` lowercases; `ContentStore` resolves slugs by a lowercased directory scan): a bare
|
|
13
|
+
* `slug === 'index'` is correct only where the input is already normalized, and silently wrong
|
|
14
|
+
* for callers handling raw URL segments or on-disk names.
|
|
23
15
|
*/
|
|
24
16
|
export function isIndexSlug(slug) {
|
|
25
17
|
return slug?.toLowerCase() === 'index';
|
|
26
18
|
}
|
|
27
19
|
/**
|
|
28
|
-
* Compute a URL path from an entry's collection path and slug
|
|
29
|
-
*
|
|
30
|
-
* Logic:
|
|
31
|
-
* - Strip the contentRoot prefix (e.g., "content/") from the collection path
|
|
32
|
-
* - Append the slug (unless it's "index", which collapses to the parent path)
|
|
33
|
-
* - Always returns a path starting with "/"
|
|
20
|
+
* Compute a URL path from an entry's collection path and slug: strip the `contentRoot` prefix,
|
|
21
|
+
* append the slug unless it is an index slug, lowercase. Always starts with `/`.
|
|
34
22
|
*
|
|
35
23
|
* Examples:
|
|
36
24
|
* ("content/posts", "hello-world", "content") => "/posts/hello-world"
|
|
@@ -39,7 +27,6 @@ export function isIndexSlug(slug) {
|
|
|
39
27
|
*/
|
|
40
28
|
export function computeEntryUrl(collection, slug, contentRoot) {
|
|
41
29
|
const root = trimSlashes(contentRoot);
|
|
42
|
-
// Strip contentRoot prefix
|
|
43
30
|
let stripped = collection;
|
|
44
31
|
if (root && collection.startsWith(`${root}/`)) {
|
|
45
32
|
stripped = collection.slice(root.length + 1);
|
|
@@ -47,9 +34,7 @@ export function computeEntryUrl(collection, slug, contentRoot) {
|
|
|
47
34
|
else if (collection === root) {
|
|
48
35
|
stripped = '';
|
|
49
36
|
}
|
|
50
|
-
// Build URL segments
|
|
51
37
|
const segments = stripped.split('/').filter(Boolean);
|
|
52
|
-
// Append slug unless it's an index slug (index entries collapse to parent)
|
|
53
38
|
if (slug && !isIndexSlug(slug)) {
|
|
54
39
|
segments.push(slug);
|
|
55
40
|
}
|
package/dist/utils/error.d.ts
CHANGED
|
@@ -1,103 +1,38 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* These utilities help convert `catch (err: unknown)` to usable error information
|
|
5
|
-
* without using `any` types.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Extract a message string from an unknown error value.
|
|
9
|
-
*
|
|
10
|
-
* @param err - The caught error (unknown type)
|
|
11
|
-
* @returns A string message suitable for logging or user display
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* try {
|
|
16
|
-
* await riskyOperation()
|
|
17
|
-
* } catch (err: unknown) {
|
|
18
|
-
* canopyLogError('Operation failed:', getErrorMessage(err))
|
|
19
|
-
* }
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
1
|
+
/** Reading a `catch (err: unknown)` value as usable error information, without `any`. */
|
|
2
|
+
/** Message string for an unknown thrown value. */
|
|
22
3
|
export declare function getErrorMessage(err: unknown): string;
|
|
23
4
|
/**
|
|
24
|
-
* Redact sensitive material from an error message before
|
|
25
|
-
*
|
|
5
|
+
* Redact sensitive material from an error message before it reaches an API client. Log the
|
|
6
|
+
* ORIGINAL message server-side; send the sanitized one.
|
|
26
7
|
*
|
|
27
|
-
* Git/filesystem
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* -
|
|
8
|
+
* Git/filesystem error text is unbounded (stderr varies by git version and locale, and hooks
|
|
9
|
+
* print anything), so this redacts the known-sensitive SHAPES rather than enumerating safe
|
|
10
|
+
* messages: credentials embedded in URLs (`https://x-access-token:tok@github.com/…`) and
|
|
11
|
+
* absolute filesystem paths (workspace roots, EFS mounts, home directories). Paths under the
|
|
12
|
+
* current working directory are shortened to relative form (CMS-internal layout like
|
|
13
|
+
* `.canopy-dev/remote.git` helps debugging and is not sensitive); absolute paths outside it
|
|
14
|
+
* become `<path>`.
|
|
33
15
|
*
|
|
34
|
-
*
|
|
35
|
-
* (
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Grep `[REDACT]` to find every site that persists or surfaces raw error text on
|
|
42
|
-
* a path that reaches a browser — worker task errors folded into
|
|
43
|
-
* `worker-status.json`, `branch.json` parse failures served by the admin
|
|
44
|
-
* branch-health endpoint, rebase-failure messages, and their tests.
|
|
45
|
-
*
|
|
46
|
-
* Those sites carried the tag `[HIGH-1]`/`[MEDIUM-2]` until 2026-08-23, IDs from
|
|
47
|
-
* a review pass whose findings list was never committed — 13 occurrences across
|
|
48
|
-
* 6 files resolving to nothing. Renamed rather than deleted because the
|
|
49
|
-
* underlying rule is real and genuinely cross-file: a Node error embeds absolute
|
|
50
|
-
* paths, and a git remote URL can embed a token, so anything on that path is
|
|
51
|
-
* redacted through the function below.
|
|
52
|
-
*
|
|
53
|
-
* The tag is a grep aid, not the rule. The rule is stated at each site.
|
|
16
|
+
* TAG: `[REDACT]` — grep it for every site that persists or surfaces raw error text on a path
|
|
17
|
+
* reaching a browser (worker task errors folded into `worker-status.json`, `branch.json` parse
|
|
18
|
+
* failures served by the admin branch-health endpoint, rebase-failure messages, and their
|
|
19
|
+
* tests). A Node error embeds absolute paths and a git remote URL can embed a token, so
|
|
20
|
+
* everything on such a path goes through this function. The tag is a grep aid; the rule itself
|
|
21
|
+
* is stated at each site.
|
|
54
22
|
*/
|
|
55
23
|
export declare function sanitizeErrorMessage(message: string): string;
|
|
56
24
|
/**
|
|
57
|
-
* Redact only credential material
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* which calls this and then also redacts paths.
|
|
25
|
+
* Redact only credential material, leaving filesystem paths intact — for server-side log lines
|
|
26
|
+
* that deliberately keep full path detail for debugging but must never persist a live token.
|
|
27
|
+
* Client-facing messages go through `sanitizeErrorMessage` instead, which calls this and then
|
|
28
|
+
* also redacts paths.
|
|
62
29
|
*/
|
|
63
30
|
export declare function redactCredentials(message: string): string;
|
|
64
|
-
/**
|
|
65
|
-
* Type guard to check if an error is a Node.js system error with a code property.
|
|
66
|
-
*
|
|
67
|
-
* @param err - The caught error (unknown type)
|
|
68
|
-
* @returns True if the error has a `code` property (like ENOENT, EACCES, etc.)
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```ts
|
|
72
|
-
* try {
|
|
73
|
-
* await fs.readFile(path)
|
|
74
|
-
* } catch (err: unknown) {
|
|
75
|
-
* if (isNodeError(err) && err.code === 'ENOENT') {
|
|
76
|
-
* return null // File not found is expected
|
|
77
|
-
* }
|
|
78
|
-
* throw err
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
31
|
+
/** True for a Node system error — an `Error` carrying a `code` (ENOENT, EACCES, …). */
|
|
82
32
|
export declare function isNodeError(err: unknown): err is NodeJS.ErrnoException;
|
|
83
|
-
/**
|
|
84
|
-
* Check if an error indicates a "file not found" condition.
|
|
85
|
-
*
|
|
86
|
-
* @param err - The caught error (unknown type)
|
|
87
|
-
* @returns True if the error is ENOENT (file/directory not found)
|
|
88
|
-
*/
|
|
33
|
+
/** True for ENOENT. */
|
|
89
34
|
export declare function isNotFoundError(err: unknown): boolean;
|
|
90
|
-
/**
|
|
91
|
-
* Check if an error indicates a "permission denied" condition.
|
|
92
|
-
*
|
|
93
|
-
* @param err - The caught error (unknown type)
|
|
94
|
-
* @returns True if the error is EACCES (permission denied)
|
|
95
|
-
*/
|
|
35
|
+
/** True for EACCES. */
|
|
96
36
|
export declare function isPermissionError(err: unknown): boolean;
|
|
97
|
-
/**
|
|
98
|
-
* Check if an error indicates a "file already exists" condition.
|
|
99
|
-
*
|
|
100
|
-
* @param err - The caught error (unknown type)
|
|
101
|
-
* @returns True if the error is EEXIST (file already exists)
|
|
102
|
-
*/
|
|
37
|
+
/** True for EEXIST. */
|
|
103
38
|
export declare function isFileExistsError(err: unknown): boolean;
|