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/worker/git-sync.js
CHANGED
|
@@ -1,47 +1,43 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { simpleGit } from 'simple-git';
|
|
4
3
|
import { BranchMetadataFileManager, getBranchMetadataFileManager } from '../branch-metadata.js';
|
|
4
|
+
import { BRANCH_META_DIR, BRANCH_META_FILE } from '../branch-metadata-file.js';
|
|
5
|
+
import { ORPHAN_YOUTH_THRESHOLD_MS } from '../branch-health.js';
|
|
6
|
+
import { classifyFinalDir, parseDirStamp, quarantineResidueAt, sweepProvisioningLeftovers, } from '../branch-provisioning.js';
|
|
5
7
|
import { invalidateBranchContentCaches } from '../content-index-generation.js';
|
|
6
|
-
import {
|
|
8
|
+
import { ensureGitExcludePattern } from '../git-manager.js';
|
|
7
9
|
import { RESERVED_SETTINGS_BRANCH_PREFIX } from '../paths/branch-name.js';
|
|
8
10
|
import { tryAcquireContentWriteLock } from '../utils/content-write-lock.js';
|
|
11
|
+
import { branchProvisioningLockName, tryAcquireProvisioningLock } from '../utils/provisioning-lock.js';
|
|
9
12
|
import { getErrorMessage, isNodeError, redactCredentials } from '../utils/error.js';
|
|
13
|
+
import { escapeForLog } from '../utils/escape-text.js';
|
|
10
14
|
import { CANOPY_META_DIR, isCanopyInternalPath, isNonFastForwardRejection } from '../utils/git.js';
|
|
11
15
|
import { MAX_REPORTED_PATHS, TRACKED_CANOPY_STATE_FIX, isUntracked, listTrackedCanopyState, restoreRetiredSchemaCache, splitByUpstreamTracking, untrackInIndex, } from './canopy-state.js';
|
|
12
16
|
import { hasPendingHistoryRewrite } from './history-rewrite.js';
|
|
13
17
|
import { runRebaseCycle } from './rebase.js';
|
|
18
|
+
import { describeMove } from './github-gateway.js';
|
|
19
|
+
import { fetchGitHubIntoRemoteGit, pushToGitHub } from './github-transfer.js';
|
|
14
20
|
import { cleanupOldTasks } from '../task-queue/cms-task-queue.js';
|
|
15
21
|
import { writeWorkerStatus } from '../task-queue/worker-status.js';
|
|
16
22
|
import { workerLog, workerLogError, workerLogWarn } from './log.js';
|
|
17
23
|
import { holdProvisionedWorkspace, releaseProvisionedWorkspace } from './provisioned-workspace.js';
|
|
24
|
+
import { maintainRemoteGit } from './remote-git-maintenance.js';
|
|
25
|
+
import { decideBaseAdvance } from './schema-gate.js';
|
|
26
|
+
import { SHARED_REPO_STATUS_ARGS, assertNoIncomingSubmodules, assertSharedRepoConfig, fetchFromRemoteGit, sharedRepoGit, } from './shared-repo-git.js';
|
|
27
|
+
import { reapplySparseCones } from './sparse-cone.js';
|
|
28
|
+
import { readEditorWork, settleStaleMirror, } from './stale-mirror.js';
|
|
18
29
|
/**
|
|
19
30
|
* [C1] Retention window for `.trash-*` branch directories left behind by the
|
|
20
31
|
* admin purge action (api/admin-branch-health.ts). Matches
|
|
21
32
|
* cleanupOldTasks's default task retention for consistency.
|
|
22
33
|
*/
|
|
23
34
|
const TRASH_RETENTION_MS = 30 * 24 * 60 * 60_000;
|
|
24
|
-
/** Matches `.trash-{dirName}-{STAMP}` names, capturing the trailing stamp. */
|
|
25
|
-
const TRASH_DIR_STAMP_RE = /-(\d{8}T\d{6}Z)$/;
|
|
26
|
-
/**
|
|
27
|
-
* Parse a purge-generated `YYYYMMDDTHHMMSSZ` stamp into a Date, or null if
|
|
28
|
-
* malformed. Age comes ONLY from this name-embedded stamp, never the dir's
|
|
29
|
-
* own mtime -- `fs.rename` preserves the original directory's mtime, so an
|
|
30
|
-
* mtime-based retention check would delete a months-stale orphan's trash on
|
|
31
|
-
* the very first cleanup pass after purge.
|
|
32
|
-
*/
|
|
33
|
-
function parseTrashStamp(stamp) {
|
|
34
|
-
const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(stamp);
|
|
35
|
-
if (!match)
|
|
36
|
-
return null;
|
|
37
|
-
const [, year, month, day, hour, minute, second] = match;
|
|
38
|
-
const date = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}.000Z`);
|
|
39
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
40
|
-
}
|
|
41
35
|
/**
|
|
42
36
|
* Push THIS deployment's own settings branch (`ensureSettingsBranch()`) from
|
|
43
|
-
* remote.git to GitHub. Non-fatal
|
|
44
|
-
*
|
|
37
|
+
* remote.git to GitHub. Non-fatal. Skipped when `trackedTips`, GitHub's tips
|
|
38
|
+
* as fetched this cycle, already has the local head, so an unchanged branch
|
|
39
|
+
* costs no GitHub round trip; every push that does happen still passes the
|
|
40
|
+
* refusal checks in `MirrorSession.pushToGitHub`.
|
|
45
41
|
*
|
|
46
42
|
* Narrowed to that ONE branch, never every local `canopycms-settings-*`:
|
|
47
43
|
* `reconcileTrackedBranches` creates local heads for branches that exist on
|
|
@@ -49,7 +45,7 @@ function parseTrashStamp(stamp) {
|
|
|
49
45
|
* can legitimately show up as a local head here. Pushing it would be this
|
|
50
46
|
* deployment shipping settings state it does not own.
|
|
51
47
|
*/
|
|
52
|
-
export async function pushSettingsBranches(ctx, git,
|
|
48
|
+
export async function pushSettingsBranches(ctx, git, trackedTips) {
|
|
53
49
|
try {
|
|
54
50
|
const settingsBranch = ctx.ensureSettingsBranch();
|
|
55
51
|
const branches = await git.branch();
|
|
@@ -75,7 +71,7 @@ export async function pushSettingsBranches(ctx, git, trackedNames) {
|
|
|
75
71
|
// With this deployment's own branch also missing, the API and this worker
|
|
76
72
|
// have resolved different deploymentNames, and every settings change the
|
|
77
73
|
// API commits is stranded in remote.git forever.
|
|
78
|
-
const strandedLocal = foreign.filter((b) => !
|
|
74
|
+
const strandedLocal = foreign.filter((b) => !trackedTips.has(b));
|
|
79
75
|
if (strandedLocal.length > 0) {
|
|
80
76
|
workerLogWarn(ownBranchMissing
|
|
81
77
|
? `Settings branch mismatch: this worker owns "${settingsBranch}", which does not ` +
|
|
@@ -93,16 +89,26 @@ export async function pushSettingsBranches(ctx, git, trackedNames) {
|
|
|
93
89
|
return;
|
|
94
90
|
}
|
|
95
91
|
try {
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
// `git` is remote.git, read for the commit to send; the push itself goes through the
|
|
93
|
+
// GitHub gateway, which alone ever sees the credential.
|
|
94
|
+
const readSha = async () => (await git.revparse(['--verify', `refs/heads/${settingsBranch}`])).trim();
|
|
95
|
+
const sha = await readSha();
|
|
96
|
+
const outcome = trackedTips.get(settingsBranch) === sha
|
|
97
|
+
? null
|
|
98
|
+
: (await pushToGitHub(ctx, {
|
|
99
|
+
branch: settingsBranch,
|
|
100
|
+
prerequisiteBranches: [settingsBranch],
|
|
101
|
+
}, readSha, ctx.shutdownSignal())).outcome;
|
|
102
|
+
if (outcome?.moved) {
|
|
103
|
+
workerLog(`Pushed settings branch ${settingsBranch} to GitHub (${describeMove(outcome)})`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
ctx.log.debug(`Settings branch ${settingsBranch} already up to date on GitHub`);
|
|
107
|
+
}
|
|
102
108
|
}
|
|
103
109
|
catch (err) {
|
|
104
|
-
// Non-fatal:
|
|
105
|
-
//
|
|
110
|
+
// Non-fatal: this call site has no task to throw a PermanentTaskError
|
|
111
|
+
// into (unlike pushBranchToGitHub).
|
|
106
112
|
// A non-fast-forward rejection here still gets its own wording: it means
|
|
107
113
|
// another deployment's worker already pushed ITS OWN state to this
|
|
108
114
|
// settings-branch name on GitHub -- an actual collision, not just the
|
|
@@ -125,63 +131,88 @@ export async function pushSettingsBranches(ctx, git, trackedNames) {
|
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
/**
|
|
128
|
-
* Bring `refs/heads/*` in `remote.git` toward
|
|
129
|
-
* `
|
|
130
|
-
*
|
|
131
|
-
* `+refs/heads/*:refs/heads/*` fetch refspec causes).
|
|
134
|
+
* Bring `refs/heads/*` in `remote.git` toward GitHub's branches as this cycle's fetch reported them
|
|
135
|
+
* (`githubTips`), WITHOUT ever rewinding or deleting a head in which stale-mirror.ts finds editor
|
|
136
|
+
* work (`EditorState`; see git-manager.ts's `GITHUB_TRACKING_REF_PREFIX` for the two failure modes
|
|
137
|
+
* a `+refs/heads/*:refs/heads/*` fetch refspec causes).
|
|
132
138
|
*
|
|
133
|
-
* Per
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* branch
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
139
|
+
* Per GitHub branch: no local `refs/heads/<name>` -> create it at GitHub's tip; local behind ->
|
|
140
|
+
* fast-forward; equal -> nothing to do; local AHEAD -> LEAVE IT ALONE, since that is unpushed
|
|
141
|
+
* editor/settings work the queued push task (or pushSettingsBranches) ships; diverged -> our own
|
|
142
|
+
* rebase loop's published rewrite with the GitHub push still queued goes to `rewritten`, and
|
|
143
|
+
* anything else to stale-mirror.ts's `settleStaleMirror`. A head holding no editor work there is a
|
|
144
|
+
* stale copy of a branch rebased and force-pushed on GitHub, and is moved to GitHub's tip
|
|
145
|
+
* (`repointed`); one holding editor work is a real collision (another deployment moved the same
|
|
146
|
+
* branch name, or GitHub's branch was rewritten under an editor's work), never resolved by picking
|
|
147
|
+
* a winner: it stays `diverged`, and the next push is rejected non-fast-forward.
|
|
142
148
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
149
|
+
* A head GitHub has no branch for is deleted through the same function when it holds no editor work
|
|
150
|
+
* and shares history with the base (`pruned`), and only when GitHub's answer names the base branch:
|
|
151
|
+
* an answer without it is not GitHub's branch list. A held `packed-refs.lock`, which every deletion
|
|
152
|
+
* needs, ends that cycle's deletions at the first.
|
|
146
153
|
*
|
|
147
|
-
* Concurrency: `remote.git` is bare and on EFS, and the Lambda pushes into it
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* is simply revisited next cycle.
|
|
154
|
+
* Concurrency: `remote.git` is bare and on EFS, and the Lambda pushes into it while this runs.
|
|
155
|
+
* Every `update-ref` below passes the expected old value (the all-zeros OID for "must not exist
|
|
156
|
+
* yet" on creation, the just-read SHA otherwise), so a concurrent Lambda write landing between the
|
|
157
|
+
* read and the write loses the ref update instead of silently clobbering it -- that branch is
|
|
158
|
+
* simply revisited next cycle.
|
|
153
159
|
*/
|
|
154
|
-
async function reconcileTrackedBranches(ctx, git) {
|
|
160
|
+
async function reconcileTrackedBranches(ctx, git, githubTips) {
|
|
155
161
|
const GIT_ZERO_OID = '0000000000000000000000000000000000000000';
|
|
156
162
|
const created = [];
|
|
157
163
|
const fastForwarded = [];
|
|
158
164
|
const ahead = [];
|
|
159
165
|
const diverged = [];
|
|
166
|
+
const uncheckedOrphans = [];
|
|
167
|
+
const repointed = [];
|
|
168
|
+
const pruned = [];
|
|
160
169
|
const rewritten = [];
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
//
|
|
170
|
+
let baseHold;
|
|
171
|
+
// A symbolic ref under refs/heads/ is none CanopyCMS writes. One for-each-ref lists is left out
|
|
172
|
+
// of `heads` and never written, since an update through it would move the ref it points at;
|
|
173
|
+
// one whose target is missing is not listed at all, and every update below passes
|
|
174
|
+
// `--no-deref`, so writing its name replaces the symbolic ref itself, never its target.
|
|
164
175
|
const raw = await git.raw([
|
|
165
176
|
'for-each-ref',
|
|
166
|
-
'--format=%(refname) %(objectname)',
|
|
177
|
+
'--format=%(refname) %(objectname) %(symref)',
|
|
167
178
|
'refs/heads/',
|
|
168
|
-
GITHUB_TRACKING_REF_PREFIX,
|
|
169
179
|
]);
|
|
170
180
|
const heads = new Map();
|
|
171
|
-
const
|
|
181
|
+
const symbolicHeads = new Set();
|
|
172
182
|
for (const line of raw.split('\n')) {
|
|
173
|
-
const
|
|
174
|
-
if (!
|
|
183
|
+
const [refname, sha, symref] = line.split(' ');
|
|
184
|
+
if (!refname || !sha)
|
|
175
185
|
continue;
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
186
|
+
const name = refname.slice('refs/heads/'.length);
|
|
187
|
+
if (symref)
|
|
188
|
+
symbolicHeads.add(name);
|
|
189
|
+
else
|
|
190
|
+
heads.set(name, sha);
|
|
191
|
+
}
|
|
192
|
+
// Undefined when this deployment's settings branch cannot be named (pushSettingsBranches
|
|
193
|
+
// reports why): any head may then be it, so every head is kept.
|
|
194
|
+
let ownSettingsBranch;
|
|
195
|
+
try {
|
|
196
|
+
ownSettingsBranch = ctx.ensureSettingsBranch();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
ownSettingsBranch = undefined;
|
|
200
|
+
}
|
|
201
|
+
let editorWork;
|
|
202
|
+
const settle = async (name, from, to) => {
|
|
203
|
+
if (ownSettingsBranch === undefined)
|
|
204
|
+
return { kind: 'kept', state: 'unresolved-settings' };
|
|
205
|
+
try {
|
|
206
|
+
return await settleStaleMirror(ctx, git, { name, from, to }, ownSettingsBranch, () => (editorWork ??= readEditorWork(ctx)), githubTips.get(ctx.baseBranch) ?? '');
|
|
179
207
|
}
|
|
180
|
-
|
|
181
|
-
|
|
208
|
+
catch (err) {
|
|
209
|
+
return { kind: 'kept', state: 'unchecked', error: getErrorMessage(err) };
|
|
182
210
|
}
|
|
183
|
-
}
|
|
184
|
-
|
|
211
|
+
};
|
|
212
|
+
const reportMovedMeanwhile = (name, error) => workerLogWarn(` Tracked-branch reconcile: ${escapeForLog(name)} was moved or locked while it was checked, so it was left as it is until the next cycle: ${escapeForLog(error.trim())}`);
|
|
213
|
+
for (const [name, trackedSha] of githubTips) {
|
|
214
|
+
if (symbolicHeads.has(name))
|
|
215
|
+
continue;
|
|
185
216
|
const localSha = heads.get(name);
|
|
186
217
|
const localRef = `refs/heads/${name}`;
|
|
187
218
|
if (!localSha) {
|
|
@@ -189,16 +220,19 @@ async function reconcileTrackedBranches(ctx, git) {
|
|
|
189
220
|
// The zero old-value asserts the ref does not already exist, guarding
|
|
190
221
|
// against a concurrent Lambda push creating this exact branch name
|
|
191
222
|
// between the for-each-ref read above and this update.
|
|
192
|
-
await git.raw(['update-ref', localRef, trackedSha, GIT_ZERO_OID]);
|
|
223
|
+
await git.raw(['update-ref', '--no-deref', localRef, trackedSha, GIT_ZERO_OID]);
|
|
193
224
|
created.push(name);
|
|
194
225
|
}
|
|
195
226
|
catch (err) {
|
|
196
|
-
workerLogWarn(` Tracked-branch reconcile: failed to create local ref for ${name} (concurrent update?): ${getErrorMessage(err)}`);
|
|
227
|
+
workerLogWarn(` Tracked-branch reconcile: failed to create local ref for ${escapeForLog(name)} (concurrent update?): ${escapeForLog(getErrorMessage(err))}`);
|
|
197
228
|
}
|
|
198
229
|
continue;
|
|
199
230
|
}
|
|
200
|
-
if (localSha === trackedSha)
|
|
201
|
-
|
|
231
|
+
if (localSha === trackedSha) {
|
|
232
|
+
if (name === ctx.baseBranch)
|
|
233
|
+
baseHold = { hold: undefined };
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
202
236
|
// [SYNC-M2] Everything below is per-branch best-effort: one unreadable ref
|
|
203
237
|
// must cost its own branch, not the whole sync cycle. A ref pointing at a
|
|
204
238
|
// missing or partially written object is plausible on EFS with the Lambda
|
|
@@ -219,23 +253,43 @@ async function reconcileTrackedBranches(ctx, git) {
|
|
|
219
253
|
// diverged: falling through to the `diverged` bucket (parseInt yields
|
|
220
254
|
// NaN, which fails both comparisons) would warn operators about a
|
|
221
255
|
// cross-deployment collision that never happened.
|
|
222
|
-
workerLogWarn(` Tracked-branch reconcile: skipping ${name}: unparseable rev-list output ${JSON.stringify(counts)}`);
|
|
256
|
+
workerLogWarn(` Tracked-branch reconcile: skipping ${escapeForLog(name)}: unparseable rev-list output ${escapeForLog(JSON.stringify(counts))}`);
|
|
223
257
|
continue;
|
|
224
258
|
}
|
|
225
259
|
if (localAheadCount === 0 && localBehindCount > 0) {
|
|
260
|
+
// The base branch's fast-forward is the one source of new content for the base
|
|
261
|
+
// workspace, every rebased branch and every new clone, so the schema gate sits here.
|
|
262
|
+
if (name === ctx.baseBranch) {
|
|
263
|
+
const decision = await decideBaseAdvance({
|
|
264
|
+
git,
|
|
265
|
+
contentBranchesPath: ctx.contentBranchesPath,
|
|
266
|
+
baseBranch: name,
|
|
267
|
+
currentSha: localSha,
|
|
268
|
+
incomingSha: trackedSha,
|
|
269
|
+
previous: ctx.ensureStatusReport().baseHold,
|
|
270
|
+
maxHoldMs: ctx.schemaHoldMaxMs,
|
|
271
|
+
});
|
|
272
|
+
baseHold = { hold: decision.kind === 'advance' ? undefined : decision.hold };
|
|
273
|
+
if (decision.kind === 'hold') {
|
|
274
|
+
workerLog(` Tracked-branch reconcile: holding ${name} for an editor deploy defining ${decision.hold.missingSchemas.join(', ')}`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
226
278
|
try {
|
|
227
|
-
await git.raw(['update-ref', localRef, trackedSha, localSha]);
|
|
279
|
+
await git.raw(['update-ref', '--no-deref', localRef, trackedSha, localSha]);
|
|
228
280
|
fastForwarded.push(name);
|
|
229
281
|
}
|
|
230
282
|
catch (err) {
|
|
231
283
|
// Concurrent Lambda push moved the ref since the read above --
|
|
232
284
|
// the guard did its job; this branch is simply revisited next cycle.
|
|
233
|
-
workerLogWarn(` Tracked-branch reconcile: failed to fast-forward ${name} (concurrent update?): ${getErrorMessage(err)}`);
|
|
285
|
+
workerLogWarn(` Tracked-branch reconcile: failed to fast-forward ${escapeForLog(name)} (concurrent update?): ${escapeForLog(getErrorMessage(err))}`);
|
|
234
286
|
}
|
|
235
287
|
}
|
|
236
288
|
else if (localBehindCount === 0 && localAheadCount > 0) {
|
|
237
289
|
// Unpushed local work. Leave it.
|
|
238
290
|
ahead.push(name);
|
|
291
|
+
if (name === ctx.baseBranch)
|
|
292
|
+
baseHold = { hold: undefined };
|
|
239
293
|
}
|
|
240
294
|
else if (await hasPendingHistoryRewrite(ctx, name)) {
|
|
241
295
|
// [SYNC-H1] Our own rebase published a rewrite into remote.git and the
|
|
@@ -243,47 +297,134 @@ async function reconcileTrackedBranches(ctx, git) {
|
|
|
243
297
|
// collision, but expected and self-resolving, so it must not fire the
|
|
244
298
|
// collision warning below.
|
|
245
299
|
rewritten.push(name);
|
|
300
|
+
if (name === ctx.baseBranch)
|
|
301
|
+
baseHold = { hold: undefined };
|
|
246
302
|
}
|
|
247
303
|
else {
|
|
248
|
-
|
|
249
|
-
|
|
304
|
+
if (name === ctx.baseBranch)
|
|
305
|
+
baseHold = { hold: undefined };
|
|
306
|
+
const outcome = await settle(name, localSha, trackedSha);
|
|
307
|
+
if (outcome.kind === 'settled')
|
|
308
|
+
repointed.push(name);
|
|
309
|
+
else if (outcome.kind === 'kept') {
|
|
310
|
+
diverged.push({ name, local: localSha, github: trackedSha, ...outcome });
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// `packed-refs-locked` belongs to deletions; a move is not expected to meet it.
|
|
314
|
+
reportMovedMeanwhile(name, outcome.kind === 'moved-meanwhile' ? outcome.error : 'packed-refs.lock is held');
|
|
315
|
+
}
|
|
250
316
|
}
|
|
251
317
|
}
|
|
252
318
|
catch (err) {
|
|
253
|
-
workerLogWarn(` Tracked-branch reconcile: skipping ${name} (unreadable ref or object?): ${getErrorMessage(err)}`);
|
|
319
|
+
workerLogWarn(` Tracked-branch reconcile: skipping ${escapeForLog(name)} (unreadable ref or object?): ${escapeForLog(getErrorMessage(err))}`);
|
|
254
320
|
continue;
|
|
255
321
|
}
|
|
256
322
|
}
|
|
257
|
-
if (
|
|
258
|
-
|
|
323
|
+
if (githubTips.has(ctx.baseBranch)) {
|
|
324
|
+
for (const [name, localSha] of heads) {
|
|
325
|
+
if (githubTips.has(name))
|
|
326
|
+
continue;
|
|
327
|
+
const outcome = await settle(name, localSha, null);
|
|
328
|
+
if (outcome.kind === 'settled')
|
|
329
|
+
pruned.push(name);
|
|
330
|
+
else if (outcome.kind === 'moved-meanwhile')
|
|
331
|
+
reportMovedMeanwhile(name, outcome.error);
|
|
332
|
+
// Every later deletion would wait for the same lock and fail the same way.
|
|
333
|
+
else if (outcome.kind === 'packed-refs-locked')
|
|
334
|
+
break;
|
|
335
|
+
else if (outcome.error !== undefined)
|
|
336
|
+
uncheckedOrphans.push({ name, error: outcome.error });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
warnOnce(ctx.warnedDivergences(), [
|
|
340
|
+
...diverged.map(divergedWarning),
|
|
341
|
+
...uncheckedOrphans.map(({ name, error }) => ({
|
|
342
|
+
// ':' cannot appear in a ref name, so this key never collides with a branch's.
|
|
343
|
+
key: `orphan:${name}`,
|
|
344
|
+
said: error,
|
|
345
|
+
line: ` Tracked-branch reconcile: ${escapeForLog(name)}, which GitHub no longer has, was ` +
|
|
346
|
+
`kept because it could not be checked: ${escapeForLog(error)}`,
|
|
347
|
+
})),
|
|
348
|
+
]);
|
|
349
|
+
if (repointed.length > 0) {
|
|
350
|
+
workerLog(` Tracked-branch reconcile: moved ${repointed.length} stale branch copy(ies) to GitHub's rewritten tip: ${repointed.map(escapeForLog).join(', ')}`);
|
|
351
|
+
}
|
|
352
|
+
if (pruned.length > 0) {
|
|
353
|
+
workerLog(` Tracked-branch reconcile: deleted ${pruned.length} branch copy(ies) GitHub no longer has: ${pruned.map(escapeForLog).join(', ')}`);
|
|
259
354
|
}
|
|
260
355
|
if (rewritten.length > 0) {
|
|
261
|
-
workerLog(` Tracked-branch reconcile: ${rewritten.length} branch(es) rebased locally with the GitHub push still pending: ${rewritten.join(', ')}`);
|
|
356
|
+
workerLog(` Tracked-branch reconcile: ${rewritten.length} branch(es) rebased locally with the GitHub push still pending: ${rewritten.map(escapeForLog).join(', ')}`);
|
|
262
357
|
}
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
358
|
+
// A base GitHub no longer has can be held for nothing.
|
|
359
|
+
if (!githubTips.has(ctx.baseBranch))
|
|
360
|
+
baseHold = { hold: undefined };
|
|
361
|
+
return {
|
|
362
|
+
summary: {
|
|
363
|
+
created,
|
|
364
|
+
fastForwarded,
|
|
365
|
+
ahead,
|
|
366
|
+
diverged: diverged.map((entry) => entry.name),
|
|
367
|
+
repointed,
|
|
368
|
+
pruned,
|
|
369
|
+
rewritten,
|
|
370
|
+
},
|
|
371
|
+
baseHold,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const EDITOR_STATE_WORDS = {
|
|
375
|
+
base: 'it is the base branch',
|
|
376
|
+
settings: 'it is a settings branch',
|
|
377
|
+
workspace: 'it has a branch workspace',
|
|
378
|
+
'queued-push': 'a push of it is queued',
|
|
379
|
+
busy: 'its workspace is being created, repaired or purged',
|
|
380
|
+
'unrelated-history': 'it shares no history with the base branch',
|
|
381
|
+
'unresolved-settings': "this deployment's settings branch cannot be named",
|
|
382
|
+
unchecked: 'it could not be checked',
|
|
383
|
+
};
|
|
384
|
+
function divergedWarning({ name, local, github, state, error }) {
|
|
266
385
|
return {
|
|
267
|
-
|
|
268
|
-
|
|
386
|
+
key: name,
|
|
387
|
+
// The reason is part of what was said, so a head first kept as `busy` is reported again with
|
|
388
|
+
// the reason that keeps it.
|
|
389
|
+
said: `${local} ${github} ${state}`,
|
|
390
|
+
line: ` Tracked-branch reconcile: ${escapeForLog(name)} diverged from GitHub and was left untouched, ` +
|
|
391
|
+
`because ${EDITOR_STATE_WORDS[state]}${error === undefined ? '' : ` (${escapeForLog(error)})`} ` +
|
|
392
|
+
`(here ${local}, GitHub ${github}). Publishing it fails until the two are reconciled; ` +
|
|
393
|
+
`System health lists it.`,
|
|
269
394
|
};
|
|
270
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Warn each line once while what it says stays the same: a diverged head's tips and reason, an
|
|
398
|
+
* unchecked orphan's error. `warned` is this worker's memory of what it has said, by key, and
|
|
399
|
+
* forgets a key once its line is no longer due.
|
|
400
|
+
*/
|
|
401
|
+
function warnOnce(warned, lines) {
|
|
402
|
+
const current = new Set(lines.map((entry) => entry.key));
|
|
403
|
+
for (const key of warned.keys())
|
|
404
|
+
if (!current.has(key))
|
|
405
|
+
warned.delete(key);
|
|
406
|
+
for (const { key, said, line } of lines) {
|
|
407
|
+
if (warned.get(key) === said)
|
|
408
|
+
continue;
|
|
409
|
+
warned.set(key, said);
|
|
410
|
+
workerLogWarn(line);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
271
413
|
export async function syncGit(ctx) {
|
|
272
414
|
if (!ctx.isRunning())
|
|
273
415
|
return;
|
|
416
|
+
// The CDK worker-down alarm counts this line (WORKER_SYNC_LOG_PHRASE in
|
|
417
|
+
// canopycms-cdk worker-lifecycle.ts); changing its text breaks the alarm.
|
|
274
418
|
workerLog('Syncing git...');
|
|
275
419
|
const cycleStartedAt = Date.now();
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
//
|
|
420
|
+
// remote.git's config is Lambda-writable: every git here runs pinned (worker/shared-repo-git.ts),
|
|
421
|
+
// and everything that carries the credential runs in the GitHub gateway's state directory instead.
|
|
422
|
+
const git = sharedRepoGit(ctx.remoteGitPath, 'bare', {
|
|
423
|
+
// DEP-H1: a hung git would stall the sync loop forever (scheduleLoop only
|
|
424
|
+
// reschedules after completion). Inactivity-based, so a slow-but-flowing
|
|
425
|
+
// transfer is unaffected.
|
|
281
426
|
timeout: { block: ctx.taskTimeoutMs },
|
|
282
427
|
});
|
|
283
|
-
// gitNetworkChildEnv because the fetch and push here reach GitHub, and
|
|
284
|
-
// because pushSettingsBranches(git) below needs its stable-English guarantee
|
|
285
|
-
// to classify a rejected settings-branch push.
|
|
286
|
-
git.env(gitNetworkChildEnv());
|
|
287
428
|
// The whole cycle is wrapped so both outcomes -- success and hard failure
|
|
288
429
|
// (e.g. the fetch throwing against a poisoned remote.git) -- record a
|
|
289
430
|
// worker-status.json snapshot. The status write itself is always best-effort
|
|
@@ -291,26 +432,62 @@ export async function syncGit(ctx) {
|
|
|
291
432
|
// mask the real error on a failed one. Failures rethrow, so scheduleLoop's
|
|
292
433
|
// per-cycle catch stays the loud path.
|
|
293
434
|
try {
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
435
|
+
// Best-effort and ahead of the GitHub fetch, so a GitHub outage never
|
|
436
|
+
// stalls upkeep and a failure here never costs the cycle. start() runs a
|
|
437
|
+
// cycle at once, so residue repair also runs on boot.
|
|
438
|
+
try {
|
|
439
|
+
await repairBranchDirResidue(ctx);
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
workerLogWarn(`Branch directory residue repair failed: ${getErrorMessage(err)}`);
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
await reapplySparseCones(ctx);
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
workerLogWarn(`Sparse-checkout cone update failed: ${getErrorMessage(err)}`);
|
|
449
|
+
}
|
|
450
|
+
// Refuses the whole cycle: everything after this runs git in remote.git, and every branch
|
|
451
|
+
// clone fetches from it.
|
|
452
|
+
await assertSharedRepoConfig(ctx.remoteGitPath, 'bare');
|
|
453
|
+
try {
|
|
454
|
+
await maintainRemoteGit(ctx.remoteGitPath);
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
workerLogWarn(`remote.git maintenance failed: ${getErrorMessage(err)}`);
|
|
458
|
+
}
|
|
459
|
+
if (stoppedForDrain(ctx, 'the GitHub fetch'))
|
|
460
|
+
return;
|
|
461
|
+
// Into remote.git's GITHUB_TRACKING_REF_PREFIX namespace rather than refs/heads/* -- see that
|
|
462
|
+
// constant's doc comment for the destructive-fetch bug this avoids. Aborted at the drain
|
|
463
|
+
// deadline: the wait for the gateway's GitHub fetch, which runs on detached, then the gateway's
|
|
464
|
+
// bundle, the unbundle and the ref update; a killed ref update leaves each ref as it was or
|
|
465
|
+
// fully moved.
|
|
466
|
+
const { refs: githubTips, needsOperator } = await fetchGitHubIntoRemoteGit(ctx, ctx.shutdownSignal());
|
|
304
467
|
workerLog('Fetched from GitHub');
|
|
305
|
-
|
|
468
|
+
if (needsOperator !== null)
|
|
469
|
+
workerLogError(needsOperator);
|
|
470
|
+
const { summary: trackedSummary, baseHold } = await reconcileTrackedBranches(ctx, git, githubTips);
|
|
471
|
+
// Recorded at once, so a step below that throws still persists the hold through the catch.
|
|
472
|
+
if (baseHold) {
|
|
473
|
+
const report = ctx.ensureStatusReport();
|
|
474
|
+
if (baseHold.hold)
|
|
475
|
+
report.baseHold = baseHold.hold;
|
|
476
|
+
else
|
|
477
|
+
delete report.baseHold;
|
|
478
|
+
}
|
|
306
479
|
// Push settings branches to GitHub (belt-and-suspenders for task queue).
|
|
307
|
-
// Ensures settings reach GitHub even if a task queue entry is lost.
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
480
|
+
// Ensures settings reach GitHub even if a task queue entry is lost. After
|
|
481
|
+
// the fetch, whose tips (`githubTips`) let it skip a branch GitHub holds.
|
|
482
|
+
await pushSettingsBranches(ctx, git, githubTips);
|
|
483
|
+
if (stoppedForDrain(ctx, 'the base-branch refresh'))
|
|
484
|
+
return;
|
|
312
485
|
const baseRefresh = await refreshBaseBranchWorkspace(ctx);
|
|
486
|
+
if (stoppedForDrain(ctx, 'the rebase cycle'))
|
|
487
|
+
return;
|
|
313
488
|
const rebaseSummary = await runRebaseCycle(ctx);
|
|
489
|
+
if (stoppedForDrain(ctx, 'the cleanup sweeps'))
|
|
490
|
+
return;
|
|
314
491
|
await cleanupOldTasks(ctx.taskDir, undefined, ctx.log);
|
|
315
492
|
// [C1] Sweep branch directories the admin purge action trashed more than
|
|
316
493
|
// TRASH_RETENTION_MS ago. Worker-only by design: purge itself never deletes
|
|
@@ -323,6 +500,10 @@ export async function syncGit(ctx) {
|
|
|
323
500
|
const report = ctx.ensureStatusReport();
|
|
324
501
|
report.lastGitSyncAt = new Date().toISOString();
|
|
325
502
|
delete report.lastGitSyncError;
|
|
503
|
+
// Recorded once the cycle has done everything it can, so System health shows it.
|
|
504
|
+
if (needsOperator !== null) {
|
|
505
|
+
report.lastGitSyncError = { message: needsOperator, at: report.lastGitSyncAt };
|
|
506
|
+
}
|
|
326
507
|
report.lastGitSync = {
|
|
327
508
|
durationMs: Date.now() - cycleStartedAt,
|
|
328
509
|
rebased: rebaseSummary.rebased,
|
|
@@ -342,7 +523,7 @@ export async function syncGit(ctx) {
|
|
|
342
523
|
const report = ctx.ensureStatusReport();
|
|
343
524
|
// [REDACT] Persisted to worker-status.json and served to the browser
|
|
344
525
|
// by the admin panel -- a fetch/push failure's message can embed the
|
|
345
|
-
// bot token
|
|
526
|
+
// bot token from the gateway's git URL.
|
|
346
527
|
report.lastGitSyncError = {
|
|
347
528
|
message: redactCredentials(getErrorMessage(err)),
|
|
348
529
|
at: new Date().toISOString(),
|
|
@@ -351,13 +532,98 @@ export async function syncGit(ctx) {
|
|
|
351
532
|
throw err;
|
|
352
533
|
}
|
|
353
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* A draining worker ends the sync cycle at the next stage boundary, never
|
|
537
|
+
* mid-stage. The early return leaves the previous cycle's `lastGitSync` in
|
|
538
|
+
* worker-status.json; the shutdown itself is recorded as `lastShutdown`.
|
|
539
|
+
*/
|
|
540
|
+
function stoppedForDrain(ctx, nextStage) {
|
|
541
|
+
if (!ctx.isDraining())
|
|
542
|
+
return false;
|
|
543
|
+
workerLog(`Git sync stopped before ${nextStage}: the worker is draining`);
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
/** Foreign repositories already reported by this process, so each is logged once. */
|
|
547
|
+
const reportedForeignDirs = new Set();
|
|
548
|
+
/**
|
|
549
|
+
* Repair what killed provisioning and deletes left under the branches root: quarantine residue
|
|
550
|
+
* at a branch's name (branch-provisioning.ts's `classifyFinalDir`) once it has been quiet for
|
|
551
|
+
* {@link ORPHAN_YOUTH_THRESHOLD_MS}, then sweep stale `.prov-*`, `.deleting-*` and `.repair-*`
|
|
552
|
+
* siblings and settings leftovers. Each directory is best-effort, like [SYNC-M2]. The provisioning
|
|
553
|
+
* lock is taken zero-retry, so a live provisioner or admin action wins and the directory is
|
|
554
|
+
* revisited next cycle.
|
|
555
|
+
* @internal Exported for tests; `syncGit` runs it.
|
|
556
|
+
*/
|
|
557
|
+
export async function repairBranchDirResidue(ctx, now = Date.now()) {
|
|
558
|
+
let entries;
|
|
559
|
+
try {
|
|
560
|
+
entries = await fs.readdir(ctx.contentBranchesPath, { withFileTypes: true });
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
if (isNodeError(err) && err.code === 'ENOENT')
|
|
564
|
+
return;
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
567
|
+
for (const entry of entries) {
|
|
568
|
+
if (!entry.isDirectory() || entry.name.startsWith('.'))
|
|
569
|
+
continue;
|
|
570
|
+
try {
|
|
571
|
+
await repairOneBranchDir(ctx, entry.name, now);
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
workerLogWarn(` Residue check of ${entry.name} failed: ${getErrorMessage(err)}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const leftovers = await sweepProvisioningLeftovers(ctx.contentBranchesPath, path.dirname(ctx.contentBranchesPath), now);
|
|
578
|
+
for (const { name, action, detail } of leftovers) {
|
|
579
|
+
const line = ` Leftover ${name}: ${action}${detail ? ` (${detail})` : ''}`;
|
|
580
|
+
if (action === 'removed')
|
|
581
|
+
workerLog(line);
|
|
582
|
+
else if (action === 'failed')
|
|
583
|
+
workerLogWarn(line);
|
|
584
|
+
else
|
|
585
|
+
workerLogError(line);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async function repairOneBranchDir(ctx, dirName, now) {
|
|
589
|
+
const dirPath = path.join(ctx.contentBranchesPath, dirName);
|
|
590
|
+
// One stat for the common case: a live (or corrupt) branch is never residue.
|
|
591
|
+
const hasMeta = await fs.access(path.join(dirPath, BRANCH_META_DIR, BRANCH_META_FILE)).then(() => true, () => false);
|
|
592
|
+
if (hasMeta)
|
|
593
|
+
return;
|
|
594
|
+
const state = await classifyFinalDir(dirPath, ctx.remoteGitPath);
|
|
595
|
+
if (state.kind === 'foreign' && !reportedForeignDirs.has(dirPath)) {
|
|
596
|
+
reportedForeignDirs.add(dirPath);
|
|
597
|
+
workerLogWarn(` ${dirName} holds a git repository this deployment did not create; left for an admin`);
|
|
598
|
+
}
|
|
599
|
+
if (state.kind !== 'residue')
|
|
600
|
+
return;
|
|
601
|
+
let release;
|
|
602
|
+
try {
|
|
603
|
+
release = await tryAcquireProvisioningLock(ctx.contentBranchesPath, branchProvisioningLockName(dirName));
|
|
604
|
+
}
|
|
605
|
+
catch (err) {
|
|
606
|
+
if (isNodeError(err) && err.code === 'ELOCKED')
|
|
607
|
+
return;
|
|
608
|
+
throw err;
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
await quarantineResidueAt(ctx.contentBranchesPath, dirName, {
|
|
612
|
+
minQuietMs: ORPHAN_YOUTH_THRESHOLD_MS,
|
|
613
|
+
expectedRemoteUrl: ctx.remoteGitPath,
|
|
614
|
+
now,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
finally {
|
|
618
|
+
await releaseProvisionedWorkspace(release, dirName);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
354
621
|
/**
|
|
355
622
|
* [C1] Remove `.trash-*` branch directories (created by the admin purge action,
|
|
356
|
-
* api/admin-branch-health.ts
|
|
357
|
-
* {@link TRASH_RETENTION_MS}. A name
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* is unexpected and worth a human looking rather than a silent skip.
|
|
623
|
+
* api/admin-branch-health.ts, and by residue quarantine, branch-provisioning.ts)
|
|
624
|
+
* whose name-embedded stamp is older than {@link TRASH_RETENTION_MS}. A name
|
|
625
|
+
* whose trailing stamp fails to parse is left alone and logged once per cycle:
|
|
626
|
+
* both writers end every name with one, so it is worth a human looking.
|
|
361
627
|
*/
|
|
362
628
|
export async function cleanupTrashedBranchDirs(ctx) {
|
|
363
629
|
let entries;
|
|
@@ -375,8 +641,7 @@ export async function cleanupTrashedBranchDirs(ctx) {
|
|
|
375
641
|
for (const name of entries) {
|
|
376
642
|
if (!name.startsWith('.trash-'))
|
|
377
643
|
continue;
|
|
378
|
-
const
|
|
379
|
-
const stampDate = stampMatch ? parseTrashStamp(stampMatch[1]) : null;
|
|
644
|
+
const stampDate = parseDirStamp(name);
|
|
380
645
|
if (!stampDate) {
|
|
381
646
|
if (!loggedUnparseable) {
|
|
382
647
|
workerLog(`CanopyCMS: Skipping trash dir with unparseable stamp: ${name}`);
|
|
@@ -471,14 +736,10 @@ export async function refreshBaseBranchWorkspace(ctx) {
|
|
|
471
736
|
};
|
|
472
737
|
// Idempotent, and applied every cycle so any clone lacking it gets it.
|
|
473
738
|
await ensureGitExcludePattern(basePath, `${CANOPY_META_DIR}/`);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
// value is a hardcoded literal ("true", the shell no-op), not user input,
|
|
479
|
-
// so allowUnsafeEditor carries no injection risk here.
|
|
480
|
-
config: ['core.editor=true'],
|
|
481
|
-
unsafe: { allowUnsafeEditor: true },
|
|
739
|
+
// A planted key would run in this worker's merge or checkout; the catch below reports it.
|
|
740
|
+
await assertSharedRepoConfig(basePath, 'worktree');
|
|
741
|
+
// The pins also keep the merge non-interactive (core.editor=true).
|
|
742
|
+
const baseGit = sharedRepoGit(basePath, 'worktree', {
|
|
482
743
|
// DEP-H1: a hung fetch/merge against this EFS-backed clone would stall
|
|
483
744
|
// the sync loop forever (scheduleLoop only reschedules after completion).
|
|
484
745
|
// Inactivity-based, so a slow-but-flowing transfer is unaffected.
|
|
@@ -494,13 +755,13 @@ export async function refreshBaseBranchWorkspace(ctx) {
|
|
|
494
755
|
`${trackedState.join(', ')}. To fix, ${TRACKED_CANOPY_STATE_FIX}.`);
|
|
495
756
|
}
|
|
496
757
|
}
|
|
497
|
-
let status = await baseGit.status();
|
|
758
|
+
let status = await baseGit.status([...SHARED_REPO_STATUS_ARGS]);
|
|
498
759
|
const lostBeforeRestore = lockLost();
|
|
499
760
|
if (lostBeforeRestore)
|
|
500
761
|
return lostBeforeRestore;
|
|
501
762
|
if (await restoreRetiredSchemaCache(baseGit, status)) {
|
|
502
763
|
workerLog(`Base branch workspace (${ctx.baseBranch}): restored the retired in-tree schema cache`);
|
|
503
|
-
status = await baseGit.status();
|
|
764
|
+
status = await baseGit.status([...SHARED_REPO_STATUS_ARGS]);
|
|
504
765
|
}
|
|
505
766
|
// Nothing makes this clone read-only, and a direct edit here wedges every
|
|
506
767
|
// editor's view of the base branch until an operator intervenes, so a dirty
|
|
@@ -525,7 +786,7 @@ export async function refreshBaseBranchWorkspace(ctx) {
|
|
|
525
786
|
// Raw (unsanitized) name from here on: these are git ref operations
|
|
526
787
|
// against remote.git's <baseBranch>, not filesystem paths, so they must use
|
|
527
788
|
// the same name GitHub knows the branch by.
|
|
528
|
-
await baseGit
|
|
789
|
+
await fetchFromRemoteGit(baseGit, ctx.remoteGitPath, ctx.baseBranch);
|
|
529
790
|
// rev-list, not status.behind, which needs an upstream tracking branch that
|
|
530
791
|
// is not guaranteed here. Against the just-fetched tip: a fetch by path
|
|
531
792
|
// updates no remote-tracking ref. Pin FETCH_HEAD to a SHA immediately -- it
|
|
@@ -547,6 +808,8 @@ export async function refreshBaseBranchWorkspace(ctx) {
|
|
|
547
808
|
await untrackInIndex(baseGit, droppedUpstream);
|
|
548
809
|
workerLog(`Base branch workspace (${ctx.baseBranch}): stopped tracking ${droppedUpstream.join(', ')}, as upstream has`);
|
|
549
810
|
}
|
|
811
|
+
// Outside the try below: a refusal is not a diverged history.
|
|
812
|
+
await assertNoIncomingSubmodules(baseGit, basePath, 'HEAD', fetchedTip);
|
|
550
813
|
try {
|
|
551
814
|
await baseGit.merge(['--ff-only', fetchedTip]);
|
|
552
815
|
}
|