@logictan/dsh-config-manager 0.1.60
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/LICENSE +21 -0
- package/README.md +105 -0
- package/cordis.patch.yml +21 -0
- package/lib/adapters/agent-instructions.d.ts +13 -0
- package/lib/adapters/agent-instructions.js +46 -0
- package/lib/adapters/agent-presets.d.ts +13 -0
- package/lib/adapters/agent-presets.js +14 -0
- package/lib/adapters/credentials.d.ts +37 -0
- package/lib/adapters/credentials.js +122 -0
- package/lib/adapters/file-collection.d.ts +15 -0
- package/lib/adapters/file-collection.js +127 -0
- package/lib/adapters/index.d.ts +57 -0
- package/lib/adapters/index.js +55 -0
- package/lib/adapters/link-report.d.ts +16 -0
- package/lib/adapters/link-report.js +47 -0
- package/lib/adapters/mcp.d.ts +28 -0
- package/lib/adapters/mcp.js +178 -0
- package/lib/adapters/plugin-files.d.ts +18 -0
- package/lib/adapters/plugin-files.js +142 -0
- package/lib/adapters/plugins.d.ts +41 -0
- package/lib/adapters/plugins.js +603 -0
- package/lib/adapters/pnpm-workspace.d.ts +26 -0
- package/lib/adapters/pnpm-workspace.js +147 -0
- package/lib/adapters/prompts.d.ts +30 -0
- package/lib/adapters/prompts.js +231 -0
- package/lib/adapters/providers.d.ts +34 -0
- package/lib/adapters/providers.js +155 -0
- package/lib/adapters/self.d.ts +17 -0
- package/lib/adapters/self.js +71 -0
- package/lib/adapters/sessions.d.ts +14 -0
- package/lib/adapters/sessions.js +15 -0
- package/lib/adapters/settings.d.ts +28 -0
- package/lib/adapters/settings.js +165 -0
- package/lib/adapters/skills.d.ts +13 -0
- package/lib/adapters/skills.js +14 -0
- package/lib/adapters/test-helpers.d.ts +125 -0
- package/lib/adapters/test-helpers.js +236 -0
- package/lib/adapters/ui.d.ts +27 -0
- package/lib/adapters/ui.js +89 -0
- package/lib/adapters/workspaces.d.ts +13 -0
- package/lib/adapters/workspaces.js +106 -0
- package/lib/client.d.ts +1026 -0
- package/lib/client.js +10047 -0
- package/lib/core/analyzer.d.ts +99 -0
- package/lib/core/analyzer.js +934 -0
- package/lib/core/backup.d.ts +138 -0
- package/lib/core/backup.js +513 -0
- package/lib/core/boot-rescue.d.ts +146 -0
- package/lib/core/boot-rescue.js +618 -0
- package/lib/core/cache-cleaner.d.ts +31 -0
- package/lib/core/cache-cleaner.js +95 -0
- package/lib/core/config-lifecycle.d.ts +180 -0
- package/lib/core/config-lifecycle.js +481 -0
- package/lib/core/config-snapshot.d.ts +135 -0
- package/lib/core/config-snapshot.js +424 -0
- package/lib/core/config-state.d.ts +70 -0
- package/lib/core/config-state.js +177 -0
- package/lib/core/consult-source.d.ts +58 -0
- package/lib/core/consult-source.js +260 -0
- package/lib/core/crash-report.d.ts +84 -0
- package/lib/core/crash-report.js +317 -0
- package/lib/core/exporter.d.ts +68 -0
- package/lib/core/exporter.js +375 -0
- package/lib/core/ghost-sweep.d.ts +19 -0
- package/lib/core/ghost-sweep.js +52 -0
- package/lib/core/importer.d.ts +60 -0
- package/lib/core/importer.js +29 -0
- package/lib/core/index.d.ts +14 -0
- package/lib/core/index.js +14 -0
- package/lib/core/journal.d.ts +197 -0
- package/lib/core/journal.js +418 -0
- package/lib/core/local-plugin-host.d.ts +35 -0
- package/lib/core/local-plugin-host.js +120 -0
- package/lib/core/local-plugin-pack.d.ts +135 -0
- package/lib/core/local-plugin-pack.js +278 -0
- package/lib/core/messages.d.ts +310 -0
- package/lib/core/messages.js +621 -0
- package/lib/core/migration-consult.d.ts +117 -0
- package/lib/core/migration-consult.js +363 -0
- package/lib/core/migration-history.d.ts +153 -0
- package/lib/core/migration-history.js +411 -0
- package/lib/core/model-tools.d.ts +50 -0
- package/lib/core/model-tools.js +148 -0
- package/lib/core/msg-types.d.ts +8 -0
- package/lib/core/msg-types.js +6 -0
- package/lib/core/phase3-child-crash.d.ts +1 -0
- package/lib/core/phase3-child-crash.js +87 -0
- package/lib/core/phase3-host.d.ts +138 -0
- package/lib/core/phase3-host.js +359 -0
- package/lib/core/phase3-prod-child.d.ts +1 -0
- package/lib/core/phase3-prod-child.js +35 -0
- package/lib/core/phase4-crash-child.d.ts +1 -0
- package/lib/core/phase4-crash-child.js +103 -0
- package/lib/core/plugin-cli.d.ts +150 -0
- package/lib/core/plugin-cli.js +497 -0
- package/lib/core/reconcile.d.ts +120 -0
- package/lib/core/reconcile.js +389 -0
- package/lib/core/recovery-orchestrator.d.ts +94 -0
- package/lib/core/recovery-orchestrator.js +327 -0
- package/lib/core/restore.d.ts +162 -0
- package/lib/core/restore.js +703 -0
- package/lib/core/rollback.d.ts +12 -0
- package/lib/core/rollback.js +145 -0
- package/lib/core/run-registry.d.ts +85 -0
- package/lib/core/run-registry.js +157 -0
- package/lib/core/startup-barrier.d.ts +53 -0
- package/lib/core/startup-barrier.js +77 -0
- package/lib/core/types.d.ts +515 -0
- package/lib/core/types.js +27 -0
- package/lib/core/undo.d.ts +73 -0
- package/lib/core/undo.js +87 -0
- package/lib/core/validator.d.ts +18 -0
- package/lib/core/validator.js +77 -0
- package/lib/core/verify-recovery.d.ts +21 -0
- package/lib/core/verify-recovery.js +256 -0
- package/lib/core/watcher.d.ts +101 -0
- package/lib/core/watcher.js +219 -0
- package/lib/index.d.ts +238 -0
- package/lib/index.js +2764 -0
- package/lib/migrations/index.d.ts +29 -0
- package/lib/migrations/index.js +56 -0
- package/lib/migrations/v1-to-v2.d.ts +9 -0
- package/lib/migrations/v1-to-v2.js +10 -0
- package/lib/profiles/index.d.ts +4 -0
- package/lib/profiles/index.js +5 -0
- package/lib/profiles/profile-manager.d.ts +106 -0
- package/lib/profiles/profile-manager.js +543 -0
- package/lib/schema/config.d.ts +28 -0
- package/lib/schema/config.js +165 -0
- package/lib/schema/index.d.ts +24 -0
- package/lib/schema/index.js +26 -0
- package/lib/schema/manifest.d.ts +28 -0
- package/lib/schema/manifest.js +109 -0
- package/lib/schema/tombstones.d.ts +46 -0
- package/lib/schema/tombstones.js +64 -0
- package/lib/schema/types.d.ts +232 -0
- package/lib/schema/types.js +8 -0
- package/lib/schema/versions.d.ts +27 -0
- package/lib/schema/versions.js +50 -0
- package/lib/security/encryption.d.ts +85 -0
- package/lib/security/encryption.js +279 -0
- package/lib/security/index.d.ts +16 -0
- package/lib/security/index.js +17 -0
- package/lib/security/integrity.d.ts +30 -0
- package/lib/security/integrity.js +78 -0
- package/lib/security/redaction.d.ts +26 -0
- package/lib/security/redaction.js +95 -0
- package/lib/security/secret-scanner.d.ts +86 -0
- package/lib/security/secret-scanner.js +404 -0
- package/lib/security/vault.d.ts +59 -0
- package/lib/security/vault.js +151 -0
- package/lib/security/zip-security.d.ts +27 -0
- package/lib/security/zip-security.js +154 -0
- package/lib/sync/autosync-config.d.ts +39 -0
- package/lib/sync/autosync-config.js +151 -0
- package/lib/sync/autosync-scheduler.d.ts +139 -0
- package/lib/sync/autosync-scheduler.js +564 -0
- package/lib/sync/fs.d.ts +18 -0
- package/lib/sync/fs.js +49 -0
- package/lib/sync/git/git-transport.d.ts +100 -0
- package/lib/sync/git/git-transport.js +437 -0
- package/lib/sync/github-auth.d.ts +131 -0
- package/lib/sync/github-auth.js +264 -0
- package/lib/sync/layout.d.ts +43 -0
- package/lib/sync/layout.js +203 -0
- package/lib/sync/snapshot-json.d.ts +9 -0
- package/lib/sync/snapshot-json.js +107 -0
- package/lib/sync/sync-config.d.ts +81 -0
- package/lib/sync/sync-config.js +281 -0
- package/lib/sync/sync-engine.d.ts +269 -0
- package/lib/sync/sync-engine.js +591 -0
- package/lib/sync/sync-history.d.ts +42 -0
- package/lib/sync/sync-history.js +78 -0
- package/lib/sync/sync-selection.d.ts +29 -0
- package/lib/sync/sync-selection.js +127 -0
- package/lib/sync/sync-session.d.ts +40 -0
- package/lib/sync/sync-session.js +54 -0
- package/lib/sync/sync-state.d.ts +37 -0
- package/lib/sync/sync-state.js +108 -0
- package/lib/sync/transport.d.ts +75 -0
- package/lib/sync/transport.js +55 -0
- package/lib/sync/ui-prefs.d.ts +34 -0
- package/lib/sync/ui-prefs.js +109 -0
- package/lib/sync/webdav/webdav-transport.d.ts +77 -0
- package/lib/sync/webdav/webdav-transport.js +451 -0
- package/lib/ui/errors.d.ts +19 -0
- package/lib/ui/errors.js +81 -0
- package/lib/ui/export-flow.d.ts +82 -0
- package/lib/ui/export-flow.js +98 -0
- package/lib/ui/i18n.d.ts +289 -0
- package/lib/ui/i18n.js +581 -0
- package/lib/ui/migration-consult-view.d.ts +91 -0
- package/lib/ui/migration-consult-view.js +115 -0
- package/lib/ui/progress.d.ts +38 -0
- package/lib/ui/progress.js +87 -0
- package/lib/ui/report.d.ts +23 -0
- package/lib/ui/report.js +153 -0
- package/lib/ui/test-helpers.d.ts +69 -0
- package/lib/ui/test-helpers.js +154 -0
- package/lib/ui/types.d.ts +257 -0
- package/lib/ui/types.js +21 -0
- package/lib/utils/atomic-write.d.ts +88 -0
- package/lib/utils/atomic-write.js +399 -0
- package/lib/utils/env-lock.d.ts +393 -0
- package/lib/utils/env-lock.js +930 -0
- package/lib/utils/hashing.d.ts +17 -0
- package/lib/utils/hashing.js +41 -0
- package/lib/utils/json.d.ts +24 -0
- package/lib/utils/json.js +76 -0
- package/lib/utils/logger.d.ts +39 -0
- package/lib/utils/logger.js +82 -0
- package/lib/utils/paths.d.ts +42 -0
- package/lib/utils/paths.js +189 -0
- package/lib/utils/proxy.d.ts +69 -0
- package/lib/utils/proxy.js +455 -0
- package/lib/utils/recursive-walk.d.ts +24 -0
- package/lib/utils/recursive-walk.js +134 -0
- package/lib/utils/zip.d.ts +54 -0
- package/lib/utils/zip.js +285 -0
- package/package.json +188 -0
- package/src/adapters/agent-instructions.ts +48 -0
- package/src/adapters/agent-presets.ts +14 -0
- package/src/adapters/credentials.test.ts +90 -0
- package/src/adapters/credentials.ts +143 -0
- package/src/adapters/file-collection.ts +137 -0
- package/src/adapters/files.test.ts +353 -0
- package/src/adapters/index.ts +94 -0
- package/src/adapters/link-report.test.ts +65 -0
- package/src/adapters/link-report.ts +61 -0
- package/src/adapters/mcp.test.ts +96 -0
- package/src/adapters/mcp.ts +185 -0
- package/src/adapters/plugin-files.ts +146 -0
- package/src/adapters/plugins.test.ts +326 -0
- package/src/adapters/plugins.ts +615 -0
- package/src/adapters/pnpm-workspace.test.ts +87 -0
- package/src/adapters/pnpm-workspace.ts +150 -0
- package/src/adapters/prompts.test.ts +110 -0
- package/src/adapters/prompts.ts +230 -0
- package/src/adapters/providers.test.ts +126 -0
- package/src/adapters/providers.ts +173 -0
- package/src/adapters/roundtrip.test.ts +190 -0
- package/src/adapters/self.test.ts +92 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.ts +15 -0
- package/src/adapters/settings.test.ts +100 -0
- package/src/adapters/settings.ts +187 -0
- package/src/adapters/skills.ts +14 -0
- package/src/adapters/test-helpers.ts +248 -0
- package/src/adapters/ui.test.ts +57 -0
- package/src/adapters/ui.ts +108 -0
- package/src/adapters/workspaces.test.ts +106 -0
- package/src/adapters/workspaces.ts +109 -0
- package/src/client/ConfigManagerSection.tsx +90 -0
- package/src/client/api.ts +66 -0
- package/src/client/client-types.ts +18 -0
- package/src/client/common/ErrorBanner.tsx +73 -0
- package/src/client/common/Icon.tsx +129 -0
- package/src/client/common/Modal.tsx +173 -0
- package/src/client/common/ToastViewport.tsx +73 -0
- package/src/client/common/toast-store.test.ts +192 -0
- package/src/client/common/toast-store.ts +181 -0
- package/src/client/common/toast-types.ts +7 -0
- package/src/client/common/ui.tsx +342 -0
- package/src/client/config-manager.module.css +2971 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +56 -0
- package/src/client/lucide-icons.d.ts +38 -0
- package/src/client/run-store.test.ts +223 -0
- package/src/client/run-store.ts +372 -0
- package/src/client/sync/SyncConfirmView.tsx +379 -0
- package/src/client/sync/SyncHistoryView.test.ts +40 -0
- package/src/client/sync/SyncHistoryView.tsx +220 -0
- package/src/client/sync/SyncSettingsView.tsx +1535 -0
- package/src/client/sync/history-model.test.ts +194 -0
- package/src/client/sync/history-model.ts +195 -0
- package/src/client/sync/sync-api.test.ts +585 -0
- package/src/client/sync/sync-api.ts +543 -0
- package/src/client/sync/sync-locales.ts +416 -0
- package/src/client/sync/sync-push-preview.test.ts +52 -0
- package/src/client/sync/sync-view-v2.test.ts +213 -0
- package/src/client/sync/sync-view.test.ts +487 -0
- package/src/client/sync/sync-view.ts +759 -0
- package/src/core/analyzer.ts +1068 -0
- package/src/core/backup.ts +605 -0
- package/src/core/boot-rescue.test.ts +847 -0
- package/src/core/boot-rescue.ts +737 -0
- package/src/core/cache-cleaner.test.ts +169 -0
- package/src/core/cache-cleaner.ts +121 -0
- package/src/core/config-lifecycle.test.ts +727 -0
- package/src/core/config-lifecycle.ts +573 -0
- package/src/core/config-snapshot.test.ts +369 -0
- package/src/core/config-snapshot.ts +522 -0
- package/src/core/config-state.test.ts +381 -0
- package/src/core/config-state.ts +207 -0
- package/src/core/consult-source.test.ts +199 -0
- package/src/core/consult-source.ts +293 -0
- package/src/core/crash-report.test.ts +380 -0
- package/src/core/crash-report.ts +369 -0
- package/src/core/exporter.ts +417 -0
- package/src/core/ghost-sweep.test.ts +94 -0
- package/src/core/ghost-sweep.ts +67 -0
- package/src/core/importer.ts +83 -0
- package/src/core/index.ts +56 -0
- package/src/core/journal.test.ts +257 -0
- package/src/core/journal.ts +518 -0
- package/src/core/local-plugin-host.ts +152 -0
- package/src/core/local-plugin-pack.test.ts +359 -0
- package/src/core/local-plugin-pack.ts +363 -0
- package/src/core/messages.test.ts +54 -0
- package/src/core/messages.ts +664 -0
- package/src/core/migration-consult.test.ts +315 -0
- package/src/core/migration-consult.ts +494 -0
- package/src/core/migration-history.test.ts +319 -0
- package/src/core/migration-history.ts +484 -0
- package/src/core/model-tools.test.ts +190 -0
- package/src/core/model-tools.ts +208 -0
- package/src/core/msg-types.ts +10 -0
- package/src/core/phase3-child-crash.ts +95 -0
- package/src/core/phase3-host.ts +411 -0
- package/src/core/phase3-p1.test.ts +212 -0
- package/src/core/phase3-p2b-fingerprint.test.ts +189 -0
- package/src/core/phase3-prod-child.ts +36 -0
- package/src/core/phase3-production-integration.test.ts +286 -0
- package/src/core/phase4-crash-child.ts +101 -0
- package/src/core/phase4-crash-injection.test.ts +166 -0
- package/src/core/plugin-cli.fs.test.ts +184 -0
- package/src/core/plugin-cli.test.ts +137 -0
- package/src/core/plugin-cli.ts +563 -0
- package/src/core/reconcile.test.ts +392 -0
- package/src/core/reconcile.ts +498 -0
- package/src/core/recovery-orchestrator.test.ts +145 -0
- package/src/core/recovery-orchestrator.ts +366 -0
- package/src/core/restore-manage.test.ts +94 -0
- package/src/core/restore.ts +849 -0
- package/src/core/rollback.ts +151 -0
- package/src/core/run-progress.test.ts +237 -0
- package/src/core/run-registry.test.ts +232 -0
- package/src/core/run-registry.ts +209 -0
- package/src/core/smoke.test.ts +842 -0
- package/src/core/startup-barrier.ts +99 -0
- package/src/core/types.ts +542 -0
- package/src/core/undo.ts +116 -0
- package/src/core/validator.ts +84 -0
- package/src/core/verify-recovery.ts +265 -0
- package/src/core/watcher.test.ts +326 -0
- package/src/core/watcher.ts +263 -0
- package/src/index.facade.test.ts +265 -0
- package/src/index.sync.test.ts +217 -0
- package/src/index.ts +2975 -0
- package/src/migrations/index.ts +71 -0
- package/src/migrations/v1-to-v2.ts +17 -0
- package/src/profiles/index.ts +8 -0
- package/src/profiles/profile-manager.test.ts +282 -0
- package/src/profiles/profile-manager.ts +630 -0
- package/src/schema/config.ts +165 -0
- package/src/schema/index.ts +71 -0
- package/src/schema/manifest-schema.test.ts +519 -0
- package/src/schema/manifest.ts +124 -0
- package/src/schema/tombstones.test.ts +131 -0
- package/src/schema/tombstones.ts +89 -0
- package/src/schema/types.ts +245 -0
- package/src/schema/versions.ts +55 -0
- package/src/security/encryption.ts +335 -0
- package/src/security/index.ts +16 -0
- package/src/security/integrity.ts +102 -0
- package/src/security/redaction.ts +101 -0
- package/src/security/secret-scanner.ts +445 -0
- package/src/security/security.test.ts +1215 -0
- package/src/security/vault.test.ts +164 -0
- package/src/security/vault.ts +198 -0
- package/src/security/zip-security.ts +180 -0
- package/src/sync/autosync-config.test.ts +147 -0
- package/src/sync/autosync-config.ts +179 -0
- package/src/sync/autosync-scheduler.test.ts +490 -0
- package/src/sync/autosync-scheduler.ts +646 -0
- package/src/sync/fs.ts +48 -0
- package/src/sync/git/git-transport.test.ts +673 -0
- package/src/sync/git/git-transport.ts +491 -0
- package/src/sync/github-auth.test.ts +280 -0
- package/src/sync/github-auth.ts +356 -0
- package/src/sync/layout.test.ts +270 -0
- package/src/sync/layout.ts +222 -0
- package/src/sync/snapshot-json.test.ts +119 -0
- package/src/sync/snapshot-json.ts +128 -0
- package/src/sync/sync-config.test.ts +310 -0
- package/src/sync/sync-config.ts +299 -0
- package/src/sync/sync-engine.test.ts +1074 -0
- package/src/sync/sync-engine.ts +754 -0
- package/src/sync/sync-history.test.ts +116 -0
- package/src/sync/sync-history.ts +120 -0
- package/src/sync/sync-selection.test.ts +186 -0
- package/src/sync/sync-selection.ts +143 -0
- package/src/sync/sync-session.test.ts +143 -0
- package/src/sync/sync-session.ts +77 -0
- package/src/sync/sync-state.test.ts +164 -0
- package/src/sync/sync-state.ts +131 -0
- package/src/sync/transport.test.ts +111 -0
- package/src/sync/transport.ts +118 -0
- package/src/sync/ui-prefs.test.ts +152 -0
- package/src/sync/ui-prefs.ts +135 -0
- package/src/sync/webdav/webdav-transport.test.ts +871 -0
- package/src/sync/webdav/webdav-transport.ts +535 -0
- package/src/ui/errors.test.ts +47 -0
- package/src/ui/errors.ts +100 -0
- package/src/ui/export-flow.test.ts +87 -0
- package/src/ui/export-flow.ts +156 -0
- package/src/ui/i18n.ts +590 -0
- package/src/ui/migration-consult-view.test.ts +151 -0
- package/src/ui/migration-consult-view.ts +178 -0
- package/src/ui/progress.test.ts +47 -0
- package/src/ui/progress.ts +102 -0
- package/src/ui/report.test.ts +101 -0
- package/src/ui/report.ts +146 -0
- package/src/ui/test-helpers.ts +184 -0
- package/src/ui/types.ts +325 -0
- package/src/utils/atomic-write.test.ts +312 -0
- package/src/utils/atomic-write.ts +422 -0
- package/src/utils/env-lock.test.ts +999 -0
- package/src/utils/env-lock.ts +1086 -0
- package/src/utils/hashing.ts +53 -0
- package/src/utils/json.ts +77 -0
- package/src/utils/logger.ts +114 -0
- package/src/utils/paths.ts +176 -0
- package/src/utils/proxy.test.ts +465 -0
- package/src/utils/proxy.ts +493 -0
- package/src/utils/recursive-walk.test.ts +148 -0
- package/src/utils/recursive-walk.ts +145 -0
- package/src/utils/zip.ts +332 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIP 解压安全(规范 §19 / 设计 §9 / core utils/zip.ts 的强化层)。
|
|
3
|
+
*
|
|
4
|
+
* 分工(避免与 core 重复):
|
|
5
|
+
* - core `utils/zip.ts` 已实现:条目名 isPathSafe、条目数/压缩体积/解压体积/单条/压缩比限额、
|
|
6
|
+
* CRC32 与尺寸校验、ZipArchive.readEntry 预算、safeExtract 基础解压。
|
|
7
|
+
* - 本模块在其上强化 core 未覆盖的攻击面:
|
|
8
|
+
* 1. **symlink 条目拒绝**:解析中央目录 external attrs 的 Unix mode(S_IFLNK 0xA000)→ 拒绝;
|
|
9
|
+
* 2. **重复条目名拒绝**:同名条目(含目录/文件互撞)→ 拒绝(防读取歧义攻击);
|
|
10
|
+
* 3. **安全解压强化**:解压后逐条 lstat 复查「必须是普通文件」,任何异常 → 中止并**完整清理**目标目录;
|
|
11
|
+
* 4. **可执行文件告警**:扩展名黑名单 → warnings(本插件从不执行 ZIP 内文件)。
|
|
12
|
+
*
|
|
13
|
+
* 与 core 注入点对齐:`parseZipHardened` / `createHardenedZipParser` 满足
|
|
14
|
+
* `ImporterOptions.parseZipOverride` 签名 `(buf, limits?) => ZipArchive`。
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { isPathSafe, isSameOrChild } from '../utils/paths.js';
|
|
19
|
+
import { DEFAULT_ZIP_SAFETY_LIMITS, ZipArchive, ZipSafetyError, } from '../utils/zip.js';
|
|
20
|
+
export { isPathSafe } from '../utils/paths.js';
|
|
21
|
+
/* ---------------- 常量 ---------------- */
|
|
22
|
+
const EOCD_SIG = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
|
|
23
|
+
const CENTRAL_SIG = 0x02014b50;
|
|
24
|
+
/** Unix 文件类型掩码(external attrs 高 16 位) */
|
|
25
|
+
const S_IFMT = 0xf000;
|
|
26
|
+
/** 符号链接 */
|
|
27
|
+
const S_IFLNK = 0xa000;
|
|
28
|
+
/** 可执行文件扩展名黑名单(§19.6:只警告,本插件不执行任何脚本) */
|
|
29
|
+
export const EXECUTABLE_EXTENSIONS = new Set([
|
|
30
|
+
'.exe', '.bat', '.cmd', '.sh', '.ps1', '.dll', '.so', '.dylib', '.bin', '.jar',
|
|
31
|
+
]);
|
|
32
|
+
/** 条目名是否疑似可执行文件(按扩展名) */
|
|
33
|
+
export function isExecutableName(name) {
|
|
34
|
+
const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
|
|
35
|
+
return EXECUTABLE_EXTENSIONS.has(ext);
|
|
36
|
+
}
|
|
37
|
+
/* ---------------- 强化解析(parseZipOverride 注入点) ---------------- */
|
|
38
|
+
/**
|
|
39
|
+
* 强化版 ZIP 解析:在 core 全部安全校验(条目名/条目数/压缩体积)之上,
|
|
40
|
+
* 增加 symlink 拒绝与重复条目名拒绝。返回 core `ZipArchive`(解压校验完全复用)。
|
|
41
|
+
*/
|
|
42
|
+
export function parseZipHardened(buf, limits = {}) {
|
|
43
|
+
const merged = { ...DEFAULT_ZIP_SAFETY_LIMITS, ...limits };
|
|
44
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
45
|
+
if (b.length < 22)
|
|
46
|
+
throw new ZipSafetyError('不是合法的 ZIP 文件(体积过小)');
|
|
47
|
+
const eocdIdx = b.lastIndexOf(EOCD_SIG);
|
|
48
|
+
if (eocdIdx < 0 || eocdIdx + 22 > b.length) {
|
|
49
|
+
throw new ZipSafetyError('不是合法的 ZIP 文件(缺少中央目录结束记录)');
|
|
50
|
+
}
|
|
51
|
+
const totalEntries = b.readUInt16LE(eocdIdx + 10);
|
|
52
|
+
const cdSize = b.readUInt32LE(eocdIdx + 12);
|
|
53
|
+
const cdOffset = b.readUInt32LE(eocdIdx + 16);
|
|
54
|
+
if (cdOffset + cdSize > b.length)
|
|
55
|
+
throw new ZipSafetyError('中央目录越界(ZIP 损坏)');
|
|
56
|
+
if (totalEntries > merged.maxEntries) {
|
|
57
|
+
throw new ZipSafetyError(`ZIP 条目数 ${totalEntries} 超过上限 ${merged.maxEntries}`);
|
|
58
|
+
}
|
|
59
|
+
const metas = [];
|
|
60
|
+
const seenNames = new Set();
|
|
61
|
+
let pos = cdOffset;
|
|
62
|
+
let compressedTotal = 0;
|
|
63
|
+
for (let i = 0; i < totalEntries; i++) {
|
|
64
|
+
if (pos + 46 > cdOffset + cdSize)
|
|
65
|
+
throw new ZipSafetyError('中央目录条目越界(ZIP 损坏)');
|
|
66
|
+
if (b.readUInt32LE(pos) !== CENTRAL_SIG)
|
|
67
|
+
throw new ZipSafetyError('中央目录签名损坏');
|
|
68
|
+
const method = b.readUInt16LE(pos + 10);
|
|
69
|
+
const crc = b.readUInt32LE(pos + 16);
|
|
70
|
+
const compSize = b.readUInt32LE(pos + 20);
|
|
71
|
+
const uncompSize = b.readUInt32LE(pos + 24);
|
|
72
|
+
const nameLen = b.readUInt16LE(pos + 28);
|
|
73
|
+
const extraLen = b.readUInt16LE(pos + 30);
|
|
74
|
+
const commentLen = b.readUInt16LE(pos + 32);
|
|
75
|
+
const externalAttrs = b.readUInt32LE(pos + 38);
|
|
76
|
+
const localOffset = b.readUInt32LE(pos + 42);
|
|
77
|
+
const name = b.subarray(pos + 46, pos + 46 + nameLen).toString('utf8');
|
|
78
|
+
// —— 强化检查 ——
|
|
79
|
+
if (!isPathSafe(name))
|
|
80
|
+
throw new ZipSafetyError(`ZIP 条目名不安全: ${name}`);
|
|
81
|
+
if (seenNames.has(name))
|
|
82
|
+
throw new ZipSafetyError(`ZIP 条目名重复: ${name}`);
|
|
83
|
+
seenNames.add(name);
|
|
84
|
+
const mode = (externalAttrs >>> 16) & S_IFMT;
|
|
85
|
+
if (mode === S_IFLNK) {
|
|
86
|
+
throw new ZipSafetyError(`ZIP 含符号链接条目,已拒绝: ${name}`);
|
|
87
|
+
}
|
|
88
|
+
if (localOffset + 30 > b.length) {
|
|
89
|
+
throw new ZipSafetyError(`条目 "${name}" 本地文件头越界`);
|
|
90
|
+
}
|
|
91
|
+
compressedTotal += compSize;
|
|
92
|
+
if (compressedTotal > merged.maxCompressedBytes) {
|
|
93
|
+
throw new ZipSafetyError('ZIP 压缩数据总量超过上限');
|
|
94
|
+
}
|
|
95
|
+
metas.push({
|
|
96
|
+
name,
|
|
97
|
+
method,
|
|
98
|
+
compressedSize: compSize,
|
|
99
|
+
uncompressedSize: uncompSize,
|
|
100
|
+
crc32: crc,
|
|
101
|
+
isDirectory: name.endsWith('/'),
|
|
102
|
+
localOffset,
|
|
103
|
+
});
|
|
104
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
105
|
+
}
|
|
106
|
+
return new ZipArchive(b, metas, merged);
|
|
107
|
+
}
|
|
108
|
+
/** 工厂:带默认限额的强化解析器(对齐 ImporterOptions.parseZipOverride 签名) */
|
|
109
|
+
export function createHardenedZipParser(defaultLimits) {
|
|
110
|
+
return (buf, limits) => parseZipHardened(buf, { ...defaultLimits, ...limits });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 安全解压到受控目录(对齐 core safeExtract 语义 + 强化):
|
|
114
|
+
* - 条目名/限额/symlink/重复名检查(parseZipHardened);
|
|
115
|
+
* - 逐条 CRC32/尺寸/预算校验(ZipArchive.readEntry);
|
|
116
|
+
* - 解压后 lstat 复查「全部产物必须是普通文件」(防符号链接/非常规写入);
|
|
117
|
+
* - 任何异常 → 中止并**完整清理** destDir 后抛出(不残留部分落盘)。
|
|
118
|
+
*/
|
|
119
|
+
export async function safeExtractHardened(zipPath, destDir, limits = {}) {
|
|
120
|
+
const data = await fs.readFile(zipPath);
|
|
121
|
+
const archive = parseZipHardened(data, limits);
|
|
122
|
+
const extracted = [];
|
|
123
|
+
const warnings = [];
|
|
124
|
+
try {
|
|
125
|
+
for (const meta of archive.entries()) {
|
|
126
|
+
if (meta.isDirectory)
|
|
127
|
+
continue;
|
|
128
|
+
const target = path.join(destDir, ...meta.name.split('/'));
|
|
129
|
+
if (!isSameOrChild(target, destDir)) {
|
|
130
|
+
throw new ZipSafetyError(`解压目标越界: ${meta.name}`);
|
|
131
|
+
}
|
|
132
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
133
|
+
await fs.writeFile(target, archive.readEntry(meta.name));
|
|
134
|
+
extracted.push(meta.name);
|
|
135
|
+
if (isExecutableName(meta.name)) {
|
|
136
|
+
warnings.push(`条目 "${meta.name}" 是潜在可执行文件,本插件不会执行它`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// 解压后复查:全部产物必须是普通文件(防 symlink 逃逸 / 非常规写入)
|
|
140
|
+
for (const rel of extracted) {
|
|
141
|
+
const p = path.join(destDir, ...rel.split('/'));
|
|
142
|
+
const st = await fs.lstat(p);
|
|
143
|
+
if (!st.isFile()) {
|
|
144
|
+
throw new ZipSafetyError(`解压产物不是普通文件: ${rel}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { files: extracted, warnings };
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=zip-security.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SyncTransportType } from './sync-config.ts';
|
|
2
|
+
export declare const AUTOSYNC_CONFIG_FILE = "sync-autosync.json";
|
|
3
|
+
export declare const AUTOSYNC_CONFIG_SCHEMA_VERSION = 2;
|
|
4
|
+
/** 统一间隔类型 */
|
|
5
|
+
export type AutosyncInterval = '5m' | '15m' | '30m' | '60m' | '6h' | '12h' | '24h';
|
|
6
|
+
/** 缺省间隔 */
|
|
7
|
+
export declare const DEFAULT_AUTOSYNC_INTERVAL: AutosyncInterval;
|
|
8
|
+
/** 缺省启动触发最小间隔阈值(5 分钟,防频繁重启反复同步) */
|
|
9
|
+
export declare const DEFAULT_STARTUP_MIN_INTERVAL_MS: number;
|
|
10
|
+
/** 最近一次自动同步执行状态 */
|
|
11
|
+
export type AutosyncRunStatus = 'success' | 'skipped' | 'failed' | 'partial';
|
|
12
|
+
/** 自动同步配置(持久化面;单通道) */
|
|
13
|
+
export interface AutosyncConfig {
|
|
14
|
+
/** 总开关(同时控制上传 + 下载) */
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
/** 统一间隔 */
|
|
17
|
+
interval: AutosyncInterval;
|
|
18
|
+
/** 重启触发的「自动下载合并」最小间隔阈值(ms) */
|
|
19
|
+
startupMinIntervalMs: number;
|
|
20
|
+
/** 连续失败计数(用于通知判定) */
|
|
21
|
+
consecutiveFailures: number;
|
|
22
|
+
/** 最近一次自动同步执行时间(ISO-8601 UTC);''/undefined = 从未执行 */
|
|
23
|
+
lastRunAt?: string;
|
|
24
|
+
/** 最近一次自动同步执行状态 */
|
|
25
|
+
lastRunStatus?: AutosyncRunStatus;
|
|
26
|
+
lastRunMessage?: string;
|
|
27
|
+
/** 最近一次自动同步触发的同步历史条目 id */
|
|
28
|
+
lastRunHistoryId?: string;
|
|
29
|
+
}
|
|
30
|
+
/** 全通道配置视图(v2 文件直接读取;status 路由一次返回两个通道的状态)。 */
|
|
31
|
+
export type AutosyncConfigByChannel = Record<SyncTransportType, AutosyncConfig>;
|
|
32
|
+
/** 缺省配置(首次无文件 / 损坏 / 不支持 schema 时回退) */
|
|
33
|
+
export declare function defaultAutosyncConfig(): AutosyncConfig;
|
|
34
|
+
/** 读取全部通道的自动同步配置(v2 文件;v1 迁移为 git;webdav 缺省)。 */
|
|
35
|
+
export declare function readAllAutosyncConfigs(dir: string): Promise<AutosyncConfigByChannel>;
|
|
36
|
+
/** 读取指定通道的自动同步配置;文件不存在 / 损坏 / 不支持 schema → 缺省值(不抛错)。 */
|
|
37
|
+
export declare function readAutosyncConfig(dir: string, channel: SyncTransportType): Promise<AutosyncConfig>;
|
|
38
|
+
/** 写入指定通道的自动同步配置(原子写:临时文件 + rename;保留另一通道;自动创建目录)。 */
|
|
39
|
+
export declare function writeAutosyncConfig(dir: string, channel: SyncTransportType, cfg: AutosyncConfig): Promise<void>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* m-autosync:自动同步配置持久化(sync-autosync.json)。
|
|
3
|
+
*
|
|
4
|
+
* 与 sync-config.json 并列独立文件:语义清楚、schema 演进独立。
|
|
5
|
+
* schemaVersion:2 —— 按同步通道拆分(git / webdav 各自独立的自动同步配置与运行状态):
|
|
6
|
+
* ```
|
|
7
|
+
* { "schemaVersion": 2,
|
|
8
|
+
* "channels": {
|
|
9
|
+
* "git": { enabled, interval, startupMinIntervalMs, consecutiveFailures, lastRunAt, ... },
|
|
10
|
+
* "webdav": { ... } } }
|
|
11
|
+
* ```
|
|
12
|
+
* v1(顶层单通道)→ 读取时归一为 v2 的 git 通道(webdav 回退缺省)。
|
|
13
|
+
*
|
|
14
|
+
* 原子写(临时文件 + rename),损坏/不支持 schema 回退缺省(enabled=false,
|
|
15
|
+
* interval='30m', startupMinIntervalMs=5*60*1000)。
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs/promises';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import crypto from 'node:crypto';
|
|
20
|
+
import { parseJsonSafe, stringifyJsonSafe } from '../utils/json.js';
|
|
21
|
+
import { atomicWriteFile } from '../utils/atomic-write.js';
|
|
22
|
+
export const AUTOSYNC_CONFIG_FILE = 'sync-autosync.json';
|
|
23
|
+
export const AUTOSYNC_CONFIG_SCHEMA_VERSION = 2;
|
|
24
|
+
/** 缺省间隔 */
|
|
25
|
+
export const DEFAULT_AUTOSYNC_INTERVAL = '30m';
|
|
26
|
+
/** 缺省启动触发最小间隔阈值(5 分钟,防频繁重启反复同步) */
|
|
27
|
+
export const DEFAULT_STARTUP_MIN_INTERVAL_MS = 5 * 60 * 1000;
|
|
28
|
+
/** 缺省配置(首次无文件 / 损坏 / 不支持 schema 时回退) */
|
|
29
|
+
export function defaultAutosyncConfig() {
|
|
30
|
+
return {
|
|
31
|
+
enabled: false,
|
|
32
|
+
interval: DEFAULT_AUTOSYNC_INTERVAL,
|
|
33
|
+
startupMinIntervalMs: DEFAULT_STARTUP_MIN_INTERVAL_MS,
|
|
34
|
+
consecutiveFailures: 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** 从 v1 顶层字段(缺 schemaVersion 或 schemaVersion=1)解析单通道配置;非法字段回退缺省。 */
|
|
38
|
+
function parseV1Channel(obj) {
|
|
39
|
+
const cfg = defaultAutosyncConfig();
|
|
40
|
+
if (typeof obj['enabled'] === 'boolean')
|
|
41
|
+
cfg.enabled = obj['enabled'];
|
|
42
|
+
if (isAutosyncInterval(obj['interval']))
|
|
43
|
+
cfg.interval = obj['interval'];
|
|
44
|
+
if (typeof obj['startupMinIntervalMs'] === 'number' && Number.isFinite(obj['startupMinIntervalMs']) && obj['startupMinIntervalMs'] > 0) {
|
|
45
|
+
cfg.startupMinIntervalMs = obj['startupMinIntervalMs'];
|
|
46
|
+
}
|
|
47
|
+
if (typeof obj['consecutiveFailures'] === 'number' && Number.isFinite(obj['consecutiveFailures']) && obj['consecutiveFailures'] >= 0) {
|
|
48
|
+
cfg.consecutiveFailures = obj['consecutiveFailures'];
|
|
49
|
+
}
|
|
50
|
+
if (typeof obj['lastRunAt'] === 'string' && obj['lastRunAt'] !== '')
|
|
51
|
+
cfg.lastRunAt = obj['lastRunAt'];
|
|
52
|
+
if (typeof obj['lastRunStatus'] === 'string' && (obj['lastRunStatus'] === 'success' || obj['lastRunStatus'] === 'skipped' || obj['lastRunStatus'] === 'failed' || obj['lastRunStatus'] === 'partial')) {
|
|
53
|
+
cfg.lastRunStatus = obj['lastRunStatus'];
|
|
54
|
+
}
|
|
55
|
+
if (typeof obj['lastRunMessage'] === 'string')
|
|
56
|
+
cfg.lastRunMessage = obj['lastRunMessage'];
|
|
57
|
+
if (typeof obj['lastRunHistoryId'] === 'string')
|
|
58
|
+
cfg.lastRunHistoryId = obj['lastRunHistoryId'];
|
|
59
|
+
return cfg;
|
|
60
|
+
}
|
|
61
|
+
/** 从 v2 channels 命名空间解析单通道配置;缺失/非法 → 缺省。 */
|
|
62
|
+
function parseV2Channel(ns) {
|
|
63
|
+
if (ns === null || typeof ns !== 'object' || Array.isArray(ns))
|
|
64
|
+
return defaultAutosyncConfig();
|
|
65
|
+
return parseV1Channel(ns);
|
|
66
|
+
}
|
|
67
|
+
/** 读取全部通道的自动同步配置(v2 文件;v1 迁移为 git;webdav 缺省)。 */
|
|
68
|
+
export async function readAllAutosyncConfigs(dir) {
|
|
69
|
+
const file = path.join(dir, AUTOSYNC_CONFIG_FILE);
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = await fs.readFile(file, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { git: defaultAutosyncConfig(), webdav: defaultAutosyncConfig() };
|
|
76
|
+
}
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = parseJsonSafe(raw);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { git: defaultAutosyncConfig(), webdav: defaultAutosyncConfig() };
|
|
83
|
+
}
|
|
84
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
85
|
+
return { git: defaultAutosyncConfig(), webdav: defaultAutosyncConfig() };
|
|
86
|
+
}
|
|
87
|
+
const obj = parsed;
|
|
88
|
+
// schemaVersion:缺省视为 v1;非缺省但 != 2 → 回退缺省
|
|
89
|
+
const ver = typeof obj['schemaVersion'] === 'number' ? obj['schemaVersion'] : 1;
|
|
90
|
+
if (ver !== 1 && ver !== AUTOSYNC_CONFIG_SCHEMA_VERSION) {
|
|
91
|
+
return { git: defaultAutosyncConfig(), webdav: defaultAutosyncConfig() };
|
|
92
|
+
}
|
|
93
|
+
if (ver === 1) {
|
|
94
|
+
// v1 迁移:顶层字段 → git 通道(webdav 缺省;首次按 v2 写回时持久化)
|
|
95
|
+
return { git: parseV1Channel(obj), webdav: defaultAutosyncConfig() };
|
|
96
|
+
}
|
|
97
|
+
const channels = obj['channels'];
|
|
98
|
+
const ch = channels !== null && typeof channels === 'object' && !Array.isArray(channels)
|
|
99
|
+
? channels
|
|
100
|
+
: {};
|
|
101
|
+
return {
|
|
102
|
+
git: parseV2Channel(ch['git']),
|
|
103
|
+
webdav: parseV2Channel(ch['webdav']),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** 读取指定通道的自动同步配置;文件不存在 / 损坏 / 不支持 schema → 缺省值(不抛错)。 */
|
|
107
|
+
export async function readAutosyncConfig(dir, channel) {
|
|
108
|
+
const all = await readAllAutosyncConfigs(dir);
|
|
109
|
+
return all[channel];
|
|
110
|
+
}
|
|
111
|
+
/** 写入指定通道的自动同步配置(原子写:临时文件 + rename;保留另一通道;自动创建目录)。 */
|
|
112
|
+
export async function writeAutosyncConfig(dir, channel, cfg) {
|
|
113
|
+
await fs.mkdir(dir, { recursive: true });
|
|
114
|
+
const existing = await readAllAutosyncConfigs(dir);
|
|
115
|
+
const channels = {
|
|
116
|
+
git: channel === 'git' ? cfg : existing.git,
|
|
117
|
+
webdav: channel === 'webdav' ? cfg : existing.webdav,
|
|
118
|
+
};
|
|
119
|
+
const payload = {
|
|
120
|
+
schemaVersion: AUTOSYNC_CONFIG_SCHEMA_VERSION,
|
|
121
|
+
channels: {
|
|
122
|
+
git: toChannelPayload(channels.git),
|
|
123
|
+
webdav: toChannelPayload(channels.webdav),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const target = path.join(dir, AUTOSYNC_CONFIG_FILE);
|
|
127
|
+
const data = stringifyJsonSafe(payload, { space: 2 });
|
|
128
|
+
await atomicWriteFile(target, data, { mode: 0o600 });
|
|
129
|
+
}
|
|
130
|
+
/** AutosyncConfig → 文件载荷(可选运行字段仅在非空时写入,保持文件干净)。 */
|
|
131
|
+
function toChannelPayload(cfg) {
|
|
132
|
+
const payload = {
|
|
133
|
+
enabled: cfg.enabled,
|
|
134
|
+
interval: cfg.interval,
|
|
135
|
+
startupMinIntervalMs: cfg.startupMinIntervalMs,
|
|
136
|
+
consecutiveFailures: cfg.consecutiveFailures,
|
|
137
|
+
};
|
|
138
|
+
if (cfg.lastRunAt !== undefined && cfg.lastRunAt !== '')
|
|
139
|
+
payload['lastRunAt'] = cfg.lastRunAt;
|
|
140
|
+
if (cfg.lastRunStatus !== undefined)
|
|
141
|
+
payload['lastRunStatus'] = cfg.lastRunStatus;
|
|
142
|
+
if (cfg.lastRunMessage !== undefined && cfg.lastRunMessage !== '')
|
|
143
|
+
payload['lastRunMessage'] = cfg.lastRunMessage;
|
|
144
|
+
if (cfg.lastRunHistoryId !== undefined && cfg.lastRunHistoryId !== '')
|
|
145
|
+
payload['lastRunHistoryId'] = cfg.lastRunHistoryId;
|
|
146
|
+
return payload;
|
|
147
|
+
}
|
|
148
|
+
function isAutosyncInterval(v) {
|
|
149
|
+
return v === '5m' || v === '15m' || v === '30m' || v === '60m' || v === '6h' || v === '12h' || v === '24h';
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=autosync-config.js.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Logger } from '../utils/logger.ts';
|
|
2
|
+
import type { MsgFunc } from '../core/messages.ts';
|
|
3
|
+
import type { SectionId } from '../schema/types.ts';
|
|
4
|
+
import type { RunRegistry } from '../core/run-registry.ts';
|
|
5
|
+
import type { SyncEngine } from './sync-engine.ts';
|
|
6
|
+
import type { MutationLockPort, MutationLockContext } from '../utils/env-lock.ts';
|
|
7
|
+
import type { AutosyncConfig, AutosyncInterval } from './autosync-config.ts';
|
|
8
|
+
import type { SyncConfig, SyncTransportType } from './sync-config.ts';
|
|
9
|
+
import { readSyncHistory } from './sync-history.ts';
|
|
10
|
+
import type { AutosyncHistoryEntry } from './sync-history.ts';
|
|
11
|
+
/** 间隔 → ms 换算(§4.3) */
|
|
12
|
+
export declare function intervalToMs(interval: AutosyncInterval): number;
|
|
13
|
+
/**
|
|
14
|
+
* 启动触发下载合并且满足阈值(now - lastRunAt >= startupMinIntervalMs)?
|
|
15
|
+
* lastRunAt 为 undefined(从未运行)→ true。
|
|
16
|
+
*/
|
|
17
|
+
export declare function shouldTriggerStartupRun(lastRunAt: string | undefined, startupMinIntervalMs: number, nowMs?: number): boolean;
|
|
18
|
+
/** runOnce 执行结果 */
|
|
19
|
+
export interface AutosyncRunResult {
|
|
20
|
+
status: 'success' | 'skipped' | 'failed' | 'partial';
|
|
21
|
+
direction: 'pull' | 'push' | 'both' | 'none';
|
|
22
|
+
skipReason?: string;
|
|
23
|
+
conflictedSections?: SectionId[];
|
|
24
|
+
appliedSections?: SectionId[];
|
|
25
|
+
pushedSnapshotId?: string;
|
|
26
|
+
pulledSnapshotId?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
historyId: string;
|
|
29
|
+
consecutiveFailures: number;
|
|
30
|
+
}
|
|
31
|
+
export interface AutoSyncSchedulerOptions {
|
|
32
|
+
syncDir: string;
|
|
33
|
+
host: {
|
|
34
|
+
log: Logger;
|
|
35
|
+
};
|
|
36
|
+
/** 注入 SyncEngine 构造器:按 SyncConfig 构造对应通道的引擎(git/webdav)。 */
|
|
37
|
+
makeSyncEngine: (cfg: SyncConfig) => SyncEngine;
|
|
38
|
+
/** 消息翻译器 */
|
|
39
|
+
msg: MsgFunc;
|
|
40
|
+
runs: RunRegistry;
|
|
41
|
+
/** 时间源(测试注入) */
|
|
42
|
+
now?: () => Date;
|
|
43
|
+
/** 注入 autosync-config 读写(按通道;测试可内存实现) */
|
|
44
|
+
readConfig?: (channel: SyncTransportType) => Promise<AutosyncConfig>;
|
|
45
|
+
writeConfig?: (channel: SyncTransportType, cfg: AutosyncConfig) => Promise<void>;
|
|
46
|
+
/** 注入 sync-config 读取(按通道) */
|
|
47
|
+
readSyncConfigFn?: (channel: SyncTransportType) => Promise<SyncConfig | null>;
|
|
48
|
+
/** 注入 sync-history 读写 */
|
|
49
|
+
readHistoryFn?: () => Promise<Awaited<ReturnType<typeof readSyncHistory>>>;
|
|
50
|
+
appendHistoryFn?: (entry: AutosyncHistoryEntry) => Promise<void>;
|
|
51
|
+
/** 注入计时器(测试用;缺省 setInterval/clearInterval) */
|
|
52
|
+
setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
53
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
54
|
+
/**
|
|
55
|
+
* 远端新快照检测(§3.2「下载=检测到远端新快照才拉取」)。
|
|
56
|
+
* 缺省:若 engine 实现了 hasNewRemoteSnapshot() 则调用;否则视为 true(保持旧行为=每次拉取)。
|
|
57
|
+
*/
|
|
58
|
+
detectRemoteNew?: (engine: SyncEngine) => Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* 本地配置变化检测(§3.1「上传=本地改动才推」)。
|
|
61
|
+
* 缺省:若 engine 实现了 hasLocalChanges() 则调用;否则视为 true(保持旧行为=每次都推)。
|
|
62
|
+
*/
|
|
63
|
+
detectLocalChange?: (engine: SyncEngine) => Promise<boolean>;
|
|
64
|
+
/** Phase 2 跨进程环境锁端口(可选注入;缺省无锁环境——自动同步 apply(写本地配置)属 GLOBAL mutation) */
|
|
65
|
+
mutationLock?: MutationLockPort;
|
|
66
|
+
/** Phase 3 SAFE MODE:注入同步谓词(被挡 → autosync skip),供 withMutationLock isBlocked 用。 */
|
|
67
|
+
isBlocked?: () => boolean;
|
|
68
|
+
/** Phase 3 recovery(可选注入):apply/push 包 intent journal(P0-A 接线)。 */
|
|
69
|
+
phase3Recovery?: {
|
|
70
|
+
runExternalIntent(opts: {
|
|
71
|
+
operationType: string;
|
|
72
|
+
lockCtx: MutationLockContext;
|
|
73
|
+
intent: {
|
|
74
|
+
adapter: string;
|
|
75
|
+
ref: string;
|
|
76
|
+
kind: string;
|
|
77
|
+
};
|
|
78
|
+
fn: () => Promise<unknown>;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
operationId: string;
|
|
81
|
+
result: unknown;
|
|
82
|
+
}>;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export declare class AutoSyncScheduler {
|
|
86
|
+
private readonly syncDir;
|
|
87
|
+
private readonly host;
|
|
88
|
+
private readonly makeSyncEngine;
|
|
89
|
+
private readonly msg;
|
|
90
|
+
private readonly runs;
|
|
91
|
+
private readonly now;
|
|
92
|
+
private readonly readConfig;
|
|
93
|
+
private readonly writeConfig;
|
|
94
|
+
private readonly readSyncConfigFn;
|
|
95
|
+
private readonly readHistoryFn;
|
|
96
|
+
private readonly appendHistoryFn;
|
|
97
|
+
private readonly setTimer;
|
|
98
|
+
private readonly clearTimer;
|
|
99
|
+
private readonly detectRemoteNew;
|
|
100
|
+
private readonly detectLocalChange;
|
|
101
|
+
private readonly mutationLock;
|
|
102
|
+
private readonly isBlocked;
|
|
103
|
+
private readonly phase3Recovery;
|
|
104
|
+
/** 本次 runOnce 由 withMutationLock 取得的下游锁上下文(供 apply/push journal-wrap),非线程级。 */
|
|
105
|
+
private lockCtxForJournal;
|
|
106
|
+
/** 每通道一个定时器(git/webdav 各自 enabled 时独立排期)。 */
|
|
107
|
+
private readonly timers;
|
|
108
|
+
private stopped;
|
|
109
|
+
private running;
|
|
110
|
+
constructor(opts: AutoSyncSchedulerOptions);
|
|
111
|
+
/** 追加一条自动同步历史(自动带上触发通道;0x 安全:绝不覆盖调用方显式 transport)。 */
|
|
112
|
+
private appendHistory;
|
|
113
|
+
/** 启动:读两个通道配置 → enabled 通道各自排定时器 → 对每个 enabled 通道执行一次启动触发。 */
|
|
114
|
+
start(): void;
|
|
115
|
+
/** 停止:清全部定时器、标记不再调度;正在执行的任务允许自然结束。 */
|
|
116
|
+
stop(): void;
|
|
117
|
+
/** 重新加载配置(路由 POST /sync/autosync 后调用;重排所有通道定时器)。 */
|
|
118
|
+
reload(): Promise<void>;
|
|
119
|
+
private refreshTimers;
|
|
120
|
+
/** 启动触发下载合并(每个 enabled 通道;受各自 startupMinIntervalMs 阈值约束)。 */
|
|
121
|
+
private startupRuns;
|
|
122
|
+
/**
|
|
123
|
+
* 执行一次指定通道的自动同步(§6.1)。
|
|
124
|
+
* @param channel - 同步通道(git / webdav;各自独立的配置与运行状态)
|
|
125
|
+
* @param opts.startup - true 表示启动触发变体(只做 Phase A+B,不做 Phase C push)
|
|
126
|
+
*/
|
|
127
|
+
runOnce(channel: SyncTransportType, opts?: {
|
|
128
|
+
startup?: boolean;
|
|
129
|
+
}): Promise<AutosyncRunResult>;
|
|
130
|
+
/** 收尾:写该通道 autosync-config(lastRunAt, lastRunStatus, consecutiveFailures, lastRunHistoryId)。 */
|
|
131
|
+
private writeFinalConfig;
|
|
132
|
+
/** 连续失败 ≥ 3 → 通知(host.log.warn)。 */
|
|
133
|
+
private maybeNotify;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 同步通道是否「已配置」:git 看 git.repoUrl 非空;webdav 看 webdav.url 非空;
|
|
137
|
+
* 未配置(null / 缺字段)→ false。作为 autosync 未配置跳过的判定依据。
|
|
138
|
+
*/
|
|
139
|
+
export declare function syncIsConfigured(cfg: SyncConfig | null): boolean;
|