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,23 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Comment-preserving serialisation for YAML content files and md/mdx frontmatter.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Stringifying a fresh plain object (`yamlStringify(data)` / `matter.stringify(body, data)`)
|
|
5
|
+
* deletes every comment in the file, because comments live in neither the object nor that round
|
|
6
|
+
* trip. So these functions re-serialise onto the file's OWN parsed document: a node whose value
|
|
7
|
+
* did not change is left untouched, and an untouched node keeps its attached comments and its
|
|
8
|
+
* original quoting/block style. Only what actually changed is rewritten.
|
|
9
9
|
*
|
|
10
|
-
* The
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* disappears, a key the caller kept survives whether or not the schema still knows about it.
|
|
17
|
-
* Data authority stays with the payload; comments are the only thing inherited from disk. That
|
|
18
|
-
* separation is deliberate: whether a surviving key SHOULD still be there is a schema question,
|
|
19
|
-
* answered one layer up by `findUnknownKeys` (validation/entry-validator.ts) at the API
|
|
20
|
-
* boundary, not by a serialiser that has no schema.
|
|
10
|
+
* The file gets no authority over its own content. The reconciler makes the document's key set
|
|
11
|
+
* match `data` exactly — a key the caller dropped disappears, a key the caller kept survives
|
|
12
|
+
* whether or not the schema still knows about it. Data authority stays with the payload;
|
|
13
|
+
* comments are the only thing inherited from disk. Whether a surviving key SHOULD still be there
|
|
14
|
+
* is a schema question, answered one layer up by `findUnknownKeys`
|
|
15
|
+
* (validation/entry-validator.ts) at the API boundary, not by a schema-blind serialiser.
|
|
21
16
|
*/
|
|
22
17
|
import matter from 'gray-matter';
|
|
23
18
|
import { isCollection, isMap, isNode, isScalar, isSeq, parseDocument, stringify as yamlStringify, } from 'yaml';
|
|
@@ -25,20 +20,18 @@ import { isBlockStructuralKey } from '../validation/block-structural-keys.js';
|
|
|
25
20
|
/**
|
|
26
21
|
* True only for a PLAIN object — one that should be reconciled key-by-key against a YAML map.
|
|
27
22
|
*
|
|
28
|
-
* The prototype check is load-bearing
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* it exactly as the pre-fix `yaml.stringify` did.
|
|
23
|
+
* The prototype check is load-bearing. A looser "object and not an array" test classifies a class
|
|
24
|
+
* instance as a record, and the reconciler then walks its (empty) own enumerable keys and emits
|
|
25
|
+
* `{}`, silently replacing the value. `Date` is the case that occurs: HTTP payloads carry none,
|
|
26
|
+
* but `ContentStore.write` is also reachable server-side (build scripts via `createBuildCanopy`,
|
|
27
|
+
* migrations), where a date field came back as `{}`. Anything non-plain falls through to
|
|
28
|
+
* `doc.createNode`, which serialises it exactly as a plain `yaml.stringify` would.
|
|
35
29
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* failure rather than corruption if a server-side caller ever hits it.
|
|
30
|
+
* Residual, and the reason this is a prototype rather than a `toJSON` check: a PLAIN object
|
|
31
|
+
* carrying its own `toJSON` is walked as a record here while `createNode` would call `toJSON`,
|
|
32
|
+
* so the same payload can serialise differently depending on what is on disk, and a `toJSON`
|
|
33
|
+
* function value makes `createNode` throw. Unreachable over HTTP (JSON payloads carry no
|
|
34
|
+
* functions), and a loud failure rather than corruption if a server-side caller hits it.
|
|
42
35
|
*/
|
|
43
36
|
function isPlainRecord(value) {
|
|
44
37
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
@@ -51,8 +44,7 @@ function isPlainRecord(value) {
|
|
|
51
44
|
*
|
|
52
45
|
* `String(...)` mirrors how parsing into a plain object projects non-string scalar keys
|
|
53
46
|
* (`1: x` reads back as `{ '1': x }`). A non-scalar (complex) key — `? [a, b] : v` — has no
|
|
54
|
-
* record counterpart at all; those pairs are dropped
|
|
55
|
-
* stringify-a-fresh-object write did to them too.
|
|
47
|
+
* record counterpart at all; those pairs are dropped.
|
|
56
48
|
*/
|
|
57
49
|
function recordKeyOf(keyNode) {
|
|
58
50
|
if (!isScalar(keyNode))
|
|
@@ -65,8 +57,8 @@ function recordKeyOf(keyNode) {
|
|
|
65
57
|
return String(value);
|
|
66
58
|
}
|
|
67
59
|
/**
|
|
68
|
-
* Copy
|
|
69
|
-
*
|
|
60
|
+
* Copy a REPLACED node's comment metadata onto its replacement, so a changed value keeps the
|
|
61
|
+
* comments written about it.
|
|
70
62
|
*/
|
|
71
63
|
function carryComments(from, to) {
|
|
72
64
|
if (!isNode(from) || !isNode(to))
|
|
@@ -81,11 +73,10 @@ function carryComments(from, to) {
|
|
|
81
73
|
target.spaceBefore = source.spaceBefore;
|
|
82
74
|
}
|
|
83
75
|
/**
|
|
84
|
-
* Identity key for sequence alignment: the JSON form of a value.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* item simply matches nothing and falls through to positional reconciliation.
|
|
76
|
+
* Identity key for sequence alignment: the JSON form of a value. Undefined when the value cannot
|
|
77
|
+
* be keyed — a cyclic structure (reachable through YAML anchors) makes `JSON.stringify` throw,
|
|
78
|
+
* and a save must never fail because of it. An unkeyable item matches nothing and falls through
|
|
79
|
+
* to positional reconciliation.
|
|
89
80
|
*/
|
|
90
81
|
function identityKey(value) {
|
|
91
82
|
try {
|
|
@@ -112,47 +103,33 @@ function nodeIdentityKey(node) {
|
|
|
112
103
|
/**
|
|
113
104
|
* How far {@link sharesFieldEvidence} descends into nested records before giving up.
|
|
114
105
|
*
|
|
115
|
-
* Termination does not
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* - The on-disk side cannot supply the matching cycle: `existing` comes from `yaml`'s `toJSON()`,
|
|
124
|
-
* and a self-referential anchor (`- &b {self: *b}`) does not round-trip into a cyclic JS
|
|
125
|
-
* object — `toJSON` degrades the unresolvable alias to a plain `{ source }` marker (verified
|
|
126
|
-
* against the `yaml` version pinned here; the cycle case `identityKey`'s comment describes is
|
|
127
|
-
* the `JSON.stringify` hazard, not this one).
|
|
128
|
-
*
|
|
129
|
-
* So the real bound today is the finite depth of the parsed document — a guarantee owned by
|
|
130
|
-
* `yaml`, not by this module. The cap makes it local and explicit, and incidentally bounds cost
|
|
131
|
-
* on pathologically nested content. Real content bottoms out far shallower: a block is item ->
|
|
132
|
-
* `value` -> fields, and an object field inside one adds a level. Exceeding the cap simply means
|
|
133
|
-
* "no evidence found", which drops a comment rather than risking a move — the safe direction.
|
|
106
|
+
* Termination does not depend on it — the cap is defence in depth. A cyclic PAYLOAD is reachable
|
|
107
|
+
* server-side (which is why `identityKey` guards `JSON.stringify`) but cannot run away here: the
|
|
108
|
+
* descent happens only when BOTH sides are records, and the on-disk side comes from `yaml`'s
|
|
109
|
+
* `toJSON()`, which degrades an unresolvable self-referential anchor to a plain `{ source }`
|
|
110
|
+
* marker rather than a cyclic object. The real bound is the parsed document's finite depth, a
|
|
111
|
+
* guarantee owned by `yaml`; the cap makes it local and bounds cost on pathologically nested
|
|
112
|
+
* content. Exceeding it means "no evidence found", which drops a comment rather than risking a
|
|
113
|
+
* move — the safe direction.
|
|
134
114
|
*/
|
|
135
115
|
const EVIDENCE_MAX_DEPTH = 6;
|
|
136
116
|
/**
|
|
137
117
|
* Does `value` share at least one non-structural leaf with `existing` — i.e. is there any field
|
|
138
118
|
* whose value an edit left alone?
|
|
139
119
|
*
|
|
140
|
-
* Two things
|
|
120
|
+
* Two things it looks past, and both halves are required together:
|
|
141
121
|
*
|
|
142
122
|
* - **Block discriminators are not evidence.** `template` (and the inline shape's `_type`) names
|
|
143
|
-
* a block's TEMPLATE, so every `hero` on the page carries the same one
|
|
144
|
-
* check
|
|
123
|
+
* a block's TEMPLATE, so every `hero` on the page carries the same one; counting it makes the
|
|
124
|
+
* check true for any two blocks of the same kind. See `../validation/block-structural-keys`.
|
|
145
125
|
* - **A block's real fields are one level down.** The canonical shape is
|
|
146
|
-
* `{ template, value: { ...fields } }`, so comparing top-level values compares `value` whole
|
|
147
|
-
* which differs the moment ANY field in it changes
|
|
148
|
-
*
|
|
149
|
-
*
|
|
126
|
+
* `{ template, value: { ...fields } }`, so comparing top-level values compares `value` whole,
|
|
127
|
+
* which differs the moment ANY field in it changes — with the point above, that leaves a block
|
|
128
|
+
* no reachable evidence at all, i.e. drops every block comment on every edit. So nested
|
|
129
|
+
* records are descended into.
|
|
150
130
|
*
|
|
151
|
-
* Arrays are compared whole rather than element-wise
|
|
152
|
-
*
|
|
153
|
-
* evidence would be the same positional guess this module exists to refuse, just one level down.
|
|
154
|
-
* The cost is that a record whose only field is a list loses its comment when that list changes,
|
|
155
|
-
* which is the single-field residual documented on {@link looksLikeSameItem}, not a new class.
|
|
131
|
+
* Arrays are compared whole rather than element-wise: pairing elements by index to harvest
|
|
132
|
+
* evidence would be the same positional guess {@link reconcileSeq} refuses, one level down.
|
|
156
133
|
*/
|
|
157
134
|
function sharesFieldEvidence(existing, value, depth) {
|
|
158
135
|
for (const key of Object.keys(value)) {
|
|
@@ -162,9 +139,8 @@ function sharesFieldEvidence(existing, value, depth) {
|
|
|
162
139
|
continue;
|
|
163
140
|
const before = existing[key];
|
|
164
141
|
const after = value[key];
|
|
165
|
-
// Descending is only
|
|
166
|
-
//
|
|
167
|
-
// even though there is no leaf inside them to match on.
|
|
142
|
+
// Descending is only an EXTRA chance to find evidence: the whole-value comparison below still
|
|
143
|
+
// runs, so a pair of structurally-empty records ({} vs {}) still matches on identity.
|
|
168
144
|
if (depth < EVIDENCE_MAX_DEPTH &&
|
|
169
145
|
isPlainRecord(before) &&
|
|
170
146
|
isPlainRecord(after) &&
|
|
@@ -179,41 +155,26 @@ function sharesFieldEvidence(existing, value, depth) {
|
|
|
179
155
|
}
|
|
180
156
|
/**
|
|
181
157
|
* Is `value` plausibly an EDITED version of the item currently at this index, rather than a
|
|
182
|
-
* different item that merely landed on the same index?
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* block, add that one — leaves the new item sitting exactly where the old one was, and pairing
|
|
186
|
-
* them purely by index moved the old item's comments onto content they do not describe. For the
|
|
187
|
-
* comments this change exists to protect ("do not delete this block") that is worse than losing
|
|
188
|
-
* them, because a lost comment reads as a deletion in review while a moved one reads as intact.
|
|
158
|
+
* different item that merely landed on the same index? Position alone is not evidence: a save
|
|
159
|
+
* that replaces one list item wholesale leaves the new item exactly where the old one was, and
|
|
160
|
+
* {@link reconcileSeq} explains why pairing them anyway is the worse failure.
|
|
189
161
|
*
|
|
190
162
|
* Records carry usable evidence: an edit changes some fields and leaves others alone, so one
|
|
191
|
-
* surviving field value means "same item, edited". The evidence has to be a real FIELD,
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* exact direction it declared unacceptable, through the case it assumed could not arise.
|
|
199
|
-
* {@link sharesFieldEvidence} is therefore discriminator-blind and record-deep.
|
|
163
|
+
* surviving field value means "same item, edited". The evidence has to be a real FIELD, which is
|
|
164
|
+
* why {@link sharesFieldEvidence} is discriminator-blind and record-deep — `template: <name>` is
|
|
165
|
+
* a category label every block of that kind carries, so counting it pairs a block deleted from an
|
|
166
|
+
* index with the unrelated survivor that shifted onto it, migrating a "keep this verbatim"
|
|
167
|
+
* comment onto other content. One shared field is the bar, not two: raising it would take a
|
|
168
|
+
* two-field block — the common size — from "keeps its comment when one field is edited" to
|
|
169
|
+
* "never keeps it".
|
|
200
170
|
*
|
|
201
|
-
* Scalars carry no evidence
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* accepted rather than fixed: giving scalars the same treatment would drop the comment on every
|
|
205
|
-
* ordinary one-line edit, which is a large, certain loss traded against a small, speculative one.
|
|
171
|
+
* Scalars carry no evidence and keep the plain same-index rule: an edited string in a list is the
|
|
172
|
+
* common case, a short scalar annotation is far less load-bearing than a block comment, and
|
|
173
|
+
* treating scalars the same way would drop the comment on every ordinary one-line edit.
|
|
206
174
|
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* making one pair meaningless, and it is now excluded.
|
|
211
|
-
*
|
|
212
|
-
* The residuals, deliberately accepted: a record whose every field changed shares nothing, so
|
|
213
|
-
* its comment is dropped rather than risked (this now includes a block edited in ALL its fields,
|
|
214
|
-
* which previously kept its comment via the discriminator — a deliberate move toward the losing
|
|
215
|
-
* side of the trade-off); and a genuine schema field NAMED `template` or `_type` is not counted
|
|
216
|
-
* as evidence, which can only ever drop a comment, never move one.
|
|
175
|
+
* Residuals, deliberately accepted: a record whose every field changed shares nothing, so its
|
|
176
|
+
* comment is dropped rather than risked; and a genuine schema field NAMED `template` or `_type`
|
|
177
|
+
* is not counted as evidence, which can only drop a comment, never move one.
|
|
217
178
|
*/
|
|
218
179
|
function looksLikeSameItem(node, value) {
|
|
219
180
|
const existing = nodePlainValue(node);
|
|
@@ -223,9 +184,8 @@ function looksLikeSameItem(node, value) {
|
|
|
223
184
|
return sharesFieldEvidence(existing, value, 0);
|
|
224
185
|
}
|
|
225
186
|
/**
|
|
226
|
-
* Reconcile one slot of the document against the value that must occupy it, returning the node
|
|
227
|
-
*
|
|
228
|
-
* did not exist).
|
|
187
|
+
* Reconcile one slot of the document against the value that must occupy it, returning the node to
|
|
188
|
+
* put there. `existing` is the node in that slot, or null/undefined for a slot that did not exist.
|
|
229
189
|
*/
|
|
230
190
|
function reconcileNode(doc, existing, value) {
|
|
231
191
|
if (isMap(existing) && isPlainRecord(value)) {
|
|
@@ -236,38 +196,34 @@ function reconcileNode(doc, existing, value) {
|
|
|
236
196
|
reconcileSeq(doc, existing, value);
|
|
237
197
|
return existing;
|
|
238
198
|
}
|
|
239
|
-
// Unchanged scalar: return the node itself, untouched. This is the case that preserves
|
|
240
|
-
//
|
|
199
|
+
// Unchanged scalar: return the node itself, untouched. This is the case that preserves comments
|
|
200
|
+
// in practice, and it keeps the author's original quoting and block style.
|
|
241
201
|
if (isScalar(existing) && Object.is(existing.value, value))
|
|
242
202
|
return existing;
|
|
243
|
-
// Changed, or a shape change (scalar <-> collection).
|
|
244
|
-
// `scalar.value` in place
|
|
245
|
-
//
|
|
203
|
+
// Changed, or a shape change (scalar <-> collection). A fresh node rather than mutating
|
|
204
|
+
// `scalar.value` in place, which would keep the old node's representation and emit `'42'` where
|
|
205
|
+
// the number 42 was meant.
|
|
246
206
|
const fresh = doc.createNode(value);
|
|
247
207
|
// Comments move with a changed VALUE, but not off a replaced STRUCTURE. `yaml` attaches a
|
|
248
|
-
// comment written above a collection's first entry to the collection node itself
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
// KEY is unaffected either way: it lives on the pair's key node, which is never replaced here.
|
|
208
|
+
// comment written above a collection's first entry to the collection node itself, so that
|
|
209
|
+
// node's comments are about its innards; carrying them onto whatever replaces the collection
|
|
210
|
+
// puts a comment over content it does not describe, which this module treats as worse than
|
|
211
|
+
// losing it. A comment written above the KEY is unaffected: it lives on the pair's key node,
|
|
212
|
+
// which is never replaced here.
|
|
254
213
|
if (!isCollection(existing))
|
|
255
214
|
carryComments(existing, fresh);
|
|
256
215
|
return fresh;
|
|
257
216
|
}
|
|
258
217
|
/**
|
|
259
|
-
* Make a map's key set match `value` exactly.
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* keys survive; keys new to `value` are appended in `value` order, which keeps the diff of a
|
|
263
|
-
* save down to the lines that actually changed.
|
|
218
|
+
* Make a map's key set match `value` exactly. Retained pairs are reconciled in place, so their key
|
|
219
|
+
* order and the comments attached to their keys survive; keys new to `value` are appended in
|
|
220
|
+
* `value` order, keeping a save's diff down to the lines that actually changed.
|
|
264
221
|
*/
|
|
265
222
|
function reconcileMap(doc, map, value) {
|
|
266
|
-
// An explicitly-undefined key is NOT a key
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
// exactly rather than approximately.
|
|
223
|
+
// An explicitly-undefined key is NOT a key: `Object.keys` reports it but `JSON.stringify` and
|
|
224
|
+
// `yaml.stringify` omit it, so without this filter a key present on disk and set to `undefined`
|
|
225
|
+
// in the payload is rewritten as `key: null` here while the create path drops it. Same rule on
|
|
226
|
+
// both paths, so "the key set matches the payload" holds exactly rather than approximately.
|
|
271
227
|
const wanted = new Set(Object.keys(value).filter((key) => value[key] !== undefined));
|
|
272
228
|
const seen = new Set();
|
|
273
229
|
const retained = [];
|
|
@@ -291,23 +247,19 @@ function reconcileMap(doc, map, value) {
|
|
|
291
247
|
/**
|
|
292
248
|
* Make a sequence's items match `value` exactly, aligning by VALUE first and position second.
|
|
293
249
|
*
|
|
294
|
-
* A list has no item identity
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* kind ("do not delete this block") that must never end up over the wrong thing. The rules are
|
|
299
|
-
* therefore ordered from most evidence to least, and stop rather than guessing:
|
|
250
|
+
* A list has no item identity, so any alignment is a guess and the failure modes are not equally
|
|
251
|
+
* bad: losing a comment shows up in review as a deletion, silently MOVING one onto content it does
|
|
252
|
+
* not describe does not, and the comments this protects are the load-bearing kind ("do not delete
|
|
253
|
+
* this block"). The rules run from most evidence to least, and stop rather than guessing:
|
|
300
254
|
*
|
|
301
|
-
* 1. **Exact value match** — reuse that old node whole, comments and all. This
|
|
302
|
-
*
|
|
255
|
+
* 1. **Exact value match** — reuse that old node whole, comments and all. This carries a comment
|
|
256
|
+
* through a pure reorder instead of stranding it on whatever moved into its index.
|
|
303
257
|
* 2. **Same index, still unclaimed, and recognisably the same item** (`looksLikeSameItem`) —
|
|
304
|
-
* reconcile against it
|
|
258
|
+
* reconcile against it: the edit-in-place case. Same-index rather than "next unclaimed old
|
|
259
|
+
* node in order", which pairs a newly-inserted item with an unrelated deleted one whenever one
|
|
260
|
+
* save both removes and adds; and evidence as well as position, because a wholesale
|
|
261
|
+
* replacement lands on the index it replaced.
|
|
305
262
|
* 3. **Otherwise a fresh node, with no comments.**
|
|
306
|
-
*
|
|
307
|
-
* Rule 2 is deliberately same-index rather than "next unclaimed old node in order": the looser
|
|
308
|
-
* form paired a newly-inserted item with an unrelated deleted one whenever one save both removed
|
|
309
|
-
* and added an item. Position alone is still not enough, though — a wholesale replacement lands
|
|
310
|
-
* on the index it replaced — which is why rule 2 also demands evidence of identity.
|
|
311
263
|
*/
|
|
312
264
|
function reconcileSeq(doc, seq, value) {
|
|
313
265
|
const oldItems = seq.items;
|
|
@@ -342,9 +294,8 @@ function reconcileSeq(doc, seq, value) {
|
|
|
342
294
|
const matched = matches[index];
|
|
343
295
|
if (matched !== undefined)
|
|
344
296
|
return oldItems[matched];
|
|
345
|
-
// `consumed` holds OLD indices claimed by rule 1, so this asks "is the node
|
|
346
|
-
//
|
|
347
|
-
// here — a candidate cannot be taken twice.
|
|
297
|
+
// `consumed` holds OLD indices claimed by rule 1, so this asks "is the node at my index still
|
|
298
|
+
// unclaimed?". Each iteration owns a distinct index, so a candidate cannot be taken twice.
|
|
348
299
|
const candidate = index < oldItems.length && !consumed.has(index) ? oldItems[index] : undefined;
|
|
349
300
|
const sameItem = candidate !== undefined && looksLikeSameItem(candidate, item) ? candidate : undefined;
|
|
350
301
|
return reconcileNode(doc, sameItem, item);
|
|
@@ -357,9 +308,9 @@ function applyDataToDocument(doc, data) {
|
|
|
357
308
|
/**
|
|
358
309
|
* Serialise entry data as a YAML file, carrying the comments of `existingRaw` through.
|
|
359
310
|
*
|
|
360
|
-
* Falls back to a plain stringify — byte-identical to
|
|
361
|
-
* nothing to preserve (a new file) or nothing trustworthy to preserve (the bytes on disk
|
|
362
|
-
* parse). A save must not fail because the previous content was malformed.
|
|
311
|
+
* Falls back to a plain stringify — byte-identical to serialising without preservation — when
|
|
312
|
+
* there is nothing to preserve (a new file) or nothing trustworthy to preserve (the bytes on disk
|
|
313
|
+
* do not parse). A save must not fail because the previous content was malformed.
|
|
363
314
|
*/
|
|
364
315
|
export function serializeYaml(data, existingRaw) {
|
|
365
316
|
if (existingRaw === undefined)
|
|
@@ -371,16 +322,15 @@ export function serializeYaml(data, existingRaw) {
|
|
|
371
322
|
return doc.toString();
|
|
372
323
|
}
|
|
373
324
|
/**
|
|
374
|
-
* Split a file into its raw frontmatter string, or undefined when there is nothing usable.
|
|
375
|
-
*
|
|
376
|
-
* Two gray-matter hazards are handled here, both of which cost a real bug when discovered:
|
|
325
|
+
* Split a file into its raw frontmatter string, or undefined when there is nothing usable. Two
|
|
326
|
+
* gray-matter hazards:
|
|
377
327
|
*
|
|
378
328
|
* 1. **The options argument is load-bearing.** `matter(str)` with no options reads and writes a
|
|
379
|
-
* process-global content-keyed cache
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
329
|
+
* process-global content-keyed cache whose HIT returns an object that has lost `.matter`, so
|
|
330
|
+
* the second save of the same file silently sees no frontmatter and drops every comment.
|
|
331
|
+
* Passing an options object skips the cache on both sides (the `if (!options)` guard in
|
|
332
|
+
* gray-matter's index.js), which also keeps this write path from polluting that cache for
|
|
333
|
+
* everyone else.
|
|
384
334
|
* 2. **It throws on malformed frontmatter.** js-yaml raises rather than returning an error list,
|
|
385
335
|
* so a file whose bytes do not parse must not be allowed to fail the save.
|
|
386
336
|
*
|
|
@@ -401,11 +351,10 @@ function extractRawFrontmatter(raw) {
|
|
|
401
351
|
return frontmatter;
|
|
402
352
|
}
|
|
403
353
|
/**
|
|
404
|
-
* Serialise an md/mdx entry, carrying the comments of `existingRaw`'s frontmatter through.
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* trailing newline stay exactly what gray-matter would have produced.
|
|
354
|
+
* Serialise an md/mdx entry, carrying the comments of `existingRaw`'s frontmatter through. The
|
|
355
|
+
* reconciled YAML goes back through `matter.stringify` via a custom stringify engine rather than
|
|
356
|
+
* being spliced between hand-written `---` lines, so delimiters, blank lines and the trailing
|
|
357
|
+
* newline stay exactly what gray-matter would have produced.
|
|
409
358
|
*/
|
|
410
359
|
export function serializeFrontmatter(body, data, existingRaw) {
|
|
411
360
|
if (existingRaw === undefined)
|
|
@@ -1,124 +1,69 @@
|
|
|
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 { type OnLockCompromised } from './provisioning-lock.js';
|
|
60
|
-
/** On-disk name of the lock marker (a directory, created by mkdir). */
|
|
61
|
-
export declare const CONTENT_WRITE_LOCK_NAME = "content-write.lock";
|
|
62
32
|
/**
|
|
63
33
|
* Default bounded wait for a content write.
|
|
64
34
|
*
|
|
65
|
-
* Deliberately short. A rebase holds this lock for a fetch
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* where in-process serialization used to be per-entry -- bounded on Lambda
|
|
77
|
-
* (one invocation per container), but real under `next dev` and build-time
|
|
78
|
-
* provisioning across worker processes. The case to watch is a write whose
|
|
79
|
-
* in-lock path triggers a full `idIndex()` rescan of a large tree over EFS:
|
|
80
|
-
* that can plausibly exceed this budget and start 409ing unrelated saves.
|
|
81
|
-
* Making it configurable for prod tuning is tracked in
|
|
35
|
+
* Deliberately short. A rebase holds this lock for a fetch, a replay and N conflict rounds of git
|
|
36
|
+
* subprocesses on EFS -- longer than an interactive save can absorb -- so the wait is not sized to
|
|
37
|
+
* outlast one. It absorbs lock handoff and other writers' short holds, and turns everything longer
|
|
38
|
+
* into a fast, explicit "retry" rather than a request hanging toward the Lambda timeout; the
|
|
39
|
+
* worker revisits the branch next cycle, so the blocked state is transient.
|
|
40
|
+
*
|
|
41
|
+
* It is ALSO the writer-vs-writer budget: the lock is per-branch-root, so every write to one
|
|
42
|
+
* branch serializes behind it -- bounded on Lambda (one invocation per container), but real under
|
|
43
|
+
* `next dev` and build-time provisioning across worker processes. The case to watch is a write
|
|
44
|
+
* whose in-lock path triggers a full `idIndex()` rescan of a large tree over EFS, which can exceed
|
|
45
|
+
* this budget and start 409ing unrelated saves. Making it configurable is tracked in
|
|
82
46
|
* .claude/future-tasks/content-write-lock-tuning-and-granularity.md.
|
|
83
47
|
*/
|
|
84
48
|
export declare const DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2000;
|
|
85
49
|
/**
|
|
86
|
-
* Thrown when the bounded wait expires with the branch's content lock still
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* may not be happening. `api/content.ts` routes this error ahead of the
|
|
95
|
-
* generic conflict specifically so the editor sees this wording, which makes
|
|
96
|
-
* the wording load-bearing rather than cosmetic.
|
|
50
|
+
* Thrown when the bounded wait expires with the branch's content lock still held. Retriable --
|
|
51
|
+
* callers translate it into a 409 with a message that says so.
|
|
52
|
+
*
|
|
53
|
+
* The message says "syncing OR another save" because BOTH produce it: the lock is taken by
|
|
54
|
+
* `write`/`delete`/`renameEntry` and the admin repair-content-duplicates action as well as by the
|
|
55
|
+
* worker's rebase, so naming only the rebase would claim more than is known. `api/content.ts`
|
|
56
|
+
* routes this error ahead of the generic conflict so the editor sees this wording, making it
|
|
57
|
+
* load-bearing rather than cosmetic.
|
|
97
58
|
*/
|
|
98
59
|
export declare class ContentWriteLockBusyError extends Error {
|
|
99
60
|
constructor(message?: string);
|
|
100
61
|
}
|
|
101
62
|
/**
|
|
102
|
-
* Acquire the branch's content-write lock WITHOUT waiting
|
|
103
|
-
*
|
|
104
|
-
* Throws an error with `code === 'ELOCKED'` when a live holder has it. Used by
|
|
105
|
-
* the worker's rebase loop (which skips the branch and retries next cycle) and
|
|
106
|
-
* by tests standing in for a writer.
|
|
63
|
+
* Acquire the branch's content-write lock WITHOUT waiting, for the worker's rebase loop, which
|
|
64
|
+
* skips the branch and retries next cycle. Throws with `code === 'ELOCKED'` on a live holder.
|
|
107
65
|
*/
|
|
108
66
|
export declare function tryAcquireContentWriteLock(branchRoot: string, onCompromised?: OnLockCompromised): Promise<() => Promise<void>>;
|
|
109
|
-
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* Retries ONLY on genuine contention (`ELOCKED`), the same discipline
|
|
113
|
-
* `withOccFileLock` uses: proper-lockfile's own retry loop retries blindly on
|
|
114
|
-
* any error, which would burn the whole budget re-hitting e.g. ENOENT after
|
|
115
|
-
* the branch directory was deleted out from under the caller.
|
|
116
|
-
*
|
|
117
|
-
* @throws ContentWriteLockBusyError when the budget expires under contention.
|
|
118
|
-
*/
|
|
119
|
-
export declare function acquireContentWriteLock(branchRoot: string, waitMs?: number, onCompromised?: OnLockCompromised): Promise<() => Promise<void>>;
|
|
120
|
-
/**
|
|
121
|
-
* Run `fn` holding the branch's content-write lock, releasing it in a
|
|
122
|
-
* `finally` so a throw can never strand it.
|
|
123
|
-
*/
|
|
67
|
+
/** Run `fn` holding the branch's content-write lock, releasing in a `finally` so a throw cannot
|
|
68
|
+
* strand it. */
|
|
124
69
|
export declare function withContentWriteLock<T>(branchRoot: string, fn: () => Promise<T>, waitMs?: number): Promise<T>;
|