dsh-config-manager 0.1.71 → 0.1.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -96
- package/README.zh-CN.md +207 -86
- package/lib/adapters/agent-instructions.js +1 -1
- package/lib/adapters/file-collection.js +1 -1
- package/lib/adapters/index.d.ts +1 -1
- package/lib/adapters/index.js +1 -1
- package/lib/adapters/link-report.d.ts +1 -0
- package/lib/adapters/link-report.js +27 -3
- package/lib/adapters/messages.d.ts +18 -0
- package/lib/adapters/messages.js +50 -0
- package/lib/adapters/plugin-files.js +3 -2
- package/lib/adapters/plugins.js +4 -3
- package/lib/adapters/prompts.d.ts +1 -0
- package/lib/adapters/prompts.js +7 -1
- package/lib/adapters/self.d.ts +7 -1
- package/lib/adapters/self.js +1 -1
- package/lib/adapters/sessions.d.ts +1 -0
- package/lib/adapters/sessions.js +4 -2
- package/lib/adapters/ui.d.ts +2 -2
- package/lib/adapters/ui.js +25 -25
- package/lib/adapters/workspaces.d.ts +1 -0
- package/lib/adapters/workspaces.js +7 -3
- package/lib/cli/actions.d.ts +9 -1
- package/lib/cli/actions.js +5 -2
- package/lib/cli/help.js +23 -10
- package/lib/cli/index.d.ts +23 -0
- package/lib/cli/index.js +90 -32
- package/lib/cli/sessions-inspect.js +3 -1
- package/lib/cli/sessions-repair.js +25 -6
- package/lib/cli/web/page.d.ts +12 -0
- package/lib/cli/web/page.js +21 -12
- package/lib/cli/web/routes.js +2 -2
- package/lib/cli/web/server.js +4 -4
- package/lib/client.d.ts +1349 -490
- package/lib/client.js +43047 -36738
- package/lib/core/backup-plan.js +30 -17
- package/lib/core/backup-verify.js +24 -23
- package/lib/core/backup.d.ts +5 -0
- package/lib/core/backup.js +51 -25
- package/lib/core/boot-rescue.js +4 -3
- package/lib/core/boot-safety.js +2 -1
- package/lib/core/command-rollback.d.ts +178 -0
- package/lib/core/command-rollback.js +289 -0
- package/lib/core/consult-source.js +7 -6
- package/lib/core/disk-usage.d.ts +74 -10
- package/lib/core/disk-usage.js +67 -4
- package/lib/core/engine-locale.d.ts +87 -0
- package/lib/core/engine-locale.js +125 -0
- package/lib/core/exporter.js +3 -3
- package/lib/core/fork-reconcile.d.ts +128 -0
- package/lib/core/fork-reconcile.js +150 -0
- package/lib/core/journal.js +6 -5
- package/lib/core/local-plugin-host.js +3 -2
- package/lib/core/local-plugin-pack.js +11 -6
- package/lib/core/messages-core.d.ts +292 -0
- package/lib/core/messages-core.js +615 -0
- package/lib/core/messages.d.ts +60 -2
- package/lib/core/messages.js +116 -13
- package/lib/core/migration-consult.js +21 -20
- package/lib/core/migration-history.js +19 -5
- package/lib/core/model-tools.d.ts +0 -22
- package/lib/core/model-tools.js +50 -27
- package/lib/core/phase3-host.js +30 -5
- package/lib/core/plugin-cli.js +18 -19
- package/lib/core/reconcile.d.ts +2 -14
- package/lib/core/reconcile.js +87 -17
- package/lib/core/recovery-orchestrator.js +19 -18
- package/lib/core/reinstall.d.ts +4 -0
- package/lib/core/reinstall.js +26 -21
- package/lib/core/restore.js +20 -17
- package/lib/core/rollback-ignore-rules.d.ts +31 -0
- package/lib/core/rollback-ignore-rules.js +140 -0
- package/lib/core/rollback-point.d.ts +1208 -0
- package/lib/core/rollback-point.js +2993 -0
- package/lib/core/run-registry.js +6 -5
- package/lib/core/session-health.d.ts +15 -0
- package/lib/core/session-meta.d.ts +42 -2
- package/lib/core/session-meta.js +140 -1
- package/lib/core/session-title.d.ts +28 -0
- package/lib/core/session-title.js +185 -0
- package/lib/core/startup-barrier.d.ts +24 -0
- package/lib/core/startup-barrier.js +22 -0
- package/lib/core/transaction-coordinator.js +4 -3
- package/lib/core/types.d.ts +18 -1
- package/lib/core/validator.js +5 -4
- package/lib/core/verify-recovery.js +31 -30
- package/lib/foreign/bundle.js +3 -2
- package/lib/foreign/continue.js +2 -1
- package/lib/foreign/crush.js +2 -1
- package/lib/foreign/gemini.js +2 -1
- package/lib/foreign/goose.js +2 -1
- package/lib/foreign/kilocode.js +2 -1
- package/lib/foreign/kimi.js +2 -1
- package/lib/foreign/messages.d.ts +15 -0
- package/lib/foreign/messages.js +40 -0
- package/lib/foreign/mimocode.js +2 -1
- package/lib/foreign/openclaw.js +2 -1
- package/lib/foreign/opencode.js +2 -1
- package/lib/foreign/pi.js +2 -1
- package/lib/foreign/qoder.js +2 -1
- package/lib/foreign/qwen.js +2 -1
- package/lib/foreign/registry.js +7 -6
- package/lib/foreign/teleagent.js +2 -1
- package/lib/foreign/workbuddy.js +2 -1
- package/lib/foreign/zcode.js +2 -1
- package/lib/foreign/zed.js +2 -1
- package/lib/index.d.ts +223 -7
- package/lib/index.js +729 -54
- package/lib/market/git-file-writer.js +20 -19
- package/lib/market/github-repos.js +35 -19
- package/lib/market/index-parser.js +31 -30
- package/lib/market/messages-market.d.ts +138 -0
- package/lib/market/messages-market.js +305 -0
- package/lib/market/my-repo.d.ts +2 -2
- package/lib/market/my-repo.js +32 -29
- package/lib/market/prepare.js +18 -15
- package/lib/market/security.js +20 -19
- package/lib/market/types.js +2 -1
- package/lib/market/url.js +2 -1
- package/lib/migrations/index.js +6 -5
- package/lib/migrations/messages-migrations.d.ts +32 -0
- package/lib/migrations/messages-migrations.js +58 -0
- package/lib/profiles/dsh-profile-launcher.js +2 -1
- package/lib/profiles/dsh-profile-runtime.js +2 -1
- package/lib/profiles/messages.d.ts +7 -0
- package/lib/profiles/messages.js +27 -0
- package/lib/routes/backup.js +18 -2
- package/lib/routes/import.js +3 -3
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +3 -2
- package/lib/routes/kit.js +6 -1
- package/lib/routes/market.js +1 -1
- package/lib/routes/messages.d.ts +42 -0
- package/lib/routes/messages.js +100 -0
- package/lib/routes/profiles.js +12 -6
- package/lib/routes/recovery.js +378 -114
- package/lib/routes/snapshots.js +51 -4
- package/lib/routes/sync.js +68 -16
- package/lib/schema/config.d.ts +11 -1
- package/lib/schema/config.js +52 -20
- package/lib/schema/index.d.ts +8 -6
- package/lib/schema/index.js +8 -6
- package/lib/schema/manifest.js +19 -18
- package/lib/schema/messages.d.ts +47 -0
- package/lib/schema/messages.js +110 -0
- package/lib/schema/section-registry.d.ts +41 -14
- package/lib/schema/section-registry.js +35 -29
- package/lib/schema/versions.js +6 -5
- package/lib/security/encryption.js +16 -15
- package/lib/security/integrity.js +8 -7
- package/lib/security/messages.d.ts +30 -0
- package/lib/security/messages.js +77 -0
- package/lib/security/redaction.js +12 -0
- package/lib/security/secret-scanner.js +16 -13
- package/lib/security/vault.js +2 -1
- package/lib/security/zip-security.js +4 -3
- package/lib/sync/ancestor.js +2 -1
- package/lib/sync/autosync-config.d.ts +11 -0
- package/lib/sync/autosync-config.js +12 -0
- package/lib/sync/autosync-scheduler.d.ts +11 -1
- package/lib/sync/autosync-scheduler.js +115 -76
- package/lib/sync/backup-files.js +3 -2
- package/lib/sync/backup-scheduler.js +6 -5
- package/lib/sync/blob-store.js +2 -1
- package/lib/sync/git/git-transport.d.ts +55 -0
- package/lib/sync/git/git-transport.js +152 -2
- package/lib/sync/github-auth.js +10 -9
- package/lib/sync/layout.js +13 -12
- package/lib/sync/messages.d.ts +63 -0
- package/lib/sync/messages.js +149 -0
- package/lib/sync/review-queue.js +6 -5
- package/lib/sync/s3/messages.d.ts +7 -0
- package/lib/sync/s3/messages.js +16 -0
- package/lib/sync/s3/s3-providers.d.ts +4 -1
- package/lib/sync/s3/s3-providers.js +5 -5
- package/lib/sync/s3/s3-transport.js +1 -1
- package/lib/sync/s3/sigv4.js +2 -1
- package/lib/sync/snapshot-crypto.js +6 -5
- package/lib/sync/snapshot-json.js +3 -2
- package/lib/sync/sync-engine.d.ts +37 -15
- package/lib/sync/sync-engine.js +67 -37
- package/lib/sync/sync-history.js +7 -6
- package/lib/sync/sync-selection.d.ts +15 -4
- package/lib/sync/sync-selection.js +17 -5
- package/lib/sync/webdav/webdav-transport.d.ts +1 -0
- package/lib/sync/webdav/webdav-transport.js +4 -1
- package/lib/ui/artifact-view.js +4 -3
- package/lib/ui/commands.d.ts +1 -1
- package/lib/ui/commands.js +10 -0
- package/lib/ui/disk-usage-view.d.ts +33 -4
- package/lib/ui/disk-usage-view.js +29 -1
- package/lib/ui/errors.d.ts +1 -1
- package/lib/ui/errors.js +4 -3
- package/lib/ui/export-flow.js +24 -20
- package/lib/ui/foreign-view.d.ts +5 -1
- package/lib/ui/foreign-view.js +26 -2
- package/lib/ui/i18n.d.ts +65 -0
- package/lib/ui/i18n.js +114 -0
- package/lib/ui/import-wizard.d.ts +7 -0
- package/lib/ui/import-wizard.js +13 -10
- package/lib/ui/progress.d.ts +2 -2
- package/lib/ui/progress.js +1 -2
- package/lib/ui/report.d.ts +5 -5
- package/lib/ui/report.js +6 -6
- package/lib/ui/rollback-view.d.ts +756 -0
- package/lib/ui/rollback-view.js +724 -0
- package/lib/ui/selection-model.d.ts +41 -0
- package/lib/ui/selection-model.js +30 -0
- package/lib/ui/session-inventory-view.d.ts +97 -3
- package/lib/ui/session-inventory-view.js +76 -2
- package/lib/ui/session-issue-labels.d.ts +43 -0
- package/lib/ui/session-issue-labels.js +48 -0
- package/lib/ui/sync-settings-view.d.ts +33 -0
- package/lib/ui/sync-settings-view.js +40 -0
- package/lib/ui/types.d.ts +8 -3
- package/lib/ui/types.js +1 -1
- package/lib/utils/atomic-write.js +6 -5
- package/lib/utils/bundle-scan.d.ts +11 -1
- package/lib/utils/bundle-scan.js +12 -1
- package/lib/utils/env-lock.d.ts +10 -0
- package/lib/utils/env-lock.js +73 -46
- package/lib/utils/json.js +6 -5
- package/lib/utils/messages-utils.d.ts +96 -0
- package/lib/utils/messages-utils.js +216 -0
- package/lib/utils/proxy.js +18 -5
- package/lib/utils/recursive-walk.d.ts +22 -0
- package/lib/utils/recursive-walk.js +75 -1
- package/lib/utils/session-log-repair.js +10 -9
- package/lib/utils/zip.js +27 -26
- package/package.json +12 -2
- package/src/adapters/agent-instructions.ts +1 -1
- package/src/adapters/file-collection.test.ts +1 -0
- package/src/adapters/file-collection.ts +1 -1
- package/src/adapters/files.test.ts +27 -0
- package/src/adapters/index.ts +1 -1
- package/src/adapters/link-report.test.ts +22 -2
- package/src/adapters/link-report.ts +29 -4
- package/src/adapters/messages.ts +54 -0
- package/src/adapters/plugin-files.ts +3 -2
- package/src/adapters/plugins.ts +4 -3
- package/src/adapters/prompts.ts +7 -1
- package/src/adapters/self.test.ts +26 -0
- package/src/adapters/self.ts +8 -2
- package/src/adapters/sessions.ts +4 -2
- package/src/adapters/settings.test.ts +38 -1
- package/src/adapters/ui.test.ts +2 -2
- package/src/adapters/ui.ts +27 -26
- package/src/adapters/workspaces.ts +7 -3
- package/src/cli/actions.test.ts +28 -0
- package/src/cli/actions.ts +11 -2
- package/src/cli/help.ts +23 -10
- package/src/cli/index-guards.test.ts +193 -0
- package/src/cli/index.ts +106 -31
- package/src/cli/sessions-inspect.test.ts +15 -1
- package/src/cli/sessions-inspect.ts +3 -1
- package/src/cli/sessions-repair.test.ts +84 -0
- package/src/cli/sessions-repair.ts +33 -7
- package/src/cli/web/console-route-parity.test.ts +115 -0
- package/src/cli/web/page.ts +23 -12
- package/src/cli/web/routes.ts +2 -1
- package/src/cli/web/server.ts +4 -4
- package/src/cli/web/web.test.ts +2 -2
- package/src/client/ConfigManagerSection.tsx +30 -2
- package/src/client/about/AboutPanel.tsx +2 -2
- package/src/client/about/PluginUpdateDialog.tsx +1 -1
- package/src/client/about/ReleaseNotesDialog.tsx +2 -2
- package/src/client/about/about-view.test.ts +13 -3
- package/src/client/about/about-view.ts +10 -6
- package/src/client/about/release-notes-view.test.ts +14 -4
- package/src/client/about/release-notes-view.ts +4 -2
- package/src/client/api.ts +18 -6
- package/src/client/chat/ChatEntryView.tsx +391 -0
- package/src/client/chat/RollbackDiffTab.tsx +61 -0
- package/src/client/chat/RollbackFileIcon.tsx +96 -0
- package/src/client/chat/RollbackReviewDialog.tsx +591 -0
- package/src/client/chat/chat-entry-view.test.ts +1174 -0
- package/src/client/chat/chat-entry-view.ts +758 -0
- package/src/client/chat/chat-locales.ts +152 -0
- package/src/client/chat/chat-node-registration.ts +64 -0
- package/src/client/chat/chat-point-window.ts +173 -0
- package/src/client/chat/chat-slots.ts +107 -0
- package/src/client/chat/rollback-diff-tab.test.ts +105 -0
- package/src/client/chat/rollback-diff-tab.ts +164 -0
- package/src/client/chat/rollback-entry-view.test.ts +1469 -0
- package/src/client/chat/rollback-entry-view.ts +941 -0
- package/src/client/chat/sidebar-slots.ts +37 -0
- package/src/client/common/ConfirmDialog.tsx +7 -3
- package/src/client/common/ContentPicker.tsx +48 -10
- package/src/client/common/DiffTable.tsx +97 -0
- package/src/client/common/ErrorBanner.tsx +4 -4
- package/src/client/common/Icon.tsx +2 -0
- package/src/client/common/Modal.tsx +8 -1
- package/src/client/common/ProgressBar.tsx +8 -2
- package/src/client/common/ReportView.tsx +4 -4
- package/src/client/common/RunsCenter.tsx +1 -0
- package/src/client/common/client-route-parity.test.ts +78 -0
- package/src/client/common/info-hint-guard.test.ts +46 -13
- package/src/client/common/picker-lazy-units.test.ts +42 -0
- package/src/client/common/plan-text-redaction.test.ts +17 -6
- package/src/client/common/progress-view-edge.test.ts +8 -7
- package/src/client/common/progress-view.test.ts +9 -9
- package/src/client/common/progress-view.ts +2 -2
- package/src/client/common/routes.ts +28 -0
- package/src/client/common/ui.tsx +73 -10
- package/src/client/config-manager.module.css +996 -21
- package/src/client/css-scale-guard.test.ts +30 -0
- package/src/client/environment/EnvironmentPanel.tsx +11 -4
- package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
- package/src/client/export/ExportView.tsx +13 -8
- package/src/client/history/HistoryPanel.tsx +4 -3
- package/src/client/history/history-api.ts +2 -2
- package/src/client/history/history-locales.ts +5 -1
- package/src/client/home/HomePanel.tsx +29 -8
- package/src/client/import/ForeignImportView.tsx +4 -5
- package/src/client/import/ImportWizardView.tsx +5 -3
- package/src/client/import/import-wizard-split.test.ts +3 -1
- package/src/client/import/import-wizard-steps.tsx +9 -9
- package/src/client/index.ts +186 -3
- package/src/client/library/LibraryActions.tsx +32 -7
- package/src/client/library/LibraryPanel.tsx +23 -3
- package/src/client/library/RestorePlanView.tsx +9 -66
- package/src/client/locales.ts +54 -5
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +13 -2
- package/src/client/market/MyConfigsView.tsx +5 -4
- package/src/client/market/MyConfigsWizard.tsx +10 -8
- package/src/client/market/market-api.test.ts +9 -8
- package/src/client/market/market-api.ts +2 -2
- package/src/client/market/market-locales.ts +3 -0
- package/src/client/market/market-view.test.ts +6 -6
- package/src/client/market/market-view.ts +3 -3
- package/src/client/market/my-configs-api.ts +2 -2
- package/src/client/market/my-configs-view.test.ts +23 -22
- package/src/client/market/my-configs-view.ts +8 -8
- package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
- package/src/client/recovery/RecoveryPanel.tsx +442 -283
- package/src/client/recovery/RollbackCard.tsx +665 -0
- package/src/client/recovery/RollbackDiffPane.tsx +58 -0
- package/src/client/recovery/RollbackFileChanges.tsx +163 -0
- package/src/client/recovery/incident-api.ts +2 -2
- package/src/client/recovery/recovery-api.ts +116 -6
- package/src/client/recovery/recovery-locales.test.ts +299 -0
- package/src/client/recovery/recovery-locales.ts +488 -149
- package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
- package/src/client/recovery/rollback-file-changes.test.ts +73 -0
- package/src/client/recovery/session-dialog-mount.test.ts +54 -0
- package/src/client/run-store.ts +84 -21
- package/src/client/sync/AutosyncCard.tsx +25 -1
- package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
- package/src/client/sync/ChannelConfigDialog.tsx +10 -10
- package/src/client/sync/DecryptPasswordCard.tsx +2 -2
- package/src/client/sync/SecurityOptionsCard.tsx +27 -17
- package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
- package/src/client/sync/SyncConfirmView.tsx +46 -8
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +31 -23
- package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
- package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
- package/src/client/sync/SyncSettingsView.test.ts +20 -1
- package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
- package/src/client/sync/WorkDirCard.tsx +112 -0
- package/src/client/sync/history-model.test.ts +42 -9
- package/src/client/sync/history-model.ts +30 -15
- package/src/client/sync/sync-api.test.ts +57 -33
- package/src/client/sync/sync-api.ts +80 -6
- package/src/client/sync/sync-locales.ts +104 -3
- package/src/client/sync/sync-view-v2.test.ts +65 -13
- package/src/client/sync/sync-view.test.ts +107 -52
- package/src/client/sync/sync-view.ts +124 -41
- package/src/core/backup-plan.test.ts +28 -0
- package/src/core/backup-plan.ts +29 -17
- package/src/core/backup-verify.ts +24 -23
- package/src/core/backup.test.ts +76 -0
- package/src/core/backup.ts +55 -25
- package/src/core/boot-rescue.ts +4 -3
- package/src/core/boot-safety.ts +2 -1
- package/src/core/command-rollback.test.ts +536 -0
- package/src/core/command-rollback.ts +402 -0
- package/src/core/consult-source.ts +7 -6
- package/src/core/disk-usage.test.ts +257 -1
- package/src/core/disk-usage.ts +134 -14
- package/src/core/engine-locale.test.ts +593 -0
- package/src/core/engine-locale.ts +175 -0
- package/src/core/exporter.ts +3 -3
- package/src/core/fork-reconcile.test.ts +279 -0
- package/src/core/fork-reconcile.ts +234 -0
- package/src/core/journal.ts +6 -5
- package/src/core/local-plugin-host.ts +3 -2
- package/src/core/local-plugin-pack.ts +11 -6
- package/src/core/messages-core.ts +657 -0
- package/src/core/messages.ts +123 -17
- package/src/core/migration-consult.ts +21 -20
- package/src/core/migration-history.ts +19 -5
- package/src/core/model-tools.ts +28 -27
- package/src/core/phase3-host.ts +21 -5
- package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
- package/src/core/plugin-cli.ts +18 -19
- package/src/core/reconcile.test.ts +117 -0
- package/src/core/reconcile.ts +85 -17
- package/src/core/recovery-orchestrator.ts +19 -18
- package/src/core/reinstall.ts +26 -21
- package/src/core/restore.ts +20 -17
- package/src/core/rollback-file-diff.test.ts +159 -0
- package/src/core/rollback-ignore-rules.ts +126 -0
- package/src/core/rollback-point.test.ts +2603 -0
- package/src/core/rollback-point.ts +3755 -0
- package/src/core/run-registry.ts +6 -5
- package/src/core/session-health.ts +15 -0
- package/src/core/session-meta.test.ts +117 -1
- package/src/core/session-meta.ts +149 -2
- package/src/core/session-title.test.ts +127 -0
- package/src/core/session-title.ts +189 -0
- package/src/core/startup-barrier.test.ts +69 -0
- package/src/core/startup-barrier.ts +27 -0
- package/src/core/transaction-coordinator.ts +4 -3
- package/src/core/types.ts +18 -1
- package/src/core/validator.ts +5 -4
- package/src/core/verify-recovery.ts +31 -30
- package/src/foreign/bundle.ts +3 -2
- package/src/foreign/continue.ts +2 -1
- package/src/foreign/crush.ts +2 -1
- package/src/foreign/file-budget.test.ts +2 -0
- package/src/foreign/gemini.ts +2 -1
- package/src/foreign/goose.ts +2 -1
- package/src/foreign/kilocode.ts +2 -1
- package/src/foreign/kimi.ts +2 -1
- package/src/foreign/messages.ts +44 -0
- package/src/foreign/mimocode.ts +2 -1
- package/src/foreign/openclaw.ts +2 -1
- package/src/foreign/opencode.ts +2 -1
- package/src/foreign/pi.ts +2 -1
- package/src/foreign/qoder.ts +2 -1
- package/src/foreign/qwen.ts +2 -1
- package/src/foreign/registry.ts +7 -6
- package/src/foreign/teleagent.ts +2 -1
- package/src/foreign/workbuddy.ts +2 -1
- package/src/foreign/zcode.ts +2 -1
- package/src/foreign/zed.ts +2 -1
- package/src/index.facade.test.ts +340 -1
- package/src/index.ts +813 -57
- package/src/market/git-file-writer.ts +20 -19
- package/src/market/github-repos.test.ts +23 -0
- package/src/market/github-repos.ts +37 -19
- package/src/market/index-parser.ts +31 -30
- package/src/market/messages-market.ts +325 -0
- package/src/market/my-repo.ts +32 -29
- package/src/market/prepare.ts +18 -15
- package/src/market/security.ts +20 -19
- package/src/market/types.ts +2 -1
- package/src/market/url.ts +2 -1
- package/src/migrations/index.ts +6 -5
- package/src/migrations/messages-migrations.ts +59 -0
- package/src/profiles/dsh-profile-launcher.ts +2 -1
- package/src/profiles/dsh-profile-runtime.ts +2 -1
- package/src/profiles/messages.ts +31 -0
- package/src/routes/backup.ts +18 -2
- package/src/routes/import.ts +3 -3
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +9 -3
- package/src/routes/market.ts +1 -1
- package/src/routes/messages.ts +104 -0
- package/src/routes/profiles.ts +12 -6
- package/src/routes/recovery.test.ts +405 -1
- package/src/routes/recovery.ts +377 -109
- package/src/routes/snapshots.ts +51 -4
- package/src/routes/sync.ts +67 -16
- package/src/schema/config.ts +53 -20
- package/src/schema/index.ts +7 -5
- package/src/schema/manifest.ts +19 -18
- package/src/schema/messages.ts +120 -0
- package/src/schema/registry.test.ts +19 -5
- package/src/schema/section-registry.ts +64 -39
- package/src/schema/versions.ts +6 -5
- package/src/security/encryption.ts +16 -15
- package/src/security/integrity.ts +8 -7
- package/src/security/messages.ts +87 -0
- package/src/security/redaction.test.ts +17 -1
- package/src/security/redaction.ts +13 -0
- package/src/security/secret-scanner.ts +19 -12
- package/src/security/security.test.ts +47 -0
- package/src/security/vault.ts +2 -1
- package/src/security/zip-security.ts +4 -3
- package/src/sync/ancestor.ts +2 -1
- package/src/sync/autosync-config.ts +21 -0
- package/src/sync/autosync-scheduler.test.ts +63 -7
- package/src/sync/autosync-scheduler.ts +118 -78
- package/src/sync/backup-files.ts +3 -2
- package/src/sync/backup-scheduler.ts +6 -5
- package/src/sync/blob-store.ts +2 -1
- package/src/sync/git/git-transport.test.ts +170 -8
- package/src/sync/git/git-transport.ts +176 -2
- package/src/sync/github-auth.ts +10 -9
- package/src/sync/layout.ts +13 -12
- package/src/sync/messages.ts +153 -0
- package/src/sync/review-queue.ts +6 -5
- package/src/sync/s3/messages.ts +16 -0
- package/src/sync/s3/s3-providers.ts +9 -6
- package/src/sync/s3/s3-transport.ts +1 -1
- package/src/sync/s3/sigv4.ts +3 -1
- package/src/sync/snapshot-crypto.ts +6 -5
- package/src/sync/snapshot-json.ts +3 -2
- package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
- package/src/sync/sync-engine.test.ts +226 -8
- package/src/sync/sync-engine.ts +103 -41
- package/src/sync/sync-history.ts +7 -6
- package/src/sync/sync-selection.test.ts +12 -2
- package/src/sync/sync-selection.ts +18 -5
- package/src/sync/webdav/webdav-transport.ts +4 -1
- package/src/ui/artifact-view.ts +4 -3
- package/src/ui/commands.test.ts +1 -0
- package/src/ui/commands.ts +11 -1
- package/src/ui/disk-usage-view.test.ts +113 -6
- package/src/ui/disk-usage-view.ts +63 -7
- package/src/ui/errors.ts +4 -3
- package/src/ui/export-flow.ts +24 -20
- package/src/ui/foreign-view.test.ts +43 -0
- package/src/ui/foreign-view.ts +30 -3
- package/src/ui/i18n.ts +125 -0
- package/src/ui/import-wizard.ts +18 -10
- package/src/ui/progress.ts +2 -2
- package/src/ui/report.ts +7 -6
- package/src/ui/rollback-view.test.ts +819 -0
- package/src/ui/rollback-view.ts +1339 -0
- package/src/ui/selection-model.test.ts +33 -1
- package/src/ui/selection-model.ts +53 -0
- package/src/ui/session-inventory-view.test.ts +109 -0
- package/src/ui/session-inventory-view.ts +138 -3
- package/src/ui/session-issue-labels.test.ts +51 -0
- package/src/ui/session-issue-labels.ts +70 -0
- package/src/ui/sync-settings-view.test.ts +36 -0
- package/src/ui/sync-settings-view.ts +49 -0
- package/src/ui/types.ts +10 -4
- package/src/utils/atomic-write.ts +6 -5
- package/src/utils/bundle-scan.test.ts +2 -2
- package/src/utils/bundle-scan.ts +12 -1
- package/src/utils/env-lock.ts +83 -49
- package/src/utils/json.ts +7 -5
- package/src/utils/messages-utils.ts +232 -0
- package/src/utils/proxy.test.ts +25 -0
- package/src/utils/proxy.ts +19 -5
- package/src/utils/recursive-walk.test.ts +43 -0
- package/src/utils/recursive-walk.ts +75 -1
- package/src/utils/session-log-repair.ts +10 -9
- package/src/utils/zip.ts +27 -26
- package/lib/core/checkpoint.d.ts +0 -362
- package/lib/core/checkpoint.js +0 -1309
- package/src/core/checkpoint.test.ts +0 -665
- package/src/core/checkpoint.ts +0 -1614
package/lib/core/backup-plan.js
CHANGED
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
* 目标越出 homeDir 或断链的链接跳过但**写进 warnings**——绝不静默少收内容;
|
|
33
33
|
* 普通目录**超过层级上限**同样留痕(cli-F2),且告警措辞按**真实来源**分派(t37:链接类 vs 目录超深);
|
|
34
34
|
* - 命中 `isReservedInternalRel` 的内部命名空间(locks/snapshots/transactions…)跳过;
|
|
35
|
+
* - 运行时依赖目录 node_modules(任意深度)**整棵跳过**(issue #76:与 plugins 分区的 npm pack 同口径,
|
|
36
|
+
* 依赖树可由包内 package.json / lock 重建),跳过了哪些目录、各多少文件一律写进 warnings;
|
|
35
37
|
* - `pluginFiles`(`dsh-ssh.json` 等第三方插件自有文件)**默认不收**:该分区与 GUI
|
|
36
38
|
* 侧同为 deviceSpecific「任意文件直通、无内容过滤」,`dsh-ssh.json` 里是明文主机
|
|
37
39
|
* 密码——必须由用户显式 `--sections pluginFiles` 选中,并附带显式告警
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
*
|
|
40
42
|
* 零 DSH 运行时依赖:仅 `node:fs/promises` + `node:path` + 本仓库零依赖模块。
|
|
41
43
|
*/
|
|
44
|
+
import { engineMsg } from './messages-core.js';
|
|
42
45
|
import fs from 'node:fs/promises';
|
|
43
46
|
import path from 'node:path';
|
|
44
47
|
import { listRecursiveFollowingLinks } from '../utils/recursive-walk.js';
|
|
@@ -110,7 +113,7 @@ const SECTION_SPECS = {
|
|
|
110
113
|
files: ['dsh-ssh.json', 'pet.json'],
|
|
111
114
|
dirs: ['plugin-config'],
|
|
112
115
|
label: 'Plugin Files',
|
|
113
|
-
risk
|
|
116
|
+
get risk() { return engineMsg('core.backupPlan.pluginFilesRisk'); },
|
|
114
117
|
},
|
|
115
118
|
// adapters/self.ts(baseDir='dsh-config-manager',SELF_CONFIG_FILES 白名单)
|
|
116
119
|
self: {
|
|
@@ -134,13 +137,16 @@ const SECTION_SPECS = {
|
|
|
134
137
|
* 跳过原因;而 `too-deep` 自 cli-F2 起对**普通目录**同样会记录(此前只有链接超深才记)—— 整块内容被
|
|
135
138
|
* 深度上限裁掉时若仍写成「链接未进备份」,用户看到的原因就与真实来源(目录层级超出上限)不符。
|
|
136
139
|
* 措辞里**不编具体层数**:上限常量住在 utils/recursive-walk.ts,本模块不 import 它的私有常量。
|
|
140
|
+
*
|
|
141
|
+
* 路径**不叠加分区前缀**:内核返回的 `s.path` 已是 homeDir 相对路径(含分区目录),再加一层
|
|
142
|
+
* 会打印成 `skills/skills/...`(真机规模探针实测),与相邻的「目录读取失败」行口径也不一致。
|
|
137
143
|
*/
|
|
138
|
-
function skippedLinkWarning(
|
|
139
|
-
const at =
|
|
144
|
+
function skippedLinkWarning(s) {
|
|
145
|
+
const at = engineMsg('core.backupPlan.pathWithReason', { path: s.path, reason: s.reason });
|
|
140
146
|
if (s.reason === 'too-deep') {
|
|
141
|
-
return
|
|
147
|
+
return engineMsg('core.backupPlan.tooDeep', { at });
|
|
142
148
|
}
|
|
143
|
-
return
|
|
149
|
+
return engineMsg('core.backupPlan.linkSkipped', { at });
|
|
144
150
|
}
|
|
145
151
|
/**
|
|
146
152
|
* 递归列出目录下全部普通文件(**相对 homeDir** 的 posix 路径,已排序);目录不存在 → 空。
|
|
@@ -149,16 +155,23 @@ function skippedLinkWarning(label, s) {
|
|
|
149
155
|
* 并把「跟随了多少链接」「哪些链接/目录没进来(及原因)」写进 warnings。
|
|
150
156
|
* 旧实现遇到链接目录直接跳过且零告警,用户拿到的是「成功但缺斤少两」的备份。
|
|
151
157
|
*/
|
|
152
|
-
async function listFilesRecursive(dirAbs, homeDir, warnings
|
|
158
|
+
async function listFilesRecursive(dirAbs, homeDir, warnings) {
|
|
153
159
|
const listing = await listRecursiveFollowingLinks(dirAbs, homeDir);
|
|
154
160
|
for (const s of listing.skippedLinks) {
|
|
155
|
-
warnings.push(skippedLinkWarning(
|
|
161
|
+
warnings.push(skippedLinkWarning(s));
|
|
156
162
|
}
|
|
157
163
|
for (const dir of listing.unreadableDirs) {
|
|
158
|
-
warnings.push(
|
|
164
|
+
warnings.push(engineMsg('core.backupPlan.dirUnreadable', { dir }));
|
|
165
|
+
}
|
|
166
|
+
// issue #76:运行时依赖目录整棵跳过(内容未进备份)——必须可见,绝不静默。
|
|
167
|
+
for (const dep of listing.skippedDepDirs) {
|
|
168
|
+
const files = dep.files === null
|
|
169
|
+
? engineMsg('core.backupPlan.countNotMeasured')
|
|
170
|
+
: engineMsg('core.backupPlan.fileCount', { count: dep.files });
|
|
171
|
+
warnings.push(engineMsg('core.backupPlan.depDirSkipped', { path: dep.path, files }));
|
|
159
172
|
}
|
|
160
173
|
if (listing.followedLinks > 0) {
|
|
161
|
-
warnings.push(
|
|
174
|
+
warnings.push(engineMsg('core.backupPlan.followedLinks', { count: listing.followedLinks }));
|
|
162
175
|
}
|
|
163
176
|
return listing.paths;
|
|
164
177
|
}
|
|
@@ -171,15 +184,15 @@ function toRelativePath(baseDir, homeRel) {
|
|
|
171
184
|
}
|
|
172
185
|
/** `--sections` 取值校验:返回选中分区或可读错误(未知 id 一律拒绝并列出合法值) */
|
|
173
186
|
export function parseSectionsArg(raw) {
|
|
174
|
-
const parts = raw.split(/[
|
|
187
|
+
const parts = raw.split(/[,\uFF0C\s]+/).filter((p) => p !== '');
|
|
175
188
|
if (parts.length === 0) {
|
|
176
|
-
return { ok: false, error: '
|
|
189
|
+
return { ok: false, error: engineMsg('core.backupPlan.sectionsMissingValue') };
|
|
177
190
|
}
|
|
178
191
|
const unknown = parts.filter((p) => !OFFLINE_BACKUP_SECTIONS.includes(p));
|
|
179
192
|
if (unknown.length > 0) {
|
|
180
193
|
return {
|
|
181
194
|
ok: false,
|
|
182
|
-
error:
|
|
195
|
+
error: engineMsg('core.backupPlan.unknownSections', { unknown: unknown.join(', '), available: OFFLINE_BACKUP_SECTIONS.join(', ') }),
|
|
183
196
|
};
|
|
184
197
|
}
|
|
185
198
|
// 去重并保持声明顺序,保证产物可复现
|
|
@@ -207,7 +220,7 @@ export async function collectBackupEntries(homeDir, only = []) {
|
|
|
207
220
|
let excluded = 0;
|
|
208
221
|
const push = (zipName, data) => {
|
|
209
222
|
if (!isPathSafe(zipName)) {
|
|
210
|
-
warnings.push(
|
|
223
|
+
warnings.push(engineMsg('core.backupPlan.unsafeEntryName', { name: zipName }));
|
|
211
224
|
excluded += 1;
|
|
212
225
|
return;
|
|
213
226
|
}
|
|
@@ -229,7 +242,7 @@ export async function collectBackupEntries(homeDir, only = []) {
|
|
|
229
242
|
catch (err) {
|
|
230
243
|
// 不存在 → 静默跳过(按需创建的文件,如 dsh-ssh.json)
|
|
231
244
|
if (err.code !== 'ENOENT') {
|
|
232
|
-
warnings.push(
|
|
245
|
+
warnings.push(engineMsg('core.backupPlan.statFailed', { path: norm, reason: err instanceof Error ? err.message : String(err) }));
|
|
233
246
|
}
|
|
234
247
|
return;
|
|
235
248
|
}
|
|
@@ -242,7 +255,7 @@ export async function collectBackupEntries(homeDir, only = []) {
|
|
|
242
255
|
push(`${prefix}${toRelativePath(spec.baseDir, norm)}`, new Uint8Array(data));
|
|
243
256
|
}
|
|
244
257
|
catch (err) {
|
|
245
|
-
warnings.push(
|
|
258
|
+
warnings.push(engineMsg('core.backupPlan.readFailed', { path: norm, reason: err instanceof Error ? err.message : String(err) }));
|
|
246
259
|
}
|
|
247
260
|
};
|
|
248
261
|
// 1) 固定文件(存在才收)
|
|
@@ -251,7 +264,7 @@ export async function collectBackupEntries(homeDir, only = []) {
|
|
|
251
264
|
// 2) 递归目录(跟随 junction/符号链接;未跟随的链接与不可读目录写进 warnings)
|
|
252
265
|
for (const dirRel of spec.dirs ?? []) {
|
|
253
266
|
const dirAbs = path.join(homeDir, ...normalizePath(dirRel).split('/'));
|
|
254
|
-
const rels = await listFilesRecursive(dirAbs, homeDir, warnings
|
|
267
|
+
const rels = await listFilesRecursive(dirAbs, homeDir, warnings);
|
|
255
268
|
for (const homeRel of rels)
|
|
256
269
|
await consider(homeRel);
|
|
257
270
|
}
|
|
@@ -264,7 +277,7 @@ export async function collectBackupEntries(homeDir, only = []) {
|
|
|
264
277
|
}
|
|
265
278
|
else {
|
|
266
279
|
empty.push(sectionId);
|
|
267
|
-
warnings.push(
|
|
280
|
+
warnings.push(engineMsg('core.backupPlan.sectionEmpty', { section: sectionId }));
|
|
268
281
|
}
|
|
269
282
|
}
|
|
270
283
|
return { entries, sections, warnings, included, empty };
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* (实测 `verifyChecksums` 只调用 `map.get()`,不会整体物化 Map)。
|
|
29
29
|
* 对 MB 级的 DSH 配置备份完全可接受。
|
|
30
30
|
*/
|
|
31
|
+
import { engineMsg } from './messages-core.js';
|
|
31
32
|
import fs from 'node:fs/promises';
|
|
32
33
|
import { verifyChecksums } from '../utils/hashing.js';
|
|
33
34
|
import { isENOENT } from '../utils/guards.js';
|
|
@@ -114,7 +115,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
114
115
|
try {
|
|
115
116
|
const stat = await fs.stat(zipPath);
|
|
116
117
|
if (!stat.isFile()) {
|
|
117
|
-
errors.push(
|
|
118
|
+
errors.push(engineMsg('core.backupVerify.notRegularFile', { zip: zipPath }));
|
|
118
119
|
return makeResult(zipPath, 'MISSING', errors, warnings);
|
|
119
120
|
}
|
|
120
121
|
sizeBytes = stat.size;
|
|
@@ -122,11 +123,11 @@ export async function verifyBackupZip(zipPath) {
|
|
|
122
123
|
}
|
|
123
124
|
catch (err) {
|
|
124
125
|
if (isENOENT(err)) {
|
|
125
|
-
errors.push(
|
|
126
|
+
errors.push(engineMsg('core.backupVerify.fileNotFound', { zip: zipPath }));
|
|
126
127
|
return makeResult(zipPath, 'MISSING', errors, warnings);
|
|
127
128
|
}
|
|
128
129
|
// 文件在但读不动(EACCES / EBUSY / IO 错):这是自检自身的失败,不是「备份坏了」
|
|
129
|
-
errors.push(
|
|
130
|
+
errors.push(engineMsg('core.backupVerify.fileUnreadable', { reason: msgOfError(err) }));
|
|
130
131
|
return makeResult(zipPath, 'VERIFY_ERROR', errors, warnings);
|
|
131
132
|
}
|
|
132
133
|
/** 已知字节数后的骨架(后续阶段失败时保留体积信息) */
|
|
@@ -137,7 +138,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
137
138
|
/* ---------------- 2. 打开为 ZIP ---------------- */
|
|
138
139
|
// 整体加密容器不是 ZIP(先识别,给出比「ZIP 损坏」准确得多的结论)
|
|
139
140
|
if (isArchiveBlob(buf)) {
|
|
140
|
-
errors.push('
|
|
141
|
+
errors.push(engineMsg('core.backupVerify.encryptedContainer'));
|
|
141
142
|
return withSize('UNSUPPORTED');
|
|
142
143
|
}
|
|
143
144
|
let archive;
|
|
@@ -145,7 +146,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
145
146
|
archive = createHardenedZipParser()(buf);
|
|
146
147
|
}
|
|
147
148
|
catch (err) {
|
|
148
|
-
errors.push(
|
|
149
|
+
errors.push(engineMsg('core.backupVerify.cannotOpenZip', { reason: msgOfError(err) }));
|
|
149
150
|
return withSize('CORRUPT');
|
|
150
151
|
}
|
|
151
152
|
const metas = archive.entries();
|
|
@@ -154,12 +155,12 @@ export async function verifyBackupZip(zipPath) {
|
|
|
154
155
|
file: zipPath, verdict, sizeBytes, entryCount, errors, warnings,
|
|
155
156
|
});
|
|
156
157
|
if (entryCount === 0) {
|
|
157
|
-
errors.push('
|
|
158
|
+
errors.push(engineMsg('core.backupVerify.emptyArchive'));
|
|
158
159
|
return withEntries('CORRUPT');
|
|
159
160
|
}
|
|
160
161
|
/* ---------------- 3. manifest.json ---------------- */
|
|
161
162
|
if (!archive.has(MANIFEST_FILE)) {
|
|
162
|
-
errors.push(
|
|
163
|
+
errors.push(engineMsg('core.backupVerify.missingManifest', { file: MANIFEST_FILE }));
|
|
163
164
|
return withEntries('CORRUPT');
|
|
164
165
|
}
|
|
165
166
|
let manifestRaw;
|
|
@@ -168,7 +169,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
168
169
|
}
|
|
169
170
|
catch (err) {
|
|
170
171
|
// 条目本身读不出来(CRC 失败/解压失败)→ 归档损坏,而非 manifest 语义非法
|
|
171
|
-
errors.push(
|
|
172
|
+
errors.push(engineMsg('core.backupVerify.manifestUnreadable', { file: MANIFEST_FILE, reason: msgOfError(err) }));
|
|
172
173
|
return withEntries('CORRUPT');
|
|
173
174
|
}
|
|
174
175
|
let manifest;
|
|
@@ -176,7 +177,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
176
177
|
manifest = parseManifest(manifestRaw);
|
|
177
178
|
}
|
|
178
179
|
catch (err) {
|
|
179
|
-
errors.push(
|
|
180
|
+
errors.push(engineMsg('core.backupVerify.invalidManifest', { file: MANIFEST_FILE, reason: msgOfError(err) }));
|
|
180
181
|
return withEntries('CORRUPT');
|
|
181
182
|
}
|
|
182
183
|
const sections = Object.entries(manifest.sections ?? {})
|
|
@@ -187,12 +188,12 @@ export async function verifyBackupZip(zipPath) {
|
|
|
187
188
|
});
|
|
188
189
|
if (!isSupported(manifest.schemaVersion)) {
|
|
189
190
|
// 版本超范围是「本插件读不了」,不是「备份坏了」——明确区分,避免误导用户重导
|
|
190
|
-
errors.push(
|
|
191
|
+
errors.push(engineMsg('core.backupVerify.unsupportedSchema', { version: describeVersion(manifest.schemaVersion) }));
|
|
191
192
|
return withManifest('UNSUPPORTED');
|
|
192
193
|
}
|
|
193
194
|
/* ---------------- 4. integrity/checksums.json ---------------- */
|
|
194
195
|
if (!archive.has(CHECKSUMS_FILE)) {
|
|
195
|
-
errors.push(
|
|
196
|
+
errors.push(engineMsg('core.backupVerify.missingChecksums', { file: CHECKSUMS_FILE }));
|
|
196
197
|
return withManifest('CORRUPT');
|
|
197
198
|
}
|
|
198
199
|
let table;
|
|
@@ -201,7 +202,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
201
202
|
}
|
|
202
203
|
catch (err) {
|
|
203
204
|
// readEntryText 的 CRC/解压失败与「表结构非法」都归 CORRUPT(两者都说明备份不可信)
|
|
204
|
-
errors.push(
|
|
205
|
+
errors.push(engineMsg('core.backupVerify.invalidChecksums', { file: CHECKSUMS_FILE, reason: msgOfError(err) }));
|
|
205
206
|
return withManifest('CORRUPT');
|
|
206
207
|
}
|
|
207
208
|
// 4a. 完整性比对:**复用官方 verifyChecksums**(与导出侧 buildChecksums 同源同口径),
|
|
@@ -209,7 +210,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
209
210
|
const unreadableNames = new Set();
|
|
210
211
|
const unreadable = [];
|
|
211
212
|
const lazyEntries = new LazyEntryMap(archive, unreadableNames, (name, reason) => {
|
|
212
|
-
unreadable.push(
|
|
213
|
+
unreadable.push(engineMsg('core.backupVerify.unreadableEntry', { name, reason }));
|
|
213
214
|
});
|
|
214
215
|
const tableKeys = Object.keys(table);
|
|
215
216
|
const check = verifyChecksums(lazyEntries, table);
|
|
@@ -219,10 +220,10 @@ export async function verifyBackupZip(zipPath) {
|
|
|
219
220
|
// 4b. 结构提示:未纳入校验表的条目(导出器只把 manifest 与校验表自身排除在外)
|
|
220
221
|
if (tableKeys.length === 0) {
|
|
221
222
|
if (entryCount > NON_DATA_ENTRIES.size) {
|
|
222
|
-
errors.push('
|
|
223
|
+
errors.push(engineMsg('core.backupVerify.emptyTableWithData'));
|
|
223
224
|
return withManifest('CORRUPT');
|
|
224
225
|
}
|
|
225
|
-
warnings.push('
|
|
226
|
+
warnings.push(engineMsg('core.backupVerify.emptyTable'));
|
|
226
227
|
}
|
|
227
228
|
const covered = new Set(tableKeys);
|
|
228
229
|
const uncovered = metas
|
|
@@ -230,7 +231,7 @@ export async function verifyBackupZip(zipPath) {
|
|
|
230
231
|
.map((m) => m.name);
|
|
231
232
|
if (uncovered.length > 0) {
|
|
232
233
|
const shown = uncovered.slice(0, 5).join(', ');
|
|
233
|
-
warnings.push(
|
|
234
|
+
warnings.push(engineMsg('core.backupVerify.uncoveredEntries', { count: uncovered.length, shown, suffix: uncovered.length > 5 ? ', …' : '' }));
|
|
234
235
|
}
|
|
235
236
|
// 4c. 结构提示:manifest 声明包含的分区在归档内应有对应条目
|
|
236
237
|
for (const id of sections) {
|
|
@@ -238,31 +239,31 @@ export async function verifyBackupZip(zipPath) {
|
|
|
238
239
|
const jsonPath = SECTION_JSON_PATHS[id];
|
|
239
240
|
if (prefix !== undefined) {
|
|
240
241
|
if (!metas.some((m) => m.name.startsWith(prefix) && m.name !== prefix)) {
|
|
241
|
-
warnings.push(
|
|
242
|
+
warnings.push(engineMsg('core.backupVerify.declaredSectionNoEntries', { id }));
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
else if (jsonPath !== undefined && !archive.has(jsonPath)) {
|
|
245
|
-
warnings.push(
|
|
246
|
+
warnings.push(engineMsg('core.backupVerify.declaredSectionPayloadMissing', { id, path: jsonPath }));
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
if (manifest.security?.encrypted === true && !metas.some((m) => SECRETS_ENC_RE.test(m.name))) {
|
|
249
|
-
warnings.push('
|
|
250
|
+
warnings.push(engineMsg('core.backupVerify.encryptedFlagNoSecrets'));
|
|
250
251
|
}
|
|
251
252
|
/* ---------------- 裁决 ---------------- */
|
|
252
253
|
if (missing.length > 0) {
|
|
253
|
-
errors.push(
|
|
254
|
+
errors.push(engineMsg('core.backupVerify.missingEntries', { count: missing.length, list: missing.join(', ') }));
|
|
254
255
|
}
|
|
255
256
|
if (mismatched.length > 0) {
|
|
256
|
-
errors.push(
|
|
257
|
+
errors.push(engineMsg('core.backupVerify.hashMismatch', { count: mismatched.length, list: mismatched.join(', ') }));
|
|
257
258
|
}
|
|
258
259
|
if (unreadable.length > 0) {
|
|
259
|
-
errors.push(
|
|
260
|
+
errors.push(engineMsg('core.backupVerify.unreadableEntries', { count: unreadable.length, list: unreadable.join(', ') }));
|
|
260
261
|
}
|
|
261
262
|
return withManifest(missing.length > 0 || mismatched.length > 0 || unreadable.length > 0 ? 'CORRUPT' : 'OK');
|
|
262
263
|
}
|
|
263
264
|
catch (err) {
|
|
264
265
|
// 5. 自检自身异常:如实报 VERIFY_ERROR,绝不降级成 OK
|
|
265
|
-
errors.push(
|
|
266
|
+
errors.push(engineMsg('core.backupVerify.verificationError', { reason: msgOfError(err) }));
|
|
266
267
|
return withSize('VERIFY_ERROR');
|
|
267
268
|
}
|
|
268
269
|
}
|
package/lib/core/backup.d.ts
CHANGED
|
@@ -144,6 +144,11 @@ export interface FileSnapshotStoreOptions {
|
|
|
144
144
|
* 未注入 → `fallbackPruneSelector`(只按 keepLast,等价既有 FIFO;分层字段被忽略 = 保守少删)。
|
|
145
145
|
*/
|
|
146
146
|
pruneSelector?: PruneSelector;
|
|
147
|
+
/**
|
|
148
|
+
* 诊断回调(可选):保留清理**被放弃**或失败时的可见线索。
|
|
149
|
+
* 缺省 = 不记录(不改变行为);宿主接 `host.log.warn` —— 「这轮为什么没清理」必须能查。
|
|
150
|
+
*/
|
|
151
|
+
onWarn?: (message: string) => void;
|
|
147
152
|
}
|
|
148
153
|
/** 文件快照存储:<dir>/<id>/snapshot.json + <dir>/<id>/blobs/* */
|
|
149
154
|
export declare class FileSnapshotStore implements SnapshotStore {
|
package/lib/core/backup.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* 快照不保存 credential 值(DSH 永不回读值)——credential 条目只记 existed 标志,
|
|
6
6
|
* 回滚时 existed=true 的条目如实标记「值需人工补录」。
|
|
7
7
|
*/
|
|
8
|
+
import { engineMsg } from './messages-core.js';
|
|
8
9
|
import fs from 'node:fs/promises';
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import crypto from 'node:crypto';
|
|
@@ -60,8 +61,11 @@ async function backupHostFiles(ctx, blobs) {
|
|
|
60
61
|
existed = await ctx.fs.exists(relPath);
|
|
61
62
|
}
|
|
62
63
|
catch (err) {
|
|
64
|
+
// 探测自身失败 = 存在性判不出来:按「存在但不可读」登记。**绝不写 existed:false** ——
|
|
65
|
+
// 那会被恢复侧读成「当时确实不存在」,进而删掉用户真实的文件。
|
|
63
66
|
ctx.log.warn(`快照探测宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
-
existed
|
|
67
|
+
backups.push({ relPath, blobPath: '', existed: true, unreadable: true });
|
|
68
|
+
continue;
|
|
65
69
|
}
|
|
66
70
|
if (!existed) {
|
|
67
71
|
backups.push({ relPath, blobPath: '', existed: false });
|
|
@@ -73,8 +77,10 @@ async function backupHostFiles(ctx, blobs) {
|
|
|
73
77
|
backups.push({ relPath, blobPath, existed: true });
|
|
74
78
|
}
|
|
75
79
|
catch (err) {
|
|
80
|
+
// 文件确实存在、只是这次读不出来(占用/权限/IO):登记 existed=true + unreadable,
|
|
81
|
+
// 恢复侧据此只跳过并告警,绝不进入「删除」分支(见 HostFileBackup.unreadable)。
|
|
76
82
|
ctx.log.warn(`快照读取宿主文件失败 ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
-
backups.push({ relPath, blobPath: '', existed:
|
|
83
|
+
backups.push({ relPath, blobPath: '', existed: true, unreadable: true });
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
return backups;
|
|
@@ -98,7 +104,7 @@ const FILE_BASES = {
|
|
|
98
104
|
// 其它分区语义处理」同类缺陷)。注册表新增文件类分区时若忘了在这里补,进程启动即失败。
|
|
99
105
|
for (const id of SECTION_IDS) {
|
|
100
106
|
if (isFileSection(id) && FILE_BASES[id] === undefined) {
|
|
101
|
-
throw new Error(
|
|
107
|
+
throw new Error(engineMsg('core.backup.fileSectionBaseMissing', { section: id }));
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
110
|
/**
|
|
@@ -257,7 +263,7 @@ async function engineSnapshotEntry(ctx, target) {
|
|
|
257
263
|
case 'secrets':
|
|
258
264
|
// secrets 已注册但**没有 adapter、也没有 ZIP 内分区 JSON**(凭据值走独立加密容器 secrets.enc)。
|
|
259
265
|
// 任何计划项都不应以它为快照目标 —— 显式报错,而不是落到 default 分支。
|
|
260
|
-
throw new Error('
|
|
266
|
+
throw new Error(engineMsg('core.backup.secretsSectionNotSnapshotTarget'));
|
|
261
267
|
default: {
|
|
262
268
|
// 编译期穷举断言:SectionId 的每一项都已被上面的 case 处理 → 此处 target.adapter 收窄为 never。
|
|
263
269
|
// 新增分区若忘了在这里实现快照语义,tsc 会直接报错(不再依赖人肉核对清单)。
|
|
@@ -265,8 +271,7 @@ async function engineSnapshotEntry(ctx, target) {
|
|
|
265
271
|
// 运行期(只可能来自运行期强转:被篡改的 plan / 旧 journal 反序列化):**显式报错**。
|
|
266
272
|
// 旧实现在这里静默返回 settingsNamespace 条目(连 existed 都没有)→ 回滚把该目标当「原本不存在」
|
|
267
273
|
// 而不做任何补偿:该分区的写入永远撤不掉,报告却仍说「已快照、可回滚」(审计 core-flow#F-10)。
|
|
268
|
-
throw new Error(
|
|
269
|
-
+ '拒绝按其它分区语义静默记录(那会让该分区的写入无法回滚)');
|
|
274
|
+
throw new Error(engineMsg('core.backup.sectionUnsupported', { section: String(unhandled) }));
|
|
270
275
|
}
|
|
271
276
|
}
|
|
272
277
|
}
|
|
@@ -405,7 +410,7 @@ export class FileSnapshotStore {
|
|
|
405
410
|
async writeBlob(dir, blobPath, data, hashes) {
|
|
406
411
|
const target = path.join(dir, blobPath);
|
|
407
412
|
if (!target.startsWith(dir))
|
|
408
|
-
throw new Error(
|
|
413
|
+
throw new Error(engineMsg('core.backup.blobPathEscaped', { path: blobPath }));
|
|
409
414
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
410
415
|
await atomicWriteFile(target, data);
|
|
411
416
|
hashes[blobPath] = sha256Hex(data);
|
|
@@ -429,7 +434,7 @@ export class FileSnapshotStore {
|
|
|
429
434
|
// 4) 验证(从磁盘重读,不信任内存对象)
|
|
430
435
|
const verified = await verifySnapshot(this.options.dir, snapshot.id);
|
|
431
436
|
if (!verified.ok) {
|
|
432
|
-
throw new Error(
|
|
437
|
+
throw new Error(engineMsg('core.backup.snapshotVerifyFailed', { id: snapshot.id, reason: verified.reason ?? '' }));
|
|
433
438
|
}
|
|
434
439
|
// 5) 原子发布 READY(重写 snapshot.json readiness='READY';metadataHash 稳定不受影响)
|
|
435
440
|
const ready = { ...snapshot, readiness: 'READY' };
|
|
@@ -439,7 +444,9 @@ export class FileSnapshotStore {
|
|
|
439
444
|
await this.prune();
|
|
440
445
|
}
|
|
441
446
|
catch (err) {
|
|
442
|
-
// 快照已 READY + verified;prune 失败(EBUSY/EPERM)不应把刚成功的快照判为 unusable
|
|
447
|
+
// 快照已 READY + verified;prune 失败(EBUSY/EPERM)不应把刚成功的快照判为 unusable ——
|
|
448
|
+
// 但必须留痕:保留清理长期失败会让快照无限堆积,用户与日志都看不到线索。
|
|
449
|
+
this.options.onWarn?.(engineMsg('core.backup.pruneFailed', { reason: err instanceof Error ? err.message : String(err) }));
|
|
443
450
|
}
|
|
444
451
|
return snapshot.id;
|
|
445
452
|
}
|
|
@@ -480,7 +487,19 @@ export class FileSnapshotStore {
|
|
|
480
487
|
async prune() {
|
|
481
488
|
const dir = this.options.dir;
|
|
482
489
|
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
483
|
-
|
|
490
|
+
// F3:被未收敛 journal 引用的快照**绝不自动清理**。判据是「读不到 ≠ 没有引用」——
|
|
491
|
+
// 引用提供者抛错时**放弃本轮清理**,绝不按空集合继续删:一次瞬时 IO 错就可能删掉
|
|
492
|
+
// 事故恢复唯一的回滚点(那是 SAFE MODE 的唯一证据)。此前这里是 `.catch(() => new Set())`。
|
|
493
|
+
let referenced = new Set();
|
|
494
|
+
if (this.options.referencedSnapshotIds !== undefined) {
|
|
495
|
+
try {
|
|
496
|
+
referenced = await this.options.referencedSnapshotIds();
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
this.options.onWarn?.(engineMsg('core.backup.pruneAbandoned', { reason: err instanceof Error ? err.message : String(err) }));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
484
503
|
// m-retention:策略读取失败 → 回退缺省(绝不因策略提供者异常而误删/崩溃)
|
|
485
504
|
const policy = await Promise.resolve()
|
|
486
505
|
.then(() => this.options.retentionPolicy?.() ?? defaultRetentionPolicyLike())
|
|
@@ -494,11 +513,18 @@ export class FileSnapshotStore {
|
|
|
494
513
|
const parsed = parseJsonSafe(await fs.readFile(path.join(dir, entry.name, 'snapshot.json'), 'utf8'));
|
|
495
514
|
if (typeof parsed.id !== 'string' || parsed.id === '' || typeof parsed.createdAt !== 'string')
|
|
496
515
|
continue;
|
|
516
|
+
// 删除目标只能是**目录名**(下面 path.join(dir, id) 就是按它删的)。目录名与内部 id 不一致时
|
|
517
|
+
// **绝不猜**:pinned/被引用的判断是对着这份 snapshot.json 做的,删除却会打在另一个目录上 ——
|
|
518
|
+
// 跳过并留痕(手工改名/半截拷贝残留会出现这种形态)。
|
|
519
|
+
if (parsed.id !== entry.name) {
|
|
520
|
+
this.options.onWarn?.(engineMsg('core.backup.dirNameIdMismatch', { dir: entry.name, id: parsed.id }));
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
497
523
|
if (parsed.pinned === true)
|
|
498
524
|
continue; // 置顶快照豁免自动清理
|
|
499
|
-
if (referenced.has(
|
|
525
|
+
if (referenced.has(entry.name))
|
|
500
526
|
continue; // Phase 4 F3:recovery 引用豁免
|
|
501
|
-
metas.push({ id:
|
|
527
|
+
metas.push({ id: entry.name, createdAt: parsed.createdAt });
|
|
502
528
|
}
|
|
503
529
|
catch {
|
|
504
530
|
// 损坏 / 非快照目录:跳过(与 listSnapshots 语义一致)
|
|
@@ -528,7 +554,7 @@ export class FileSnapshotStore {
|
|
|
528
554
|
const target = path.join(this.snapshotDir(id), blobPath);
|
|
529
555
|
const dir = this.options.dir;
|
|
530
556
|
if (!target.startsWith(dir))
|
|
531
|
-
throw new Error(
|
|
557
|
+
throw new Error(engineMsg('core.backup.blobPathEscaped', { path: blobPath }));
|
|
532
558
|
return fs.readFile(target);
|
|
533
559
|
}
|
|
534
560
|
/** 标记快照生命周期状态:重写 <dir>/<id>/snapshot.json(保留其余字段)。 */
|
|
@@ -559,55 +585,55 @@ export function computeMetadataHash(snapshot) {
|
|
|
559
585
|
export async function verifySnapshot(snapshotsDir, id) {
|
|
560
586
|
const dir = path.join(snapshotsDir, id);
|
|
561
587
|
if (!dir.startsWith(path.resolve(snapshotsDir) + path.sep)) {
|
|
562
|
-
return { ok: false, reason: '
|
|
588
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotPathEscaped') };
|
|
563
589
|
}
|
|
564
590
|
let raw;
|
|
565
591
|
try {
|
|
566
592
|
raw = await fs.readFile(path.join(dir, 'snapshot.json'), 'utf8');
|
|
567
593
|
}
|
|
568
594
|
catch {
|
|
569
|
-
return { ok: false, reason: '
|
|
595
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotJsonMissing') };
|
|
570
596
|
}
|
|
571
597
|
const snapshot = parseJsonSafe(raw);
|
|
572
598
|
if (snapshot === null || typeof snapshot !== 'object' || typeof snapshot.id !== 'string' || snapshot.id === '') {
|
|
573
|
-
return { ok: false, reason: '
|
|
599
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotJsonInvalid') };
|
|
574
600
|
}
|
|
575
601
|
if (snapshot.id !== id) {
|
|
576
|
-
return { ok: false, reason: '
|
|
602
|
+
return { ok: false, reason: engineMsg('core.backup.snapshotIdDirMismatch') };
|
|
577
603
|
}
|
|
578
604
|
let manifestRaw;
|
|
579
605
|
try {
|
|
580
606
|
manifestRaw = await fs.readFile(path.join(dir, 'manifest.json'), 'utf8');
|
|
581
607
|
}
|
|
582
608
|
catch {
|
|
583
|
-
return { ok: false, reason: '
|
|
609
|
+
return { ok: false, reason: engineMsg('core.backup.manifestMissing') };
|
|
584
610
|
}
|
|
585
611
|
const manifest = parseJsonSafe(manifestRaw);
|
|
586
612
|
if (manifest === null || typeof manifest !== 'object' || manifest.snapshotId !== id) {
|
|
587
|
-
return { ok: false, reason: '
|
|
613
|
+
return { ok: false, reason: engineMsg('core.backup.manifestInvalid') };
|
|
588
614
|
}
|
|
589
615
|
// entryCount 匹配(manifest 声明条目数与实际一致,防计数篡改)
|
|
590
616
|
if (typeof manifest.entryCount !== 'number' || manifest.entryCount !== snapshot.entries.length) {
|
|
591
|
-
return { ok: false, reason: '
|
|
617
|
+
return { ok: false, reason: engineMsg('core.backup.entryCountMismatch') };
|
|
592
618
|
}
|
|
593
619
|
// metadataHash 匹配(破坏性内容)
|
|
594
620
|
if (computeMetadataHash(snapshot) !== manifest.metadataHash) {
|
|
595
|
-
return { ok: false, reason: '
|
|
621
|
+
return { ok: false, reason: engineMsg('core.backup.metadataHashMismatch') };
|
|
596
622
|
}
|
|
597
623
|
// blob hashes 匹配 + 存在
|
|
598
624
|
for (const [blobPath, expectedHash] of Object.entries(manifest.blobHashes)) {
|
|
599
625
|
const target = path.join(dir, blobPath);
|
|
600
626
|
if (!target.startsWith(dir))
|
|
601
|
-
return { ok: false, reason:
|
|
627
|
+
return { ok: false, reason: engineMsg('core.backup.blobPathEscaped', { path: blobPath }) };
|
|
602
628
|
let data;
|
|
603
629
|
try {
|
|
604
630
|
data = await fs.readFile(target);
|
|
605
631
|
}
|
|
606
632
|
catch {
|
|
607
|
-
return { ok: false, reason:
|
|
633
|
+
return { ok: false, reason: engineMsg('core.backup.blobMissing', { path: blobPath }) };
|
|
608
634
|
}
|
|
609
635
|
if (sha256Hex(data) !== expectedHash) {
|
|
610
|
-
return { ok: false, reason:
|
|
636
|
+
return { ok: false, reason: engineMsg('core.backup.blobHashMismatch', { path: blobPath }) };
|
|
611
637
|
}
|
|
612
638
|
}
|
|
613
639
|
// Review C P2:snapshot.json 引用的每个 blob(entries.copiedTo / hostFileBackups.blobPath)都必须被 manifest 覆盖,
|
|
@@ -623,7 +649,7 @@ export async function verifySnapshot(snapshotsDir, id) {
|
|
|
623
649
|
}
|
|
624
650
|
for (const relBlob of referencedBlobs) {
|
|
625
651
|
if (!(relBlob in manifest.blobHashes)) {
|
|
626
|
-
return { ok: false, reason:
|
|
652
|
+
return { ok: false, reason: engineMsg('core.backup.blobNotInManifest', { blob: relBlob }) };
|
|
627
653
|
}
|
|
628
654
|
}
|
|
629
655
|
return { ok: true };
|
package/lib/core/boot-rescue.js
CHANGED
|
@@ -34,6 +34,7 @@ import fs from 'node:fs/promises';
|
|
|
34
34
|
import path from 'node:path';
|
|
35
35
|
import { atomicWriteFile } from '../utils/atomic-write.js';
|
|
36
36
|
import { sha256Hex } from '../utils/hashing.js';
|
|
37
|
+
import { engineMsg } from './messages-core.js';
|
|
37
38
|
/* ---------------------------------------------------------------- 常量 */
|
|
38
39
|
/** 默认备份目录(相对 homeDir 的 posix 路径;backupDir 缺省时使用) */
|
|
39
40
|
const DEFAULT_BACKUP_DIR_REL = 'dsh-config-manager/transactions/rescue-backups';
|
|
@@ -340,13 +341,13 @@ async function readRescueState(backupDir) {
|
|
|
340
341
|
/** 救援模式 profile patch 的头注释(说明本文件被谁接管、bundle 层的实际状态)。 */
|
|
341
342
|
const RESCUE_PATCH_HEADER_LINES = [
|
|
342
343
|
'# dsh-config-manager RESCUE MODE',
|
|
343
|
-
'
|
|
344
|
+
engineMsg('core.bootRescue.patchTakenOver'),
|
|
344
345
|
];
|
|
345
346
|
/** 救援模式 profile patch 的头注释(按 bundle 处理方式给出准确说明,不得夸大能力)。 */
|
|
346
347
|
function rescuePatchHeader(bundlesDisabled) {
|
|
347
348
|
const note = bundlesDisabled
|
|
348
|
-
? '
|
|
349
|
-
: '
|
|
349
|
+
? engineMsg('core.bootRescue.bundlesNarrowed')
|
|
350
|
+
: engineMsg('core.bootRescue.bundlesNote');
|
|
350
351
|
return [...RESCUE_PATCH_HEADER_LINES, note, ''].join('\n');
|
|
351
352
|
}
|
|
352
353
|
/** 单引号 YAML 标量(按 YAML 规则把 `'` 转义为 `''`)。 */
|
package/lib/core/boot-safety.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { RESCUE_KEEP_BUNDLE_PREFIXES, computeSafeBundles } from './boot-rescue.js';
|
|
22
22
|
import { BOOT_CRITICAL_RELS, profileCriticalRels } from './boot-paths.js';
|
|
23
23
|
import { zhMsg } from './messages.js';
|
|
24
|
+
import { engineMsg } from './messages-core.js';
|
|
24
25
|
/** 可解析性检查的目标(.json → JSON.parse;.yaml/.yml → 注入的 parseYaml)。 */
|
|
25
26
|
function parseKind(rel) {
|
|
26
27
|
if (rel.endsWith('.json'))
|
|
@@ -118,7 +119,7 @@ export async function auditBootSafety(deps) {
|
|
|
118
119
|
const drop = new Set(prunable.map((p) => p.name));
|
|
119
120
|
const next = (Array.isArray(bundles) ? bundles : []).filter((b) => !(typeof b === 'string' && drop.has(b)));
|
|
120
121
|
if (!writeBundles(pkg, next))
|
|
121
|
-
throw new Error('
|
|
122
|
+
throw new Error(engineMsg('core.bootSafety.bundlesNotArray'));
|
|
122
123
|
await deps.writeText(pkgRel, JSON.stringify(pkg, null, 2) + '\n');
|
|
123
124
|
written = true;
|
|
124
125
|
}
|