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
package/dist/git-manager.js
CHANGED
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import fs from 'node:fs/promises';
|
|
17
17
|
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
18
19
|
import { simpleGit, } from 'simple-git';
|
|
19
20
|
import { invalidateContentIndexesForRoot } from './content-index-registry.js';
|
|
20
21
|
import { invalidateBranchContentCaches } from './content-index-generation.js';
|
|
21
22
|
import { createDebugLogger } from './utils/debug.js';
|
|
22
|
-
import { getErrorMessage, isNotFoundError } from './utils/error.js';
|
|
23
|
+
import { getErrorMessage, isNodeError, isNotFoundError, redactCredentials } from './utils/error.js';
|
|
23
24
|
import { isMissingRemoteRefFailure, isNetworkRemoteUrl, resolveBaseBranch, stageAllExceptCanopyState, } from './utils/git.js';
|
|
25
|
+
import { BaseBranchUnresolvedError } from './utils/base-branch.js';
|
|
26
|
+
import { canopyLogWarn } from './utils/logger.js';
|
|
24
27
|
import { acquireProvisioningLock } from './utils/provisioning-lock.js';
|
|
25
28
|
const log = createDebugLogger({ prefix: 'GitManager' });
|
|
26
29
|
/**
|
|
@@ -74,8 +77,8 @@ export function gitChildEnv(overrides) {
|
|
|
74
77
|
* classifier into a no-op.
|
|
75
78
|
*
|
|
76
79
|
* `GIT_SSH_COMMAND` stays out even though it is a "network" variable:
|
|
77
|
-
* simple-git hard-blocks it (`allowUnsafeSshCommand`), and
|
|
78
|
-
*
|
|
80
|
+
* simple-git hard-blocks it (`allowUnsafeSshCommand`), and the worker's GitHub
|
|
81
|
+
* gateway builds an `https://` URL, so the worker never reaches GitHub over SSH.
|
|
79
82
|
*/
|
|
80
83
|
const GIT_NETWORK_ENV_PASSTHROUGH = /^((HTTPS?|ALL)_PROXY|(https?|all)_proxy|NO_PROXY|no_proxy|GIT_SSL_(CAINFO|CAPATH|NO_VERIFY|VERSION)|CURL_CA_BUNDLE|SSL_CERT_(FILE|DIR)|REQUESTS_CA_BUNDLE|NODE_EXTRA_CA_CERTS)$/;
|
|
81
84
|
export function gitNetworkChildEnv() {
|
|
@@ -88,6 +91,136 @@ export function gitNetworkChildEnv() {
|
|
|
88
91
|
}
|
|
89
92
|
return { ...env, ...FORCE_C_LOCALE };
|
|
90
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* `-c` on every git process a GitManager starts, so gc and auto-maintenance
|
|
96
|
+
* never run inside a Lambda, which can be frozen mid-run with a half-written
|
|
97
|
+
* pack or a `gc.pid` left on EFS. A provisioning clone also persists them
|
|
98
|
+
* into the workspace's own config. `remote.git`'s `receive-pack` never sees
|
|
99
|
+
* them; {@link REMOTE_GIT_CONFIG} covers that side.
|
|
100
|
+
*/
|
|
101
|
+
const NO_AUTO_GC_CONFIG = ['gc.auto=0', 'maintenance.auto=false'];
|
|
102
|
+
/**
|
|
103
|
+
* Settings written into every bare `remote.git` CanopyCMS creates: the worker's (prod) and the
|
|
104
|
+
* simulated remote (dev). A push runs `receive-pack` with remote.git's own config and none of the
|
|
105
|
+
* pusher's: git unsets `GIT_CONFIG_PARAMETERS` before spawning it for a local-path push. Unset, a
|
|
106
|
+
* push starts auto-housekeeping there (detached, or inside a Lambda that is frozen mid-run) that
|
|
107
|
+
* repacks while another process clones, and pushes under the default `unpackLimit` (100) leave
|
|
108
|
+
* loose objects that every local clone copies one NFS round trip at a time.
|
|
109
|
+
*
|
|
110
|
+
* Never an `extensions.*` key: once a repo is at `repositoryformatversion` 1, a git that does not
|
|
111
|
+
* know the key refuses it, and Lambda runs git 2.39.
|
|
112
|
+
* @internal Exported for tests.
|
|
113
|
+
*/
|
|
114
|
+
export const REMOTE_GIT_CONFIG = [
|
|
115
|
+
['gc.auto', '0'],
|
|
116
|
+
['receive.autogc', 'false'],
|
|
117
|
+
['maintenance.auto', 'false'],
|
|
118
|
+
['transfer.unpackLimit', '1'],
|
|
119
|
+
];
|
|
120
|
+
/**
|
|
121
|
+
* Write each of {@link REMOTE_GIT_CONFIG} into the bare repo `gitDir` only where it differs. `git`
|
|
122
|
+
* is how the worker passes the instance it runs every shared repository with.
|
|
123
|
+
*/
|
|
124
|
+
export async function ensureRemoteGitConfig(gitDir, git = simpleGit().env(gitChildEnv({}))) {
|
|
125
|
+
const current = new Map();
|
|
126
|
+
const listed = await git.raw(['--git-dir', gitDir, 'config', '--local', '--list']);
|
|
127
|
+
for (const line of listed.split('\n')) {
|
|
128
|
+
const eq = line.indexOf('=');
|
|
129
|
+
if (eq > 0)
|
|
130
|
+
current.set(line.slice(0, eq).toLowerCase(), line.slice(eq + 1));
|
|
131
|
+
}
|
|
132
|
+
for (const [key, value] of REMOTE_GIT_CONFIG) {
|
|
133
|
+
if (current.get(key.toLowerCase()) === value)
|
|
134
|
+
continue;
|
|
135
|
+
await git.raw(['--git-dir', gitDir, 'config', '--local', key, value]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Above either count, {@link repackBareRemoteIfNeeded} repacks. */
|
|
139
|
+
const REMOTE_GIT_MAX_LOOSE_OBJECTS = 50;
|
|
140
|
+
const REMOTE_GIT_MAX_PACKS = 6;
|
|
141
|
+
async function countObjects(git, gitDir) {
|
|
142
|
+
const output = await git.raw(['--git-dir', gitDir, 'count-objects', '-v', '--end-of-options']);
|
|
143
|
+
const fields = new Map(output.split('\n').map((line) => {
|
|
144
|
+
const [name, value = ''] = line.split(': ');
|
|
145
|
+
return [name, value.trim()];
|
|
146
|
+
}));
|
|
147
|
+
const field = (name) => {
|
|
148
|
+
const value = fields.get(name);
|
|
149
|
+
if (!value || !/^\d+$/.test(value)) {
|
|
150
|
+
throw new Error(`count-objects -v printed no '${name}' count: ${output.trim()}`);
|
|
151
|
+
}
|
|
152
|
+
return Number(value);
|
|
153
|
+
};
|
|
154
|
+
return { loose: field('count'), packs: field('packs') };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Repack the bare repo `gitDir` once it holds more than {@link REMOTE_GIT_MAX_LOOSE_OBJECTS}
|
|
158
|
+
* loose objects or {@link REMOTE_GIT_MAX_PACKS} packs, then pack its refs. Under
|
|
159
|
+
* {@link REMOTE_GIT_CONFIG} every push adds a pack, and nothing else ever compacts them.
|
|
160
|
+
*
|
|
161
|
+
* Safe beside concurrent pushes and clones, which is why it is `--cruft` with no expiry:
|
|
162
|
+
* unreachable objects move into a cruft pack rather than being dropped, so a push that read an
|
|
163
|
+
* old object before the repack still finds it, and a repack only deletes packs it listed when it
|
|
164
|
+
* began. A clone that hardlinked a pack keeps that inode when the repack unlinks the remote's
|
|
165
|
+
* name for it, and packs are never modified in place. `repack -d` also removes the loose objects
|
|
166
|
+
* it packed and their emptied fan-out directories. `git` as for `ensureRemoteGitConfig`.
|
|
167
|
+
*/
|
|
168
|
+
export async function repackBareRemoteIfNeeded(gitDir, git = simpleGit().env(gitChildEnv({}))) {
|
|
169
|
+
const before = await countObjects(git, gitDir);
|
|
170
|
+
if (before.loose <= REMOTE_GIT_MAX_LOOSE_OBJECTS && before.packs <= REMOTE_GIT_MAX_PACKS) {
|
|
171
|
+
return { repacked: false, before };
|
|
172
|
+
}
|
|
173
|
+
const startedAt = Date.now();
|
|
174
|
+
await git.raw(['--git-dir', gitDir, 'repack', '-a', '-d', '--cruft', '-q', '--end-of-options']);
|
|
175
|
+
await git.raw(['--git-dir', gitDir, 'pack-refs', '--all', '--end-of-options']);
|
|
176
|
+
const after = await countObjects(git, gitDir);
|
|
177
|
+
return { repacked: true, before, after, ms: Date.now() - startedAt };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* simple-git reads a git that exited by signal (exit code null, often with no stderr) as success,
|
|
181
|
+
* so a clone or checkout the OOM killer stopped would pass for complete. Given as `errors` to a
|
|
182
|
+
* GitManager's own instance, to `cloneRepo`'s, and to the worker's ref probes of an existing
|
|
183
|
+
* remote.git (worker/cms-worker.ts); its static helpers and the bare-remote functions do not pass
|
|
184
|
+
* it yet (safeinsights/canopycms#845).
|
|
185
|
+
*/
|
|
186
|
+
export function failOnSignalExit(error, result) {
|
|
187
|
+
if (error)
|
|
188
|
+
return error;
|
|
189
|
+
if (result.exitCode === null) {
|
|
190
|
+
return new Error('git was killed by a signal before it finished');
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
/** EFS cost is per-file latency, which parallel file creation overlaps. git >= 2.32. */
|
|
195
|
+
const PARALLEL_CHECKOUT_ARGS = [
|
|
196
|
+
'-c',
|
|
197
|
+
'checkout.workers=8',
|
|
198
|
+
'-c',
|
|
199
|
+
'checkout.thresholdForParallelism=1',
|
|
200
|
+
];
|
|
201
|
+
async function dirState(dir) {
|
|
202
|
+
try {
|
|
203
|
+
return (await fs.readdir(dir)).length === 0 ? 'empty' : 'occupied';
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
if (isNodeError(err) && err.code === 'ENOENT')
|
|
207
|
+
return 'absent';
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Config every workspace clone writes into its own `.git/config`: the managed
|
|
213
|
+
* marker that ensureRemote's guard checks, and a fallback author for internal
|
|
214
|
+
* commits such as the orphan settings init (ensureAuthor() sets the real one
|
|
215
|
+
* before user-facing commits).
|
|
216
|
+
*/
|
|
217
|
+
export function managedWorkspaceConfig(gitBotAuthorName, gitBotAuthorEmail) {
|
|
218
|
+
return {
|
|
219
|
+
'canopycms.managed': 'true',
|
|
220
|
+
'user.name': gitBotAuthorName,
|
|
221
|
+
'user.email': gitBotAuthorEmail,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
91
224
|
// In-memory lock to prevent concurrent remote.git initialization
|
|
92
225
|
const remoteInitLocks = new Map();
|
|
93
226
|
/**
|
|
@@ -105,8 +238,8 @@ const remoteInitLocks = new Map();
|
|
|
105
238
|
* next push no-ops and the editor's commit never arrives while the branch still
|
|
106
239
|
* reports `synced`. Confining `+`/`--prune` to this namespace keeps them off
|
|
107
240
|
* those local heads; `reconcileTrackedBranches()` (worker/git-sync.ts) is what
|
|
108
|
-
*
|
|
109
|
-
*
|
|
241
|
+
* then brings `refs/heads/*` toward GitHub's branches, never moving a head it
|
|
242
|
+
* finds editor work in.
|
|
110
243
|
*/
|
|
111
244
|
export const GITHUB_TRACKING_REF_PREFIX = 'refs/remotes/github/';
|
|
112
245
|
/**
|
|
@@ -174,10 +307,19 @@ export class GitRemoteRefMissingError extends Error {
|
|
|
174
307
|
* The HTTP layer maps it to a 503 (`http/worker-not-ready.ts`).
|
|
175
308
|
*/
|
|
176
309
|
export class RemoteNotReadyError extends Error {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
310
|
+
/**
|
|
311
|
+
* @param workerStartupFailure - The startup failure the worker recorded, if it recorded one;
|
|
312
|
+
* a current one means the worker is not coming without an admin's fix.
|
|
313
|
+
* @param configured - `expectedRemotePath` is a configured local remote, not the auto-detected one.
|
|
314
|
+
*/
|
|
315
|
+
constructor(expectedRemotePath, workerStartupFailure, configured = false) {
|
|
316
|
+
super(configured
|
|
317
|
+
? `CanopyCMS: no git remote is available yet. The configured remote ${expectedRemotePath} ` +
|
|
318
|
+
`does not exist; the worker may still be starting.`
|
|
319
|
+
: `CanopyCMS: no git remote is available yet. defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is ` +
|
|
320
|
+
`not set and the CMS worker has not created ${expectedRemotePath}; the worker may still be starting.`);
|
|
180
321
|
this.expectedRemotePath = expectedRemotePath;
|
|
322
|
+
this.workerStartupFailure = workerStartupFailure;
|
|
181
323
|
this.name = 'RemoteNotReadyError';
|
|
182
324
|
}
|
|
183
325
|
}
|
|
@@ -244,7 +386,12 @@ export class GitManager {
|
|
|
244
386
|
this.baseBranch = options.baseBranch ?? 'main';
|
|
245
387
|
this.remote = options.remote ?? 'origin';
|
|
246
388
|
this.skipIndexMarker = options.skipIndexMarker ?? false;
|
|
247
|
-
this.git = simpleGit({
|
|
389
|
+
this.git = simpleGit({
|
|
390
|
+
baseDir: this.repoPath,
|
|
391
|
+
errors: failOnSignalExit,
|
|
392
|
+
...gitOptions,
|
|
393
|
+
config: [...NO_AUTO_GC_CONFIG, ...(gitOptions?.config ?? [])],
|
|
394
|
+
});
|
|
248
395
|
// `this.git` is for LOCAL working-tree ops: in the intended prod topology
|
|
249
396
|
// `origin` resolves to a local path (an auto-detected/initialized
|
|
250
397
|
// `remote.git`), so its env is gitChildEnv's allowlist, which drops
|
|
@@ -252,21 +399,54 @@ export class GitManager {
|
|
|
252
399
|
// gitNetworkChildEnv instead. Under the `allowNetworkRemoteInProd` escape
|
|
253
400
|
// hatch `this.remote` CAN be a network URL, and these calls do hit it with
|
|
254
401
|
// that restricted env: a known limitation of the escape hatch, tracked in
|
|
255
|
-
//
|
|
402
|
+
// safeinsights/canopycms#754.
|
|
256
403
|
//
|
|
257
404
|
// GIT_CEILING_DIRECTORIES stops git traversing above repoPath to a parent
|
|
258
405
|
// .git: a corrupt or missing workspace .git must fail, never silently
|
|
259
406
|
// operate on the host repo above.
|
|
260
407
|
this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path.dirname(this.repoPath) }));
|
|
261
408
|
}
|
|
262
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Single-branch clone of `baseBranch`, retried once: a clone can lose a race
|
|
411
|
+
* with the worker's repack of `remote.git` deleting a pack it had just
|
|
412
|
+
* listed. Before the retry it clears only what it created itself, so a
|
|
413
|
+
* target that held files beforehand is never touched.
|
|
414
|
+
*/
|
|
415
|
+
static async cloneRepo(remoteUrl, targetPath, baseBranch = 'main', options = {}) {
|
|
263
416
|
log.debug('git', 'Cloning repository', {
|
|
264
417
|
remoteUrl,
|
|
265
418
|
targetPath,
|
|
266
419
|
baseBranch,
|
|
267
420
|
});
|
|
268
|
-
const
|
|
269
|
-
|
|
421
|
+
const args = ['--single-branch', '--branch', baseBranch];
|
|
422
|
+
if (options.noCheckout)
|
|
423
|
+
args.push('--no-checkout');
|
|
424
|
+
if (options.remoteName)
|
|
425
|
+
args.push('--origin', options.remoteName);
|
|
426
|
+
for (const [key, value] of Object.entries(options.config ?? {})) {
|
|
427
|
+
args.push('-c', `${key}=${value}`);
|
|
428
|
+
}
|
|
429
|
+
for (const setting of NO_AUTO_GC_CONFIG)
|
|
430
|
+
args.push('-c', setting);
|
|
431
|
+
const git = simpleGit({ config: [...NO_AUTO_GC_CONFIG], errors: failOnSignalExit });
|
|
432
|
+
git.env({
|
|
433
|
+
...(isNetworkRemoteUrl(remoteUrl) ? gitNetworkChildEnv() : gitChildEnv({})),
|
|
434
|
+
GIT_CEILING_DIRECTORIES: path.dirname(targetPath),
|
|
435
|
+
});
|
|
436
|
+
const before = await dirState(targetPath);
|
|
437
|
+
try {
|
|
438
|
+
await git.clone(remoteUrl, targetPath, args);
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
if (before === 'occupied')
|
|
442
|
+
throw err;
|
|
443
|
+
canopyLogWarn(`[canopy] clone into ${targetPath} failed, retrying once: ` +
|
|
444
|
+
redactCredentials(getErrorMessage(err)));
|
|
445
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
446
|
+
if (before === 'empty')
|
|
447
|
+
await fs.mkdir(targetPath);
|
|
448
|
+
await git.clone(remoteUrl, targetPath, args);
|
|
449
|
+
}
|
|
270
450
|
log.debug('git', 'Clone complete');
|
|
271
451
|
}
|
|
272
452
|
/**
|
|
@@ -315,9 +495,24 @@ export class GitManager {
|
|
|
315
495
|
if (!isNotFoundError(err))
|
|
316
496
|
throw err;
|
|
317
497
|
}
|
|
498
|
+
if (remoteExists) {
|
|
499
|
+
// The worker's per-cycle repack never runs in dev; once per call keeps pushes from
|
|
500
|
+
// piling up packs. Best-effort: a remote it cannot compact still serves.
|
|
501
|
+
try {
|
|
502
|
+
const repack = await repackBareRemoteIfNeeded(options.remotePath);
|
|
503
|
+
if (repack.repacked)
|
|
504
|
+
log.debug('git', 'Repacked local simulated remote', repack);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
log.debug('git', 'Could not repack local simulated remote', {
|
|
508
|
+
error: getErrorMessage(err),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
318
512
|
if (remoteExists &&
|
|
319
513
|
(await GitManager.bareRemoteHasBranch(options.remotePath, options.baseBranch))) {
|
|
320
514
|
log.debug('git', 'Remote already has base branch, skipping');
|
|
515
|
+
await ensureRemoteGitConfig(options.remotePath);
|
|
321
516
|
return;
|
|
322
517
|
}
|
|
323
518
|
// Find the actual git root directory — the subdirectory snapshot path
|
|
@@ -375,6 +570,7 @@ export class GitManager {
|
|
|
375
570
|
options.remotePath,
|
|
376
571
|
]);
|
|
377
572
|
}
|
|
573
|
+
await ensureRemoteGitConfig(options.remotePath);
|
|
378
574
|
// Push baseBranch to remote (not current HEAD)
|
|
379
575
|
await GitManager.pushBranchToLocalRemote({
|
|
380
576
|
sourceGit,
|
|
@@ -457,12 +653,13 @@ export class GitManager {
|
|
|
457
653
|
.filter(Boolean);
|
|
458
654
|
if (options.namespaces === 'tracking') {
|
|
459
655
|
// GitHub's view only. Local heads in `remote.git` are NOT evidence of a
|
|
460
|
-
// collision for branch CREATION:
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
656
|
+
// collision for branch CREATION: one can outlive its branch (the sync
|
|
657
|
+
// loop's reconcileTrackedBranches deletes it no sooner than the cycle
|
|
658
|
+
// after GitHub does, and never while it finds editor work in it, and an
|
|
659
|
+
// editor delete's own removal is best-effort), so an
|
|
660
|
+
// ordinary create -> publish -> merge -> delete -> reuse-the-name cycle
|
|
661
|
+
// would otherwise report an untrue collision on a name the user just
|
|
662
|
+
// deleted. This deployment's own live branches are already
|
|
466
663
|
// covered by the branch registry check that runs before this one.
|
|
467
664
|
return refs.includes(`${GITHUB_TRACKING_REF_PREFIX}${branch}`);
|
|
468
665
|
}
|
|
@@ -473,10 +670,10 @@ export class GitManager {
|
|
|
473
670
|
* Delete `refs/heads/<branch>` from a bare local mirror, if present. A
|
|
474
671
|
* no-op (not an error) when the ref doesn't exist.
|
|
475
672
|
*
|
|
476
|
-
* The
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
673
|
+
* The path that removes a deleted branch's local head at once; the sync
|
|
674
|
+
* loop's reconcileTrackedBranches removes one only after GitHub no longer
|
|
675
|
+
* has the branch. Called by api/branch.ts's deleteBranchHandler: a head
|
|
676
|
+
* left in `remote.git` makes the
|
|
480
677
|
* create -> publish -> squash-merge -> delete -> reuse-the-name cycle reject
|
|
481
678
|
* the reused branch's first publish non-fast-forward against the stale head
|
|
482
679
|
* (`GitManager.push()` pushes the branch to the same name, and a squash-merged old tip is
|
|
@@ -634,11 +831,18 @@ export class GitManager {
|
|
|
634
831
|
`intentionally runs git against a network remote (e.g. a single-VM deployment), set ` +
|
|
635
832
|
`config.allowNetworkRemoteInProd: true to acknowledge this.`);
|
|
636
833
|
}
|
|
637
|
-
/**
|
|
834
|
+
/**
|
|
835
|
+
* The not-ready error for a mode whose remote is auto-detected, carrying the worker's recorded
|
|
836
|
+
* startup failure, or `undefined` for other modes.
|
|
837
|
+
*/
|
|
638
838
|
static async remoteNotReadyError(mode) {
|
|
639
839
|
const { operatingStrategy } = await import('./operating-mode/index.js');
|
|
640
840
|
const { autoDetectRemotePath } = operatingStrategy(mode).getRemoteUrlConfig();
|
|
641
|
-
|
|
841
|
+
if (!autoDetectRemotePath)
|
|
842
|
+
return undefined;
|
|
843
|
+
const { getTaskQueueDir } = await import('./task-queue/task-queue-config.js');
|
|
844
|
+
const { readWorkerStartupFailure } = await import('./task-queue/worker-status.js');
|
|
845
|
+
return new RemoteNotReadyError(autoDetectRemotePath, await readWorkerStartupFailure(getTaskQueueDir({ mode })));
|
|
642
846
|
}
|
|
643
847
|
/**
|
|
644
848
|
* Resolves the remote URL for git operations following the priority:
|
|
@@ -676,18 +880,22 @@ export class GitManager {
|
|
|
676
880
|
}
|
|
677
881
|
// Auto-detect a pre-existing remote.git at the expected path (in prod,
|
|
678
882
|
// created by the EC2 worker on EFS)
|
|
883
|
+
// Only a missing path means "not created yet". Anything else there is broken, and waiting
|
|
884
|
+
// for the worker would not fix it.
|
|
679
885
|
if (config.autoDetectRemotePath) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
886
|
+
const remotePath = config.autoDetectRemotePath;
|
|
887
|
+
const stat = await fs.stat(remotePath).catch((err) => {
|
|
888
|
+
if (isNotFoundError(err))
|
|
889
|
+
return undefined;
|
|
890
|
+
throw new Error(`CanopyCMS: cannot read the git remote at ${remotePath}: ${getErrorMessage(err)}`);
|
|
891
|
+
});
|
|
892
|
+
if (stat?.isDirectory()) {
|
|
893
|
+
log.debug('git', 'Auto-detected local remote', { path: remotePath });
|
|
894
|
+
return remotePath;
|
|
688
895
|
}
|
|
689
|
-
|
|
690
|
-
|
|
896
|
+
if (stat) {
|
|
897
|
+
throw new Error(`CanopyCMS: ${remotePath} exists but is not a directory, so it cannot be the git remote. ` +
|
|
898
|
+
`The CMS worker creates it as a bare repository; remove what is there and restart the worker.`);
|
|
691
899
|
}
|
|
692
900
|
}
|
|
693
901
|
if (config.shouldAutoInitLocal) {
|
|
@@ -705,6 +913,82 @@ export class GitManager {
|
|
|
705
913
|
}
|
|
706
914
|
return undefined;
|
|
707
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* {@link resolveRemoteUrl} for a workspace about to be cloned, which needs a
|
|
918
|
+
* remote: none resolving is the worker-not-ready error in a mode whose
|
|
919
|
+
* remote the worker creates, and a configuration error otherwise.
|
|
920
|
+
*/
|
|
921
|
+
static async resolveCloneRemoteUrl(options) {
|
|
922
|
+
const remoteUrl = await GitManager.resolveRemoteUrl(options);
|
|
923
|
+
if (remoteUrl)
|
|
924
|
+
return remoteUrl;
|
|
925
|
+
const notReady = await GitManager.remoteNotReadyError(options.mode);
|
|
926
|
+
if (notReady)
|
|
927
|
+
throw notReady;
|
|
928
|
+
throw new Error('CanopyCMS: defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is required to initialize workspace');
|
|
929
|
+
}
|
|
930
|
+
static async detectBaseBranch(config, whenNoRemote) {
|
|
931
|
+
const options = {
|
|
932
|
+
mode: 'prod',
|
|
933
|
+
remoteUrl: config.remoteUrl,
|
|
934
|
+
defaultRemoteUrl: config.defaultRemoteUrl,
|
|
935
|
+
sourceRoot: config.sourceRoot,
|
|
936
|
+
allowNetworkRemoteInProd: config.allowNetworkRemoteInProd,
|
|
937
|
+
// Read only to seed dev's simulated remote, which prod never does.
|
|
938
|
+
baseBranch: '',
|
|
939
|
+
};
|
|
940
|
+
const remoteUrl = whenNoRemote === 'throw'
|
|
941
|
+
? await GitManager.resolveCloneRemoteUrl(options)
|
|
942
|
+
: await GitManager.resolveRemoteUrl(options);
|
|
943
|
+
if (!remoteUrl)
|
|
944
|
+
return undefined;
|
|
945
|
+
if (isNetworkRemoteUrl(remoteUrl)) {
|
|
946
|
+
throw new BaseBranchUnresolvedError(`defaultBaseBranch is not set, and the base branch cannot be read locally from the ` +
|
|
947
|
+
`network remote ${redactCredentials(remoteUrl)}.`);
|
|
948
|
+
}
|
|
949
|
+
const remoteGitDir = /^file:\/\//i.test(remoteUrl) ? fileURLToPath(remoteUrl) : remoteUrl;
|
|
950
|
+
// A configured local remote is not existence-checked by resolveRemoteUrl; absent, it is the
|
|
951
|
+
// same not-yet-created remote the auto-detected one is.
|
|
952
|
+
if (await fs.stat(remoteGitDir).then(() => false, isNotFoundError)) {
|
|
953
|
+
if (whenNoRemote === 'pending')
|
|
954
|
+
return undefined;
|
|
955
|
+
const notReady = await GitManager.remoteNotReadyError('prod');
|
|
956
|
+
if (notReady?.expectedRemotePath === path.resolve(remoteGitDir))
|
|
957
|
+
throw notReady;
|
|
958
|
+
throw new RemoteNotReadyError(remoteGitDir, notReady?.workerStartupFailure, true);
|
|
959
|
+
}
|
|
960
|
+
return resolveBaseBranch({ mode: 'prod', remoteGitDir });
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* The base branch a workspace about to be provisioned forks from: the configured one, else
|
|
964
|
+
* dev's git HEAD (resolveBaseBranch), else prod's remote HEAD ({@link detectBaseBranch}), which
|
|
965
|
+
* rejects with RemoteNotReadyError while the worker has not created the remote.
|
|
966
|
+
*/
|
|
967
|
+
static async resolveWorkspaceBaseBranch(options) {
|
|
968
|
+
if (options.mode === 'prod' && !options.baseBranch) {
|
|
969
|
+
return GitManager.detectBaseBranch(options, 'throw');
|
|
970
|
+
}
|
|
971
|
+
return resolveBaseBranch({
|
|
972
|
+
defaultBaseBranch: options.baseBranch,
|
|
973
|
+
mode: options.mode,
|
|
974
|
+
detectFrom: options.sourceRoot
|
|
975
|
+
? path.resolve(process.cwd(), options.sourceRoot)
|
|
976
|
+
: process.cwd(),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* {@link cloneRepo} for a workspace, failing with one message that names the
|
|
981
|
+
* workspace, the remote and the base branch, which git's own error mixes.
|
|
982
|
+
*/
|
|
983
|
+
static async cloneWorkspace(remoteUrl, workspacePath, baseBranch, options) {
|
|
984
|
+
try {
|
|
985
|
+
await GitManager.cloneRepo(remoteUrl, workspacePath, baseBranch, options);
|
|
986
|
+
}
|
|
987
|
+
catch (err) {
|
|
988
|
+
throw new Error(`Failed to clone branch workspace at ${workspacePath} ` +
|
|
989
|
+
`from ${remoteUrl} (base branch '${baseBranch}'): ${getErrorMessage(err)}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
708
992
|
/**
|
|
709
993
|
* Check whether a git repository is already initialized at `workspacePath`.
|
|
710
994
|
*
|
|
@@ -735,15 +1019,7 @@ export class GitManager {
|
|
|
735
1019
|
* Note: Does NOT configure git author - that should be done before commits, not during init.
|
|
736
1020
|
*/
|
|
737
1021
|
static async initializeWorkspace(options) {
|
|
738
|
-
|
|
739
|
-
// current HEAD when baseBranch is not explicitly set).
|
|
740
|
-
const baseBranch = await resolveBaseBranch({
|
|
741
|
-
defaultBaseBranch: options.baseBranch,
|
|
742
|
-
mode: options.mode,
|
|
743
|
-
detectFrom: options.sourceRoot
|
|
744
|
-
? path.resolve(process.cwd(), options.sourceRoot)
|
|
745
|
-
: process.cwd(),
|
|
746
|
-
});
|
|
1022
|
+
const baseBranch = await GitManager.resolveWorkspaceBaseBranch(options);
|
|
747
1023
|
const remoteName = options.remoteName ?? 'origin';
|
|
748
1024
|
const repoExists = await GitManager.repoExistsAt(options.workspacePath);
|
|
749
1025
|
if (!repoExists) {
|
|
@@ -763,10 +1039,12 @@ export class GitManager {
|
|
|
763
1039
|
throw cleanupErr;
|
|
764
1040
|
}
|
|
765
1041
|
}
|
|
1042
|
+
const step = (name, run) => options.provisionLog ? options.provisionLog.step(name, run) : run();
|
|
1043
|
+
const identity = managedWorkspaceConfig(options.gitBotAuthorName, options.gitBotAuthorEmail);
|
|
766
1044
|
let justCloned = false;
|
|
767
1045
|
let resolvedRemoteUrl;
|
|
768
1046
|
if (!repoExists) {
|
|
769
|
-
const remoteUrl = await GitManager.
|
|
1047
|
+
const remoteUrl = await GitManager.resolveCloneRemoteUrl({
|
|
770
1048
|
mode: options.mode,
|
|
771
1049
|
remoteUrl: options.remoteUrl,
|
|
772
1050
|
defaultRemoteUrl: options.defaultRemoteUrl,
|
|
@@ -774,32 +1052,15 @@ export class GitManager {
|
|
|
774
1052
|
sourceRoot: options.sourceRoot,
|
|
775
1053
|
allowNetworkRemoteInProd: options.allowNetworkRemoteInProd,
|
|
776
1054
|
});
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
await GitManager.cloneRepo(remoteUrl, options.workspacePath, baseBranch);
|
|
785
|
-
}
|
|
786
|
-
catch (err) {
|
|
787
|
-
// The raw git error ("Cloning into <workspace>… branch <base> not found")
|
|
788
|
-
// mixes the workspace name and the base branch — spell both out.
|
|
789
|
-
throw new Error(`Failed to clone branch workspace at ${options.workspacePath} ` +
|
|
790
|
-
`from ${remoteUrl} (base branch '${baseBranch}'): ${getErrorMessage(err)}`);
|
|
791
|
-
}
|
|
1055
|
+
// A content clone skips its own checkout because checkoutFreshClone runs
|
|
1056
|
+
// the one checkout this workspace needs.
|
|
1057
|
+
await step('clone', () => GitManager.cloneWorkspace(remoteUrl, options.workspacePath, baseBranch, {
|
|
1058
|
+
remoteName,
|
|
1059
|
+
noCheckout: options.branchType === 'content',
|
|
1060
|
+
config: identity,
|
|
1061
|
+
}));
|
|
792
1062
|
justCloned = true;
|
|
793
1063
|
resolvedRemoteUrl = remoteUrl;
|
|
794
|
-
// Mark as managed immediately after clone so ensureRemote's guard works,
|
|
795
|
-
// and set a fallback author identity: GIT_CEILING_DIRECTORIES blocks
|
|
796
|
-
// global gitconfig, and internal commits (e.g. orphan branch init) need
|
|
797
|
-
// one. ensureAuthor() sets the real bot author before user-facing commits.
|
|
798
|
-
const freshGit = simpleGit({ baseDir: options.workspacePath });
|
|
799
|
-
freshGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path.dirname(options.workspacePath) }));
|
|
800
|
-
await freshGit.addConfig('canopycms.managed', 'true');
|
|
801
|
-
await freshGit.addConfig('user.name', options.gitBotAuthorName);
|
|
802
|
-
await freshGit.addConfig('user.email', options.gitBotAuthorEmail);
|
|
803
1064
|
}
|
|
804
1065
|
// Settings (orphan) workspaces never host ContentStores, so they skip the
|
|
805
1066
|
// on-disk content-index generation marker.
|
|
@@ -809,33 +1070,28 @@ export class GitManager {
|
|
|
809
1070
|
remote: remoteName,
|
|
810
1071
|
skipIndexMarker: options.branchType === 'orphan',
|
|
811
1072
|
});
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
// commits and needs an author). Idempotent — the clone above may have set them.
|
|
815
|
-
await git.git.addConfig('canopycms.managed', 'true');
|
|
816
|
-
await git.git.addConfig('user.name', options.gitBotAuthorName);
|
|
817
|
-
await git.git.addConfig('user.email', options.gitBotAuthorEmail);
|
|
818
|
-
log.debug('git', 'Marked workspace as CanopyCMS-managed', {
|
|
819
|
-
workspacePath: options.workspacePath,
|
|
820
|
-
});
|
|
821
|
-
// Configure the remote only if we didn't just clone (clone sets up 'origin')
|
|
1073
|
+
// A reused workspace gets the same marker and identity the clone writes,
|
|
1074
|
+
// set before ensureRemote checks the marker; a healthy one writes nothing.
|
|
822
1075
|
if (!justCloned) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
remoteUrl
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1076
|
+
await step('config', async () => {
|
|
1077
|
+
await git.ensureLocalConfig(identity);
|
|
1078
|
+
const remoteUrl = await GitManager.resolveRemoteUrl({
|
|
1079
|
+
mode: options.mode,
|
|
1080
|
+
remoteUrl: options.remoteUrl,
|
|
1081
|
+
defaultRemoteUrl: options.defaultRemoteUrl,
|
|
1082
|
+
baseBranch,
|
|
1083
|
+
sourceRoot: options.sourceRoot,
|
|
1084
|
+
allowNetworkRemoteInProd: options.allowNetworkRemoteInProd,
|
|
1085
|
+
});
|
|
1086
|
+
if (remoteUrl) {
|
|
1087
|
+
await git.ensureRemote(remoteUrl);
|
|
1088
|
+
}
|
|
1089
|
+
resolvedRemoteUrl = remoteUrl;
|
|
830
1090
|
});
|
|
831
|
-
if (remoteUrl) {
|
|
832
|
-
await git.ensureRemote(remoteUrl);
|
|
833
|
-
}
|
|
834
|
-
resolvedRemoteUrl = remoteUrl;
|
|
835
1091
|
}
|
|
836
1092
|
if (options.branchType === 'orphan') {
|
|
837
1093
|
try {
|
|
838
|
-
await git.createOrphanSettingsBranch(options.branchName, {});
|
|
1094
|
+
await step('checkout', () => git.createOrphanSettingsBranch(options.branchName, {}));
|
|
839
1095
|
}
|
|
840
1096
|
catch (err) {
|
|
841
1097
|
// A remote that is unreadable because the worker has not (re)created
|
|
@@ -850,16 +1106,19 @@ export class GitManager {
|
|
|
850
1106
|
// workspace. Commits here stage explicit paths, but a crash-orphaned lock
|
|
851
1107
|
// dir must never be committable by a future broad stage either. Runs on
|
|
852
1108
|
// every init, so existing clones pick it up.
|
|
853
|
-
await git.ensureGitExclude('*.lock');
|
|
1109
|
+
await step('exclude', () => git.ensureGitExclude('*.lock'));
|
|
854
1110
|
}
|
|
855
1111
|
else {
|
|
856
|
-
await
|
|
1112
|
+
await step('checkout', () => justCloned
|
|
1113
|
+
? git.checkoutFreshClone(options.branchName)
|
|
1114
|
+
: git.checkoutBranch(options.branchName));
|
|
857
1115
|
// Excludes runtime metadata (.canopy-meta/) from git tracking on content
|
|
858
1116
|
// branches. Settings workspaces don't need it: they stage explicit file
|
|
859
1117
|
// paths at the workspace root and skip the index marker entirely
|
|
860
1118
|
// (skipIndexMarker), so nothing under .canopy-meta/ is ever staged.
|
|
861
|
-
|
|
862
|
-
|
|
1119
|
+
const pattern = options.gitExcludePattern;
|
|
1120
|
+
if (pattern) {
|
|
1121
|
+
await step('exclude', () => git.ensureGitExclude(pattern));
|
|
863
1122
|
}
|
|
864
1123
|
}
|
|
865
1124
|
return git;
|
|
@@ -902,6 +1161,24 @@ export class GitManager {
|
|
|
902
1161
|
await this.invalidateContentIndexes();
|
|
903
1162
|
}
|
|
904
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* The one checkout of a `--no-checkout` clone, which has the base branch
|
|
1166
|
+
* and its remote-tracking ref and nothing else, so neither the branch
|
|
1167
|
+
* listing nor the fetch that {@link checkoutBranch} starts with is needed.
|
|
1168
|
+
* The missing index makes git populate the whole tree, even for the base
|
|
1169
|
+
* branch HEAD already names.
|
|
1170
|
+
*/
|
|
1171
|
+
async checkoutFreshClone(branch) {
|
|
1172
|
+
const target = branch === this.baseBranch
|
|
1173
|
+
? ['checkout', branch]
|
|
1174
|
+
: ['checkout', '-b', branch, `${this.remote}/${this.baseBranch}`];
|
|
1175
|
+
try {
|
|
1176
|
+
await this.git.raw([...PARALLEL_CHECKOUT_ARGS, ...target]);
|
|
1177
|
+
}
|
|
1178
|
+
finally {
|
|
1179
|
+
await this.invalidateContentIndexes();
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
905
1182
|
async checkoutBranchInner(branch) {
|
|
906
1183
|
const branches = await this.git.branch();
|
|
907
1184
|
if (branches.all.includes(branch)) {
|
|
@@ -1061,9 +1338,17 @@ export class GitManager {
|
|
|
1061
1338
|
throw err;
|
|
1062
1339
|
}
|
|
1063
1340
|
}
|
|
1341
|
+
/** `--sparse` stages a named path outside a sparse clone's cone (branch-sparse.ts). */
|
|
1064
1342
|
async add(files) {
|
|
1065
1343
|
const fileArray = Array.isArray(files) ? files : [files];
|
|
1066
|
-
await this.git.add
|
|
1344
|
+
await this.git.raw(['add', '--sparse', '--', ...fileArray]);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Restrict the working tree to `dirs` in cone mode, which always keeps the root-level files.
|
|
1348
|
+
* Runs before a `--no-checkout` clone's one checkout, which invalidates the content caches.
|
|
1349
|
+
*/
|
|
1350
|
+
async setSparseCone(dirs) {
|
|
1351
|
+
await this.git.raw(['sparse-checkout', 'set', '--cone', '--', ...dirs]);
|
|
1067
1352
|
}
|
|
1068
1353
|
/** Stage every working-tree change except canopycms's own state. See {@link stageAllExceptCanopyState}. */
|
|
1069
1354
|
async addAllExceptCanopyState() {
|
|
@@ -1072,6 +1357,19 @@ export class GitManager {
|
|
|
1072
1357
|
async commit(message) {
|
|
1073
1358
|
await this.git.commit(message);
|
|
1074
1359
|
}
|
|
1360
|
+
/** The checked-out commit's SHA. */
|
|
1361
|
+
async headSha() {
|
|
1362
|
+
return (await this.git.revparse(['--verify', 'HEAD'])).trim();
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Move the checked-out branch back to `sha`, leaving everything after it unstaged in the
|
|
1366
|
+
* working tree (`reset --mixed`). Takes a SHA the caller read itself, never a relative ref:
|
|
1367
|
+
* a commit call that committed nothing still resolves, so "the last commit" is not always
|
|
1368
|
+
* the caller's.
|
|
1369
|
+
*/
|
|
1370
|
+
async resetKeepingChanges(sha) {
|
|
1371
|
+
await this.git.raw(['reset', '--mixed', sha]);
|
|
1372
|
+
}
|
|
1075
1373
|
async push(branch) {
|
|
1076
1374
|
const target = branch ?? (await this.currentBranchName());
|
|
1077
1375
|
// Explicit full-ref refspec (local:remote) so push works for branches not
|
|
@@ -1149,9 +1447,7 @@ export class GitManager {
|
|
|
1149
1447
|
* constraint.
|
|
1150
1448
|
*/
|
|
1151
1449
|
async listChangedPathsSinceBase() {
|
|
1152
|
-
|
|
1153
|
-
// `--verify`: without it rev-parse echoes `--end-of-options` back as output.
|
|
1154
|
-
const baseTip = (await this.git.revparse(['--verify', '--end-of-options', 'FETCH_HEAD'])).trim();
|
|
1450
|
+
const baseTip = await this.fetchBaseTip();
|
|
1155
1451
|
// -z: NUL-separated and unquoted, so no path needs unescaping.
|
|
1156
1452
|
const output = await this.git.raw([
|
|
1157
1453
|
'diff',
|
|
@@ -1163,6 +1459,44 @@ export class GitManager {
|
|
|
1163
1459
|
]);
|
|
1164
1460
|
return output.split('\0').filter((p) => p.length > 0);
|
|
1165
1461
|
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Repo-relative paths of every file in the base branch's just-fetched tip or in its merge base
|
|
1464
|
+
* with HEAD, limited to `under` (a repo-relative directory) when given. The merge base counts
|
|
1465
|
+
* because a file the base deleted after the fork is still the base's on a branch not yet
|
|
1466
|
+
* rebased. Reads commits, never the working tree, so a sparse clone lists the whole base.
|
|
1467
|
+
*/
|
|
1468
|
+
async listBaseFiles(under) {
|
|
1469
|
+
const tip = await this.fetchBaseTip();
|
|
1470
|
+
// With no shared commit merge-base exits 1 silently, which simple-git resolves as ''.
|
|
1471
|
+
const forkPoint = (await this.git.raw(['merge-base', '--end-of-options', 'HEAD', tip])).trim();
|
|
1472
|
+
const files = new Set(await this.listTreeFiles(tip, under));
|
|
1473
|
+
if (forkPoint && forkPoint !== tip) {
|
|
1474
|
+
for (const file of await this.listTreeFiles(forkPoint, under))
|
|
1475
|
+
files.add(file);
|
|
1476
|
+
}
|
|
1477
|
+
return [...files];
|
|
1478
|
+
}
|
|
1479
|
+
/** Fetch the base branch and return its tip, pinned as pullBaseInner explains. */
|
|
1480
|
+
async fetchBaseTip() {
|
|
1481
|
+
await this.git.raw(['fetch', '--end-of-options', this.remote, this.baseBranch]);
|
|
1482
|
+
// `--verify`: without it rev-parse echoes `--end-of-options` back as output.
|
|
1483
|
+
return (await this.git.revparse(['--verify', '--end-of-options', 'FETCH_HEAD'])).trim();
|
|
1484
|
+
}
|
|
1485
|
+
/** Repo-relative paths of every file in `rev`'s tree, under `under` when given. */
|
|
1486
|
+
async listTreeFiles(rev, under) {
|
|
1487
|
+
// -z: NUL-separated and unquoted, so no path needs unescaping.
|
|
1488
|
+
const output = await this.git.raw([
|
|
1489
|
+
'ls-tree',
|
|
1490
|
+
'-r',
|
|
1491
|
+
'-z',
|
|
1492
|
+
'--name-only',
|
|
1493
|
+
'--full-tree',
|
|
1494
|
+
'--end-of-options',
|
|
1495
|
+
rev,
|
|
1496
|
+
...(under ? [under] : []),
|
|
1497
|
+
]);
|
|
1498
|
+
return output.split('\0').filter((p) => p.length > 0);
|
|
1499
|
+
}
|
|
1166
1500
|
async ensureAuthor(author) {
|
|
1167
1501
|
const config = (await this.git.listConfig());
|
|
1168
1502
|
// Verify this is a CanopyCMS-managed workspace before setting author
|
|
@@ -1181,6 +1515,14 @@ export class GitManager {
|
|
|
1181
1515
|
await this.git.addConfig('user.email', author.email);
|
|
1182
1516
|
}
|
|
1183
1517
|
}
|
|
1518
|
+
/** Writes each key into this repo's own config only where it differs. */
|
|
1519
|
+
async ensureLocalConfig(values) {
|
|
1520
|
+
const local = (await this.git.listConfig('local'));
|
|
1521
|
+
for (const [key, value] of Object.entries(values)) {
|
|
1522
|
+
if (local.all[key] !== value)
|
|
1523
|
+
await this.git.addConfig(key, value);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1184
1526
|
async ensureRemote(remoteUrl) {
|
|
1185
1527
|
// Safety: verify this is a managed workspace before modifying remotes.
|
|
1186
1528
|
// Prevents accidentally overwriting the host repo's origin if git
|
|
@@ -1289,12 +1631,13 @@ export class GitManager {
|
|
|
1289
1631
|
// above), so it can't be reinterpreted as a flag; git's own ref-name
|
|
1290
1632
|
// validation additionally rejects a leading-hyphen value here.
|
|
1291
1633
|
await this.git.raw(['checkout', '--orphan', branchName]);
|
|
1292
|
-
//
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1634
|
+
// The orphan starts from the base branch's index. `--sparse` also removes entries outside a
|
|
1635
|
+
// sparse cone, which a plain `rm` leaves in the index, and so in the first commit.
|
|
1636
|
+
await this.git.raw(['rm', '-r', '-f', '-q', '--sparse', '--ignore-unmatch', '--', '.']);
|
|
1637
|
+
const leftover = (await this.git.raw(['ls-files'])).trim();
|
|
1638
|
+
if (leftover) {
|
|
1639
|
+
throw new Error(`Settings branch '${branchName}' was not created: the base branch's files ` +
|
|
1640
|
+
`(${leftover.split('\n').slice(0, 3).join(', ')}) stayed in its index`);
|
|
1298
1641
|
}
|
|
1299
1642
|
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
1300
1643
|
const absolutePath = path.join(this.repoPath, filePath);
|
|
@@ -1371,8 +1714,7 @@ export class GitManager {
|
|
|
1371
1714
|
* The base comes from the remote when the clone lacks it (it was cloned at an earlier base).
|
|
1372
1715
|
*/
|
|
1373
1716
|
async assertNotContentHistory(branchName, tip) {
|
|
1374
|
-
const
|
|
1375
|
-
const strays = tree.split('\n').filter((file) => file && !SETTINGS_BRANCH_FILES.has(file));
|
|
1717
|
+
const strays = (await this.listTreeFiles(tip)).filter((file) => !SETTINGS_BRANCH_FILES.has(file));
|
|
1376
1718
|
if (strays.length > 0) {
|
|
1377
1719
|
const shown = strays.slice(0, 3).join(', ') + (strays.length > 3 ? ', …' : '');
|
|
1378
1720
|
throw new SettingsBranchHasContentHistoryError(branchName, this.remote, `it holds ${shown}`);
|