@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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* m-sync-json:SyncSnapshot 的 JSON 安全序列化 / 反序列化。
|
|
3
|
+
*
|
|
4
|
+
* 问题根因:文件类分区(FilesSection.files[].data)在内存中是 Uint8Array,
|
|
5
|
+
* 而 `JSON.stringify` 对 TypedArray 会序列化成数字索引对象({"0":0,"1":1,…}),
|
|
6
|
+
* `JSON.parse` 读回的是普通对象而非 Uint8Array。凡是「整份快照走 JSON」的通道都会
|
|
7
|
+
* 破坏文件字节 —— WebDAV(<id>.json 单文件快照)与加密载荷(整体序列化后加密)皆如此,
|
|
8
|
+
* 后续 `Buffer.from(对象)` 直接抛
|
|
9
|
+
* 「The "data" argument must be of type string or an instance of Buffer…」。
|
|
10
|
+
*
|
|
11
|
+
* 修复:序列化前把 Uint8Array 转 { "$bin": <base64> }(二进制安全且紧凑),
|
|
12
|
+
* 反序列化时还原成 Uint8Array。转换只在 FilesSection.files[].data 精确位置发生,
|
|
13
|
+
* 不影响普通 JSON 分区;加密快照(EncryptedSections)本身只含字符串,原样透传。
|
|
14
|
+
*/
|
|
15
|
+
import type { FilesSection, SectionData, SectionId } from '../schema/types.ts';
|
|
16
|
+
import { parseJsonSafe, stringifyJsonSafe } from '../utils/json.ts';
|
|
17
|
+
import type { EncryptedSections, SyncSnapshot } from './transport.ts';
|
|
18
|
+
import { isEncryptedSections } from './transport.ts';
|
|
19
|
+
|
|
20
|
+
/** 二进制数据在 JSON 中的标记对象键(base64 载荷)。 */
|
|
21
|
+
const BIN_KEY = '$bin';
|
|
22
|
+
|
|
23
|
+
/** duck-typing:文件类分区(version===1 + files 数组) */
|
|
24
|
+
function isFilesSectionLike(v: unknown): v is FilesSection {
|
|
25
|
+
if (v === null || typeof v !== 'object') return false;
|
|
26
|
+
const o = v as { version?: unknown; files?: unknown };
|
|
27
|
+
return o.version === 1 && Array.isArray(o.files);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 单个文件分区:Uint8Array → { $bin: base64 }(非二进制原样保留)。 */
|
|
31
|
+
function encodeFileData(data: unknown): unknown {
|
|
32
|
+
if (data instanceof Uint8Array) {
|
|
33
|
+
return { [BIN_KEY]: Buffer.from(data).toString('base64') };
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 单个文件分区:{ $bin: base64 } → Uint8Array(非标记对象原样保留)。 */
|
|
39
|
+
function decodeFileData(data: unknown): Uint8Array {
|
|
40
|
+
if (data !== null && typeof data === 'object') {
|
|
41
|
+
const bin = (data as Record<string, unknown>)[BIN_KEY];
|
|
42
|
+
if (typeof bin === 'string') {
|
|
43
|
+
// 普通 Uint8Array(非 Buffer 子类):与内存中文件分区形态一致,深比较等价
|
|
44
|
+
return new Uint8Array(Buffer.from(bin, 'base64'));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return data as Uint8Array;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 单个分区 JSON 化:文件类分区逐文件转 base64;JSON 分区原样。 */
|
|
51
|
+
function sectionToJsonSafe(section: SectionData): SectionData {
|
|
52
|
+
if (!isFilesSectionLike(section)) return section;
|
|
53
|
+
return {
|
|
54
|
+
version: 1,
|
|
55
|
+
files: section.files.map((f) => ({
|
|
56
|
+
relativePath: f.relativePath,
|
|
57
|
+
data: encodeFileData(f.data) as Uint8Array,
|
|
58
|
+
...(f.contentHash !== undefined ? { contentHash: f.contentHash } : {}),
|
|
59
|
+
})),
|
|
60
|
+
} as FilesSection;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 单个分区从 JSON 还原:文件类分区逐文件还原 Uint8Array。 */
|
|
64
|
+
function sectionFromJsonSafe(section: unknown): SectionData {
|
|
65
|
+
if (!isFilesSectionLike(section)) return section as SectionData;
|
|
66
|
+
return {
|
|
67
|
+
version: 1,
|
|
68
|
+
files: section.files.map((f) => ({
|
|
69
|
+
relativePath: f.relativePath,
|
|
70
|
+
data: decodeFileData(f.data),
|
|
71
|
+
...(f.contentHash !== undefined ? { contentHash: f.contentHash } : {}),
|
|
72
|
+
})),
|
|
73
|
+
} as FilesSection;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** sections 整体 JSON 化(加密密文载荷原样透传)。 */
|
|
77
|
+
export function sectionsToJsonSafe(sections: SyncSnapshot['sections']): SyncSnapshot['sections'] {
|
|
78
|
+
if (isEncryptedSections(sections)) return sections;
|
|
79
|
+
const out: Partial<Record<SectionId, SectionData>> = {};
|
|
80
|
+
for (const [id, data] of Object.entries(sections as Partial<Record<SectionId, SectionData>>)) {
|
|
81
|
+
out[id as SectionId] = sectionToJsonSafe(data as SectionData);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** sections 从 JSON 还原(加密密文载荷原样透传)。 */
|
|
87
|
+
export function sectionsFromJsonSafe(sections: unknown): SyncSnapshot['sections'] {
|
|
88
|
+
if (isEncryptedSections(sections)) return sections as EncryptedSections;
|
|
89
|
+
if (sections === null || typeof sections !== 'object' || Array.isArray(sections)) {
|
|
90
|
+
throw new Error('快照 sections 必须是对象');
|
|
91
|
+
}
|
|
92
|
+
const out: Partial<Record<SectionId, SectionData>> = {};
|
|
93
|
+
for (const [id, data] of Object.entries(sections as Record<string, unknown>)) {
|
|
94
|
+
out[id as SectionId] = sectionFromJsonSafe(data);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 整份快照序列化为 JSON 字符串(本地不落盘,仅用于传输/加密载荷)。 */
|
|
100
|
+
export function serializeSnapshot(snapshot: SyncSnapshot): string {
|
|
101
|
+
return stringifyJsonSafe({
|
|
102
|
+
id: snapshot.id,
|
|
103
|
+
createdAt: snapshot.createdAt,
|
|
104
|
+
manifest: snapshot.manifest,
|
|
105
|
+
sections: sectionsToJsonSafe(snapshot.sections),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 从 JSON 字符串还原快照(形状非法抛错;调用方负责包装成通道错误)。 */
|
|
110
|
+
export function deserializeSnapshot(raw: string): SyncSnapshot {
|
|
111
|
+
const parsed = parseJsonSafe(raw);
|
|
112
|
+
const snap = parsed as Partial<SyncSnapshot> | null;
|
|
113
|
+
const okShape =
|
|
114
|
+
snap !== null && typeof snap === 'object' && !Array.isArray(snap)
|
|
115
|
+
&& typeof snap.id === 'string'
|
|
116
|
+
&& typeof snap.createdAt === 'string'
|
|
117
|
+
&& typeof snap.manifest === 'object' && snap.manifest !== null
|
|
118
|
+
&& typeof snap.sections === 'object' && snap.sections !== null;
|
|
119
|
+
if (!okShape) {
|
|
120
|
+
throw new Error('快照 JSON 形状非法(缺少 id/createdAt/manifest/sections)');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
id: (snap as { id: string }).id,
|
|
124
|
+
createdAt: (snap as { createdAt: string }).createdAt,
|
|
125
|
+
manifest: (snap as { manifest: SyncSnapshot['manifest'] }).manifest,
|
|
126
|
+
sections: sectionsFromJsonSafe((snap as { sections: unknown }).sections),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* m-sync-ui:sync-config.json schemaVersion v3(双命名空间共存)往返与迁移测试。
|
|
3
|
+
*
|
|
4
|
+
* schema v3 统一契约:
|
|
5
|
+
* - 顶层形状 { schemaVersion:3, transport:'git'|'webdav', git:{...}, webdav:{...} }。
|
|
6
|
+
* git 与 webdav 两个命名空间可并存:切换通道保存时保留另一通道配置(repoUrl/url 不丢失)。
|
|
7
|
+
* - webdav 命名空间字段:url(必填,不含凭据)/ username?(可选)。
|
|
8
|
+
* - 读入返回可辨识联合 SyncConfig(schemaVersion=2,按 transport 选取对应通道)+ isGitConfig()/isWebDavConfig() 守卫。
|
|
9
|
+
* - 兼容旧 v1({schemaVersion:1, repoUrl, gitBin?} 或缺 schemaVersion)与 v2 文件
|
|
10
|
+
* → 读取时归一为 v2 git/webdav 形态;写入时统一升级为 v3 双命名空间。
|
|
11
|
+
*
|
|
12
|
+
* 安全不变量:配置文件绝不出现密码/token;url 校验拒绝空白/非 http(s)/userinfo。
|
|
13
|
+
*/
|
|
14
|
+
import test from 'node:test';
|
|
15
|
+
import assert from 'node:assert/strict';
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
readSyncConfig, readFullSyncConfig, writeSyncConfig, isGitConfig, isWebDavConfig,
|
|
22
|
+
SYNC_CONFIG_FILE, SYNC_CONFIG_SCHEMA_VERSION, SYNC_CONFIG_SUPPORTED_VERSIONS,
|
|
23
|
+
validateWebDavUrl, type SyncConfig,
|
|
24
|
+
} from './sync-config.ts';
|
|
25
|
+
|
|
26
|
+
test('writeSyncConfig + readSyncConfig(git 通道):写入 v3 双命名空间形态(无另一通道时不写空命名空间)', async () => {
|
|
27
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-git2-'));
|
|
28
|
+
try {
|
|
29
|
+
const cfg: SyncConfig = { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } };
|
|
30
|
+
await writeSyncConfig(dir, cfg);
|
|
31
|
+
const loaded = await readSyncConfig(dir);
|
|
32
|
+
assert.deepEqual(loaded, cfg);
|
|
33
|
+
assert.ok(isGitConfig(loaded!));
|
|
34
|
+
assert.equal(isWebDavConfig(loaded!), false);
|
|
35
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, SYNC_CONFIG_FILE), 'utf8'));
|
|
36
|
+
assert.equal(raw.schemaVersion, SYNC_CONFIG_SCHEMA_VERSION);
|
|
37
|
+
assert.equal(raw.transport, 'git');
|
|
38
|
+
assert.equal(raw.git.repoUrl, 'git@github.com:foo/bar.git');
|
|
39
|
+
assert.equal(raw.webdav, undefined);
|
|
40
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('writeSyncConfig + readSyncConfig(webdav 通道):写入 v3 双命名空间形态', async () => {
|
|
44
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-webdav-'));
|
|
45
|
+
try {
|
|
46
|
+
const cfg: SyncConfig = { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com/remote.php/dav/files/user' } };
|
|
47
|
+
await writeSyncConfig(dir, cfg);
|
|
48
|
+
const loaded = await readSyncConfig(dir);
|
|
49
|
+
assert.deepEqual(loaded, cfg);
|
|
50
|
+
assert.ok(isWebDavConfig(loaded!));
|
|
51
|
+
assert.equal(isGitConfig(loaded!), false);
|
|
52
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, SYNC_CONFIG_FILE), 'utf8'));
|
|
53
|
+
assert.equal(raw.schemaVersion, SYNC_CONFIG_SCHEMA_VERSION);
|
|
54
|
+
assert.equal(raw.transport, 'webdav');
|
|
55
|
+
assert.equal(raw.webdav.url, cfg.webdav.url);
|
|
56
|
+
assert.equal(raw.webdav.username, undefined);
|
|
57
|
+
assert.equal(raw.git, undefined);
|
|
58
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('writeSyncConfig(webdav 通道):username 非空才写入', async () => {
|
|
62
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-webdavfull-'));
|
|
63
|
+
try {
|
|
64
|
+
const cfg: SyncConfig = { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com', username: 'alice' } };
|
|
65
|
+
await writeSyncConfig(dir, cfg);
|
|
66
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, SYNC_CONFIG_FILE), 'utf8'));
|
|
67
|
+
assert.equal(raw.webdav.username, 'alice');
|
|
68
|
+
const loaded = await readSyncConfig(dir);
|
|
69
|
+
assert.deepEqual(loaded, cfg);
|
|
70
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('通道切换:先配置 git 再配置 webdav → 文件保留两个命名空间,git repoUrl 不丢失', async () => {
|
|
74
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-switch-'));
|
|
75
|
+
try {
|
|
76
|
+
// 1) 配置 git 通道
|
|
77
|
+
await writeSyncConfig(dir, { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } });
|
|
78
|
+
// 2) 切到 webdav 通道并保存(此前 bug:覆盖文件导致 git repoUrl 丢失)
|
|
79
|
+
await writeSyncConfig(dir, {
|
|
80
|
+
schemaVersion: 2,
|
|
81
|
+
transport: 'webdav',
|
|
82
|
+
webdav: { url: 'https://dav.example.com/remote.php/dav/files/user', username: 'alice' },
|
|
83
|
+
});
|
|
84
|
+
// 文件同时含两个命名空间
|
|
85
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, SYNC_CONFIG_FILE), 'utf8'));
|
|
86
|
+
assert.equal(raw.schemaVersion, SYNC_CONFIG_SCHEMA_VERSION);
|
|
87
|
+
assert.equal(raw.transport, 'webdav');
|
|
88
|
+
assert.equal(raw.git.repoUrl, 'git@github.com:foo/bar.git');
|
|
89
|
+
assert.equal(raw.webdav.url, 'https://dav.example.com/remote.php/dav/files/user');
|
|
90
|
+
// 当前通道视图为 webdav
|
|
91
|
+
const loaded = await readSyncConfig(dir);
|
|
92
|
+
assert.ok(isWebDavConfig(loaded!));
|
|
93
|
+
// 完整视图可回读两通道
|
|
94
|
+
const full = await readFullSyncConfig(dir);
|
|
95
|
+
assert.equal(full?.git?.repoUrl, 'git@github.com:foo/bar.git');
|
|
96
|
+
assert.equal(full?.webdav?.username, 'alice');
|
|
97
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('通道切换:再切回 git → webdav 配置同样保留', async () => {
|
|
101
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-switchback-'));
|
|
102
|
+
try {
|
|
103
|
+
await writeSyncConfig(dir, { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } });
|
|
104
|
+
await writeSyncConfig(dir, { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com' } });
|
|
105
|
+
// 切回 git 并更新 repoUrl
|
|
106
|
+
await writeSyncConfig(dir, { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/new.git' } });
|
|
107
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, SYNC_CONFIG_FILE), 'utf8'));
|
|
108
|
+
assert.equal(raw.transport, 'git');
|
|
109
|
+
assert.equal(raw.git.repoUrl, 'git@github.com:foo/new.git', 'git repoUrl 更新为最新值');
|
|
110
|
+
assert.equal(raw.webdav.url, 'https://dav.example.com', 'webdav 配置保留');
|
|
111
|
+
const loaded = await readSyncConfig(dir);
|
|
112
|
+
assert.ok(isGitConfig(loaded!));
|
|
113
|
+
assert.equal(loaded!.git.repoUrl, 'git@github.com:foo/new.git');
|
|
114
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('readSyncConfig:旧 v1 文件(无 schemaVersion)→ 归一为 v2 git 形态(旧 gitBin 被忽略)', async () => {
|
|
118
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-v1legacy-'));
|
|
119
|
+
try {
|
|
120
|
+
await fs.writeFile(
|
|
121
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
122
|
+
JSON.stringify({ repoUrl: 'git@github.com:foo/bar.git', gitBin: '/bin/git' }),
|
|
123
|
+
'utf8',
|
|
124
|
+
);
|
|
125
|
+
const loaded = await readSyncConfig(dir);
|
|
126
|
+
assert.deepEqual(loaded, { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } });
|
|
127
|
+
// v1 亦可读出完整视图(git 命名空间)
|
|
128
|
+
const full = await readFullSyncConfig(dir);
|
|
129
|
+
assert.equal(full?.git?.repoUrl, 'git@github.com:foo/bar.git');
|
|
130
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('readSyncConfig:显式 schemaVersion=1 的旧文件 → 归一为 v2 git 形态', async () => {
|
|
134
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-v1-'));
|
|
135
|
+
try {
|
|
136
|
+
await fs.writeFile(
|
|
137
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
138
|
+
JSON.stringify({ schemaVersion: 1, repoUrl: 'git@github.com:foo/bar.git' }),
|
|
139
|
+
'utf8',
|
|
140
|
+
);
|
|
141
|
+
const loaded = await readSyncConfig(dir);
|
|
142
|
+
assert.deepEqual(loaded, { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } });
|
|
143
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('readSyncConfig:v2 旧文件(单命名空间)→ 正常读取', async () => {
|
|
147
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-v2-'));
|
|
148
|
+
try {
|
|
149
|
+
await fs.writeFile(
|
|
150
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
151
|
+
JSON.stringify({ schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com', username: 'bob' } }),
|
|
152
|
+
'utf8',
|
|
153
|
+
);
|
|
154
|
+
const loaded = await readSyncConfig(dir);
|
|
155
|
+
assert.ok(isWebDavConfig(loaded!));
|
|
156
|
+
assert.equal(loaded!.webdav.url, 'https://dav.example.com');
|
|
157
|
+
// 完整视图读回
|
|
158
|
+
const full = await readFullSyncConfig(dir);
|
|
159
|
+
assert.equal(full?.webdav?.username, 'bob');
|
|
160
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('readSyncConfig(webdav):缺 webdav.url → 返回 null(未配置)', async () => {
|
|
164
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-webdavnourl-'));
|
|
165
|
+
try {
|
|
166
|
+
await fs.writeFile(
|
|
167
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
168
|
+
JSON.stringify({ schemaVersion: 2, transport: 'webdav', webdav: { username: 'alice' } }),
|
|
169
|
+
'utf8',
|
|
170
|
+
);
|
|
171
|
+
const loaded = await readSyncConfig(dir);
|
|
172
|
+
assert.equal(loaded, null);
|
|
173
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('readSyncConfig(git):缺 git.repoUrl → 返回 null(未配置)', async () => {
|
|
177
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-gitnourl-'));
|
|
178
|
+
try {
|
|
179
|
+
await fs.writeFile(
|
|
180
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
181
|
+
JSON.stringify({ schemaVersion: 2, transport: 'git', git: {} }),
|
|
182
|
+
'utf8',
|
|
183
|
+
);
|
|
184
|
+
const loaded = await readSyncConfig(dir);
|
|
185
|
+
assert.equal(loaded, null);
|
|
186
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('readSyncConfig:transport 非法值 → 返回 null(拒绝垃圾)', async () => {
|
|
190
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-badtrans-'));
|
|
191
|
+
try {
|
|
192
|
+
await fs.writeFile(
|
|
193
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
194
|
+
JSON.stringify({ schemaVersion: 2, transport: 'ftp', webdav: { url: 'https://x.example.com' } }),
|
|
195
|
+
'utf8',
|
|
196
|
+
);
|
|
197
|
+
const loaded = await readSyncConfig(dir);
|
|
198
|
+
assert.equal(loaded, null);
|
|
199
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('readSyncConfig:不支持的 schemaVersion → 返回 null(拒绝)', async () => {
|
|
203
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-badver-'));
|
|
204
|
+
try {
|
|
205
|
+
await fs.writeFile(
|
|
206
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
207
|
+
JSON.stringify({ schemaVersion: 99, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } }),
|
|
208
|
+
'utf8',
|
|
209
|
+
);
|
|
210
|
+
const loaded = await readSyncConfig(dir);
|
|
211
|
+
assert.equal(loaded, null);
|
|
212
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('readSyncConfig:schemaVersion 非数字 → 返回 null', async () => {
|
|
216
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-badtype-'));
|
|
217
|
+
try {
|
|
218
|
+
await fs.writeFile(
|
|
219
|
+
path.join(dir, SYNC_CONFIG_FILE),
|
|
220
|
+
JSON.stringify({ schemaVersion: 'v2', transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } }),
|
|
221
|
+
'utf8',
|
|
222
|
+
);
|
|
223
|
+
const loaded = await readSyncConfig(dir);
|
|
224
|
+
assert.equal(loaded, null);
|
|
225
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('readSyncConfig:损坏 JSON → 返回 null', async () => {
|
|
229
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-badjson-'));
|
|
230
|
+
try {
|
|
231
|
+
await fs.writeFile(path.join(dir, SYNC_CONFIG_FILE), '{not-json', 'utf8');
|
|
232
|
+
const loaded = await readSyncConfig(dir);
|
|
233
|
+
assert.equal(loaded, null);
|
|
234
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('readSyncConfig:文件不存在 → 返回 null(未配置)', async () => {
|
|
238
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-missing-'));
|
|
239
|
+
try {
|
|
240
|
+
const loaded = await readSyncConfig(dir);
|
|
241
|
+
assert.equal(loaded, null);
|
|
242
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('writeSyncConfig:自动创建目录', async () => {
|
|
246
|
+
const base = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-mkdir-'));
|
|
247
|
+
try {
|
|
248
|
+
const dir = path.join(base, 'nested', 'sync');
|
|
249
|
+
await writeSyncConfig(dir, { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com' } });
|
|
250
|
+
const loaded = await readSyncConfig(dir);
|
|
251
|
+
assert.ok(loaded);
|
|
252
|
+
assert.equal(loaded.transport, 'webdav');
|
|
253
|
+
} finally { await fs.rm(base, { recursive: true, force: true }); }
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('isGitConfig / isWebDavConfig 守卫:只命中对应通道', () => {
|
|
257
|
+
const git: SyncConfig = { schemaVersion: 2, transport: 'git', git: { repoUrl: 'x' } };
|
|
258
|
+
const webdav: SyncConfig = { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com' } };
|
|
259
|
+
assert.equal(isGitConfig(git), true);
|
|
260
|
+
assert.equal(isGitConfig(webdav), false);
|
|
261
|
+
assert.equal(isWebDavConfig(webdav), true);
|
|
262
|
+
assert.equal(isWebDavConfig(git), false);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test('SYNC_CONFIG_SUPPORTED_VERSIONS 包含 1、2 与 3', () => {
|
|
266
|
+
assert.ok(SYNC_CONFIG_SUPPORTED_VERSIONS.includes(1));
|
|
267
|
+
assert.ok(SYNC_CONFIG_SUPPORTED_VERSIONS.includes(2));
|
|
268
|
+
assert.ok(SYNC_CONFIG_SUPPORTED_VERSIONS.includes(3));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('readFullSyncConfig:文件不存在 → null', async () => {
|
|
272
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-fullmissing-'));
|
|
273
|
+
try {
|
|
274
|
+
const full = await readFullSyncConfig(dir);
|
|
275
|
+
assert.equal(full, null);
|
|
276
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('readFullSyncConfig:损坏 JSON → null', async () => {
|
|
280
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-sync-cfg-fullbadjson-'));
|
|
281
|
+
try {
|
|
282
|
+
await fs.writeFile(path.join(dir, SYNC_CONFIG_FILE), '{bad', 'utf8');
|
|
283
|
+
const full = await readFullSyncConfig(dir);
|
|
284
|
+
assert.equal(full, null);
|
|
285
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('validateWebDavUrl:空字符串 → 返回错误(必填)', () => {
|
|
289
|
+
assert.ok(validateWebDavUrl(''));
|
|
290
|
+
assert.ok(validateWebDavUrl(' '));
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('validateWebDavUrl:含空白字符 → 返回错误', () => {
|
|
294
|
+
assert.ok(validateWebDavUrl('https://dav.example.com /x'));
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('validateWebDavUrl:非 http(s) → 返回错误', () => {
|
|
298
|
+
assert.ok(validateWebDavUrl('ftp://dav.example.com'));
|
|
299
|
+
assert.ok(validateWebDavUrl('dav.example.com'));
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('validateWebDavUrl:含 userinfo(username:password@)→ 拒绝(凭据不入 URL)', () => {
|
|
303
|
+
assert.ok(validateWebDavUrl('https://user:pass@dav.example.com'));
|
|
304
|
+
assert.ok(validateWebDavUrl('https://user@dav.example.com'));
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('validateWebDavUrl:合法 http(s) 地址 → 返回 null(合法)', () => {
|
|
308
|
+
assert.equal(validateWebDavUrl('https://dav.example.com/remote.php/dav/files/user'), null);
|
|
309
|
+
assert.equal(validateWebDavUrl('http://dav.local:8080/'), null);
|
|
310
|
+
});
|