canopycms 0.0.68-int.98 → 0.1.0
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/CHANGELOG.md +2131 -0
- package/README.md +47 -63
- package/dist/ai/generate.d.ts +5 -0
- package/dist/ai/generate.js +244 -115
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/json-to-markdown.d.ts +21 -1
- package/dist/ai/json-to-markdown.js +120 -57
- package/dist/ai/references.d.ts +72 -0
- package/dist/ai/references.js +223 -0
- package/dist/ai/resolve-branch.js +9 -2
- package/dist/ai/to-plain-text.d.ts +17 -8
- package/dist/ai/to-plain-text.js +18 -15
- package/dist/ai/types.d.ts +84 -3
- package/dist/api/admin-branch-health.d.ts +24 -1
- package/dist/api/admin-branch-health.js +52 -20
- package/dist/api/admin.d.ts +23 -5
- package/dist/api/admin.js +36 -1
- package/dist/api/assets.js +88 -78
- package/dist/api/branch-create-window.d.ts +12 -0
- package/dist/api/branch-create-window.js +13 -0
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +1 -1
- package/dist/api/branch-review.d.ts +3 -3
- package/dist/api/branch-review.js +16 -7
- package/dist/api/branch-status.d.ts +16 -6
- package/dist/api/branch-status.js +57 -12
- package/dist/api/branch-withdraw.d.ts +2 -2
- package/dist/api/branch-withdraw.js +8 -3
- package/dist/api/branch.d.ts +25 -12
- package/dist/api/branch.js +164 -103
- package/dist/api/client.d.ts +78 -58
- package/dist/api/client.js +150 -117
- package/dist/api/comments.d.ts +12 -3
- package/dist/api/comments.js +118 -9
- package/dist/api/content.d.ts +9 -9
- package/dist/api/content.js +131 -22
- package/dist/api/entries-constants.d.ts +14 -3
- package/dist/api/entries-constants.js +14 -3
- package/dist/api/entries.d.ts +40 -2
- package/dist/api/entries.js +102 -6
- package/dist/api/github-sync.d.ts +19 -0
- package/dist/api/github-sync.js +50 -3
- package/dist/api/groups.d.ts +4 -4
- package/dist/api/groups.js +2 -1
- package/dist/api/guards.js +23 -3
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +1 -0
- package/dist/api/permissions.d.ts +21 -2
- package/dist/api/permissions.js +50 -3
- package/dist/api/request-deadline.d.ts +59 -0
- package/dist/api/request-deadline.js +73 -0
- package/dist/api/resolve-references.js +8 -7
- package/dist/api/route-builder.d.ts +14 -0
- package/dist/api/route-builder.js +14 -1
- package/dist/api/schema.d.ts +11 -9
- package/dist/api/schema.js +2 -0
- package/dist/api/settings-helpers.d.ts +5 -1
- package/dist/api/settings-helpers.js +10 -2
- package/dist/api/types.d.ts +18 -0
- package/dist/api/users-constants.d.ts +6 -0
- package/dist/api/users-constants.js +6 -0
- package/dist/assets/asset-url.d.ts +28 -16
- package/dist/assets/asset-url.js +51 -25
- package/dist/assets/finalize.js +3 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +153 -0
- package/dist/assets/materialize.js +402 -0
- package/dist/assets/store-local.d.ts +19 -5
- package/dist/assets/store-local.js +69 -10
- package/dist/assets/store-s3.d.ts +38 -7
- package/dist/assets/store-s3.js +128 -22
- package/dist/assets/transform-directives.d.ts +52 -13
- package/dist/assets/transform-directives.js +87 -27
- package/dist/assets/transform.d.ts +3 -3
- package/dist/assets/transform.js +13 -8
- package/dist/assets/types.d.ts +31 -4
- package/dist/auth/cache.d.ts +1 -1
- package/dist/auth/cache.js +1 -1
- package/dist/auth/caching-auth-plugin.d.ts +2 -0
- package/dist/auth/caching-auth-plugin.js +12 -2
- package/dist/auth/file-based-auth-cache.d.ts +8 -0
- package/dist/auth/file-based-auth-cache.js +18 -8
- package/dist/auth/person-name.d.ts +5 -0
- package/dist/auth/person-name.js +8 -0
- package/dist/auth/plugin.d.ts +10 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/auth/user-metadata-lookup.d.ts +21 -0
- package/dist/auth/user-metadata-lookup.js +116 -0
- package/dist/authorization/branch.d.ts +1 -1
- package/dist/authorization/branch.js +12 -13
- package/dist/authorization/groups/schema.d.ts +4 -4
- package/dist/authorization/helpers.d.ts +8 -0
- package/dist/authorization/helpers.js +8 -0
- package/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/protected-branch.d.ts +5 -4
- package/dist/authorization/protected-branch.js +7 -5
- package/dist/branch-health.d.ts +35 -8
- package/dist/branch-health.js +54 -18
- package/dist/branch-metadata-error.d.ts +27 -0
- package/dist/branch-metadata-error.js +23 -0
- package/dist/branch-metadata-file.d.ts +205 -24
- package/dist/branch-metadata-file.js +126 -25
- package/dist/branch-metadata.d.ts +45 -6
- package/dist/branch-metadata.js +142 -50
- package/dist/branch-provisioning.d.ts +167 -0
- package/dist/branch-provisioning.js +501 -0
- package/dist/branch-registry.d.ts +8 -0
- package/dist/branch-registry.js +61 -9
- package/dist/branch-schema-cache.d.ts +29 -12
- package/dist/branch-schema-cache.js +64 -21
- package/dist/branch-sparse.d.ts +23 -0
- package/dist/branch-sparse.js +118 -0
- package/dist/branch-workspace.d.ts +42 -3
- package/dist/branch-workspace.js +239 -100
- package/dist/build/asset-refs.d.ts +94 -0
- package/dist/build/asset-refs.js +267 -0
- package/dist/build/generate-ai-content.js +5 -0
- package/dist/build/inline-flight.d.ts +37 -0
- package/dist/build/inline-flight.js +142 -0
- package/dist/cli/asset-refs.d.ts +52 -0
- package/dist/cli/asset-refs.js +194 -0
- package/dist/cli/cli.d.ts +15 -1
- package/dist/cli/cli.js +8543 -3193
- package/dist/cli/configured-asset-store.d.ts +12 -0
- package/dist/cli/configured-asset-store.js +46 -0
- package/dist/cli/generate-ai-content.js +4244 -1072
- package/dist/cli/init.js +217 -126
- package/dist/cli/project-detect.d.ts +2 -0
- package/dist/cli/project-detect.js +2 -0
- package/dist/cli/template-files/Dockerfile.cms.template +13 -5
- package/dist/cli/template-files/cms-stack.ts.template +16 -13
- package/dist/client.d.ts +1 -2
- package/dist/client.js +1 -2
- package/dist/comment-store.d.ts +25 -0
- package/dist/comment-store.js +43 -8
- package/dist/config/flatten.js +2 -0
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schemas/config.d.ts +12 -29
- package/dist/config/schemas/config.js +9 -12
- package/dist/config/schemas/field.d.ts +3 -0
- package/dist/config/schemas/field.js +58 -4
- package/dist/config/schemas/media.d.ts +4 -13
- package/dist/config/schemas/media.js +5 -8
- package/dist/config/schemas/url.d.ts +2 -20
- package/dist/config/schemas/url.js +2 -25
- package/dist/config/types.d.ts +116 -22
- package/dist/config/types.js +3 -9
- package/dist/config/validation.d.ts +25 -4
- package/dist/config/validation.js +116 -3
- package/dist/content-id-index.js +2 -2
- package/dist/content-index-generation.d.ts +1 -1
- package/dist/content-index-generation.js +1 -1
- package/dist/content-listing.d.ts +10 -1
- package/dist/content-listing.js +22 -5
- package/dist/content-reader.d.ts +9 -0
- package/dist/content-reader.js +16 -11
- package/dist/content-store.d.ts +75 -12
- package/dist/content-store.js +249 -86
- package/dist/content-tree.js +2 -2
- package/dist/context.js +5 -2
- package/dist/dangling-reference-log.d.ts +17 -0
- package/dist/dangling-reference-log.js +29 -0
- package/dist/dev-content-watcher.d.ts +1 -1
- package/dist/dev-content-watcher.js +1 -1
- package/dist/editor/BranchManager.d.ts +23 -7
- package/dist/editor/BranchManager.js +119 -54
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +1 -2
- package/dist/editor/CommentsPanel.d.ts +14 -0
- package/dist/editor/CommentsPanel.js +68 -43
- package/dist/editor/Editor.d.ts +11 -5
- package/dist/editor/Editor.js +891 -218
- package/dist/editor/EditorAuthGate.d.ts +7 -1
- package/dist/editor/EditorAuthGate.js +41 -9
- package/dist/editor/EntryNavigator.d.ts +22 -0
- package/dist/editor/EntryNavigator.js +353 -105
- package/dist/editor/FormHeader.d.ts +25 -0
- package/dist/editor/FormHeader.js +82 -0
- package/dist/editor/FormRenderer.d.ts +27 -3
- package/dist/editor/FormRenderer.js +171 -76
- package/dist/editor/PreviewFrame.d.ts +40 -0
- package/dist/editor/PreviewFrame.js +215 -0
- package/dist/editor/PreviewPane.d.ts +47 -0
- package/dist/editor/PreviewPane.js +147 -0
- package/dist/editor/PreviewToolbarTools.d.ts +22 -0
- package/dist/editor/PreviewToolbarTools.js +73 -0
- package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
- package/dist/editor/admin/SystemHealthPanel.js +163 -33
- package/dist/editor/admin/useSystemHealth.d.ts +18 -0
- package/dist/editor/admin/useSystemHealth.js +70 -1
- package/dist/editor/branch-status.d.ts +17 -0
- package/dist/editor/branch-status.js +29 -0
- package/dist/editor/canopy-path.d.ts +9 -6
- package/dist/editor/canopy-path.js +8 -11
- package/dist/editor/client-reference-resolver.d.ts +43 -16
- package/dist/editor/client-reference-resolver.js +155 -64
- package/dist/editor/comments/BranchComments.d.ts +1 -1
- package/dist/editor/comments/BranchComments.js +2 -2
- package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
- package/dist/editor/comments/CloseRefusedNotice.js +7 -0
- package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
- package/dist/editor/comments/CommentThreadsPopover.js +274 -0
- package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
- package/dist/editor/comments/CommentWriteAlert.js +13 -0
- package/dist/editor/comments/EntryComments.d.ts +10 -4
- package/dist/editor/comments/EntryComments.js +14 -17
- package/dist/editor/comments/FieldWrapper.d.ts +18 -4
- package/dist/editor/comments/FieldWrapper.js +62 -28
- package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
- package/dist/editor/comments/InlineCommentThread.js +11 -47
- package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
- package/dist/editor/comments/ThreadCarousel.js +18 -21
- package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
- package/dist/editor/comments/ThreadReplyControls.js +92 -0
- package/dist/editor/comments/field-comment-context.d.ts +14 -0
- package/dist/editor/comments/field-comment-context.js +14 -0
- package/dist/editor/comments/form-comments.d.ts +48 -0
- package/dist/editor/comments/form-comments.js +178 -0
- package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
- package/dist/editor/comments/item-anchor-copy.js +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
- package/dist/editor/comments/popover-placement.d.ts +18 -0
- package/dist/editor/comments/popover-placement.js +58 -0
- package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
- package/dist/editor/comments/use-tracked-writes.js +53 -0
- package/dist/editor/components/BranchAccessList.d.ts +15 -0
- package/dist/editor/components/BranchAccessList.js +52 -0
- package/dist/editor/components/BranchLockBanner.d.ts +22 -0
- package/dist/editor/components/BranchLockBanner.js +39 -0
- package/dist/editor/components/BranchPill.d.ts +32 -0
- package/dist/editor/components/BranchPill.js +33 -0
- package/dist/editor/components/BranchesDrawer.d.ts +11 -0
- package/dist/editor/components/BranchesDrawer.js +5 -0
- package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
- package/dist/editor/components/ConfirmDeleteModal.js +3 -2
- package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
- package/dist/editor/components/DestructiveMenuItem.js +8 -0
- package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
- package/dist/editor/components/EditorCrashScreen.js +22 -0
- package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
- package/dist/editor/components/EditorErrorBoundary.js +40 -0
- package/dist/editor/components/EditorHeader.d.ts +56 -106
- package/dist/editor/components/EditorHeader.js +130 -131
- package/dist/editor/components/EditorRail.d.ts +49 -0
- package/dist/editor/components/EditorRail.js +65 -0
- package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
- package/dist/editor/components/EntryBreadcrumb.js +56 -0
- package/dist/editor/components/EntryCreateModal.d.ts +12 -7
- package/dist/editor/components/EntryCreateModal.js +51 -57
- package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
- package/dist/editor/components/NoEditPermissionNotice.js +5 -4
- package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
- package/dist/editor/components/QuickOpenPalette.js +66 -0
- package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
- package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
- package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
- package/dist/editor/components/ReasonedMenuItem.js +15 -0
- package/dist/editor/components/ReferencedByList.d.ts +11 -0
- package/dist/editor/components/ReferencedByList.js +28 -0
- package/dist/editor/components/RelativeTime.d.ts +10 -0
- package/dist/editor/components/RelativeTime.js +10 -0
- package/dist/editor/components/RenameEntryModal.d.ts +9 -1
- package/dist/editor/components/RenameEntryModal.js +19 -31
- package/dist/editor/components/RetryToastMessage.d.ts +9 -0
- package/dist/editor/components/RetryToastMessage.js +6 -0
- package/dist/editor/components/SaveAllResult.d.ts +25 -0
- package/dist/editor/components/SaveAllResult.js +86 -0
- package/dist/editor/components/SaveSplitButton.d.ts +30 -0
- package/dist/editor/components/SaveSplitButton.js +108 -0
- package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
- package/dist/editor/components/StagedChangesDrawer.js +92 -0
- package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
- package/dist/editor/components/UnavailableEntryNotice.js +9 -0
- package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
- package/dist/editor/components/UnavailableTypeMessage.js +9 -0
- package/dist/editor/components/UndoToastMessage.d.ts +18 -0
- package/dist/editor/components/UndoToastMessage.js +10 -0
- package/dist/editor/components/UserBadge.d.ts +2 -5
- package/dist/editor/components/UserBadge.js +62 -33
- package/dist/editor/components/ValidationSummary.d.ts +14 -0
- package/dist/editor/components/ValidationSummary.js +24 -0
- package/dist/editor/components/index.d.ts +2 -2
- package/dist/editor/components/index.js +2 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -2
- package/dist/editor/context/ApiClientContext.js +19 -8
- package/dist/editor/context/AssetContext.d.ts +13 -35
- package/dist/editor/context/AssetContext.js +12 -31
- package/dist/editor/context/EditorStateContext.d.ts +4 -4
- package/dist/editor/context/EditorStateContext.js +4 -4
- package/dist/editor/context/SWRProvider.d.ts +3 -3
- package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
- package/dist/editor/context/SiteMdxAllowContext.js +7 -0
- package/dist/editor/context/index.d.ts +2 -1
- package/dist/editor/context/index.js +2 -1
- package/dist/editor/contrast.d.ts +19 -0
- package/dist/editor/contrast.js +41 -0
- package/dist/editor/copy.d.ts +18 -0
- package/dist/editor/copy.js +18 -0
- package/dist/editor/editor-config.js +16 -0
- package/dist/editor/editor-utils.d.ts +34 -8
- package/dist/editor/editor-utils.js +42 -20
- package/dist/editor/entry-search.d.ts +20 -0
- package/dist/editor/entry-search.js +34 -0
- package/dist/editor/field-breadcrumb.d.ts +12 -0
- package/dist/editor/field-breadcrumb.js +85 -0
- package/dist/editor/field-props.d.ts +86 -0
- package/dist/editor/field-props.js +27 -0
- package/dist/editor/field-target.d.ts +30 -0
- package/dist/editor/field-target.js +77 -0
- package/dist/editor/fields/BlockField.d.ts +12 -0
- package/dist/editor/fields/BlockField.js +89 -73
- package/dist/editor/fields/CodeField.d.ts +2 -0
- package/dist/editor/fields/CodeField.js +6 -2
- package/dist/editor/fields/DateTimeField.d.ts +4 -29
- package/dist/editor/fields/DateTimeField.js +79 -41
- package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
- package/dist/editor/fields/ExpandAllButton.js +4 -0
- package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
- package/dist/editor/fields/FieldCrashFallback.js +30 -0
- package/dist/editor/fields/FieldLabel.d.ts +37 -0
- package/dist/editor/fields/FieldLabel.js +19 -0
- package/dist/editor/fields/ImageField.d.ts +4 -0
- package/dist/editor/fields/ImageField.js +29 -14
- package/dist/editor/fields/InlineGroupField.js +4 -3
- package/dist/editor/fields/ItemList.d.ts +42 -0
- package/dist/editor/fields/ItemList.js +130 -0
- package/dist/editor/fields/MarkdownField.d.ts +32 -0
- package/dist/editor/fields/MarkdownField.js +223 -46
- package/dist/editor/fields/MdxImageDialog.js +1 -1
- package/dist/editor/fields/NumberField.d.ts +2 -0
- package/dist/editor/fields/NumberField.js +8 -5
- package/dist/editor/fields/NumberListField.d.ts +2 -0
- package/dist/editor/fields/NumberListField.js +19 -16
- package/dist/editor/fields/ObjectField.d.ts +2 -7
- package/dist/editor/fields/ObjectField.js +15 -5
- package/dist/editor/fields/ObjectListField.d.ts +29 -0
- package/dist/editor/fields/ObjectListField.js +57 -0
- package/dist/editor/fields/ReferenceField.d.ts +2 -0
- package/dist/editor/fields/ReferenceField.js +34 -9
- package/dist/editor/fields/SelectField.d.ts +2 -0
- package/dist/editor/fields/SelectField.js +11 -2
- package/dist/editor/fields/StringListField.d.ts +4 -1
- package/dist/editor/fields/StringListField.js +22 -8
- package/dist/editor/fields/TextField.d.ts +2 -0
- package/dist/editor/fields/TextField.js +6 -2
- package/dist/editor/fields/ToggleField.d.ts +2 -0
- package/dist/editor/fields/ToggleField.js +8 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
- package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
- package/dist/editor/fields/form-expansion.d.ts +9 -0
- package/dist/editor/fields/form-expansion.js +2 -0
- package/dist/editor/fields/item-expansion.d.ts +15 -0
- package/dist/editor/fields/item-expansion.js +60 -0
- package/dist/editor/fields/list-item-keys.d.ts +8 -0
- package/dist/editor/fields/list-item-keys.js +50 -0
- package/dist/editor/fields/list-item-title.d.ts +20 -0
- package/dist/editor/fields/list-item-title.js +85 -0
- package/dist/editor/fields/markdown-export-options.d.ts +2 -0
- package/dist/editor/fields/markdown-export-options.js +8 -0
- package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
- package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
- package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
- package/dist/editor/fields/mdx-jsx-support.js +157 -69
- package/dist/editor/fields/rich-text-failures.d.ts +5 -0
- package/dist/editor/fields/rich-text-failures.js +26 -0
- package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
- package/dist/editor/fields/toolbar-wrap.js +35 -0
- package/dist/editor/form-outline.d.ts +13 -0
- package/dist/editor/form-outline.js +70 -0
- package/dist/editor/group-manager/GroupCard.d.ts +4 -1
- package/dist/editor/group-manager/GroupCard.js +12 -3
- package/dist/editor/group-manager/GroupForm.js +1 -1
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
- package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
- package/dist/editor/group-manager/MemberList.d.ts +2 -1
- package/dist/editor/group-manager/MemberList.js +15 -4
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
- package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
- package/dist/editor/group-manager/index.js +34 -5
- package/dist/editor/group-manager/types.d.ts +5 -2
- package/dist/editor/hooks/create-branch-request.d.ts +20 -0
- package/dist/editor/hooks/create-branch-request.js +54 -0
- package/dist/editor/hooks/save-all.d.ts +68 -0
- package/dist/editor/hooks/save-all.js +61 -0
- package/dist/editor/hooks/useBranchActions.d.ts +11 -1
- package/dist/editor/hooks/useBranchActions.js +42 -19
- package/dist/editor/hooks/useBranchManager.d.ts +21 -0
- package/dist/editor/hooks/useBranchManager.js +321 -125
- package/dist/editor/hooks/useBranchesData.d.ts +8 -4
- package/dist/editor/hooks/useBranchesData.js +9 -5
- package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
- package/dist/editor/hooks/useCommentSystem.js +79 -55
- package/dist/editor/hooks/useCommentsData.d.ts +11 -7
- package/dist/editor/hooks/useCommentsData.js +20 -19
- package/dist/editor/hooks/useDraftManager.d.ts +77 -3
- package/dist/editor/hooks/useDraftManager.js +887 -94
- package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
- package/dist/editor/hooks/useEditorLayout.js +98 -20
- package/dist/editor/hooks/useEntriesData.d.ts +2 -1
- package/dist/editor/hooks/useEntriesData.js +18 -4
- package/dist/editor/hooks/useEntryManager.d.ts +41 -2
- package/dist/editor/hooks/useEntryManager.js +227 -41
- package/dist/editor/hooks/useGroupManager.js +13 -16
- package/dist/editor/hooks/usePermissionManager.js +14 -6
- package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
- package/dist/editor/hooks/usePreviewMarks.js +33 -0
- package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
- package/dist/editor/hooks/useReferenceResolution.js +34 -177
- package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
- package/dist/editor/hooks/useSchemaManager.js +97 -39
- package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
- package/dist/editor/hooks/useUserMetadata.js +76 -47
- package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
- package/dist/editor/hooks/user-metadata-batcher.js +33 -0
- package/dist/editor/media/AssetCard.d.ts +6 -7
- package/dist/editor/media/AssetCard.js +24 -31
- package/dist/editor/media/AssetListRow.d.ts +16 -0
- package/dist/editor/media/AssetListRow.js +41 -0
- package/dist/editor/media/CropStep.js +6 -1
- package/dist/editor/media/MediaLibrary.d.ts +4 -3
- package/dist/editor/media/MediaLibrary.js +11 -4
- package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
- package/dist/editor/media/MediaLibraryBody.js +195 -80
- package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
- package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
- package/dist/editor/media/crop-math.d.ts +3 -4
- package/dist/editor/media/crop-math.js +11 -22
- package/dist/editor/media/editor-image-src.d.ts +8 -0
- package/dist/editor/media/editor-image-src.js +10 -0
- package/dist/editor/media/format-bytes.d.ts +2 -0
- package/dist/editor/media/format-bytes.js +23 -0
- package/dist/editor/media/media-library-prefs.d.ts +23 -0
- package/dist/editor/media/media-library-prefs.js +49 -0
- package/dist/editor/media/media-library-view.d.ts +34 -0
- package/dist/editor/media/media-library-view.js +40 -0
- package/dist/editor/media/useAssetLibrary.d.ts +30 -0
- package/dist/editor/media/useAssetLibrary.js +126 -0
- package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
- package/dist/editor/media/useStableAssetOrder.js +37 -0
- package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
- package/dist/editor/media/useThumbnailFailure.js +16 -0
- package/dist/editor/permission-manager/GroupSelector.js +2 -2
- package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
- package/dist/editor/permission-manager/PermissionEditor.js +15 -4
- package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
- package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.js +3 -3
- package/dist/editor/permission-manager/index.js +12 -3
- package/dist/editor/permission-manager/types.d.ts +5 -2
- package/dist/editor/platform.d.ts +3 -0
- package/dist/editor/platform.js +9 -0
- package/dist/editor/preview-asset-base.d.ts +9 -0
- package/dist/editor/preview-asset-base.js +17 -0
- package/dist/editor/preview-bridge.d.ts +51 -27
- package/dist/editor/preview-bridge.js +103 -144
- package/dist/editor/preview-marks.d.ts +15 -0
- package/dist/editor/preview-marks.js +74 -0
- package/dist/editor/preview-path.d.ts +12 -0
- package/dist/editor/preview-path.js +45 -1
- package/dist/editor/preview-scale.d.ts +27 -0
- package/dist/editor/preview-scale.js +34 -0
- package/dist/editor/raw-asset-base.d.ts +5 -0
- package/dist/editor/raw-asset-base.js +14 -0
- package/dist/editor/relative-time.d.ts +4 -4
- package/dist/editor/relative-time.js +16 -8
- package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
- package/dist/editor/schema-editor/CollectionEditor.js +361 -62
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
- package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
- package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
- package/dist/editor/schema-editor/collection-draft.js +40 -0
- package/dist/editor/schema-editor/index.d.ts +1 -1
- package/dist/editor/schema-label.d.ts +10 -0
- package/dist/editor/schema-label.js +17 -0
- package/dist/editor/settings-conflict.d.ts +5 -0
- package/dist/editor/settings-conflict.js +8 -0
- package/dist/editor/shell/ActivityRail.d.ts +37 -0
- package/dist/editor/shell/ActivityRail.js +91 -0
- package/dist/editor/shell/EditorShell.d.ts +17 -0
- package/dist/editor/shell/EditorShell.js +31 -0
- package/dist/editor/shell/PaneLayout.d.ts +28 -0
- package/dist/editor/shell/PaneLayout.js +116 -0
- package/dist/editor/shell/SidePanelHost.d.ts +35 -0
- package/dist/editor/shell/SidePanelHost.js +184 -0
- package/dist/editor/shell/TopBar.d.ts +12 -0
- package/dist/editor/shell/TopBar.js +14 -0
- package/dist/editor/shell/host-chrome.d.ts +16 -0
- package/dist/editor/shell/host-chrome.js +69 -0
- package/dist/editor/slug.d.ts +16 -0
- package/dist/editor/slug.js +43 -0
- package/dist/editor/theme.d.ts +2 -1
- package/dist/editor/theme.js +314 -13
- package/dist/editor/unanswered.d.ts +17 -0
- package/dist/editor/unanswered.js +26 -0
- package/dist/editor/unavailable-entry-type.d.ts +20 -0
- package/dist/editor/unavailable-entry-type.js +28 -0
- package/dist/editor/undo-removal.d.ts +57 -0
- package/dist/editor/undo-removal.js +163 -0
- package/dist/editor/user-display.d.ts +4 -0
- package/dist/editor/user-display.js +5 -0
- package/dist/editor/utils/confirm-modal.d.ts +8 -0
- package/dist/editor/utils/confirm-modal.js +12 -0
- package/dist/editor/utils/editor-errors.d.ts +19 -0
- package/dist/editor/utils/editor-errors.js +32 -0
- package/dist/editor/utils/focus-return.d.ts +56 -0
- package/dist/editor/utils/focus-return.js +106 -0
- package/dist/entry-link-resolver.d.ts +11 -1
- package/dist/entry-link-resolver.js +65 -5
- package/dist/entry-schema-registry.d.ts +3 -3
- package/dist/entry-schema-registry.js +7 -4
- package/dist/entry-schema.d.ts +49 -3
- package/dist/entry-schema.js +18 -0
- package/dist/git-manager.d.ts +172 -10
- package/dist/git-manager.js +449 -107
- package/dist/github-service.d.ts +39 -2
- package/dist/github-service.js +101 -7
- package/dist/http/handler.js +74 -9
- package/dist/http/types.d.ts +2 -0
- package/dist/http/worker-not-ready.d.ts +9 -4
- package/dist/http/worker-not-ready.js +59 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/item-keys.d.ts +46 -0
- package/dist/item-keys.js +235 -0
- package/dist/operating-mode/editor-mode-check.d.ts +15 -0
- package/dist/operating-mode/editor-mode-check.js +24 -0
- package/dist/operating-mode/mode-env.d.ts +13 -14
- package/dist/operating-mode/mode-env.js +13 -14
- package/dist/operating-mode/types.d.ts +2 -2
- package/dist/paths/branch.d.ts +0 -1
- package/dist/paths/branch.js +1 -8
- package/dist/paths/index.d.ts +1 -1
- package/dist/paths/index.js +1 -1
- package/dist/paths/validation.js +0 -2
- package/dist/preview.d.ts +8 -0
- package/dist/preview.js +7 -0
- package/dist/reference-resolver.js +1 -0
- package/dist/resolve-canopy-user.d.ts +1 -1
- package/dist/resolve-canopy-user.js +1 -1
- package/dist/schema/meta-loader.d.ts +18 -8
- package/dist/schema/meta-loader.js +42 -9
- package/dist/schema/registry-fingerprint.d.ts +14 -0
- package/dist/schema/registry-fingerprint.js +63 -0
- package/dist/schema/resolver.d.ts +7 -4
- package/dist/schema/resolver.js +7 -6
- package/dist/schema/schema-store.d.ts +7 -10
- package/dist/schema/schema-store.js +13 -11
- package/dist/schema/schema-unavailable-error.d.ts +11 -0
- package/dist/schema/schema-unavailable-error.js +15 -0
- package/dist/schema/types.d.ts +22 -0
- package/dist/schema-registry-record.d.ts +35 -0
- package/dist/schema-registry-record.js +134 -0
- package/dist/server.d.ts +79 -9
- package/dist/server.js +62 -8
- package/dist/services.d.ts +33 -5
- package/dist/services.js +155 -37
- package/dist/settings-workspace.d.ts +23 -3
- package/dist/settings-workspace.js +159 -45
- package/dist/stamp-on-publish.d.ts +36 -0
- package/dist/stamp-on-publish.js +71 -0
- package/dist/static/index.d.ts +58 -5
- package/dist/static/index.js +133 -3
- package/dist/static/seo.d.ts +5 -5
- package/dist/static/seo.js +5 -5
- package/dist/submission-attribution.d.ts +8 -0
- package/dist/submission-attribution.js +22 -0
- package/dist/system-users.d.ts +12 -0
- package/dist/system-users.js +12 -0
- package/dist/task-queue/cms-task-queue.d.ts +1 -1
- package/dist/task-queue/cms-task-queue.js +1 -1
- package/dist/task-queue/index.d.ts +1 -1
- package/dist/task-queue/index.js +1 -1
- package/dist/task-queue/task-queue.d.ts +6 -0
- package/dist/task-queue/task-queue.js +29 -0
- package/dist/task-queue/worker-status.d.ts +35 -9
- package/dist/task-queue/worker-status.js +84 -17
- package/dist/types.d.ts +90 -9
- package/dist/url-collision.js +3 -3
- package/dist/url-exclusivity-fixtures.d.ts +8 -1
- package/dist/url-exclusivity-fixtures.js +11 -1
- package/dist/user.js +2 -1
- package/dist/utils/atomic-write.js +2 -1
- package/dist/utils/base-branch.d.ts +13 -0
- package/dist/utils/base-branch.js +23 -0
- package/dist/utils/content-serialize.d.ts +15 -7
- package/dist/utils/content-serialize.js +353 -51
- package/dist/utils/content-write-lock.d.ts +6 -1
- package/dist/utils/content-write-lock.js +21 -6
- package/dist/utils/debug.d.ts +1 -1
- package/dist/utils/debug.js +5 -3
- package/dist/utils/error.js +13 -2
- package/dist/utils/escape-text.d.ts +7 -0
- package/dist/utils/escape-text.js +27 -0
- package/dist/utils/fs.d.ts +12 -4
- package/dist/utils/fs.js +18 -5
- package/dist/utils/git.d.ts +21 -1
- package/dist/utils/git.js +63 -8
- package/dist/utils/json-source-splice.d.ts +14 -0
- package/dist/utils/json-source-splice.js +120 -0
- package/dist/utils/markdown-body-splice.d.ts +39 -0
- package/dist/utils/markdown-body-splice.js +421 -0
- package/dist/utils/occ-json-write.js +18 -12
- package/dist/utils/provision-log.d.ts +25 -0
- package/dist/utils/provision-log.js +49 -0
- package/dist/utils/provisioning-lock.d.ts +32 -1
- package/dist/utils/provisioning-lock.js +78 -32
- package/dist/utils/sanitize-href.d.ts +9 -23
- package/dist/utils/sanitize-href.js +14 -28
- package/dist/utils/title-field.d.ts +10 -0
- package/dist/utils/title-field.js +47 -0
- package/dist/utils/yaml-source-splice.d.ts +58 -0
- package/dist/utils/yaml-source-splice.js +515 -0
- package/dist/validation/block-structural-keys.d.ts +1 -1
- package/dist/validation/block-structural-keys.js +1 -1
- package/dist/validation/deletion-checker.d.ts +21 -29
- package/dist/validation/deletion-checker.js +41 -117
- package/dist/validation/entry-validator.d.ts +6 -10
- package/dist/validation/entry-validator.js +19 -26
- package/dist/validation/field-traversal.d.ts +22 -0
- package/dist/validation/field-traversal.js +39 -8
- package/dist/validation/markdown-safety.d.ts +68 -0
- package/dist/validation/markdown-safety.js +596 -0
- package/dist/validation/mdx-allowlist.d.ts +55 -0
- package/dist/validation/mdx-allowlist.js +183 -0
- package/dist/validation/reference-validator.d.ts +2 -0
- package/dist/validation/reference-validator.js +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-policy.d.ts +124 -0
- package/dist/worker/branch-policy.js +243 -0
- package/dist/worker/bundle-header.d.ts +117 -0
- package/dist/worker/bundle-header.js +253 -0
- package/dist/worker/cms-worker.d.ts +236 -142
- package/dist/worker/cms-worker.js +772 -285
- package/dist/worker/gateway-egress.d.ts +43 -0
- package/dist/worker/gateway-egress.js +209 -0
- package/dist/worker/gateway-instance.d.ts +42 -0
- package/dist/worker/gateway-instance.js +129 -0
- package/dist/worker/git-process.d.ts +81 -0
- package/dist/worker/git-process.js +234 -0
- package/dist/worker/git-sync.d.ts +34 -17
- package/dist/worker/git-sync.js +394 -131
- package/dist/worker/github-auth.d.ts +9 -9
- package/dist/worker/github-auth.js +13 -15
- package/dist/worker/github-gateway-http.d.ts +62 -0
- package/dist/worker/github-gateway-http.js +977 -0
- package/dist/worker/github-gateway-wire.d.ts +590 -0
- package/dist/worker/github-gateway-wire.js +488 -0
- package/dist/worker/github-gateway.d.ts +238 -0
- package/dist/worker/github-gateway.js +726 -0
- package/dist/worker/github-mirror-upkeep.d.ts +75 -0
- package/dist/worker/github-mirror-upkeep.js +266 -0
- package/dist/worker/github-mirror.d.ts +364 -0
- package/dist/worker/github-mirror.js +1030 -0
- package/dist/worker/github-transfer.d.ts +56 -0
- package/dist/worker/github-transfer.js +444 -0
- package/dist/worker/history-rewrite.d.ts +4 -5
- package/dist/worker/history-rewrite.js +12 -17
- package/dist/worker/provisioned-workspace.d.ts +5 -5
- package/dist/worker/provisioned-workspace.js +5 -5
- package/dist/worker/rebase.d.ts +1 -1
- package/dist/worker/rebase.js +58 -54
- package/dist/worker/remote-git-maintenance.d.ts +3 -0
- package/dist/worker/remote-git-maintenance.js +12 -0
- package/dist/worker/schema-gate.d.ts +49 -0
- package/dist/worker/schema-gate.js +226 -0
- package/dist/worker/self-checks.d.ts +107 -0
- package/dist/worker/self-checks.js +358 -0
- package/dist/worker/shared-repo-git.d.ts +96 -0
- package/dist/worker/shared-repo-git.js +459 -0
- package/dist/worker/sparse-cone.d.ts +20 -0
- package/dist/worker/sparse-cone.js +136 -0
- package/dist/worker/stale-mirror.d.ts +82 -0
- package/dist/worker/stale-mirror.js +130 -0
- package/dist/worker/task-runner.d.ts +5 -3
- package/dist/worker/task-runner.js +287 -186
- package/dist/worker/worker-context.d.ts +36 -37
- package/package.json +35 -7
- package/dist/editor/EditorPanes.d.ts +0 -10
- package/dist/editor/EditorPanes.js +0 -98
- package/dist/editor/components/EditorFooter.d.ts +0 -11
- package/dist/editor/components/EditorFooter.js +0 -9
- package/dist/editor/components/EditorSidebar.d.ts +0 -67
- package/dist/editor/components/EditorSidebar.js +0 -22
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The base branch is unknown, and prod never assumes one. */
|
|
2
|
+
export class BaseBranchUnresolvedError extends Error {
|
|
3
|
+
constructor(detail) {
|
|
4
|
+
super(`CanopyCMS: ${detail} Set defaultBaseBranch in your CanopyCMS config to the branch ` +
|
|
5
|
+
'editing branches fork from.');
|
|
6
|
+
this.name = 'BaseBranchUnresolvedError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* `config`'s resolved base branch. Prod resolves an unset one from the remote's HEAD
|
|
11
|
+
* (createCanopyServices, then resolvePendingBaseBranch per request), so an unset value in prod
|
|
12
|
+
* means that has not happened yet, or the path skipped it, and it throws rather than guessing
|
|
13
|
+
* 'main'. In the other modes a config that never went through
|
|
14
|
+
* services keeps the 'main' default; services itself resolves dev's from git HEAD.
|
|
15
|
+
*/
|
|
16
|
+
export function baseBranchOf(config) {
|
|
17
|
+
if (config.defaultBaseBranch)
|
|
18
|
+
return config.defaultBaseBranch;
|
|
19
|
+
if (config.mode === 'prod') {
|
|
20
|
+
throw new BaseBranchUnresolvedError('the prod base branch has not been resolved.');
|
|
21
|
+
}
|
|
22
|
+
return 'main';
|
|
23
|
+
}
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* deletes every comment in the file, because comments live in neither the object nor that round
|
|
6
6
|
* trip. So these functions re-serialise onto the file's OWN parsed document: a node whose value
|
|
7
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
|
|
8
|
+
* original quoting/block style. Only what actually changed is rewritten, and
|
|
9
|
+
* `yaml-source-splice.ts` writes it into the file's own text, so untouched lines keep their bytes
|
|
10
|
+
* wherever it can vouch for the result.
|
|
9
11
|
*
|
|
10
12
|
* The file gets no authority over its own content. The reconciler makes the document's key set
|
|
11
13
|
* match `data` exactly — a key the caller dropped disappears, a key the caller kept survives
|
|
@@ -14,18 +16,24 @@
|
|
|
14
16
|
* is a schema question, answered one layer up by `findUnknownKeys`
|
|
15
17
|
* (validation/entry-validator.ts) at the API boundary, not by a schema-blind serialiser.
|
|
16
18
|
*/
|
|
19
|
+
import { type MarkdownBodyFormat } from './markdown-body-splice.js';
|
|
17
20
|
/**
|
|
18
21
|
* Serialise entry data as a YAML file, carrying the comments of `existingRaw` through.
|
|
19
22
|
*
|
|
20
23
|
* Falls back to a plain stringify — byte-identical to serialising without preservation — when
|
|
21
24
|
* there is nothing to preserve (a new file) or nothing trustworthy to preserve (the bytes on disk
|
|
22
|
-
* do not parse
|
|
25
|
+
* do not parse, or the reconciled document does not read back as its own data). A save must not
|
|
26
|
+
* fail because the previous content was malformed.
|
|
23
27
|
*/
|
|
24
28
|
export declare function serializeYaml(data: Record<string, unknown>, existingRaw?: string): string;
|
|
25
29
|
/**
|
|
26
|
-
* Serialise an md/mdx entry, carrying the comments of `existingRaw`'s frontmatter
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* newline stay exactly what gray-matter
|
|
30
|
+
* Serialise an md/mdx entry, carrying the comments of `existingRaw`'s frontmatter and the source
|
|
31
|
+
* text of its unchanged body blocks through. The reconciled YAML goes back through
|
|
32
|
+
* `matter.stringify` via a custom stringify engine rather than being spliced between
|
|
33
|
+
* hand-written `---` lines, so delimiters and the trailing newline stay exactly what gray-matter
|
|
34
|
+
* would have produced. The result is kept only if the read path reads `data` back from it;
|
|
35
|
+
* otherwise gray-matter writes it, without the comments. A fresh string the read path would
|
|
36
|
+
* misread is quoted (`readsAsAnotherValue`), so this is left for a changed value meeting text the
|
|
37
|
+
* two parsers read differently, as 14 meets a file's `014`.
|
|
30
38
|
*/
|
|
31
|
-
export declare function serializeFrontmatter(
|
|
39
|
+
export declare function serializeFrontmatter(editorBody: string, data: Record<string, unknown>, existingRaw: string | undefined, format: MarkdownBodyFormat): string;
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* deletes every comment in the file, because comments live in neither the object nor that round
|
|
6
6
|
* trip. So these functions re-serialise onto the file's OWN parsed document: a node whose value
|
|
7
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
|
|
8
|
+
* original quoting/block style. Only what actually changed is rewritten, and
|
|
9
|
+
* `yaml-source-splice.ts` writes it into the file's own text, so untouched lines keep their bytes
|
|
10
|
+
* wherever it can vouch for the result.
|
|
9
11
|
*
|
|
10
12
|
* The file gets no authority over its own content. The reconciler makes the document's key set
|
|
11
13
|
* match `data` exactly — a key the caller dropped disappears, a key the caller kept survives
|
|
@@ -14,9 +16,31 @@
|
|
|
14
16
|
* is a schema question, answered one layer up by `findUnknownKeys`
|
|
15
17
|
* (validation/entry-validator.ts) at the API boundary, not by a schema-blind serialiser.
|
|
16
18
|
*/
|
|
19
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
17
20
|
import matter from 'gray-matter';
|
|
18
|
-
import { isCollection, isMap, isNode, isScalar, isSeq, parseDocument, stringify as yamlStringify, } from 'yaml';
|
|
21
|
+
import { isCollection, isMap, isNode, isScalar, isSeq, parseDocument, Scalar, stringify as yamlStringify, visit, } from 'yaml';
|
|
19
22
|
import { isBlockStructuralKey } from '../validation/block-structural-keys.js';
|
|
23
|
+
import { ITEM_KEY, isItemKey } from '../item-keys.js';
|
|
24
|
+
import { createDebugLogger } from './debug.js';
|
|
25
|
+
import { getErrorMessage } from './error.js';
|
|
26
|
+
import { preserveMarkdownSource } from './markdown-body-splice.js';
|
|
27
|
+
import { snapshotDocument, spliceSource, withSourceLineEndings } from './yaml-source-splice.js';
|
|
28
|
+
const log = createDebugLogger({ prefix: 'ContentSerialize' });
|
|
29
|
+
/** A fresh node for `value`, its strings that `ctx.quote` names single-quoted. */
|
|
30
|
+
function createNode(ctx, value, asKey = false) {
|
|
31
|
+
const node = ctx.doc.createNode(value);
|
|
32
|
+
const { quote } = ctx;
|
|
33
|
+
if (quote !== undefined) {
|
|
34
|
+
visit(node, {
|
|
35
|
+
Scalar(key, scalar) {
|
|
36
|
+
if (typeof scalar.value === 'string' && quote(scalar.value, asKey || key === 'key')) {
|
|
37
|
+
scalar.type = Scalar.QUOTE_SINGLE;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
20
44
|
/**
|
|
21
45
|
* True only for a PLAIN object — one that should be reconciled key-by-key against a YAML map.
|
|
22
46
|
*
|
|
@@ -86,6 +110,27 @@ function identityKey(value) {
|
|
|
86
110
|
return undefined;
|
|
87
111
|
}
|
|
88
112
|
}
|
|
113
|
+
/** A list item's usable `ITEM_KEY`, or undefined. */
|
|
114
|
+
function itemKeyOf(value) {
|
|
115
|
+
const key = isPlainRecord(value) ? value[ITEM_KEY] : undefined;
|
|
116
|
+
return isItemKey(key) ? key : undefined;
|
|
117
|
+
}
|
|
118
|
+
/** {@link identityKey} with every `ITEM_KEY` left out, or undefined when there were none. */
|
|
119
|
+
function unkeyedIdentityKey(value) {
|
|
120
|
+
let found = false;
|
|
121
|
+
try {
|
|
122
|
+
const key = JSON.stringify(value, (name, v) => {
|
|
123
|
+
if (name !== ITEM_KEY)
|
|
124
|
+
return v;
|
|
125
|
+
found = true;
|
|
126
|
+
return undefined;
|
|
127
|
+
});
|
|
128
|
+
return found ? key : undefined;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
89
134
|
function nodePlainValue(node) {
|
|
90
135
|
if (!isNode(node))
|
|
91
136
|
return node;
|
|
@@ -181,19 +226,24 @@ function looksLikeSameItem(node, value) {
|
|
|
181
226
|
// No record evidence available on either side — fall back to position.
|
|
182
227
|
if (!isPlainRecord(existing) || !isPlainRecord(value))
|
|
183
228
|
return true;
|
|
229
|
+
// Two keyed list items are the same item exactly when their keys match (`item-keys.ts`).
|
|
230
|
+
const before = existing[ITEM_KEY];
|
|
231
|
+
const after = value[ITEM_KEY];
|
|
232
|
+
if (isItemKey(before) && isItemKey(after))
|
|
233
|
+
return before === after;
|
|
184
234
|
return sharesFieldEvidence(existing, value, 0);
|
|
185
235
|
}
|
|
186
236
|
/**
|
|
187
237
|
* Reconcile one slot of the document against the value that must occupy it, returning the node to
|
|
188
238
|
* put there. `existing` is the node in that slot, or null/undefined for a slot that did not exist.
|
|
189
239
|
*/
|
|
190
|
-
function reconcileNode(
|
|
240
|
+
function reconcileNode(ctx, existing, value) {
|
|
191
241
|
if (isMap(existing) && isPlainRecord(value)) {
|
|
192
|
-
reconcileMap(
|
|
242
|
+
reconcileMap(ctx, existing, value);
|
|
193
243
|
return existing;
|
|
194
244
|
}
|
|
195
245
|
if (isSeq(existing) && Array.isArray(value)) {
|
|
196
|
-
reconcileSeq(
|
|
246
|
+
reconcileSeq(ctx, existing, value);
|
|
197
247
|
return existing;
|
|
198
248
|
}
|
|
199
249
|
// Unchanged scalar: return the node itself, untouched. This is the case that preserves comments
|
|
@@ -203,7 +253,16 @@ function reconcileNode(doc, existing, value) {
|
|
|
203
253
|
// Changed, or a shape change (scalar <-> collection). A fresh node rather than mutating
|
|
204
254
|
// `scalar.value` in place, which would keep the old node's representation and emit `'42'` where
|
|
205
255
|
// the number 42 was meant.
|
|
206
|
-
const fresh =
|
|
256
|
+
const fresh = createNode(ctx, value);
|
|
257
|
+
if (isNode(existing))
|
|
258
|
+
ctx.replaced.set(fresh, existing);
|
|
259
|
+
if (isScalar(existing) && isScalar(fresh) && typeof value === 'string') {
|
|
260
|
+
const style = carriedStyle(existing, value);
|
|
261
|
+
if (style !== undefined) {
|
|
262
|
+
fresh.type = style;
|
|
263
|
+
ctx.restyled.push(fresh);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
207
266
|
// Comments move with a changed VALUE, but not off a replaced STRUCTURE. `yaml` attaches a
|
|
208
267
|
// comment written above a collection's first entry to the collection node itself, so that
|
|
209
268
|
// node's comments are about its innards; carrying them onto whatever replaces the collection
|
|
@@ -214,12 +273,33 @@ function reconcileNode(doc, existing, value) {
|
|
|
214
273
|
carryComments(existing, fresh);
|
|
215
274
|
return fresh;
|
|
216
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* The style a changed string inherits from the string it replaces: its quoting, or its block
|
|
278
|
+
* style (`>-`, `|`) unless the new value has no content or leads with whitespace, which `yaml`
|
|
279
|
+
* can print lossily in a block (`" "` reads back as `""`). Never `PLAIN`: forcing it stops `yaml` choosing a
|
|
280
|
+
* block for a multi-line value, and some (`Requirements:\nBring a laptop`) then print as
|
|
281
|
+
* unparseable YAML. An unset style is plain where plain is safe.
|
|
282
|
+
*/
|
|
283
|
+
function carriedStyle(existing, value) {
|
|
284
|
+
if (typeof existing.value !== 'string')
|
|
285
|
+
return undefined;
|
|
286
|
+
switch (existing.type) {
|
|
287
|
+
case Scalar.QUOTE_DOUBLE:
|
|
288
|
+
case Scalar.QUOTE_SINGLE:
|
|
289
|
+
return existing.type;
|
|
290
|
+
case Scalar.BLOCK_FOLDED:
|
|
291
|
+
case Scalar.BLOCK_LITERAL:
|
|
292
|
+
return /\S/.test(value) && !/^\s/.test(value) ? existing.type : undefined;
|
|
293
|
+
default:
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
217
297
|
/**
|
|
218
298
|
* Make a map's key set match `value` exactly. Retained pairs are reconciled in place, so their key
|
|
219
299
|
* order and the comments attached to their keys survive; keys new to `value` are appended in
|
|
220
300
|
* `value` order, keeping a save's diff down to the lines that actually changed.
|
|
221
301
|
*/
|
|
222
|
-
function reconcileMap(
|
|
302
|
+
function reconcileMap(ctx, map, value) {
|
|
223
303
|
// An explicitly-undefined key is NOT a key: `Object.keys` reports it but `JSON.stringify` and
|
|
224
304
|
// `yaml.stringify` omit it, so without this filter a key present on disk and set to `undefined`
|
|
225
305
|
// in the payload is rewritten as `key: null` here while the create path drops it. Same rule on
|
|
@@ -234,34 +314,40 @@ function reconcileMap(doc, map, value) {
|
|
|
234
314
|
if (key === undefined || !wanted.has(key) || seen.has(key))
|
|
235
315
|
continue;
|
|
236
316
|
seen.add(key);
|
|
237
|
-
pair.value = reconcileNode(
|
|
317
|
+
pair.value = reconcileNode(ctx, pair.value, value[key]);
|
|
238
318
|
retained.push(pair);
|
|
239
319
|
}
|
|
240
320
|
map.items = retained;
|
|
241
321
|
for (const key of Object.keys(value)) {
|
|
242
322
|
if (seen.has(key) || !wanted.has(key))
|
|
243
323
|
continue;
|
|
244
|
-
map.set(
|
|
324
|
+
map.set(createNode(ctx, key, true), createNode(ctx, value[key]));
|
|
245
325
|
}
|
|
246
326
|
}
|
|
247
327
|
/**
|
|
248
|
-
* Make a sequence's items match `value` exactly, aligning by VALUE first
|
|
328
|
+
* Make a sequence's items match `value` exactly, aligning by VALUE first, then by item key
|
|
329
|
+
* (`item-keys.ts`), then by position.
|
|
249
330
|
*
|
|
250
|
-
*
|
|
251
|
-
* bad: losing a comment shows up in review as a deletion, silently MOVING one onto
|
|
252
|
-
* not describe does not, and the comments this protects are the load-bearing kind
|
|
253
|
-
* this block"). The rules run from most evidence to least, and stop rather than
|
|
331
|
+
* Without keys a list has no item identity, so any alignment is a guess and the failure modes are
|
|
332
|
+
* not equally bad: losing a comment shows up in review as a deletion, silently MOVING one onto
|
|
333
|
+
* content it does not describe does not, and the comments this protects are the load-bearing kind
|
|
334
|
+
* ("do not delete this block"). The rules run from most evidence to least, and stop rather than
|
|
335
|
+
* guessing:
|
|
254
336
|
*
|
|
255
337
|
* 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.
|
|
257
|
-
*
|
|
338
|
+
* through a pure reorder instead of stranding it on whatever moved into its index. On the save
|
|
339
|
+
* that first writes keys, an item also matches the un-keyed node it was read from, reconciled
|
|
340
|
+
* so the keys are written.
|
|
341
|
+
* 2. **Same key** — an edited keyed item is reconciled against the node holding its key, wherever
|
|
342
|
+
* it was, so its comments move with it.
|
|
343
|
+
* 3. **Same index, still unclaimed, and recognisably the same item** (`looksLikeSameItem`) —
|
|
258
344
|
* reconcile against it: the edit-in-place case. Same-index rather than "next unclaimed old
|
|
259
345
|
* node in order", which pairs a newly-inserted item with an unrelated deleted one whenever one
|
|
260
346
|
* save both removes and adds; and evidence as well as position, because a wholesale
|
|
261
|
-
* replacement lands on the index it replaced.
|
|
262
|
-
*
|
|
347
|
+
* replacement lands on the index it replaced. Items whose keys differ are never the same.
|
|
348
|
+
* 4. **Otherwise a fresh node, with no comments.**
|
|
263
349
|
*/
|
|
264
|
-
function reconcileSeq(
|
|
350
|
+
function reconcileSeq(ctx, seq, value) {
|
|
265
351
|
const oldItems = seq.items;
|
|
266
352
|
// Old indices by identity, in order, so equal items are consumed first-come-first-served.
|
|
267
353
|
const byIdentity = new Map();
|
|
@@ -276,11 +362,8 @@ function reconcileSeq(doc, seq, value) {
|
|
|
276
362
|
byIdentity.set(key, [index]);
|
|
277
363
|
});
|
|
278
364
|
const consumed = new Set();
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
if (key === undefined)
|
|
282
|
-
return undefined;
|
|
283
|
-
const bucket = byIdentity.get(key);
|
|
365
|
+
const claim = (key) => {
|
|
366
|
+
const bucket = key === undefined ? undefined : byIdentity.get(key);
|
|
284
367
|
while (bucket && bucket.length > 0) {
|
|
285
368
|
const index = bucket.shift();
|
|
286
369
|
if (index !== undefined && !consumed.has(index)) {
|
|
@@ -289,37 +372,104 @@ function reconcileSeq(doc, seq, value) {
|
|
|
289
372
|
}
|
|
290
373
|
}
|
|
291
374
|
return undefined;
|
|
375
|
+
};
|
|
376
|
+
// The save that first writes item keys (`item-keys.ts`) adds them to items the file holds
|
|
377
|
+
// without, so an item is also the same as an un-keyed one equal to it but for its keys. That
|
|
378
|
+
// node is reconciled rather than kept whole, which is what writes the keys in.
|
|
379
|
+
const reconciles = new Set();
|
|
380
|
+
const matches = value.map((item, index) => {
|
|
381
|
+
const exact = claim(identityKey(item));
|
|
382
|
+
if (exact !== undefined)
|
|
383
|
+
return exact;
|
|
384
|
+
const unkeyed = claim(unkeyedIdentityKey(item));
|
|
385
|
+
if (unkeyed !== undefined)
|
|
386
|
+
reconciles.add(index);
|
|
387
|
+
return unkeyed;
|
|
388
|
+
});
|
|
389
|
+
// An edited keyed item is the node holding its key, wherever it moved, so its comments go with
|
|
390
|
+
// it. Claimed after every unedited item, so none of those loses its node to it.
|
|
391
|
+
const oldKeys = oldItems.map((node) => itemKeyOf(nodePlainValue(node)));
|
|
392
|
+
value.forEach((item, index) => {
|
|
393
|
+
const key = matches[index] === undefined ? itemKeyOf(item) : undefined;
|
|
394
|
+
if (key === undefined)
|
|
395
|
+
return;
|
|
396
|
+
const at = oldKeys.findIndex((k, j) => k === key && !consumed.has(j));
|
|
397
|
+
if (at === -1)
|
|
398
|
+
return;
|
|
399
|
+
consumed.add(at);
|
|
400
|
+
matches[index] = at;
|
|
401
|
+
reconciles.add(index);
|
|
292
402
|
});
|
|
293
403
|
seq.items = value.map((item, index) => {
|
|
294
404
|
const matched = matches[index];
|
|
295
|
-
if (matched !== undefined)
|
|
296
|
-
return oldItems[matched];
|
|
297
|
-
|
|
405
|
+
if (matched !== undefined) {
|
|
406
|
+
return reconciles.has(index) ? reconcileNode(ctx, oldItems[matched], item) : oldItems[matched];
|
|
407
|
+
}
|
|
408
|
+
// `consumed` holds OLD indices claimed by rules 1 and 2, so this asks "is the node at my index still
|
|
298
409
|
// unclaimed?". Each iteration owns a distinct index, so a candidate cannot be taken twice.
|
|
299
410
|
const candidate = index < oldItems.length && !consumed.has(index) ? oldItems[index] : undefined;
|
|
300
411
|
const sameItem = candidate !== undefined && looksLikeSameItem(candidate, item) ? candidate : undefined;
|
|
301
|
-
return reconcileNode(
|
|
412
|
+
return reconcileNode(ctx, sameItem, item);
|
|
302
413
|
});
|
|
303
414
|
}
|
|
304
|
-
/**
|
|
305
|
-
|
|
306
|
-
|
|
415
|
+
/**
|
|
416
|
+
* Re-serialise `data` onto the YAML text `raw`, or undefined when `raw` does not parse or the
|
|
417
|
+
* reconciled document cannot be printed so that it reads back as its own data.
|
|
418
|
+
*
|
|
419
|
+
* The reconciler decides WHAT changes; {@link spliceSource} then writes those changes into the
|
|
420
|
+
* source text, so untouched lines keep the author's own folding and spacing. When it cannot
|
|
421
|
+
* vouch for a splice, the result is the reconciled document's own `toString()`, which is itself
|
|
422
|
+
* kept only if it parses back to the same data: a carried style that `yaml` prints lossily is
|
|
423
|
+
* dropped and the document re-printed, and failing that the caller writes a plain stringify.
|
|
424
|
+
* The worst case is a re-folded file, or one without its comments; never different data.
|
|
425
|
+
* `rootAtColumnZero` refuses a splice whose first line is indented, for frontmatter, whose
|
|
426
|
+
* framing trims the first line's indentation.
|
|
427
|
+
*/
|
|
428
|
+
function reconcileYamlSource(raw, data, { rootAtColumnZero = false, quote, } = {}) {
|
|
429
|
+
const doc = parseDocument(raw);
|
|
430
|
+
if (doc.errors.length > 0)
|
|
431
|
+
return undefined;
|
|
432
|
+
const snapshot = snapshotDocument(doc);
|
|
433
|
+
const ctx = { doc, replaced: new WeakMap(), restyled: [], quote };
|
|
434
|
+
doc.contents = reconcileNode(ctx, doc.contents, data);
|
|
435
|
+
let reconciled = withSourceLineEndings(doc.toString(), raw);
|
|
436
|
+
if (!readsBackAs(reconciled, doc)) {
|
|
437
|
+
for (const scalar of ctx.restyled)
|
|
438
|
+
scalar.type = undefined;
|
|
439
|
+
reconciled = withSourceLineEndings(doc.toString(), raw);
|
|
440
|
+
if (!readsBackAs(reconciled, doc))
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
const spliced = spliceSource(raw, doc, snapshot, ctx.replaced, reconciled);
|
|
445
|
+
if (spliced !== undefined && !(rootAtColumnZero && /^\s*[ \t]\S/.test(spliced))) {
|
|
446
|
+
return spliced;
|
|
447
|
+
}
|
|
448
|
+
log.debug('content-serialize', 'source splice not used; writing as yaml prints it');
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
log.debug('content-serialize', 'source splice failed; writing as yaml prints it', {
|
|
452
|
+
error: getErrorMessage(err),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return reconciled;
|
|
456
|
+
}
|
|
457
|
+
function readsBackAs(text, doc) {
|
|
458
|
+
const reparsed = parseDocument(text);
|
|
459
|
+
return reparsed.errors.length === 0 && isDeepStrictEqual(reparsed.toJS(), doc.toJS());
|
|
307
460
|
}
|
|
308
461
|
/**
|
|
309
462
|
* Serialise entry data as a YAML file, carrying the comments of `existingRaw` through.
|
|
310
463
|
*
|
|
311
464
|
* Falls back to a plain stringify — byte-identical to serialising without preservation — when
|
|
312
465
|
* there is nothing to preserve (a new file) or nothing trustworthy to preserve (the bytes on disk
|
|
313
|
-
* do not parse
|
|
466
|
+
* do not parse, or the reconciled document does not read back as its own data). A save must not
|
|
467
|
+
* fail because the previous content was malformed.
|
|
314
468
|
*/
|
|
315
469
|
export function serializeYaml(data, existingRaw) {
|
|
316
470
|
if (existingRaw === undefined)
|
|
317
471
|
return yamlStringify(data);
|
|
318
|
-
|
|
319
|
-
if (doc.errors.length > 0)
|
|
320
|
-
return yamlStringify(data);
|
|
321
|
-
applyDataToDocument(doc, data);
|
|
322
|
-
return doc.toString();
|
|
472
|
+
return reconcileYamlSource(existingRaw, data) ?? yamlStringify(data);
|
|
323
473
|
}
|
|
324
474
|
/**
|
|
325
475
|
* Split a file into its raw frontmatter string, or undefined when there is nothing usable. Two
|
|
@@ -348,26 +498,174 @@ function extractRawFrontmatter(raw) {
|
|
|
348
498
|
const frontmatter = parsed.matter;
|
|
349
499
|
if (typeof frontmatter !== 'string' || frontmatter.trim() === '')
|
|
350
500
|
return undefined;
|
|
351
|
-
|
|
501
|
+
// gray-matter ends the frontmatter at `\n---`, so a CRLF file's keeps the closing line's `\r`.
|
|
502
|
+
return frontmatter.replace(/\r$/, '');
|
|
503
|
+
}
|
|
504
|
+
/** The body of a file as gray-matter splits it, or undefined when it cannot split it. */
|
|
505
|
+
function extractBody(raw) {
|
|
506
|
+
try {
|
|
507
|
+
return matter(raw, {}).content;
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Whether `data` has a value to write. Without one, gray-matter's YAML engine writes no frontmatter
|
|
515
|
+
* block (it skips `{}`); the reconcile engine can still write a comment-only block, under which a
|
|
516
|
+
* flush body is also read back intact.
|
|
517
|
+
*/
|
|
518
|
+
function writesFrontmatter(data) {
|
|
519
|
+
return Object.values(data).some((value) => value !== undefined);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* The body to write: `body`'s content in `existingRaw`'s text wherever they agree
|
|
523
|
+
* (`preserveMarkdownSource`), opening with the blank line the frontmatter is followed by on disk.
|
|
524
|
+
* gray-matter leaves that blank line in the body, and the editor's body does not carry it, so it
|
|
525
|
+
* is restored here even when the splice falls back. A body under frontmatter the file did not
|
|
526
|
+
* have, or under frontmatter with no body yet, gets one blank line, Prettier's style.
|
|
527
|
+
*
|
|
528
|
+
* With no frontmatter written, the body opens the file: it loses its leading blank lines, except
|
|
529
|
+
* the one newline that stops a body starting with `---` (after any BOM, which gray-matter skips)
|
|
530
|
+
* reading back as frontmatter.
|
|
531
|
+
*/
|
|
532
|
+
function bodyToWrite(body, data, format, existingRaw) {
|
|
533
|
+
const priorBody = existingRaw === undefined ? undefined : extractBody(existingRaw);
|
|
534
|
+
const spliced = priorBody === undefined ? body : preserveMarkdownSource(priorBody, body, format);
|
|
535
|
+
if (!writesFrontmatter(data)) {
|
|
536
|
+
const flush = spliced.replace(/^(?:\r?\n)+/, '');
|
|
537
|
+
return /^\uFEFF?---/.test(flush) ? `\n${flush}` : flush;
|
|
538
|
+
}
|
|
539
|
+
if (/^\r?\n/.test(spliced) || !/\S/.test(spliced))
|
|
540
|
+
return spliced;
|
|
541
|
+
const priorKeptBody = priorBody !== undefined &&
|
|
542
|
+
/\S/.test(priorBody) &&
|
|
543
|
+
existingRaw !== undefined &&
|
|
544
|
+
extractRawFrontmatter(existingRaw) !== undefined;
|
|
545
|
+
const lead = priorKeptBody ? (/^(?:\r?\n)*/.exec(priorBody)?.[0] ?? '') : '\n';
|
|
546
|
+
return lead + spliced;
|
|
547
|
+
}
|
|
548
|
+
/** `value` as JSON carries it, or undefined when it cannot be serialised. */
|
|
549
|
+
function asJson(value) {
|
|
550
|
+
const key = identityKey(value);
|
|
551
|
+
return key === undefined || key === 'undefined' ? undefined : JSON.parse(key);
|
|
552
|
+
}
|
|
553
|
+
/** Frontmatter data as the read path hands it over the API, or undefined when it cannot split. */
|
|
554
|
+
function frontmatterAsRead(raw) {
|
|
555
|
+
try {
|
|
556
|
+
return asJson(matter(raw, {}).data);
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* `value` with every part it carries unchanged from `read` replaced by `disk`'s value for that
|
|
564
|
+
* part. The read path parses frontmatter with gray-matter's js-yaml, which reads YAML 1.1 (a bare
|
|
565
|
+
* date is a `Date`, sent back as a timestamp string; `014` is octal), and the reconciler compares
|
|
566
|
+
* against `yaml`'s YAML 1.2 reading; so a value the editor sent back as it read it would otherwise
|
|
567
|
+
* count as changed and rewrite its line.
|
|
568
|
+
*/
|
|
569
|
+
function withUnchangedAsOnDisk(value, read, disk) {
|
|
570
|
+
const sent = identityKey(value);
|
|
571
|
+
if (sent !== undefined && sent === identityKey(read))
|
|
572
|
+
return disk;
|
|
573
|
+
if (isPlainRecord(value) && isPlainRecord(read) && isPlainRecord(disk)) {
|
|
574
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [
|
|
575
|
+
key,
|
|
576
|
+
withUnchangedAsOnDisk(child, read[key], disk[key]),
|
|
577
|
+
]));
|
|
578
|
+
}
|
|
579
|
+
if (Array.isArray(value) && Array.isArray(read) && Array.isArray(disk)) {
|
|
580
|
+
if (read.length !== disk.length)
|
|
581
|
+
return value;
|
|
582
|
+
// An item sent as read anywhere in the list, so an insert or a reorder keeps the rest as read.
|
|
583
|
+
// On the save that first writes item keys, it is found by what it was read as without them,
|
|
584
|
+
// and keeps the keys while its unchanged values take their disk form.
|
|
585
|
+
const readKeys = read.map(identityKey);
|
|
586
|
+
const claimed = new Set();
|
|
587
|
+
const claim = (key) => {
|
|
588
|
+
const at = readKeys.findIndex((k, j) => k !== undefined && k === key && !claimed.has(j));
|
|
589
|
+
if (at >= 0)
|
|
590
|
+
claimed.add(at);
|
|
591
|
+
return at;
|
|
592
|
+
};
|
|
593
|
+
const readItemKeys = read.map(itemKeyOf);
|
|
594
|
+
const exact = value.map((child) => claim(identityKey(child)));
|
|
595
|
+
return value.map((child, i) => {
|
|
596
|
+
if (exact[i] >= 0)
|
|
597
|
+
return disk[exact[i]];
|
|
598
|
+
// An edited keyed item compares against the item holding its key, wherever it was read.
|
|
599
|
+
const key = itemKeyOf(child);
|
|
600
|
+
const byKey = key === undefined ? -1 : readItemKeys.findIndex((k, j) => k === key && !claimed.has(j));
|
|
601
|
+
if (byKey >= 0) {
|
|
602
|
+
claimed.add(byKey);
|
|
603
|
+
return withUnchangedAsOnDisk(child, read[byKey], disk[byKey]);
|
|
604
|
+
}
|
|
605
|
+
const unkeyed = unkeyedIdentityKey(child);
|
|
606
|
+
const asRead = unkeyed === undefined ? -1 : claim(unkeyed);
|
|
607
|
+
if (asRead >= 0)
|
|
608
|
+
return withUnchangedAsOnDisk(child, read[asRead], disk[asRead]);
|
|
609
|
+
// By position only when nothing says these are different items.
|
|
610
|
+
const other = i < read.length ? readItemKeys[i] : undefined;
|
|
611
|
+
const differs = key !== undefined && other !== undefined && other !== key;
|
|
612
|
+
return i < read.length && !differs ? withUnchangedAsOnDisk(child, read[i], disk[i]) : child;
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Whether gray-matter, the read path's reader, reads `text` written plain as anything the API
|
|
619
|
+
* would not carry as that string: as a value, `1:30` is 90 and a bare date a `Date`, while a
|
|
620
|
+
* timestamp the editor's date field writes reads as a date the API carries as the same text, and
|
|
621
|
+
* stays plain; as a key, anything but the key itself.
|
|
622
|
+
*/
|
|
623
|
+
function readsAsAnotherValue(text, asKey) {
|
|
624
|
+
if (/[\r\n]/.test(text))
|
|
625
|
+
return false;
|
|
626
|
+
// `matter.stringify` trims the frontmatter it writes, so an edge character `trim` drops is lost.
|
|
627
|
+
if (text !== text.trim())
|
|
628
|
+
return true;
|
|
629
|
+
try {
|
|
630
|
+
const { data } = matter(`---\n${asKey ? `${text}: v` : `v: ${text}`}\n---\n`, {});
|
|
631
|
+
if (!isPlainRecord(data))
|
|
632
|
+
return true;
|
|
633
|
+
const keys = Object.keys(data);
|
|
634
|
+
if (asKey)
|
|
635
|
+
return keys.length !== 1 || keys[0] !== text;
|
|
636
|
+
return identityKey(data.v) !== identityKey(text);
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
352
641
|
}
|
|
353
642
|
/**
|
|
354
|
-
* Serialise an md/mdx entry, carrying the comments of `existingRaw`'s frontmatter
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* newline stay exactly what gray-matter
|
|
643
|
+
* Serialise an md/mdx entry, carrying the comments of `existingRaw`'s frontmatter and the source
|
|
644
|
+
* text of its unchanged body blocks through. The reconciled YAML goes back through
|
|
645
|
+
* `matter.stringify` via a custom stringify engine rather than being spliced between
|
|
646
|
+
* hand-written `---` lines, so delimiters and the trailing newline stay exactly what gray-matter
|
|
647
|
+
* would have produced. The result is kept only if the read path reads `data` back from it;
|
|
648
|
+
* otherwise gray-matter writes it, without the comments. A fresh string the read path would
|
|
649
|
+
* misread is quoted (`readsAsAnotherValue`), so this is left for a changed value meeting text the
|
|
650
|
+
* two parsers read differently, as 14 meets a file's `014`.
|
|
358
651
|
*/
|
|
359
|
-
export function serializeFrontmatter(
|
|
652
|
+
export function serializeFrontmatter(editorBody, data, existingRaw, format) {
|
|
653
|
+
// An object, never the body string: `matter.stringify` re-parses a string as a whole file, so a
|
|
654
|
+
// body starting with `---` would be read as frontmatter and dropped.
|
|
655
|
+
const file = { content: bodyToWrite(editorBody, data, format, existingRaw) };
|
|
360
656
|
if (existingRaw === undefined)
|
|
361
|
-
return matter.stringify(
|
|
657
|
+
return matter.stringify(file, data);
|
|
362
658
|
const existingFrontmatter = extractRawFrontmatter(existingRaw);
|
|
363
659
|
if (existingFrontmatter === undefined)
|
|
364
|
-
return matter.stringify(
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const reconciled =
|
|
370
|
-
|
|
660
|
+
return matter.stringify(file, data);
|
|
661
|
+
const onDisk = parseDocument(existingFrontmatter);
|
|
662
|
+
const toReconcile = onDisk.errors.length === 0
|
|
663
|
+
? withUnchangedAsOnDisk(data, frontmatterAsRead(existingRaw), onDisk.toJS())
|
|
664
|
+
: data;
|
|
665
|
+
const reconciled = reconcileYamlSource(existingFrontmatter, isPlainRecord(toReconcile) ? toReconcile : data, { rootAtColumnZero: true, quote: readsAsAnotherValue });
|
|
666
|
+
if (reconciled === undefined)
|
|
667
|
+
return matter.stringify(file, data);
|
|
668
|
+
const written = matter.stringify(file, data, {
|
|
371
669
|
engines: {
|
|
372
670
|
yaml: {
|
|
373
671
|
parse: () => ({}),
|
|
@@ -375,4 +673,8 @@ export function serializeFrontmatter(body, data, existingRaw) {
|
|
|
375
673
|
},
|
|
376
674
|
},
|
|
377
675
|
});
|
|
676
|
+
if (isDeepStrictEqual(frontmatterAsRead(written), asJson(data)))
|
|
677
|
+
return written;
|
|
678
|
+
log.debug('content-serialize', 'reconciled frontmatter reads back differently; writing as gray-matter prints it');
|
|
679
|
+
return matter.stringify(file, data);
|
|
378
680
|
}
|
|
@@ -44,7 +44,7 @@ import { type OnLockCompromised } from './provisioning-lock.js';
|
|
|
44
44
|
* `next dev` and build-time provisioning across worker processes. The case to watch is a write
|
|
45
45
|
* whose in-lock path triggers a full `idIndex()` rescan of a large tree over EFS, which can exceed
|
|
46
46
|
* this budget and start 409ing unrelated saves. Making it configurable is tracked in
|
|
47
|
-
*
|
|
47
|
+
* safeinsights/canopycms#640.
|
|
48
48
|
*/
|
|
49
49
|
export declare const DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2000;
|
|
50
50
|
/**
|
|
@@ -75,6 +75,11 @@ export declare class ContentWriteLockBusyError extends Error {
|
|
|
75
75
|
* skips the branch and retries next cycle. Throws with `code === 'ELOCKED'` on a live holder.
|
|
76
76
|
*/
|
|
77
77
|
export declare function tryAcquireContentWriteLock(branchRoot: string, onCompromised?: OnLockCompromised): Promise<() => Promise<void>>;
|
|
78
|
+
/**
|
|
79
|
+
* Throw unless the calling async chain runs inside {@link withContentWriteLock} for `branchRoot`:
|
|
80
|
+
* the check for code that writes under a lock its caller took.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertContentWriteLockHeld(branchRoot: string): void;
|
|
78
83
|
/** Run `fn` holding the branch's content-write lock, releasing in a `finally` so a throw cannot
|
|
79
84
|
* strand it. */
|
|
80
85
|
export declare function withContentWriteLock<T>(branchRoot: string, fn: () => Promise<T>, waitMs?: number): Promise<T>;
|