@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,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* m-sync-transport:SyncTransport 抽象层。
|
|
3
|
+
* 纯类型 + 纯 helper,零副作用、零 fs —— 传输通道实现(git 等)由后续波次提供。
|
|
4
|
+
* 与核心引擎同原则:只依赖 src/schema/types.ts 的纯类型,不 import 任何 DSH 运行时包。
|
|
5
|
+
*/
|
|
6
|
+
import type { EncryptionInfo, Manifest, SectionData, SectionId } from '../schema/types.ts';
|
|
7
|
+
import { hashSection } from './sync-state.ts';
|
|
8
|
+
|
|
9
|
+
/** manifest 摘要:快照列表展示 / 变更判断所需的轻量来源信息 */
|
|
10
|
+
export interface ManifestSummary {
|
|
11
|
+
schemaVersion: number;
|
|
12
|
+
dshVersion: string;
|
|
13
|
+
platform: string;
|
|
14
|
+
/** 快照包含的分区(按 manifest.sections 中为 true 的键) */
|
|
15
|
+
sectionIds: SectionId[];
|
|
16
|
+
containsSecrets: boolean;
|
|
17
|
+
/** 快照是否加密(sections 为 EncryptedSections 密文载荷);缺省 false(旧快照兼容) */
|
|
18
|
+
encrypted?: boolean;
|
|
19
|
+
/** 产生该快照的同步通道(git / webdav;供同步历史展示触发来源;旧快照缺省 undefined) */
|
|
20
|
+
transport?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 快照元信息:list() 条目 / upload() 返回值 */
|
|
24
|
+
export interface SyncSnapshotMeta {
|
|
25
|
+
id: string;
|
|
26
|
+
createdAt: string; // ISO-8601 UTC
|
|
27
|
+
/** 各分区内容 hash(sectionId → hashSection 结果),用于变更检测。
|
|
28
|
+
* 加密快照的 sections 为密文载荷,不参与明文 hash 比较 → 空对象。 */
|
|
29
|
+
sections: Partial<Record<SectionId, string>>;
|
|
30
|
+
manifest: ManifestSummary;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 加密快照的 sections 载荷:整个明文 sections 对象序列化后整体加密(AES-256-GCM)。 */
|
|
34
|
+
export interface EncryptedSections {
|
|
35
|
+
encrypted: {
|
|
36
|
+
/** 加密参数(salt/iv/authTag base64;与 security/encryption.ts 的 EncryptionInfo 对齐) */
|
|
37
|
+
info: EncryptionInfo;
|
|
38
|
+
/** base64:带 DSC1 头的密文(明文 = 序列化的 sections Record) */
|
|
39
|
+
data: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 快照载荷:upload() 入参 / download() 返回 */
|
|
44
|
+
export interface SyncSnapshot {
|
|
45
|
+
id: string;
|
|
46
|
+
createdAt: string; // ISO-8601 UTC
|
|
47
|
+
manifest: ManifestSummary;
|
|
48
|
+
/** JSON 分区数据 + 文件类分区(FilesSection);加密快照为 EncryptedSections 密文载荷 */
|
|
49
|
+
sections: Partial<Record<SectionId, SectionData>> | EncryptedSections;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 远端快照传输通道契约。
|
|
54
|
+
* 实现约定:同 id 重复 upload 视为覆盖(幂等友好);download 对不存在的 id 必须抛错。
|
|
55
|
+
*/
|
|
56
|
+
export interface SyncTransport {
|
|
57
|
+
readonly type: string;
|
|
58
|
+
/** 列出远端已有快照(按 createdAt 升序) */
|
|
59
|
+
list(): Promise<SyncSnapshotMeta[]>;
|
|
60
|
+
/** 上传快照,返回其元信息(含各分区 hash) */
|
|
61
|
+
upload(snapshot: SyncSnapshot): Promise<SyncSnapshotMeta>;
|
|
62
|
+
/** 下载快照完整载荷 */
|
|
63
|
+
download(id: string): Promise<SyncSnapshot>;
|
|
64
|
+
/** 删除远端快照(不存在视为成功) */
|
|
65
|
+
delete(id: string): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 由快照计算元信息:sections hash 记录 + manifest 摘要透传。
|
|
69
|
+
* 加密快照(sections 为密文载荷)→ sections hash 记录为空(密文无法与本地明文比较)。 */
|
|
70
|
+
export function computeSnapshotMeta(snapshot: SyncSnapshot): SyncSnapshotMeta {
|
|
71
|
+
const sections: SyncSnapshotMeta['sections'] = {};
|
|
72
|
+
if (!isEncryptedSections(snapshot.sections)) {
|
|
73
|
+
for (const [id, data] of Object.entries(snapshot.sections)) {
|
|
74
|
+
sections[id as SectionId] = hashSection(data as SectionData);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { id: snapshot.id, createdAt: snapshot.createdAt, sections, manifest: snapshot.manifest };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 判定远端索引条目与本地快照「内容相同」(快照级跳过判定,供 webdav 通道增量上传用):
|
|
81
|
+
* 仅比较各分区内容 hash(sections 全等),不比较 createdAt / manifest 摘要
|
|
82
|
+
* (同 id 重复上传但内容未变 → 视为幂等覆盖,无需重新 PUT 载荷)。
|
|
83
|
+
* - 本地 sections 为空对象(加密快照:密文无法与远端明文 hash 比较)→ 返回 false,
|
|
84
|
+
* 「无法比较」必须照常上传,绝不跳过;
|
|
85
|
+
* - 键集合与每个键的 hash 值全部相等 → true;否则 false。
|
|
86
|
+
*/
|
|
87
|
+
export function sectionsEqual(remote: SyncSnapshotMeta, local: SyncSnapshotMeta): boolean {
|
|
88
|
+
const r = remote.sections;
|
|
89
|
+
const l = local.sections;
|
|
90
|
+
if (Object.keys(l).length === 0) return false; // 本地为空(加密快照)→ 无法比较
|
|
91
|
+
if (Object.keys(r).length !== Object.keys(l).length) return false;
|
|
92
|
+
for (const key of Object.keys(r)) {
|
|
93
|
+
if (r[key as SectionId] !== l[key as SectionId]) return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 判定 sections 是否为加密密文载荷(duck-typing:含 encrypted.info + encrypted.data 字符串)。 */
|
|
99
|
+
export function isEncryptedSections(sections: unknown): sections is EncryptedSections {
|
|
100
|
+
if (sections === null || typeof sections !== 'object') return false;
|
|
101
|
+
const enc = (sections as { encrypted?: unknown }).encrypted;
|
|
102
|
+
if (enc === null || typeof enc !== 'object') return false;
|
|
103
|
+
const e = enc as { info?: unknown; data?: unknown };
|
|
104
|
+
return typeof e.data === 'string' && e.data !== '' && e.info !== null && typeof e.info === 'object';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 从导出 Manifest 提取摘要 */
|
|
108
|
+
export function manifestSummaryFrom(manifest: Manifest): ManifestSummary {
|
|
109
|
+
return {
|
|
110
|
+
schemaVersion: manifest.schemaVersion,
|
|
111
|
+
dshVersion: manifest.source.dshVersion,
|
|
112
|
+
platform: manifest.source.platform,
|
|
113
|
+
sectionIds: Object.entries(manifest.sections)
|
|
114
|
+
.filter(([, included]) => included)
|
|
115
|
+
.map(([id]) => id) as SectionId[],
|
|
116
|
+
containsSecrets: manifest.security.containsSecrets,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui-prefs 测试:ui-prefs.json 读写往返、缺省值、损坏 JSON 回退缺省、非法通道值忽略、
|
|
3
|
+
* Star 引导弹窗状态字段往返与非法值忽略、updateUiPrefs 局部原子更新不互相覆盖。
|
|
4
|
+
*/
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import fs from 'node:fs/promises';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
defaultUiPrefs, readUiPrefs, writeUiPrefs, updateUiPrefs,
|
|
13
|
+
UI_PREFS_FILE, UI_PREFS_SCHEMA_VERSION,
|
|
14
|
+
} from './ui-prefs.ts';
|
|
15
|
+
|
|
16
|
+
test('writeUiPrefs + readUiPrefs:写入通道 → 读回一致 + 原始文件校验', async () => {
|
|
17
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-rt-'));
|
|
18
|
+
try {
|
|
19
|
+
await writeUiPrefs(dir, { schemaVersion: 1, lastSyncChannel: 'webdav' });
|
|
20
|
+
const prefs = await readUiPrefs(dir);
|
|
21
|
+
assert.equal(prefs.lastSyncChannel, 'webdav');
|
|
22
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, UI_PREFS_FILE), 'utf8'));
|
|
23
|
+
assert.equal(raw.schemaVersion, UI_PREFS_SCHEMA_VERSION);
|
|
24
|
+
assert.equal(raw.lastSyncChannel, 'webdav');
|
|
25
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('writeUiPrefs:无通道 → 文件不写 lastSyncChannel 字段', async () => {
|
|
29
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-none-'));
|
|
30
|
+
try {
|
|
31
|
+
await writeUiPrefs(dir, defaultUiPrefs());
|
|
32
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, UI_PREFS_FILE), 'utf8'));
|
|
33
|
+
assert.equal(raw.schemaVersion, UI_PREFS_SCHEMA_VERSION);
|
|
34
|
+
assert.equal('lastSyncChannel' in raw, false, '未配置通道不落字段');
|
|
35
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('readUiPrefs:文件不存在 → 缺省(无通道)', async () => {
|
|
39
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-default-'));
|
|
40
|
+
try {
|
|
41
|
+
const prefs = await readUiPrefs(dir);
|
|
42
|
+
assert.equal(prefs.lastSyncChannel, undefined);
|
|
43
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('readUiPrefs:损坏 JSON → 回退缺省', async () => {
|
|
47
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-corrupt-'));
|
|
48
|
+
try {
|
|
49
|
+
await fs.writeFile(path.join(dir, UI_PREFS_FILE), '{not-json', 'utf8');
|
|
50
|
+
const prefs = await readUiPrefs(dir);
|
|
51
|
+
assert.equal(prefs.lastSyncChannel, undefined);
|
|
52
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('readUiPrefs:不支持的 schemaVersion → 回退缺省', async () => {
|
|
56
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-schema-'));
|
|
57
|
+
try {
|
|
58
|
+
await fs.writeFile(
|
|
59
|
+
path.join(dir, UI_PREFS_FILE),
|
|
60
|
+
JSON.stringify({ schemaVersion: 99, lastSyncChannel: 'git' }),
|
|
61
|
+
'utf8',
|
|
62
|
+
);
|
|
63
|
+
const prefs = await readUiPrefs(dir);
|
|
64
|
+
assert.equal(prefs.lastSyncChannel, undefined);
|
|
65
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('readUiPrefs:非法通道值 → 忽略(回退缺省)', async () => {
|
|
69
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-bad-'));
|
|
70
|
+
try {
|
|
71
|
+
await fs.writeFile(
|
|
72
|
+
path.join(dir, UI_PREFS_FILE),
|
|
73
|
+
JSON.stringify({ schemaVersion: 1, lastSyncChannel: 'ftp' }),
|
|
74
|
+
'utf8',
|
|
75
|
+
);
|
|
76
|
+
const prefs = await readUiPrefs(dir);
|
|
77
|
+
assert.equal(prefs.lastSyncChannel, undefined, '非 git/webdav 值被忽略');
|
|
78
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('Star 引导状态:写入三字段 → 读回一致 + 原始文件校验', async () => {
|
|
82
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-star-'));
|
|
83
|
+
try {
|
|
84
|
+
await writeUiPrefs(dir, {
|
|
85
|
+
schemaVersion: 1,
|
|
86
|
+
starPromptFirstSeenAt: 1700000000000,
|
|
87
|
+
starPromptDismissed: false,
|
|
88
|
+
starPromptClicked: true,
|
|
89
|
+
});
|
|
90
|
+
const prefs = await readUiPrefs(dir);
|
|
91
|
+
assert.equal(prefs.starPromptFirstSeenAt, 1700000000000);
|
|
92
|
+
assert.equal(prefs.starPromptDismissed, undefined, 'false 不落字段');
|
|
93
|
+
assert.equal(prefs.starPromptClicked, true);
|
|
94
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, UI_PREFS_FILE), 'utf8'));
|
|
95
|
+
assert.equal(raw.starPromptFirstSeenAt, 1700000000000);
|
|
96
|
+
assert.equal('starPromptDismissed' in raw, false, 'false 不写字段');
|
|
97
|
+
assert.equal(raw.starPromptClicked, true);
|
|
98
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('readUiPrefs:Star 字段非法值(字符串时间戳 / 非布尔)→ 忽略', async () => {
|
|
102
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-star-bad-'));
|
|
103
|
+
try {
|
|
104
|
+
await fs.writeFile(
|
|
105
|
+
path.join(dir, UI_PREFS_FILE),
|
|
106
|
+
JSON.stringify({ schemaVersion: 1, starPromptFirstSeenAt: 'yesterday', starPromptDismissed: 'yes' }),
|
|
107
|
+
'utf8',
|
|
108
|
+
);
|
|
109
|
+
const prefs = await readUiPrefs(dir);
|
|
110
|
+
assert.equal(prefs.starPromptFirstSeenAt, undefined);
|
|
111
|
+
assert.equal(prefs.starPromptDismissed, undefined);
|
|
112
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('updateUiPrefs:局部补丁合并,不覆盖未涉及的字段(防多端点互覆盖)', async () => {
|
|
116
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-upd-'));
|
|
117
|
+
try {
|
|
118
|
+
await writeUiPrefs(dir, { schemaVersion: 1, lastSyncChannel: 'webdav' });
|
|
119
|
+
// star-prompt 端点更新:只补弹窗字段,lastSyncChannel 必须保留
|
|
120
|
+
const next = await updateUiPrefs(dir, { starPromptFirstSeenAt: 1700000000000, starPromptClicked: true });
|
|
121
|
+
assert.equal(next.lastSyncChannel, 'webdav', '未涉及的 lastSyncChannel 保留');
|
|
122
|
+
assert.equal(next.starPromptFirstSeenAt, 1700000000000);
|
|
123
|
+
assert.equal(next.starPromptClicked, true);
|
|
124
|
+
// sync/ui-prefs 端点更新:只补通道字段,弹窗字段必须保留
|
|
125
|
+
const after = await updateUiPrefs(dir, { lastSyncChannel: 'git' });
|
|
126
|
+
assert.equal(after.lastSyncChannel, 'git');
|
|
127
|
+
assert.equal(after.starPromptFirstSeenAt, 1700000000000, '未涉及的 Star 字段保留');
|
|
128
|
+
assert.equal(after.starPromptClicked, true);
|
|
129
|
+
// 磁盘最终态
|
|
130
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, UI_PREFS_FILE), 'utf8'));
|
|
131
|
+
assert.equal(raw.lastSyncChannel, 'git');
|
|
132
|
+
assert.equal(raw.starPromptFirstSeenAt, 1700000000000);
|
|
133
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('更新内容弹窗状态:写入字段 → 读回一致 + 原始文件校验', async () => {
|
|
137
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'dsh-ui-prefs-rn-'));
|
|
138
|
+
try {
|
|
139
|
+
await writeUiPrefs(dir, {
|
|
140
|
+
schemaVersion: 1,
|
|
141
|
+
releaseNotesLastSeenVersion: '0.1.54',
|
|
142
|
+
releaseNotesDismissed: true,
|
|
143
|
+
});
|
|
144
|
+
const prefs = await readUiPrefs(dir);
|
|
145
|
+
assert.equal(prefs.releaseNotesLastSeenVersion, '0.1.54');
|
|
146
|
+
assert.equal(prefs.releaseNotesDismissed, true);
|
|
147
|
+
const raw = JSON.parse(await fs.readFile(path.join(dir, UI_PREFS_FILE), 'utf8'));
|
|
148
|
+
assert.equal(raw.releaseNotesLastSeenVersion, '0.1.54');
|
|
149
|
+
assert.equal(raw.releaseNotesDismissed, true);
|
|
150
|
+
} finally { await fs.rm(dir, { recursive: true, force: true }); }
|
|
151
|
+
});
|
|
152
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* m-self:插件自身 UI 偏好持久化(ui-prefs.json)。
|
|
3
|
+
*
|
|
4
|
+
* 与 sync-config.json / sync-selection.json 并列独立文件:语义清楚、schema 演进独立。
|
|
5
|
+
* 当前存 lastSyncChannel(用户上次选择的同步通道 git/webdav)+ Star 引导弹窗状态
|
|
6
|
+
* (starPromptFirstSeenAt / starPromptDismissed / starPromptClicked,见 src/ui/star-prompt.ts)。
|
|
7
|
+
*
|
|
8
|
+
* 背景(self 分区设计):此前该偏好只存浏览器 localStorage(键
|
|
9
|
+
* dsh.configManager.syncChannel),换浏览器/换机器即丢失,且 Host 进程读不到
|
|
10
|
+
* (自动同步在浏览器关闭时也运行)。迁移到磁盘后:
|
|
11
|
+
* - Host 侧可读可写(status 响应回填、POST /sync/ui-prefs 保存);
|
|
12
|
+
* - 随 self 分区进入导出备份,迁移到新机器时恢复;
|
|
13
|
+
* - localStorage 仅保留为前端同步读取的降级通道(status 未带回填时的兜底)。
|
|
14
|
+
*
|
|
15
|
+
* 字段 { schemaVersion, lastSyncChannel?: 'git' | 'webdav',
|
|
16
|
+
* starPromptFirstSeenAt?: number, starPromptDismissed?: boolean,
|
|
17
|
+
* starPromptClicked?: boolean }:
|
|
18
|
+
* - 缺省/未配置 = undefined(UI 回退到 sync-config.transport / 首次进入页面);
|
|
19
|
+
* - 原子写(临时文件 + rename),损坏/不支持 schema 回退缺省;
|
|
20
|
+
* - 多端点共写同一文件:一律经 updateUiPrefs(read → merge → write)原子更新,
|
|
21
|
+
* 避免「sync/ui-prefs 写 lastSyncChannel」与「star-prompt 写弹窗状态」互相覆盖。
|
|
22
|
+
*/
|
|
23
|
+
import fs from 'node:fs/promises';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import crypto from 'node:crypto';
|
|
26
|
+
|
|
27
|
+
import { parseJsonSafe, stringifyJsonSafe } from '../utils/json.ts';
|
|
28
|
+
import { atomicWriteFile } from '../utils/atomic-write.ts';
|
|
29
|
+
|
|
30
|
+
export const UI_PREFS_FILE = 'ui-prefs.json';
|
|
31
|
+
export const UI_PREFS_SCHEMA_VERSION = 1;
|
|
32
|
+
|
|
33
|
+
/** 同步通道类型(与 sync-selection / sync-config 共享语义;避免循环 import 自行声明) */
|
|
34
|
+
export type UiPrefsChannel = 'git' | 'webdav';
|
|
35
|
+
|
|
36
|
+
/** 插件自身 UI 偏好(持久化面)。 */
|
|
37
|
+
export interface UiPrefs {
|
|
38
|
+
schemaVersion: number;
|
|
39
|
+
/** 用户上次选择的同步通道;未配置为 undefined */
|
|
40
|
+
lastSyncChannel?: UiPrefsChannel;
|
|
41
|
+
/** Star 引导弹窗:首次进入页面时间(ms 时间戳);未配置 = 尚未进入过页面 */
|
|
42
|
+
starPromptFirstSeenAt?: number;
|
|
43
|
+
/** Star 引导弹窗:用户点过「不再提示」(永久不再弹) */
|
|
44
|
+
starPromptDismissed?: boolean;
|
|
45
|
+
/** Star 引导弹窗:用户点过「去点 Star」(方案 A:引导完成,不再弹) */
|
|
46
|
+
starPromptClicked?: boolean;
|
|
47
|
+
/** 更新内容弹窗:上次已记录/展示过的插件版本号(如 '0.1.54') */
|
|
48
|
+
releaseNotesLastSeenVersion?: string;
|
|
49
|
+
/** 更新内容弹窗:用户点过「永不提示」(永久不再自动弹出) */
|
|
50
|
+
releaseNotesDismissed?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 缺省配置(首次无文件 / 损坏 / 不支持 schema 时回退) */
|
|
54
|
+
export function defaultUiPrefs(): UiPrefs {
|
|
55
|
+
return { schemaVersion: UI_PREFS_SCHEMA_VERSION };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 读取 UI 偏好;文件不存在 / 损坏 / 不支持 schema → 缺省值(不抛错)。 */
|
|
59
|
+
export async function readUiPrefs(dir: string): Promise<UiPrefs> {
|
|
60
|
+
const file = path.join(dir, UI_PREFS_FILE);
|
|
61
|
+
let raw: string;
|
|
62
|
+
try {
|
|
63
|
+
raw = await fs.readFile(file, 'utf8');
|
|
64
|
+
} catch {
|
|
65
|
+
return defaultUiPrefs();
|
|
66
|
+
}
|
|
67
|
+
let parsed: unknown;
|
|
68
|
+
try {
|
|
69
|
+
parsed = parseJsonSafe(raw);
|
|
70
|
+
} catch {
|
|
71
|
+
return defaultUiPrefs();
|
|
72
|
+
}
|
|
73
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
74
|
+
return defaultUiPrefs();
|
|
75
|
+
}
|
|
76
|
+
const obj = parsed as Record<string, unknown>;
|
|
77
|
+
if (obj['schemaVersion'] !== undefined && obj['schemaVersion'] !== UI_PREFS_SCHEMA_VERSION) {
|
|
78
|
+
return defaultUiPrefs();
|
|
79
|
+
}
|
|
80
|
+
if (obj['schemaVersion'] === undefined) {
|
|
81
|
+
return defaultUiPrefs();
|
|
82
|
+
}
|
|
83
|
+
const prefs = defaultUiPrefs();
|
|
84
|
+
if (obj['lastSyncChannel'] === 'git' || obj['lastSyncChannel'] === 'webdav') {
|
|
85
|
+
prefs.lastSyncChannel = obj['lastSyncChannel'];
|
|
86
|
+
}
|
|
87
|
+
if (typeof obj['starPromptFirstSeenAt'] === 'number' && Number.isFinite(obj['starPromptFirstSeenAt'])) {
|
|
88
|
+
prefs.starPromptFirstSeenAt = obj['starPromptFirstSeenAt'];
|
|
89
|
+
}
|
|
90
|
+
if (obj['starPromptDismissed'] === true) {
|
|
91
|
+
prefs.starPromptDismissed = true;
|
|
92
|
+
}
|
|
93
|
+
if (obj['starPromptClicked'] === true) {
|
|
94
|
+
prefs.starPromptClicked = true;
|
|
95
|
+
}
|
|
96
|
+
if (typeof obj['releaseNotesLastSeenVersion'] === 'string' && obj['releaseNotesLastSeenVersion'].trim().length > 0) {
|
|
97
|
+
prefs.releaseNotesLastSeenVersion = obj['releaseNotesLastSeenVersion'].trim();
|
|
98
|
+
}
|
|
99
|
+
if (obj['releaseNotesDismissed'] === true) {
|
|
100
|
+
prefs.releaseNotesDismissed = true;
|
|
101
|
+
}
|
|
102
|
+
return prefs;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 写入 UI 偏好(原子写:临时文件 + rename;自动创建目录)。 */
|
|
106
|
+
export async function writeUiPrefs(dir: string, prefs: UiPrefs): Promise<void> {
|
|
107
|
+
await fs.mkdir(dir, { recursive: true });
|
|
108
|
+
const payload: Record<string, unknown> = {
|
|
109
|
+
schemaVersion: UI_PREFS_SCHEMA_VERSION,
|
|
110
|
+
...(prefs.lastSyncChannel !== undefined ? { lastSyncChannel: prefs.lastSyncChannel } : {}),
|
|
111
|
+
...(prefs.starPromptFirstSeenAt !== undefined ? { starPromptFirstSeenAt: prefs.starPromptFirstSeenAt } : {}),
|
|
112
|
+
...(prefs.starPromptDismissed === true ? { starPromptDismissed: true } : {}),
|
|
113
|
+
...(prefs.starPromptClicked === true ? { starPromptClicked: true } : {}),
|
|
114
|
+
...(prefs.releaseNotesLastSeenVersion !== undefined ? { releaseNotesLastSeenVersion: prefs.releaseNotesLastSeenVersion } : {}),
|
|
115
|
+
...(prefs.releaseNotesDismissed === true ? { releaseNotesDismissed: true } : {}),
|
|
116
|
+
};
|
|
117
|
+
const target = path.join(dir, UI_PREFS_FILE);
|
|
118
|
+
const data = stringifyJsonSafe(payload, { space: 2 });
|
|
119
|
+
await atomicWriteFile(target, data, { mode: 0o600 });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 局部原子更新:read → merge → write。
|
|
124
|
+
* 多端点共写 ui-prefs.json 时(sync/ui-prefs 与 star-prompt),任一端点写全量对象
|
|
125
|
+
* 都会丢掉另一端点刚写入的字段;统一走本函数按补丁合并,杜绝互相覆盖。
|
|
126
|
+
*/
|
|
127
|
+
export async function updateUiPrefs(dir: string, patch: UiPrefsPatch): Promise<UiPrefs> {
|
|
128
|
+
const current = await readUiPrefs(dir);
|
|
129
|
+
const next: UiPrefs = { ...current, ...patch };
|
|
130
|
+
await writeUiPrefs(dir, next);
|
|
131
|
+
return next;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** updateUiPrefs 的补丁类型(UiPrefs 全部可选字段,schemaVersion 不可补丁)。 */
|
|
135
|
+
export type UiPrefsPatch = Partial<Omit<UiPrefs, 'schemaVersion'>>;
|