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
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* Mutual exclusion is not proven: on EFS a stale cached mtime lets a waiter take over a live lock,
|
|
30
30
|
* leaving two unsynchronized writers -- the unlocked behaviour, so still a strict improvement.
|
|
31
31
|
*/
|
|
32
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
32
33
|
import path from 'node:path';
|
|
33
34
|
import { getErrorMessage, isNodeError } from './error.js';
|
|
34
35
|
import { canopyLogWarn } from './logger.js';
|
|
@@ -51,7 +52,7 @@ const CONTENT_WRITE_LOCK_NAME = 'content-write.lock';
|
|
|
51
52
|
* `next dev` and build-time provisioning across worker processes. The case to watch is a write
|
|
52
53
|
* whose in-lock path triggers a full `idIndex()` rescan of a large tree over EFS, which can exceed
|
|
53
54
|
* this budget and start 409ing unrelated saves. Making it configurable is tracked in
|
|
54
|
-
*
|
|
55
|
+
* safeinsights/canopycms#640.
|
|
55
56
|
*/
|
|
56
57
|
export const DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2000;
|
|
57
58
|
/**
|
|
@@ -65,7 +66,7 @@ export const DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2000;
|
|
|
65
66
|
* load-bearing rather than cosmetic.
|
|
66
67
|
*/
|
|
67
68
|
export class ContentWriteLockBusyError extends Error {
|
|
68
|
-
constructor(message = 'This branch is busy
|
|
69
|
+
constructor(message = 'This branch is busy syncing or saving another change, so your change was not saved. Try again in a moment.',
|
|
69
70
|
/**
|
|
70
71
|
* `'not-run'`: the lock was never acquired, so nothing happened. `'unknown'`: the work ran
|
|
71
72
|
* but the lock was lost during it (see {@link withContentWriteLock}).
|
|
@@ -91,7 +92,7 @@ const CONTENT_WRITE_LOCK_STALE_MS = 30_000;
|
|
|
91
92
|
* skips the branch and retries next cycle. Throws with `code === 'ELOCKED'` on a live holder.
|
|
92
93
|
*/
|
|
93
94
|
export function tryAcquireContentWriteLock(branchRoot, onCompromised) {
|
|
94
|
-
return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised, CONTENT_WRITE_LOCK_STALE_MS);
|
|
95
|
+
return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised, CONTENT_WRITE_LOCK_STALE_MS, false);
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
97
98
|
* Acquire the branch's content-write lock with a short bounded wait.
|
|
@@ -121,6 +122,17 @@ async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTENT_WRIT
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
}
|
|
125
|
+
/** The branch roots whose content-write lock the current async call chain holds. */
|
|
126
|
+
const heldRoots = new AsyncLocalStorage();
|
|
127
|
+
/**
|
|
128
|
+
* Throw unless the calling async chain runs inside {@link withContentWriteLock} for `branchRoot`:
|
|
129
|
+
* the check for code that writes under a lock its caller took.
|
|
130
|
+
*/
|
|
131
|
+
export function assertContentWriteLockHeld(branchRoot) {
|
|
132
|
+
if (heldRoots.getStore()?.has(path.resolve(branchRoot)))
|
|
133
|
+
return;
|
|
134
|
+
throw new Error(`The content-write lock for ${branchRoot} is not held by this call`);
|
|
135
|
+
}
|
|
124
136
|
/** Run `fn` holding the branch's content-write lock, releasing in a `finally` so a throw cannot
|
|
125
137
|
* strand it. */
|
|
126
138
|
export async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS) {
|
|
@@ -135,7 +147,9 @@ export async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONT
|
|
|
135
147
|
});
|
|
136
148
|
let result;
|
|
137
149
|
try {
|
|
138
|
-
|
|
150
|
+
const held = new Set(heldRoots.getStore());
|
|
151
|
+
held.add(path.resolve(branchRoot));
|
|
152
|
+
result = await heldRoots.run(held, fn);
|
|
139
153
|
}
|
|
140
154
|
finally {
|
|
141
155
|
await release();
|
|
@@ -144,8 +158,9 @@ export async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONT
|
|
|
144
158
|
// Deliberately NOT the default "was not saved" message: `fn()` completed, so the write is on
|
|
145
159
|
// disk and only the proof of exclusivity is lost. "Reload, then decide" rather than "retry",
|
|
146
160
|
// which would resend a now-stale expectedVersion and bounce off the caller's own landed write
|
|
147
|
-
// as a phantom editor collision.
|
|
148
|
-
|
|
161
|
+
// as a phantom editor collision. The API marks this case `WRITE_OUTCOME_UNKNOWN` so the
|
|
162
|
+
// editor can hold further saves of the entry until it is re-read.
|
|
163
|
+
throw new ContentWriteLockBusyError("We couldn't confirm your change was saved because the branch was syncing at the same time. Reload to check before saving again (your unsaved edits will be lost).", 'unknown');
|
|
149
164
|
}
|
|
150
165
|
return result;
|
|
151
166
|
}
|
package/dist/utils/debug.d.ts
CHANGED
|
@@ -41,6 +41,6 @@ export declare class DebugLogger {
|
|
|
41
41
|
export declare function createDebugLogger(options?: DebugOptions): DebugLogger;
|
|
42
42
|
/**
|
|
43
43
|
* Default logger for E2E test infrastructure.
|
|
44
|
-
* @internal No importer; deletion candidate in
|
|
44
|
+
* @internal No importer; deletion candidate in safeinsights/canopycms#725.
|
|
45
45
|
*/
|
|
46
46
|
export declare const testLogger: DebugLogger;
|
package/dist/utils/debug.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** False where no `process` exists: a browser bundle with no shim for it, such as Storybook's. */
|
|
2
|
+
const envFlag = (name) => typeof process !== 'undefined' && process.env[name] === 'true';
|
|
1
3
|
const LOG_LEVELS = {
|
|
2
4
|
DEBUG: 0,
|
|
3
5
|
INFO: 1,
|
|
@@ -11,7 +13,7 @@ export class DebugLogger {
|
|
|
11
13
|
}
|
|
12
14
|
shouldLog(level) {
|
|
13
15
|
// Check enabled at call time to support runtime env var changes
|
|
14
|
-
const enabled = this.options.enabled ??
|
|
16
|
+
const enabled = this.options.enabled ?? envFlag('CANOPYCMS_DEBUG');
|
|
15
17
|
if (!enabled)
|
|
16
18
|
return false;
|
|
17
19
|
const minLevel = this.options.minLevel ?? 'DEBUG';
|
|
@@ -92,10 +94,10 @@ export function createDebugLogger(options) {
|
|
|
92
94
|
}
|
|
93
95
|
/**
|
|
94
96
|
* Default logger for E2E test infrastructure.
|
|
95
|
-
* @internal No importer; deletion candidate in
|
|
97
|
+
* @internal No importer; deletion candidate in safeinsights/canopycms#725.
|
|
96
98
|
*/
|
|
97
99
|
export const testLogger = createDebugLogger({
|
|
98
|
-
enabled:
|
|
100
|
+
enabled: envFlag('E2E_DEBUG'),
|
|
99
101
|
prefix: 'E2E',
|
|
100
102
|
throwOnError: false,
|
|
101
103
|
});
|
package/dist/utils/error.js
CHANGED
|
@@ -30,11 +30,15 @@ export function getErrorMessage(err) {
|
|
|
30
30
|
*/
|
|
31
31
|
export function sanitizeErrorMessage(message) {
|
|
32
32
|
let result = redactCredentials(message);
|
|
33
|
+
// `file://` URLs (ESM and dev-bundle stack frames) carry an absolute path the POSIX rule below
|
|
34
|
+
// skips, since it leaves URL slashes alone.
|
|
35
|
+
result = result.replace(/\bfile:\/\/[^\s'")]*/g, '<path>');
|
|
33
36
|
// Paths under the project root become relative (split/join avoids regex-escaping issues with
|
|
34
37
|
// arbitrary cwd values). The bare-cwd replacement is anchored to a token boundary so a sibling
|
|
35
38
|
// directory that merely shares the cwd prefix (`${cwd}-other/…`) stays absolute and is fully
|
|
36
|
-
// redacted below instead of leaking a mangled remainder.
|
|
37
|
-
|
|
39
|
+
// redacted below instead of leaking a mangled remainder. The editor calls this in a browser,
|
|
40
|
+
// where `process.cwd` may not exist.
|
|
41
|
+
const cwd = typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : '/';
|
|
38
42
|
if (cwd !== '/') {
|
|
39
43
|
result = result.split(`${cwd}/`).join('');
|
|
40
44
|
const cwdPattern = cwd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -73,6 +77,13 @@ export function redactCredentials(message) {
|
|
|
73
77
|
// literal `://` (leaving the scheme untouched) — a `\w+` scheme prefix backtracks
|
|
74
78
|
// polynomially on long word-character runs (CodeQL js/polynomial-redos).
|
|
75
79
|
result = result.replace(/(:\/\/)[^/\s@]+@/g, '$1***@');
|
|
80
|
+
// An `Authorization` header's credential: the worker's GitHub-bound git carries the token as
|
|
81
|
+
// `AUTHORIZATION: basic <base64>` (worker/github-mirror.ts).
|
|
82
|
+
result = result.replace(/(\bauthorization\s*:\s*(?:basic|bearer|token)\s+)[A-Za-z0-9+/=._~-]+/gi, '$1***');
|
|
83
|
+
// That header's base64 of `x-access-token:<token>` wherever it appears on its own, URL-encoded
|
|
84
|
+
// too. The 15-byte prefix encodes to the same 20 characters whatever the token, with no padding
|
|
85
|
+
// to shift it.
|
|
86
|
+
result = result.replace(/eC1hY2Nlc3MtdG9rZW46(?:[A-Za-z0-9+/]|%2[BbFf])*(?:=|%3[Dd]){0,2}/g, '***');
|
|
76
87
|
// Bare token shapes, for messages embedding a token outside URL userinfo: GitHub token
|
|
77
88
|
// prefixes and Bearer values.
|
|
78
89
|
result = result.replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{8,}/g, '***');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `text` on one line, every character outside printable ASCII escaped (`\n`, `\t`, `\r`, `\xHH`,
|
|
3
|
+
* `\u{H}`), and `\` doubled so an escape cannot be forged. Bidi controls and zero-width characters
|
|
4
|
+
* are outside printable ASCII too, so a branch name cannot reorder or hide part of a line. Pure,
|
|
5
|
+
* so the editor uses it too, to show a GitHub-sourced name as exactly what it is.
|
|
6
|
+
*/
|
|
7
|
+
export declare function escapeForLog(text: string): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `text` on one line, every character outside printable ASCII escaped (`\n`, `\t`, `\r`, `\xHH`,
|
|
3
|
+
* `\u{H}`), and `\` doubled so an escape cannot be forged. Bidi controls and zero-width characters
|
|
4
|
+
* are outside printable ASCII too, so a branch name cannot reorder or hide part of a line. Pure,
|
|
5
|
+
* so the editor uses it too, to show a GitHub-sourced name as exactly what it is.
|
|
6
|
+
*/
|
|
7
|
+
export function escapeForLog(text) {
|
|
8
|
+
let out = '';
|
|
9
|
+
for (const char of text) {
|
|
10
|
+
const code = char.codePointAt(0) ?? 0;
|
|
11
|
+
if (char === '\\')
|
|
12
|
+
out += '\\\\';
|
|
13
|
+
else if (code >= 0x20 && code <= 0x7e)
|
|
14
|
+
out += char;
|
|
15
|
+
else if (char === '\n')
|
|
16
|
+
out += '\\n';
|
|
17
|
+
else if (char === '\t')
|
|
18
|
+
out += '\\t';
|
|
19
|
+
else if (char === '\r')
|
|
20
|
+
out += '\\r';
|
|
21
|
+
else if (code <= 0xff)
|
|
22
|
+
out += `\\x${code.toString(16).padStart(2, '0')}`;
|
|
23
|
+
else
|
|
24
|
+
out += `\\u{${code.toString(16)}}`;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
package/dist/utils/fs.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/** Check if a path exists on disk. */
|
|
2
2
|
export declare function filePathExists(filePath: string): Promise<boolean>;
|
|
3
3
|
/**
|
|
4
|
-
* Read a UTF-8 file, or undefined if it does not exist. Only ENOENT
|
|
5
|
-
* permissions or I/O failure still throws, so a caller cannot mistake
|
|
6
|
-
* "not there yet".
|
|
4
|
+
* Read a UTF-8 file and its mtime from ONE open, or undefined if it does not exist. Only ENOENT
|
|
5
|
+
* is swallowed — a permissions or I/O failure still throws, so a caller cannot mistake
|
|
6
|
+
* "unreadable" for "not there yet".
|
|
7
|
+
*
|
|
8
|
+
* The mtime comes from the open handle, not a path `stat`: opening a file on NFS checks it against
|
|
9
|
+
* the server (close-to-open, nfs(5)), where a path `stat` can be answered from the client's
|
|
10
|
+
* attribute cache for a file another host has since replaced or deleted. So the mtime
|
|
11
|
+
* and the bytes describe the same file, as the server has it.
|
|
7
12
|
*/
|
|
8
|
-
export declare function
|
|
13
|
+
export declare function readFileWithMtimeIfExists(filePath: string): Promise<{
|
|
14
|
+
content: string;
|
|
15
|
+
mtimeMs: number;
|
|
16
|
+
} | undefined>;
|
package/dist/utils/fs.js
CHANGED
|
@@ -13,17 +13,30 @@ export async function filePathExists(filePath) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Read a UTF-8 file, or undefined if it does not exist. Only ENOENT
|
|
17
|
-
* permissions or I/O failure still throws, so a caller cannot mistake
|
|
18
|
-
* "not there yet".
|
|
16
|
+
* Read a UTF-8 file and its mtime from ONE open, or undefined if it does not exist. Only ENOENT
|
|
17
|
+
* is swallowed — a permissions or I/O failure still throws, so a caller cannot mistake
|
|
18
|
+
* "unreadable" for "not there yet".
|
|
19
|
+
*
|
|
20
|
+
* The mtime comes from the open handle, not a path `stat`: opening a file on NFS checks it against
|
|
21
|
+
* the server (close-to-open, nfs(5)), where a path `stat` can be answered from the client's
|
|
22
|
+
* attribute cache for a file another host has since replaced or deleted. So the mtime
|
|
23
|
+
* and the bytes describe the same file, as the server has it.
|
|
19
24
|
*/
|
|
20
|
-
export async function
|
|
25
|
+
export async function readFileWithMtimeIfExists(filePath) {
|
|
26
|
+
let handle;
|
|
21
27
|
try {
|
|
22
|
-
|
|
28
|
+
handle = await fs.open(filePath, 'r');
|
|
23
29
|
}
|
|
24
30
|
catch (err) {
|
|
25
31
|
if (isNotFoundError(err))
|
|
26
32
|
return undefined;
|
|
27
33
|
throw err;
|
|
28
34
|
}
|
|
35
|
+
try {
|
|
36
|
+
const { mtimeMs } = await handle.stat();
|
|
37
|
+
return { content: await handle.readFile('utf8'), mtimeMs };
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await handle.close();
|
|
41
|
+
}
|
|
29
42
|
}
|
package/dist/utils/git.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ export declare function isStaleLeaseRejection(message: string): boolean;
|
|
|
47
47
|
* GitHub refuses only content it does not already hold: carrying a base-branch workflow change by
|
|
48
48
|
* rebase, merge or fast-forward is accepted. Retrying the identical push can never succeed, so the
|
|
49
49
|
* worker fails it fast. The text is GitHub's own, not git's, so no locale pinning is needed.
|
|
50
|
+
*
|
|
51
|
+
* A scan of each line's backquote-delimited spans, linear in the message, which is git's output of
|
|
52
|
+
* any length: this rule as a regex, with its run between lead and tail, is quadratic on a line
|
|
53
|
+
* that repeats the lead without the tail.
|
|
50
54
|
*/
|
|
51
55
|
export declare function workflowPushRefusalFile(message: string): string | null;
|
|
52
56
|
/**
|
|
@@ -76,6 +80,10 @@ export declare function isCanopyInternalPath(repoRelativePath: string): boolean;
|
|
|
76
80
|
* {@link CANOPY_META_DIR}, including files an adopter committed there by mistake. Stage-all then
|
|
77
81
|
* unstage, because git rejects `:(exclude)` pathspecs (six spellings tried) with "paths are ignored" when
|
|
78
82
|
* the excluded directory is itself ignored, which `.git/info/exclude` makes it in every clone.
|
|
83
|
+
*
|
|
84
|
+
* `--sparse`: in a sparse clone a plain `add -A` fails on any file present outside the cone.
|
|
85
|
+
* With it, such a file is staged like any other, and a tracked file absent only because it is
|
|
86
|
+
* outside the cone is still not staged as a deletion. A no-op in a full clone.
|
|
79
87
|
*/
|
|
80
88
|
export declare function stageAllExceptCanopyState(git: SimpleGit): Promise<void>;
|
|
81
89
|
/**
|
|
@@ -96,12 +104,22 @@ export declare function isRebaseInProgress(repoPath: string): Promise<boolean>;
|
|
|
96
104
|
* detached.
|
|
97
105
|
*/
|
|
98
106
|
export declare function detectHeadBranch(repoRoot: string, fallback?: string): Promise<string>;
|
|
107
|
+
/**
|
|
108
|
+
* The branch the HEAD of the repository at `gitDir` names; for a bare repo, its default branch.
|
|
109
|
+
* Throws when HEAD is detached, unreadable, or names a branch with no commit.
|
|
110
|
+
*
|
|
111
|
+
* `--git-dir` rather than cwd discovery, which `safe.bareRepository=explicit` refuses for bare
|
|
112
|
+
* repos. `git` is how the worker passes the instance it runs a shared repository with.
|
|
113
|
+
*/
|
|
114
|
+
export declare function readHeadBranch(gitDir: string, git?: SimpleGit): Promise<string>;
|
|
99
115
|
/**
|
|
100
116
|
* Resolve the base branch — the fork point for CMS editing branches and the branch workspace
|
|
101
117
|
* clones are seeded from. The single definition of base-branch behavior (see ARCHITECTURE.md
|
|
102
118
|
* "Branch Identity"): a configured `defaultBaseBranch` always wins in both modes; dev mode
|
|
103
119
|
* otherwise detects the current git HEAD, so workspaces fork from the developer's checked-out
|
|
104
|
-
* branch
|
|
120
|
+
* branch, falling back to 'main'; prod otherwise reads the HEAD of `remoteGitDir`, the bare repo
|
|
121
|
+
* the workspaces clone from, and throws when it is not given one or cannot read it. Prod never
|
|
122
|
+
* assumes 'main'.
|
|
105
123
|
*
|
|
106
124
|
* Static deployments never reach git operations, so callers on static paths must short-circuit
|
|
107
125
|
* before calling this (see createCanopyServices).
|
|
@@ -111,4 +129,6 @@ export declare function resolveBaseBranch(options: {
|
|
|
111
129
|
mode: OperatingMode;
|
|
112
130
|
/** Repo root used for dev-mode HEAD detection. Defaults to process.cwd(). */
|
|
113
131
|
detectFrom?: string;
|
|
132
|
+
/** Prod: the local bare remote whose HEAD names the base branch (GitManager.detectBaseBranch). */
|
|
133
|
+
remoteGitDir?: string;
|
|
114
134
|
}): Promise<string>;
|
package/dist/utils/git.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { simpleGit } from 'simple-git';
|
|
4
|
+
import { BaseBranchUnresolvedError } from './base-branch.js';
|
|
5
|
+
import { getErrorMessage } from './error.js';
|
|
4
6
|
const NETWORK_SCHEME_PATTERN = /^(https?|ssh|git):\/\//i;
|
|
5
7
|
// git's "transport helper" syntax (`ext::sh -c ...`, `fd::7`): any `scheme::` prefix hands the
|
|
6
8
|
// URL to `git-remote-<scheme>`, which can run arbitrary commands — always network/untrusted.
|
|
@@ -88,10 +90,11 @@ const STALE_LEASE_REASON = 'stale info';
|
|
|
88
90
|
export function isStaleLeaseRejection(message) {
|
|
89
91
|
return message.includes(REJECTED_MARKER) && message.includes(STALE_LEASE_REASON);
|
|
90
92
|
}
|
|
91
|
-
// GitHub's reason text when a push would introduce workflow content the credential may not write
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
const
|
|
93
|
+
// GitHub's reason text when a push would introduce workflow content the credential may not write:
|
|
94
|
+
// `refusing to allow <credential kind> to create or update workflow `<file>``, where the kind
|
|
95
|
+
// ("a GitHub App", "an OAuth App", ...) holds no backquote and the file is the next quoted span.
|
|
96
|
+
const WORKFLOW_REFUSAL_LEADS = ['refusing to allow a ', 'refusing to allow an '];
|
|
97
|
+
const WORKFLOW_REFUSAL_TAIL = ' to create or update workflow ';
|
|
95
98
|
/**
|
|
96
99
|
* The workflow file named by GitHub's refusal of a push that would add workflow content the
|
|
97
100
|
* credential lacks the workflows permission for, or null if the message is not that refusal.
|
|
@@ -99,9 +102,30 @@ const WORKFLOW_REFUSAL_PATTERN = /refusing to allow an? [^`\n]+? to create or up
|
|
|
99
102
|
* GitHub refuses only content it does not already hold: carrying a base-branch workflow change by
|
|
100
103
|
* rebase, merge or fast-forward is accepted. Retrying the identical push can never succeed, so the
|
|
101
104
|
* worker fails it fast. The text is GitHub's own, not git's, so no locale pinning is needed.
|
|
105
|
+
*
|
|
106
|
+
* A scan of each line's backquote-delimited spans, linear in the message, which is git's output of
|
|
107
|
+
* any length: this rule as a regex, with its run between lead and tail, is quadratic on a line
|
|
108
|
+
* that repeats the lead without the tail.
|
|
102
109
|
*/
|
|
103
110
|
export function workflowPushRefusalFile(message) {
|
|
104
|
-
|
|
111
|
+
for (const line of message.split('\n')) {
|
|
112
|
+
const spans = line.split('`');
|
|
113
|
+
// The last span is closed by no backquote, so the file can be at most the one before it.
|
|
114
|
+
for (let i = 0; i + 2 < spans.length; i++) {
|
|
115
|
+
const span = spans[i];
|
|
116
|
+
const file = spans[i + 1];
|
|
117
|
+
if (file === '' || !span.endsWith(WORKFLOW_REFUSAL_TAIL))
|
|
118
|
+
continue;
|
|
119
|
+
const head = span.slice(0, -WORKFLOW_REFUSAL_TAIL.length);
|
|
120
|
+
const named = WORKFLOW_REFUSAL_LEADS.some((lead) => {
|
|
121
|
+
const at = head.indexOf(lead);
|
|
122
|
+
return at !== -1 && at + lead.length < head.length;
|
|
123
|
+
});
|
|
124
|
+
if (named)
|
|
125
|
+
return file;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
105
129
|
}
|
|
106
130
|
// git's message when `git fetch <remote> <branch>` names a ref the remote does not have. Both
|
|
107
131
|
// spellings occur: modern git prints the lowercase form, older versions and some transports
|
|
@@ -168,9 +192,13 @@ export function isCanopyInternalPath(repoRelativePath) {
|
|
|
168
192
|
* {@link CANOPY_META_DIR}, including files an adopter committed there by mistake. Stage-all then
|
|
169
193
|
* unstage, because git rejects `:(exclude)` pathspecs (six spellings tried) with "paths are ignored" when
|
|
170
194
|
* the excluded directory is itself ignored, which `.git/info/exclude` makes it in every clone.
|
|
195
|
+
*
|
|
196
|
+
* `--sparse`: in a sparse clone a plain `add -A` fails on any file present outside the cone.
|
|
197
|
+
* With it, such a file is staged like any other, and a tracked file absent only because it is
|
|
198
|
+
* outside the cone is still not staged as a deletion. A no-op in a full clone.
|
|
171
199
|
*/
|
|
172
200
|
export async function stageAllExceptCanopyState(git) {
|
|
173
|
-
await git.
|
|
201
|
+
await git.raw(['add', '-A', '--sparse']);
|
|
174
202
|
await git.raw(['reset', '-q', '--', CANOPY_META_DIR]);
|
|
175
203
|
}
|
|
176
204
|
/**
|
|
@@ -209,12 +237,30 @@ export async function detectHeadBranch(repoRoot, fallback = 'main') {
|
|
|
209
237
|
return fallback;
|
|
210
238
|
}
|
|
211
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* The branch the HEAD of the repository at `gitDir` names; for a bare repo, its default branch.
|
|
242
|
+
* Throws when HEAD is detached, unreadable, or names a branch with no commit.
|
|
243
|
+
*
|
|
244
|
+
* `--git-dir` rather than cwd discovery, which `safe.bareRepository=explicit` refuses for bare
|
|
245
|
+
* repos. `git` is how the worker passes the instance it runs a shared repository with.
|
|
246
|
+
*/
|
|
247
|
+
export async function readHeadBranch(gitDir, git = simpleGit()) {
|
|
248
|
+
const ref = (await git.raw(['--git-dir', gitDir, 'symbolic-ref', 'HEAD'])).trim();
|
|
249
|
+
if (!ref.startsWith('refs/heads/')) {
|
|
250
|
+
throw new Error(`its HEAD does not name a branch (${ref || 'empty'})`);
|
|
251
|
+
}
|
|
252
|
+
// No `--quiet`: simple-git rejects only on a non-zero exit WITH stderr output.
|
|
253
|
+
await git.raw(['--git-dir', gitDir, 'rev-parse', '--verify', `${ref}^{commit}`]);
|
|
254
|
+
return ref.slice('refs/heads/'.length);
|
|
255
|
+
}
|
|
212
256
|
/**
|
|
213
257
|
* Resolve the base branch — the fork point for CMS editing branches and the branch workspace
|
|
214
258
|
* clones are seeded from. The single definition of base-branch behavior (see ARCHITECTURE.md
|
|
215
259
|
* "Branch Identity"): a configured `defaultBaseBranch` always wins in both modes; dev mode
|
|
216
260
|
* otherwise detects the current git HEAD, so workspaces fork from the developer's checked-out
|
|
217
|
-
* branch
|
|
261
|
+
* branch, falling back to 'main'; prod otherwise reads the HEAD of `remoteGitDir`, the bare repo
|
|
262
|
+
* the workspaces clone from, and throws when it is not given one or cannot read it. Prod never
|
|
263
|
+
* assumes 'main'.
|
|
218
264
|
*
|
|
219
265
|
* Static deployments never reach git operations, so callers on static paths must short-circuit
|
|
220
266
|
* before calling this (see createCanopyServices).
|
|
@@ -225,5 +271,14 @@ export async function resolveBaseBranch(options) {
|
|
|
225
271
|
if (options.mode === 'dev') {
|
|
226
272
|
return detectHeadBranch(options.detectFrom ?? process.cwd());
|
|
227
273
|
}
|
|
228
|
-
|
|
274
|
+
if (!options.remoteGitDir) {
|
|
275
|
+
throw new BaseBranchUnresolvedError('defaultBaseBranch is not set, and no remote was given to read the base branch from.');
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
return await readHeadBranch(options.remoteGitDir);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
throw new BaseBranchUnresolvedError(`defaultBaseBranch is not set, and the base branch could not be read from ` +
|
|
282
|
+
`${options.remoteGitDir}: ${getErrorMessage(err)}.`);
|
|
283
|
+
}
|
|
229
284
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-preserving serialisation for JSON content files.
|
|
3
|
+
*
|
|
4
|
+
* A value `data` leaves alone keeps its bytes in the file; a value that changed is written at
|
|
5
|
+
* its own slot, in the file's indent and line ending; and anything this module cannot vouch for
|
|
6
|
+
* (a file that is not a JSON object, an edit set that does not read back as `data`) is written as
|
|
7
|
+
* `JSON.stringify(data, null, 2)`. Retained keys keep the file's order and new keys are appended.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Serialise entry data as a JSON file, keeping `existingRaw`'s formatting wherever `data`
|
|
11
|
+
* leaves a value alone. Writes `JSON.stringify(data, null, 2)` plus a newline when there is
|
|
12
|
+
* no existing file or the splice cannot be trusted; a save never fails on the old content.
|
|
13
|
+
*/
|
|
14
|
+
export declare function serializeJson(data: Record<string, unknown>, existingRaw?: string): string;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-preserving serialisation for JSON content files.
|
|
3
|
+
*
|
|
4
|
+
* A value `data` leaves alone keeps its bytes in the file; a value that changed is written at
|
|
5
|
+
* its own slot, in the file's indent and line ending; and anything this module cannot vouch for
|
|
6
|
+
* (a file that is not a JSON object, an edit set that does not read back as `data`) is written as
|
|
7
|
+
* `JSON.stringify(data, null, 2)`. Retained keys keep the file's order and new keys are appended.
|
|
8
|
+
*/
|
|
9
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
10
|
+
import { applyEdits, modify } from 'jsonc-parser';
|
|
11
|
+
import { createDebugLogger } from './debug.js';
|
|
12
|
+
import { getErrorMessage } from './error.js';
|
|
13
|
+
const log = createDebugLogger({ prefix: 'JsonSourceSplice' });
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function plainStringify(data) {
|
|
18
|
+
return `${JSON.stringify(data, null, 2)}\n`;
|
|
19
|
+
}
|
|
20
|
+
/** The file's own indent (from its first indented line) and line ending. */
|
|
21
|
+
function detectFormatting(text) {
|
|
22
|
+
const indent = /^([ \t]+)\S/m.exec(text)?.[1];
|
|
23
|
+
const eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
24
|
+
if (indent?.startsWith('\t'))
|
|
25
|
+
return { insertSpaces: false, tabSize: 1, eol };
|
|
26
|
+
return { insertSpaces: true, tabSize: indent?.length ?? 2, eol };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Append `key: value` to the object at `path`. A formatted `modify` re-renders the object's
|
|
30
|
+
* previous last member too, so when the object's closing brace has a line of its own the member
|
|
31
|
+
* is rendered here in the object's own indent and nothing else is touched; any other layout
|
|
32
|
+
* (an empty or one-line object) takes the formatted `modify`.
|
|
33
|
+
*/
|
|
34
|
+
function insertMember(text, path, key, value, formattingOptions) {
|
|
35
|
+
const at = [...path, key];
|
|
36
|
+
const [edit] = modify(text, at, value, {});
|
|
37
|
+
const closing = edit ? /^[ \t]*\r?\n([ \t]*)\}/.exec(text.slice(edit.offset)) : null;
|
|
38
|
+
if (edit && closing && edit.length === 0 && edit.content.startsWith(',')) {
|
|
39
|
+
const unit = formattingOptions.insertSpaces ? ' '.repeat(formattingOptions.tabSize ?? 2) : '\t';
|
|
40
|
+
const eol = formattingOptions.eol ?? '\n';
|
|
41
|
+
const indent = closing[1] + unit;
|
|
42
|
+
const rendered = JSON.stringify(value, null, unit)
|
|
43
|
+
.split('\n')
|
|
44
|
+
.join(eol + indent);
|
|
45
|
+
return applyEdits(text, [
|
|
46
|
+
{ ...edit, content: `,${eol}${indent}${JSON.stringify(key)}: ${rendered}` },
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
return applyEdits(text, modify(text, at, value, { formattingOptions }));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove the member at `path`. Unformatted, since a formatted removal re-renders the neighbouring
|
|
53
|
+
* member too; and an unformatted removal of an object's first member runs from the `{` to the
|
|
54
|
+
* next member, taking the whitespace in front of it, so a removal that starts in whitespace and
|
|
55
|
+
* ends at a member starts at the removed member instead.
|
|
56
|
+
*/
|
|
57
|
+
function removeMember(text, path) {
|
|
58
|
+
return applyEdits(text, modify(text, path, undefined, {}).map((edit) => {
|
|
59
|
+
const removed = text.slice(edit.offset, edit.offset + edit.length);
|
|
60
|
+
const lead = /^\s+/.exec(removed)?.[0].length ?? 0;
|
|
61
|
+
const atMember = /\S/.test(text.charAt(edit.offset + edit.length));
|
|
62
|
+
return edit.content === '' && lead > 0 && atMember
|
|
63
|
+
? { ...edit, offset: edit.offset + lead, length: edit.length - lead }
|
|
64
|
+
: edit;
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Edit `text` so the value at `path` goes from `before` to `after`, touching only what differs.
|
|
69
|
+
* Both sides are JSON-normalised, so `undefined` never appears and a key is absent or present.
|
|
70
|
+
*/
|
|
71
|
+
function reconcile(text, path, before, after, formattingOptions) {
|
|
72
|
+
if (isRecord(before) && isRecord(after)) {
|
|
73
|
+
// Removing every member one by one would leave the braces on their own lines.
|
|
74
|
+
if (Object.keys(after).length === 0 && Object.keys(before).length > 0) {
|
|
75
|
+
return applyEdits(text, modify(text, path, {}, { formattingOptions }));
|
|
76
|
+
}
|
|
77
|
+
let next = text;
|
|
78
|
+
for (const key of Object.keys(before)) {
|
|
79
|
+
if (!Object.prototype.hasOwnProperty.call(after, key))
|
|
80
|
+
next = removeMember(next, [...path, key]);
|
|
81
|
+
}
|
|
82
|
+
for (const key of Object.keys(after)) {
|
|
83
|
+
next = Object.prototype.hasOwnProperty.call(before, key)
|
|
84
|
+
? reconcile(next, [...path, key], before[key], after[key], formattingOptions)
|
|
85
|
+
: insertMember(next, path, key, after[key], formattingOptions);
|
|
86
|
+
}
|
|
87
|
+
return next;
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(before) && Array.isArray(after) && before.length === after.length) {
|
|
90
|
+
return after.reduce((next, item, index) => reconcile(next, [...path, index], before[index], item, formattingOptions), text);
|
|
91
|
+
}
|
|
92
|
+
if (JSON.stringify(before) === JSON.stringify(after))
|
|
93
|
+
return text;
|
|
94
|
+
return applyEdits(text, modify(text, path, after, { formattingOptions }));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Serialise entry data as a JSON file, keeping `existingRaw`'s formatting wherever `data`
|
|
98
|
+
* leaves a value alone. Writes `JSON.stringify(data, null, 2)` plus a newline when there is
|
|
99
|
+
* no existing file or the splice cannot be trusted; a save never fails on the old content.
|
|
100
|
+
*/
|
|
101
|
+
export function serializeJson(data, existingRaw) {
|
|
102
|
+
if (existingRaw === undefined)
|
|
103
|
+
return plainStringify(data);
|
|
104
|
+
try {
|
|
105
|
+
const normalised = JSON.parse(JSON.stringify(data));
|
|
106
|
+
const existing = JSON.parse(existingRaw);
|
|
107
|
+
if (!isRecord(normalised) || !isRecord(existing))
|
|
108
|
+
return plainStringify(data);
|
|
109
|
+
const spliced = reconcile(existingRaw, [], existing, normalised, detectFormatting(existingRaw));
|
|
110
|
+
if (isDeepStrictEqual(JSON.parse(spliced), normalised))
|
|
111
|
+
return spliced;
|
|
112
|
+
log.debug('json-source-splice', 'splice does not read back as data; writing JSON.stringify');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
log.debug('json-source-splice', 'splice failed; writing JSON.stringify', {
|
|
116
|
+
error: getErrorMessage(err),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return plainStringify(data);
|
|
120
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-preserving save for md/mdx bodies.
|
|
3
|
+
*
|
|
4
|
+
* The editor sends a body re-serialised from its own document model, so every block comes back
|
|
5
|
+
* in the serialiser's style (bullet and emphasis markers, rule style, escapes, no blank line
|
|
6
|
+
* after the frontmatter) whether or not the user touched it. This writes the on-disk text of
|
|
7
|
+
* every block whose MEANING the edit left alone, and the editor's text only where it changed:
|
|
8
|
+
*
|
|
9
|
+
* - Top-level blocks are aligned by a position-free canonical form of their mdast, so a block
|
|
10
|
+
* matches only when it parses to the same tree. A matched block is written from disk.
|
|
11
|
+
* - A changed block paired with an original of the same kind is spliced by its children when it
|
|
12
|
+
* is a list (by item) or a list item (by child block), at any depth, so editing one item leaves
|
|
13
|
+
* its siblings alone. New items take the original list's marker and numbering.
|
|
14
|
+
* - Whitespace between two output blocks that stand for adjacent on-disk blocks (an edited block
|
|
15
|
+
* stands for the original it is paired with) is the disk's; elsewhere it is the editor's,
|
|
16
|
+
* re-indented into the original's nesting and written in its line endings.
|
|
17
|
+
*
|
|
18
|
+
* The result is kept only if it parses to exactly the tree the editor's body parses to. A splice
|
|
19
|
+
* that fails that check is retried without descending into lists, and failing that the editor's
|
|
20
|
+
* body is written as sent: the worst case is the editor's own serialisation, never different
|
|
21
|
+
* content.
|
|
22
|
+
*/
|
|
23
|
+
export type MarkdownBodyFormat = 'md' | 'mdx';
|
|
24
|
+
/**
|
|
25
|
+
* `text`'s top-level blocks, each as its source text and its `canonical` form, or undefined when
|
|
26
|
+
* `text` does not parse: two bodies mean the same to the splice exactly when their `meaning`
|
|
27
|
+
* lists are equal.
|
|
28
|
+
*
|
|
29
|
+
* @internal Exported for the round-trip corpus test.
|
|
30
|
+
*/
|
|
31
|
+
export declare function markdownBlocks(text: string, format: MarkdownBodyFormat): {
|
|
32
|
+
text: string;
|
|
33
|
+
meaning: string;
|
|
34
|
+
}[] | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The body to write for an edit from `original` (on disk) to `updated` (the editor's body):
|
|
37
|
+
* `updated`'s content, in `original`'s text wherever the two agree. See the module header.
|
|
38
|
+
*/
|
|
39
|
+
export declare function preserveMarkdownSource(original: string, updated: string, format: MarkdownBodyFormat): string;
|