@narumitw/pi-sync 0.49.14 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -241
- package/dist/chunks/chunk-2B2ELUF5.ts +29 -0
- package/dist/chunks/chunk-2B2ELUF5.ts.map +7 -0
- package/dist/chunks/chunk-6LP3MCR2.ts +115 -0
- package/dist/chunks/chunk-6LP3MCR2.ts.map +7 -0
- package/dist/chunks/chunk-6UHKGALE.ts +134 -0
- package/dist/chunks/chunk-6UHKGALE.ts.map +7 -0
- package/dist/chunks/{chunk-RGJ6SOYR.ts → chunk-AUSDLD6F.ts} +4 -4
- package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
- package/dist/chunks/{chunk-3NDD5QPM.ts → chunk-AXXS2BPA.ts} +9 -9
- package/dist/chunks/chunk-AXXS2BPA.ts.map +7 -0
- package/dist/chunks/{chunk-APZVBSM6.ts → chunk-C7XY5NF5.ts} +214 -365
- package/dist/chunks/chunk-C7XY5NF5.ts.map +7 -0
- package/dist/chunks/{chunk-4U6AFLJB.ts → chunk-CHH26D4F.ts} +3 -2
- package/dist/chunks/chunk-CHH26D4F.ts.map +7 -0
- package/dist/chunks/chunk-CPEHGXJV.ts +50 -0
- package/dist/chunks/chunk-CPEHGXJV.ts.map +7 -0
- package/dist/chunks/{chunk-5MQ76BK5.ts → chunk-D5X27LCA.ts} +2 -2
- package/dist/chunks/chunk-D5X27LCA.ts.map +7 -0
- package/dist/chunks/{chunk-LJDSHNS3.ts → chunk-GWMHAQG4.ts} +4 -4
- package/dist/chunks/chunk-GWMHAQG4.ts.map +7 -0
- package/dist/chunks/{chunk-FR6CVXAL.ts → chunk-HUDQATCL.ts} +25 -25
- package/dist/chunks/chunk-HUDQATCL.ts.map +7 -0
- package/dist/chunks/{chunk-YQ6UW7IF.ts → chunk-JAAKLL6M.ts} +2 -2
- package/dist/chunks/chunk-JAAKLL6M.ts.map +7 -0
- package/dist/chunks/chunk-JSYMGJQQ.ts +51 -0
- package/dist/chunks/chunk-JSYMGJQQ.ts.map +7 -0
- package/dist/chunks/chunk-L7TBO32M.ts +25 -0
- package/dist/chunks/chunk-L7TBO32M.ts.map +7 -0
- package/dist/chunks/{chunk-4F3W4CSC.ts → chunk-LCRI3ABW.ts} +22 -8
- package/dist/chunks/chunk-LCRI3ABW.ts.map +7 -0
- package/dist/chunks/chunk-LTBWGSBV.ts +12 -0
- package/dist/chunks/chunk-LTBWGSBV.ts.map +7 -0
- package/dist/chunks/chunk-NJFUIVYL.ts +17 -0
- package/dist/chunks/chunk-NJFUIVYL.ts.map +7 -0
- package/dist/chunks/{chunk-EWX3TPLQ.ts → chunk-OEX4ABY6.ts} +39 -37
- package/dist/chunks/chunk-OEX4ABY6.ts.map +7 -0
- package/dist/chunks/chunk-OUHGBFYF.ts +1255 -0
- package/dist/chunks/chunk-OUHGBFYF.ts.map +7 -0
- package/dist/chunks/{chunk-HGV24P4T.ts → chunk-Q2TUM5X6.ts} +3 -3
- package/dist/chunks/chunk-Q2TUM5X6.ts.map +7 -0
- package/dist/chunks/{chunk-BHJUISB4.ts → chunk-SL64RLXR.ts} +8 -6
- package/dist/chunks/chunk-SL64RLXR.ts.map +7 -0
- package/dist/chunks/chunk-T3HH3UEJ.ts +96 -0
- package/dist/chunks/chunk-T3HH3UEJ.ts.map +7 -0
- package/dist/chunks/chunk-TEN2ZBJV.ts +101 -0
- package/dist/chunks/chunk-TEN2ZBJV.ts.map +7 -0
- package/dist/chunks/chunk-THMF4BVL.ts +18 -0
- package/dist/chunks/chunk-THMF4BVL.ts.map +7 -0
- package/dist/chunks/{chunk-LONPFFNP.ts → chunk-X5OM6GXT.ts} +9 -7
- package/dist/chunks/chunk-X5OM6GXT.ts.map +7 -0
- package/dist/chunks/chunk-XBX4HBLG.ts +57 -0
- package/dist/chunks/chunk-XBX4HBLG.ts.map +7 -0
- package/dist/chunks/{chunk-5WJNWFTG.ts → chunk-YKJHPTGO.ts} +23 -55
- package/dist/chunks/chunk-YKJHPTGO.ts.map +7 -0
- package/dist/chunks/chunk-Z4DQF6L3.ts +12 -0
- package/dist/chunks/chunk-Z4DQF6L3.ts.map +7 -0
- package/dist/chunks/{chunk-F5QL2GPY.ts → chunk-ZI5MXMEX.ts} +5 -99
- package/dist/chunks/chunk-ZI5MXMEX.ts.map +7 -0
- package/dist/chunks/{file-selection-4UABOP44.ts → file-selection-FTTKAOYX.ts} +10 -7
- package/dist/chunks/file-selection-FTTKAOYX.ts.map +7 -0
- package/dist/chunks/{git-backend-E3UOUUOM.ts → git-backend-OQW46G2X.ts} +42 -17
- package/dist/chunks/git-backend-OQW46G2X.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-U23ODD32.ts +22 -0
- package/dist/chunks/manager-ui-ABATHC36.ts +1680 -0
- package/dist/chunks/manager-ui-ABATHC36.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-ZE3TY67S.ts +21 -0
- package/dist/chunks/{s3-backend-3QZGO2EF.ts → s3-backend-3P33LQHU.ts} +96 -18
- package/dist/chunks/s3-backend-3P33LQHU.ts.map +7 -0
- package/dist/chunks/{setup-switch-X3W3FALZ.ts → setup-switch-IS2PEZ3J.ts} +9 -9
- package/dist/chunks/setup-wizard-VB3VDOV5.ts +89 -0
- package/dist/chunks/setup-wizard-VB3VDOV5.ts.map +7 -0
- package/dist/chunks/{snapshot-WBSV4MQG.ts → snapshot-HKEWCVK6.ts} +7 -6
- package/dist/chunks/sync-inspection-FEP4L5TR.ts +20 -0
- package/dist/chunks/{sync-operations-MPAFVVHA.ts → sync-operations-ONJ27QRL.ts} +381 -299
- package/dist/chunks/sync-operations-ONJ27QRL.ts.map +7 -0
- package/dist/chunks/{sync-state-36H7ULG2.ts → sync-state-Q2FTXTW6.ts} +6 -6
- package/dist/chunks/sync-state-Q2FTXTW6.ts.map +7 -0
- package/dist/chunks/{webdav-backend-ZD5RHRIU.ts → webdav-backend-ECIKD7SO.ts} +26 -13
- package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +7 -0
- package/dist/index.ts +477 -342
- package/dist/index.ts.map +4 -4
- package/docs/settings.md +211 -0
- package/package.json +6 -8
- package/src/{backend-factory.ts → backends/backend-factory.ts} +4 -4
- package/src/backends/backend-identity.ts +40 -0
- package/src/backends/backend-types.ts +81 -0
- package/src/{git-backend.ts → backends/git/git-backend.ts} +43 -19
- package/src/{git-config.ts → backends/git/git-config.ts} +1 -0
- package/src/{git-storage.ts → backends/git/git-storage.ts} +2 -2
- package/src/{s3-backend.ts → backends/s3/s3-backend.ts} +45 -9
- package/src/{s3-client.ts → backends/s3/s3-client.ts} +56 -8
- package/src/{sync-backend.ts → backends/sync-backend.ts} +1 -1
- package/src/{webdav-backend.ts → backends/webdav/webdav-backend.ts} +22 -10
- package/src/{webdav-client.ts → backends/webdav/webdav-client.ts} +1 -1
- package/src/{webdav-config.ts → backends/webdav/webdav-config.ts} +1 -0
- package/src/commands/command-execution.ts +330 -0
- package/src/commands/command-handler.ts +201 -0
- package/src/commands/command-types.ts +18 -0
- package/src/{command.ts → commands/command.ts} +2 -1
- package/src/commands/setup-completions.ts +6 -0
- package/src/settings/config.ts +238 -0
- package/src/{settings-management.ts → settings/settings-management.ts} +40 -14
- package/src/settings/settings-store.ts +109 -0
- package/src/settings/settings-types.ts +128 -0
- package/src/settings/settings-validation.ts +443 -0
- package/src/snapshot/session-paths.ts +84 -0
- package/src/{snapshot-apply.ts → snapshot/snapshot-apply.ts} +3 -3
- package/src/{snapshot-codec.ts → snapshot/snapshot-codec.ts} +2 -2
- package/src/{snapshot-transaction.ts → snapshot/snapshot-transaction.ts} +5 -4
- package/src/snapshot/snapshot-types.ts +38 -0
- package/src/{snapshot.ts → snapshot/snapshot.ts} +39 -20
- package/src/state/json-file.ts +20 -0
- package/src/{lock.ts → state/lock.ts} +3 -2
- package/src/{operation-availability.ts → state/operation-availability.ts} +2 -2
- package/src/state/state-types.ts +20 -0
- package/src/state/sync-state-store.ts +62 -0
- package/src/sync/automatic-sync.ts +103 -0
- package/src/{remote-snapshot.ts → sync/remote-snapshot.ts} +27 -4
- package/src/{setup-switch.ts → sync/setup-switch.ts} +6 -7
- package/src/sync/signals.ts +10 -0
- package/src/sync/startup-check.ts +128 -0
- package/src/{sync-decision.ts → sync/sync-decision.ts} +6 -3
- package/src/sync/sync-error-guidance.ts +53 -0
- package/src/sync/sync-inspection.ts +51 -0
- package/src/sync/sync-loaders.ts +54 -0
- package/src/{sync-operations.ts → sync/sync-mutations.ts} +41 -330
- package/src/sync/sync-operations.ts +12 -0
- package/src/{sync-policy.ts → sync/sync-policy.ts} +2 -2
- package/src/sync/sync-queries.ts +235 -0
- package/src/{sync-state.ts → sync/sync-state.ts} +5 -4
- package/src/sync-extension.ts +64 -708
- package/src/sync.ts +8 -8
- package/src/types.ts +47 -284
- package/src/ui/automatic-sync-summary.ts +6 -0
- package/src/{cancellable-operation.ts → ui/cancellable-operation.ts} +4 -4
- package/src/{file-selection.ts → ui/file-selection.ts} +5 -3
- package/src/{manager-attention.ts → ui/manager-attention.ts} +5 -1
- package/src/{manager-recovery.ts → ui/manager-recovery.ts} +6 -2
- package/src/{manager-state.ts → ui/manager-state.ts} +35 -14
- package/src/ui/manager-ui.ts +386 -0
- package/src/{remote-selection-ui.ts → ui/remote-selection-ui.ts} +21 -13
- package/src/{secret-input.ts → ui/secret-input.ts} +22 -4
- package/src/{settings-ui.ts → ui/settings-ui.ts} +42 -11
- package/src/ui/setup/git-ui.ts +293 -0
- package/src/{s3-credentials-ui.ts → ui/setup/s3-credentials-ui.ts} +13 -21
- package/src/ui/setup/s3-ui.ts +217 -0
- package/src/ui/setup/setup-actions.ts +127 -0
- package/src/ui/setup/setup-location-ui.ts +167 -0
- package/src/ui/setup/setup-name-ui.ts +34 -0
- package/src/ui/setup/setup-prompts.ts +92 -0
- package/src/ui/setup/setup-review.ts +70 -0
- package/src/ui/setup/setup-switcher.ts +181 -0
- package/src/ui/setup/setup-wizard.ts +39 -0
- package/src/ui/setup/text-input.ts +78 -0
- package/src/ui/setup/webdav-ui.ts +330 -0
- package/src/{storage-connections-ui.ts → ui/storage-connections-ui.ts} +99 -69
- package/src/{sync-attention.ts → ui/sync-attention.ts} +75 -6
- package/src/{sync-format.ts → ui/sync-format.ts} +7 -13
- package/src/{sync-resolution-ui.ts → ui/sync-resolution-ui.ts} +4 -3
- package/src/{sync-setups-ui.ts → ui/sync-setups-ui.ts} +23 -10
- package/src/ui/terminal-text.ts +4 -0
- package/dist/chunks/chunk-3NDD5QPM.ts.map +0 -7
- package/dist/chunks/chunk-4F3W4CSC.ts.map +0 -7
- package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
- package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
- package/dist/chunks/chunk-5WJNWFTG.ts.map +0 -7
- package/dist/chunks/chunk-APZVBSM6.ts.map +0 -7
- package/dist/chunks/chunk-BHJUISB4.ts.map +0 -7
- package/dist/chunks/chunk-EWX3TPLQ.ts.map +0 -7
- package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
- package/dist/chunks/chunk-FR6CVXAL.ts.map +0 -7
- package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
- package/dist/chunks/chunk-JNOOSPEW.ts +0 -82
- package/dist/chunks/chunk-JNOOSPEW.ts.map +0 -7
- package/dist/chunks/chunk-LJDSHNS3.ts.map +0 -7
- package/dist/chunks/chunk-LONPFFNP.ts.map +0 -7
- package/dist/chunks/chunk-RGJ6SOYR.ts.map +0 -7
- package/dist/chunks/chunk-W64NEHXT.ts +0 -167
- package/dist/chunks/chunk-W64NEHXT.ts.map +0 -7
- package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
- package/dist/chunks/file-selection-4UABOP44.ts.map +0 -7
- package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-RNRQA3PW.ts +0 -18
- package/dist/chunks/manager-ui-QO2P4V3Z.ts +0 -2757
- package/dist/chunks/manager-ui-QO2P4V3Z.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-LHSAGKGA.ts +0 -17
- package/dist/chunks/s3-backend-3QZGO2EF.ts.map +0 -7
- package/dist/chunks/sync-operations-MPAFVVHA.ts.map +0 -7
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
- package/src/config.ts +0 -910
- package/src/git-ui.ts +0 -307
- package/src/manager-helpers.ts +0 -64
- package/src/manager-ui.ts +0 -997
- package/src/webdav-ui.ts +0 -421
- /package/dist/chunks/{manager-result-dispatcher-RNRQA3PW.ts.map → manager-result-dispatcher-U23ODD32.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-LHSAGKGA.ts.map → remote-selection-ui-ZE3TY67S.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-X3W3FALZ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
- /package/dist/chunks/{snapshot-WBSV4MQG.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
- /package/dist/chunks/{sync-state-36H7ULG2.ts.map → sync-inspection-FEP4L5TR.ts.map} +0 -0
- /package/src/{git-runner.ts → backends/git/git-runner.ts} +0 -0
- /package/src/{config-errors.ts → settings/config-errors.ts} +0 -0
- /package/src/{config-file.ts → settings/config-file.ts} +0 -0
- /package/src/{snapshot-paths.ts → snapshot/snapshot-paths.ts} +0 -0
- /package/src/{lock-policy.ts → state/lock-policy.ts} +0 -0
- /package/src/{lockfile-fs.ts → state/lockfile-fs.ts} +0 -0
- /package/src/{state-directory.ts → state/state-directory.ts} +0 -0
- /package/src/{sync-errors.ts → sync/sync-errors.ts} +0 -0
- /package/src/{manager-result-dispatcher.ts → ui/manager-result-dispatcher.ts} +0 -0
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
2
|
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readSnapshotForHead,
|
|
7
|
-
requireCompatibleRemoteSelection
|
|
8
|
-
} from "./chunk-FR6CVXAL.ts";
|
|
9
|
-
import {
|
|
10
|
-
applySnapshotTransaction,
|
|
11
|
-
recoverPendingSnapshotTransactions
|
|
12
|
-
} from "./chunk-3NDD5QPM.ts";
|
|
13
|
-
import {
|
|
14
|
-
inspectLock,
|
|
15
|
-
isLockGuardHeld,
|
|
16
|
-
isStaleLock,
|
|
17
|
-
withLock
|
|
18
|
-
} from "./chunk-LJDSHNS3.ts";
|
|
19
|
-
import {
|
|
20
|
-
SyncDecisionRequiredError,
|
|
21
|
-
countPreservedRemoteFiles,
|
|
22
|
-
errorMessage,
|
|
23
|
-
formatDiff,
|
|
24
|
-
formatPullSummary,
|
|
25
|
-
formatPushSummary,
|
|
26
|
-
formatRollbackSummary,
|
|
27
|
-
formatSnapshotOnlyDiff,
|
|
28
|
-
publicationCapabilityDescription,
|
|
29
|
-
safeTerminalText
|
|
30
|
-
} from "./chunk-W64NEHXT.ts";
|
|
4
|
+
inspectSync
|
|
5
|
+
} from "./chunk-XBX4HBLG.ts";
|
|
31
6
|
import {
|
|
32
7
|
canPullRemoteSessionsOnFirstSync,
|
|
33
8
|
canPullRemoteSettingsOnFirstSync,
|
|
@@ -40,7 +15,56 @@ import {
|
|
|
40
15
|
snapshotHashesMatchState,
|
|
41
16
|
snapshotsMatch,
|
|
42
17
|
syncPolicyChanged
|
|
43
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-AUSDLD6F.ts";
|
|
19
|
+
import {
|
|
20
|
+
applySnapshotTransaction,
|
|
21
|
+
recoverPendingSnapshotTransactions
|
|
22
|
+
} from "./chunk-AXXS2BPA.ts";
|
|
23
|
+
import {
|
|
24
|
+
inspectLock,
|
|
25
|
+
isLockGuardHeld,
|
|
26
|
+
isStaleLock,
|
|
27
|
+
withLock
|
|
28
|
+
} from "./chunk-GWMHAQG4.ts";
|
|
29
|
+
import {
|
|
30
|
+
throwIfAborted
|
|
31
|
+
} from "./chunk-NJFUIVYL.ts";
|
|
32
|
+
import {
|
|
33
|
+
readStateForConfig,
|
|
34
|
+
writeStateForConfig
|
|
35
|
+
} from "./chunk-JSYMGJQQ.ts";
|
|
36
|
+
import {
|
|
37
|
+
encodeSnapshot
|
|
38
|
+
} from "./chunk-Q2TUM5X6.ts";
|
|
39
|
+
import {
|
|
40
|
+
stateDir
|
|
41
|
+
} from "./chunk-ZI5MXMEX.ts";
|
|
42
|
+
import {
|
|
43
|
+
expectedRemoteHead
|
|
44
|
+
} from "./chunk-D5X27LCA.ts";
|
|
45
|
+
import {
|
|
46
|
+
formatRemoteSelectionStatus,
|
|
47
|
+
readRemoteSnapshot,
|
|
48
|
+
readSnapshotForHead,
|
|
49
|
+
requireCompatibleRemoteSelection
|
|
50
|
+
} from "./chunk-HUDQATCL.ts";
|
|
51
|
+
import {
|
|
52
|
+
createSyncBackend
|
|
53
|
+
} from "./chunk-L7TBO32M.ts";
|
|
54
|
+
import {
|
|
55
|
+
safeTerminalText
|
|
56
|
+
} from "./chunk-LTBWGSBV.ts";
|
|
57
|
+
import {
|
|
58
|
+
loadConfig,
|
|
59
|
+
syncConfigReviewIdentity,
|
|
60
|
+
syncSessionsWarnings
|
|
61
|
+
} from "./chunk-C7XY5NF5.ts";
|
|
62
|
+
import "./chunk-6LP3MCR2.ts";
|
|
63
|
+
import "./chunk-TEN2ZBJV.ts";
|
|
64
|
+
import {
|
|
65
|
+
SyncDecisionRequiredError,
|
|
66
|
+
errorMessage
|
|
67
|
+
} from "./chunk-2B2ELUF5.ts";
|
|
44
68
|
import {
|
|
45
69
|
createSnapshot,
|
|
46
70
|
filterSnapshotForConfigPolicy,
|
|
@@ -50,30 +74,19 @@ import {
|
|
|
50
74
|
regenerateSnapshotIdentity,
|
|
51
75
|
scanSnapshot,
|
|
52
76
|
sessionSnapshotPathFromAbsolute,
|
|
53
|
-
sessionStorageRoot,
|
|
54
77
|
snapshotIncludesSessions,
|
|
55
78
|
snapshotTarget,
|
|
56
79
|
snapshotWithoutSessions
|
|
57
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-OEX4ABY6.ts";
|
|
81
|
+
import {
|
|
82
|
+
sessionStorageRoot
|
|
83
|
+
} from "./chunk-THMF4BVL.ts";
|
|
58
84
|
import {
|
|
59
85
|
agentDir,
|
|
60
|
-
loadConfig,
|
|
61
|
-
readStateForConfig,
|
|
62
86
|
sessionDirForApply,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
writeStateForConfig
|
|
67
|
-
} from "./chunk-APZVBSM6.ts";
|
|
68
|
-
import {
|
|
69
|
-
encodeSnapshot
|
|
70
|
-
} from "./chunk-HGV24P4T.ts";
|
|
71
|
-
import {
|
|
72
|
-
stateDir
|
|
73
|
-
} from "./chunk-F5QL2GPY.ts";
|
|
74
|
-
import {
|
|
75
|
-
expectedRemoteHead
|
|
76
|
-
} from "./chunk-5MQ76BK5.ts";
|
|
87
|
+
sessionDirFromContext,
|
|
88
|
+
snapshotOptionsForContext
|
|
89
|
+
} from "./chunk-T3HH3UEJ.ts";
|
|
77
90
|
import {
|
|
78
91
|
assertWithinRoot,
|
|
79
92
|
includeFromSelectionConfig,
|
|
@@ -81,16 +94,15 @@ import {
|
|
|
81
94
|
isDeniedPath,
|
|
82
95
|
isPathInside,
|
|
83
96
|
parentPaths,
|
|
84
|
-
remoteSelectionMismatch,
|
|
85
97
|
safeJoin,
|
|
86
98
|
toPosix
|
|
87
|
-
} from "./chunk-
|
|
99
|
+
} from "./chunk-JAAKLL6M.ts";
|
|
88
100
|
|
|
89
|
-
// src/sync-
|
|
101
|
+
// src/sync/sync-mutations.ts
|
|
90
102
|
import fs2 from "node:fs/promises";
|
|
91
103
|
import path2 from "node:path";
|
|
92
104
|
|
|
93
|
-
// src/snapshot-apply.ts
|
|
105
|
+
// src/snapshot/snapshot-apply.ts
|
|
94
106
|
import { createHash } from "node:crypto";
|
|
95
107
|
import fs from "node:fs/promises";
|
|
96
108
|
import path from "node:path";
|
|
@@ -297,7 +309,125 @@ async function assertNoSymlinkParents(root, target) {
|
|
|
297
309
|
}
|
|
298
310
|
}
|
|
299
311
|
|
|
300
|
-
// src/sync-
|
|
312
|
+
// src/ui/sync-format.ts
|
|
313
|
+
function formatDiff(local, remote) {
|
|
314
|
+
const localMap = fileHashMap(local);
|
|
315
|
+
const remoteMap = fileHashMap(remote);
|
|
316
|
+
const allPaths = [.../* @__PURE__ */ new Set([...Object.keys(localMap), ...Object.keys(remoteMap)])].sort();
|
|
317
|
+
const lines = [
|
|
318
|
+
`local: ${local.files.length} files`,
|
|
319
|
+
`remote: ${remote.id} (${remote.files.length} files)`,
|
|
320
|
+
""
|
|
321
|
+
];
|
|
322
|
+
let changed = 0;
|
|
323
|
+
for (const filePath of allPaths) {
|
|
324
|
+
if (!localMap[filePath]) {
|
|
325
|
+
lines.push(`Remote only: ${filePath}`);
|
|
326
|
+
changed += 1;
|
|
327
|
+
} else if (!remoteMap[filePath]) {
|
|
328
|
+
lines.push(`Local only: ${filePath}`);
|
|
329
|
+
changed += 1;
|
|
330
|
+
} else if (localMap[filePath] !== remoteMap[filePath]) {
|
|
331
|
+
lines.push(`Different: ${filePath}`);
|
|
332
|
+
changed += 1;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (changed === 0) lines.push("No file differences.");
|
|
336
|
+
return lines.join("\n");
|
|
337
|
+
}
|
|
338
|
+
function formatSnapshotOnlyDiff(title, snapshot) {
|
|
339
|
+
return [`${title}: ${snapshot.id}`, ...snapshot.files.map((file) => `Add: ${file.path}`)].join(
|
|
340
|
+
"\n"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function formatPushSummary(config, destination, upload, head, preservedRemoteFileCount = 0, remote) {
|
|
344
|
+
return [
|
|
345
|
+
`Sync setup: ${safeTerminalText(config.setupName)}`,
|
|
346
|
+
`Storage location: ${safeTerminalText(destination)}`,
|
|
347
|
+
`Upload ${upload.files.length} files from ${safeTerminalText(agentDir())}.`,
|
|
348
|
+
`Sessions: ${upload.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
|
|
349
|
+
head ? `Remote latest: ${head.snapshotId}` : "Remote latest: empty",
|
|
350
|
+
"Publication effect: the backend's active head will reference the new immutable snapshot.",
|
|
351
|
+
...remote && inspectRemoteSelection(config.include, remote).kind === "different" ? [
|
|
352
|
+
"Included-content policy effect: replace the differing remote selection with this setup's local selection."
|
|
353
|
+
] : [],
|
|
354
|
+
formatPublicationPreview(remote, upload),
|
|
355
|
+
preservedRemoteFileCount > 0 ? `Possible secrets in locally managed files were scanned before this prompt; ${preservedRemoteFileCount} preserved remote file(s) were not rescanned.` : "Possible secrets were scanned before this prompt."
|
|
356
|
+
].join("\n");
|
|
357
|
+
}
|
|
358
|
+
function formatApplyPreview(local, remote) {
|
|
359
|
+
return formatDirectionalChanges(local, remote, {
|
|
360
|
+
add: "Add locally",
|
|
361
|
+
update: "Update locally",
|
|
362
|
+
remove: "Remove locally"
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function formatPullSummary(config, destination, local, remote, protectedSessionCount) {
|
|
366
|
+
return [
|
|
367
|
+
`Sync setup: ${safeTerminalText(config.setupName)}`,
|
|
368
|
+
`Storage location: ${safeTerminalText(destination)}`,
|
|
369
|
+
`Snapshot: ${safeTerminalText(remote.id)}`,
|
|
370
|
+
`Sessions: ${remote.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
|
|
371
|
+
`Protected live sessions: ${protectedSessionCount || "none"}`,
|
|
372
|
+
formatApplyPreview(local, remote),
|
|
373
|
+
"A local backup is created before these writes/deletes. The remote active snapshot is unchanged."
|
|
374
|
+
].join("\n");
|
|
375
|
+
}
|
|
376
|
+
function formatRollbackSummary(config, destination, local, remote, requestedSnapshot, protectedSessionCount) {
|
|
377
|
+
return [
|
|
378
|
+
`Sync setup: ${safeTerminalText(config.setupName)}`,
|
|
379
|
+
`Storage location: ${safeTerminalText(destination)}`,
|
|
380
|
+
`Snapshot: ${safeTerminalText(requestedSnapshot)}`,
|
|
381
|
+
`Sessions: ${remote.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
|
|
382
|
+
`Protected live sessions: ${protectedSessionCount || "none"}`,
|
|
383
|
+
formatApplyPreview(local, remote),
|
|
384
|
+
"A local backup is created before applying; the backend's active head will change."
|
|
385
|
+
].join("\n");
|
|
386
|
+
}
|
|
387
|
+
function formatPublicationPreview(remote, upload) {
|
|
388
|
+
if (!remote) {
|
|
389
|
+
return ["Remote is empty.", ...upload.files.map((file) => `Add remotely: ${file.path}`)].join(
|
|
390
|
+
"\n"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
return formatDirectionalChanges(remote, upload, {
|
|
394
|
+
add: "Add remotely",
|
|
395
|
+
update: "Update remotely",
|
|
396
|
+
remove: "Remove remotely"
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function formatDirectionalChanges(before, after, labels) {
|
|
400
|
+
const beforeMap = fileHashMap(before);
|
|
401
|
+
const afterMap = fileHashMap(after);
|
|
402
|
+
const paths = [.../* @__PURE__ */ new Set([...Object.keys(beforeMap), ...Object.keys(afterMap)])].sort();
|
|
403
|
+
const lines = [];
|
|
404
|
+
for (const filePath of paths) {
|
|
405
|
+
if (!beforeMap[filePath]) lines.push(`${labels.add}: ${filePath}`);
|
|
406
|
+
else if (!afterMap[filePath]) lines.push(`${labels.remove}: ${filePath}`);
|
|
407
|
+
else if (beforeMap[filePath] !== afterMap[filePath])
|
|
408
|
+
lines.push(`${labels.update}: ${filePath}`);
|
|
409
|
+
}
|
|
410
|
+
if (lines.length === 0) lines.push("No file changes.");
|
|
411
|
+
return lines.join("\n");
|
|
412
|
+
}
|
|
413
|
+
function countPreservedRemoteFiles(local, upload) {
|
|
414
|
+
const localPaths = new Set(local.files.map((file) => file.path));
|
|
415
|
+
return upload.files.filter((file) => !localPaths.has(file.path)).length;
|
|
416
|
+
}
|
|
417
|
+
function publicationCapabilityDescription(capability) {
|
|
418
|
+
switch (capability) {
|
|
419
|
+
case "lease-protected":
|
|
420
|
+
return "lease-protected (exact expected-ref update)";
|
|
421
|
+
case "atomic-conditional":
|
|
422
|
+
return "atomic-conditional (verified atomic precondition)";
|
|
423
|
+
case "conditional-required":
|
|
424
|
+
return "conditional-required (read-only until atomic preconditions are verified)";
|
|
425
|
+
case "read-check-write-verify":
|
|
426
|
+
return "read-check-write-verify (visible races rejected; simultaneous unseen races remain possible)";
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/sync/sync-decision.ts
|
|
301
431
|
function createSyncDecision(options) {
|
|
302
432
|
const { config, state, local, remote, kind } = options;
|
|
303
433
|
const policyChanged = Boolean(state.lastAppliedSnapshot) && syncPolicyChanged(state, config);
|
|
@@ -344,10 +474,9 @@ function safeList(values) {
|
|
|
344
474
|
return values.length > 0 ? values.map(safeTerminalText).join(", ") : "none";
|
|
345
475
|
}
|
|
346
476
|
|
|
347
|
-
// src/sync-
|
|
477
|
+
// src/sync/sync-mutations.ts
|
|
348
478
|
var STATUS_KEY = "sync";
|
|
349
479
|
var VERSION = 1;
|
|
350
|
-
var DEFAULT_PROFILE = "default";
|
|
351
480
|
var POST_LOCAL_COMMIT_TIMEOUT_MS = 3e4;
|
|
352
481
|
var PublicationStatePersistenceError = class extends Error {
|
|
353
482
|
head;
|
|
@@ -373,173 +502,11 @@ var RollbackPublicationError = class extends Error {
|
|
|
373
502
|
this.backupPath = backupPath;
|
|
374
503
|
}
|
|
375
504
|
};
|
|
376
|
-
function backendFor(config, factory) {
|
|
377
|
-
return factory(config);
|
|
378
|
-
}
|
|
379
|
-
async function status(ctx, options, factory = createSyncBackend) {
|
|
380
|
-
const config = await loadConfig(options.setup);
|
|
381
|
-
throwIfAborted(options.signal);
|
|
382
|
-
ctx.ui.setStatus(STATUS_KEY, `checking ${config.setupName}`);
|
|
383
|
-
const backend = await backendFor(config, factory);
|
|
384
|
-
const local = await createSnapshot(
|
|
385
|
-
config.snapshotIdentity,
|
|
386
|
-
snapshotOptionsForContext(ctx, config)
|
|
387
|
-
);
|
|
388
|
-
throwIfAborted(options.signal);
|
|
389
|
-
const state = await readStateForConfig(config);
|
|
390
|
-
throwIfAborted(options.signal);
|
|
391
|
-
const head = await backend.readHead(options.signal);
|
|
392
|
-
throwIfAborted(options.signal);
|
|
393
|
-
const selectionState = head ? inspectRemoteSelection(config.include, { selection: head.selection, files: [] }) : void 0;
|
|
394
|
-
const localChanged = hasLocalChanges(local, state, config);
|
|
395
|
-
const remoteText = head ? `remote: ${head.snapshotId} from ${head.machine} at ${head.createdAt}` : "remote: empty";
|
|
396
|
-
const remoteChanged = remoteChangedSinceState(
|
|
397
|
-
head,
|
|
398
|
-
state,
|
|
399
|
-
config,
|
|
400
|
-
(left, right) => backend.sameRevision(left, right)
|
|
401
|
-
);
|
|
402
|
-
const warnings = syncSessionsWarnings(config);
|
|
403
|
-
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
404
|
-
ctx.ui.notify(
|
|
405
|
-
[
|
|
406
|
-
`sync setup: ${config.setupName}`,
|
|
407
|
-
`storage connection: ${config.connectionName}`,
|
|
408
|
-
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
409
|
-
`publication safety: ${publicationCapabilityDescription(backend.capability)}`,
|
|
410
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
411
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
412
|
-
remoteText,
|
|
413
|
-
formatRemoteSelectionStatus(selectionState),
|
|
414
|
-
`local files: ${local.files.length}`,
|
|
415
|
-
`local changed since last sync: ${localChanged ? "yes" : "no"}`,
|
|
416
|
-
`remote changed since last sync: ${remoteChanged ? "yes" : "no"}`,
|
|
417
|
-
...warnings
|
|
418
|
-
].join("\n"),
|
|
419
|
-
localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0 ? "warning" : "info"
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
async function diff(ctx, options, factory = createSyncBackend) {
|
|
423
|
-
const config = await loadConfig(options.setup);
|
|
424
|
-
throwIfAborted(options.signal);
|
|
425
|
-
ctx.ui.setStatus(STATUS_KEY, `checking ${config.setupName}`);
|
|
426
|
-
const backend = await backendFor(config, factory);
|
|
427
|
-
const local = await createSnapshot(
|
|
428
|
-
config.snapshotIdentity,
|
|
429
|
-
snapshotOptionsForContext(ctx, config)
|
|
430
|
-
);
|
|
431
|
-
throwIfAborted(options.signal);
|
|
432
|
-
const { snapshot: remote, selectionState } = await readRemoteSnapshot(
|
|
433
|
-
backend,
|
|
434
|
-
config,
|
|
435
|
-
options.signal,
|
|
436
|
-
{ allowSelectionDifference: true }
|
|
437
|
-
);
|
|
438
|
-
throwIfAborted(options.signal);
|
|
439
|
-
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
440
|
-
const warnings = syncSessionsWarnings(config);
|
|
441
|
-
const header = [
|
|
442
|
-
`sync setup: ${config.setupName}`,
|
|
443
|
-
`storage connection: ${config.connectionName}`,
|
|
444
|
-
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
445
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
446
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
447
|
-
formatRemoteSelectionStatus(selectionState),
|
|
448
|
-
...warnings
|
|
449
|
-
].join("\n");
|
|
450
|
-
const level = warnings.length > 0 || selectionState?.kind === "different" ? "warning" : "info";
|
|
451
|
-
if (!remote) {
|
|
452
|
-
ctx.ui.notify(
|
|
453
|
-
`${header}
|
|
454
|
-
|
|
455
|
-
${formatSnapshotOnlyDiff("Remote is empty. Local push would upload", local)}`,
|
|
456
|
-
level
|
|
457
|
-
);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
ctx.ui.notify(`${header}
|
|
461
|
-
|
|
462
|
-
${formatDiff(local, remote)}`, level);
|
|
463
|
-
}
|
|
464
|
-
async function doctor(ctx, options, factory = createSyncBackend) {
|
|
465
|
-
const messages = [];
|
|
466
|
-
let level = "info";
|
|
467
|
-
let snapshotOptions = {};
|
|
468
|
-
let profile = DEFAULT_PROFILE;
|
|
469
|
-
let backend;
|
|
470
|
-
let backendSummary = [];
|
|
471
|
-
try {
|
|
472
|
-
const config = await loadConfig(options.setup);
|
|
473
|
-
throwIfAborted(options.signal);
|
|
474
|
-
backend = await backendFor(config, factory);
|
|
475
|
-
profile = config.snapshotIdentity;
|
|
476
|
-
snapshotOptions = snapshotOptionsForContext(ctx, config);
|
|
477
|
-
messages.push(
|
|
478
|
-
`config: ok (sync setup ${config.setupName})`,
|
|
479
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
480
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`
|
|
481
|
-
);
|
|
482
|
-
backendSummary = [
|
|
483
|
-
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
484
|
-
`publication safety: ${publicationCapabilityDescription(backend.capability)}`
|
|
485
|
-
];
|
|
486
|
-
const warnings = syncSessionsWarnings(config);
|
|
487
|
-
if (warnings.length > 0) {
|
|
488
|
-
level = "warning";
|
|
489
|
-
messages.push(...warnings);
|
|
490
|
-
}
|
|
491
|
-
} catch (error) {
|
|
492
|
-
throwIfAborted(options.signal);
|
|
493
|
-
level = "warning";
|
|
494
|
-
messages.push(`config: ${errorMessage(error)}`);
|
|
495
|
-
}
|
|
496
|
-
const local = await createSnapshot(profile, snapshotOptions);
|
|
497
|
-
throwIfAborted(options.signal);
|
|
498
|
-
const secrets = scanSnapshot(local);
|
|
499
|
-
if (secrets.length > 0) {
|
|
500
|
-
level = "warning";
|
|
501
|
-
messages.push("secret scan: possible secrets found:");
|
|
502
|
-
messages.push(...secrets.map((secret) => `- ${secret}`));
|
|
503
|
-
} else {
|
|
504
|
-
messages.push(`secret scan: ok (${local.files.length} files checked)`);
|
|
505
|
-
}
|
|
506
|
-
const lock = await inspectLock();
|
|
507
|
-
throwIfAborted(options.signal);
|
|
508
|
-
if (lock.status === "valid" && isStaleLock(lock.lock)) {
|
|
509
|
-
level = "warning";
|
|
510
|
-
messages.push(
|
|
511
|
-
`lock: stale (pid ${lock.lock.pid}); run /sync unlock after verifying no sync is running`
|
|
512
|
-
);
|
|
513
|
-
} else if (lock.status === "valid") {
|
|
514
|
-
messages.push(`lock: held by pid ${lock.lock.pid} since ${lock.lock.startedAt}`);
|
|
515
|
-
} else if (lock.status === "unreadable") {
|
|
516
|
-
level = "warning";
|
|
517
|
-
messages.push(
|
|
518
|
-
"lock: unreadable; use /sync unlock --stale only after verifying no sync is running"
|
|
519
|
-
);
|
|
520
|
-
} else if (await isLockGuardHeld()) {
|
|
521
|
-
throwIfAborted(options.signal);
|
|
522
|
-
level = "warning";
|
|
523
|
-
messages.push("lock: guard active while metadata is missing or still being initialized");
|
|
524
|
-
} else {
|
|
525
|
-
messages.push("lock: free");
|
|
526
|
-
}
|
|
527
|
-
if (backend) {
|
|
528
|
-
messages.push(...backendSummary);
|
|
529
|
-
const diagnostics = await backend.diagnose(options.signal);
|
|
530
|
-
throwIfAborted(options.signal);
|
|
531
|
-
for (const diagnostic of diagnostics) {
|
|
532
|
-
messages.push(diagnostic.message);
|
|
533
|
-
if (diagnostic.level !== "info") level = "warning";
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
ctx.ui.notify(messages.join("\n"), level);
|
|
537
|
-
}
|
|
538
505
|
async function push(ctx, options, input, factory = createSyncBackend) {
|
|
539
506
|
const config = input?.config ?? await loadConfig(options.setup);
|
|
540
507
|
throwIfAborted(options.signal);
|
|
541
508
|
ctx.ui.setStatus(STATUS_KEY, `pushing ${config.setupName}`);
|
|
542
|
-
const backend = input?.backend ?? await
|
|
509
|
+
const backend = input?.backend ?? await factory(config);
|
|
543
510
|
const state = input?.state ?? await readStateForConfig(config);
|
|
544
511
|
throwIfAborted(options.signal);
|
|
545
512
|
const local = input?.local ?? await createSnapshot(config.snapshotIdentity, snapshotOptionsForContext(ctx, config));
|
|
@@ -586,12 +553,14 @@ async function push(ctx, options, input, factory = createSyncBackend) {
|
|
|
586
553
|
remoteForUpload,
|
|
587
554
|
options.signal
|
|
588
555
|
);
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
556
|
+
if (!config.skipSecretScan) {
|
|
557
|
+
const secrets = scanSnapshot(local);
|
|
558
|
+
if (secrets.length > 0) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`Refusing to push possible secrets:
|
|
593
561
|
${secrets.map((s) => `- ${s}`).join("\n")}`
|
|
594
|
-
|
|
562
|
+
);
|
|
563
|
+
}
|
|
595
564
|
}
|
|
596
565
|
if (!await confirmPush(ctx, options, config, backend, local, upload, head, remoteForUpload)) {
|
|
597
566
|
return "cancelled";
|
|
@@ -657,7 +626,7 @@ async function pull(ctx, options, factory = createSyncBackend) {
|
|
|
657
626
|
const config = await loadConfig(options.setup);
|
|
658
627
|
throwIfAborted(options.signal);
|
|
659
628
|
ctx.ui.setStatus(STATUS_KEY, `pulling ${config.setupName}`);
|
|
660
|
-
const backend = await
|
|
629
|
+
const backend = await factory(config);
|
|
661
630
|
const state = await readStateForConfig(config);
|
|
662
631
|
throwIfAborted(options.signal);
|
|
663
632
|
const local = await createSnapshot(
|
|
@@ -746,7 +715,7 @@ async function pull(ctx, options, factory = createSyncBackend) {
|
|
|
746
715
|
async function syncBoth(ctx, options, factory = createSyncBackend) {
|
|
747
716
|
const config = await loadConfig(options.setup);
|
|
748
717
|
throwIfAborted(options.signal);
|
|
749
|
-
const backend = await
|
|
718
|
+
const backend = await factory(config);
|
|
750
719
|
const state = await readStateForConfig(config);
|
|
751
720
|
throwIfAborted(options.signal);
|
|
752
721
|
const local = await createSnapshot(
|
|
@@ -859,52 +828,12 @@ async function syncBoth(ctx, options, factory = createSyncBackend) {
|
|
|
859
828
|
}
|
|
860
829
|
if (!options.silent) ctx.ui.notify("pi-sync is already up to date.", "info");
|
|
861
830
|
}
|
|
862
|
-
async function history(ctx, options, factory = createSyncBackend) {
|
|
863
|
-
const config = await loadConfig(options.setup);
|
|
864
|
-
throwIfAborted(options.signal);
|
|
865
|
-
const backend = await backendFor(config, factory);
|
|
866
|
-
const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
|
|
867
|
-
throwIfAborted(options.signal);
|
|
868
|
-
if (snapshots.length === 0) {
|
|
869
|
-
ctx.ui.notify("No remote pi-sync history found.", "info");
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
const currentSnapshot = snapshots[0]?.snapshotId;
|
|
873
|
-
if (ctx.mode === "tui") {
|
|
874
|
-
const labels = snapshots.map(
|
|
875
|
-
(item, index2) => `${index2 + 1}. ${item.createdAt} \xB7 ${safeTerminalText(item.machine)} \xB7 ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " \xB7 sessions" : ""}`
|
|
876
|
-
);
|
|
877
|
-
const selected = await ctx.ui.select(
|
|
878
|
-
`History for sync setup \u201C${safeTerminalText(config.setupName)}\u201D
|
|
879
|
-
|
|
880
|
-
Choose a snapshot to preview rollback.`,
|
|
881
|
-
[...labels, "Back"]
|
|
882
|
-
);
|
|
883
|
-
if (!selected || selected === "Back") return;
|
|
884
|
-
throwIfAborted(options.signal);
|
|
885
|
-
const index = labels.indexOf(selected);
|
|
886
|
-
const snapshot = snapshots[index];
|
|
887
|
-
if (!snapshot) return;
|
|
888
|
-
await withLock(
|
|
889
|
-
"rollback",
|
|
890
|
-
() => rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
|
|
891
|
-
backendIdentity: backend.identity,
|
|
892
|
-
setup: config.setupName
|
|
893
|
-
})
|
|
894
|
-
);
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
ctx.ui.notify(
|
|
898
|
-
snapshots.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`).join("\n"),
|
|
899
|
-
"info"
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
831
|
async function rollback(ctx, options, factory = createSyncBackend, expectedSelection) {
|
|
903
832
|
const target = options.args[0];
|
|
904
833
|
if (!target) throw new Error("Usage: /sync rollback <snapshot-id> [--yes]");
|
|
905
834
|
const config = await loadConfig(options.setup);
|
|
906
835
|
throwIfAborted(options.signal);
|
|
907
|
-
const backend = await
|
|
836
|
+
const backend = await factory(config);
|
|
908
837
|
if (expectedSelection && (backend.identity !== expectedSelection.backendIdentity || config.setupName !== expectedSelection.setup)) {
|
|
909
838
|
throw new Error(
|
|
910
839
|
"Sync setup or storage location changed while history was open; reopen history and retry."
|
|
@@ -996,21 +925,6 @@ function protectedSessionPaths(ctx) {
|
|
|
996
925
|
const snapshotPath = sessionFile ? sessionSnapshotPathFromAbsolute(sessionFile, sessionDirFromContext(ctx)) : void 0;
|
|
997
926
|
return snapshotPath ? /* @__PURE__ */ new Set([snapshotPath]) : /* @__PURE__ */ new Set();
|
|
998
927
|
}
|
|
999
|
-
function snapshotOptionsForContext(ctx, config) {
|
|
1000
|
-
return {
|
|
1001
|
-
include: config.include,
|
|
1002
|
-
sessionDir: sessionDirFromContext(ctx)
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
function sessionDirFromContext(ctx) {
|
|
1006
|
-
const manager = ctx.sessionManager;
|
|
1007
|
-
const usesDefaultSessionDir = manager.usesDefaultSessionDir;
|
|
1008
|
-
if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
|
|
1009
|
-
return void 0;
|
|
1010
|
-
}
|
|
1011
|
-
const getSessionDir = manager.getSessionDir;
|
|
1012
|
-
return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
|
|
1013
|
-
}
|
|
1014
928
|
async function maybeReload(ctx, signal) {
|
|
1015
929
|
if (signal?.aborted || !("reload" in ctx)) return;
|
|
1016
930
|
if (ctx.hasUI && await ctx.ui.confirm(
|
|
@@ -1040,21 +954,6 @@ async function snapshotForUpload(backend, config, local, head, remote, signal, o
|
|
|
1040
954
|
}
|
|
1041
955
|
return mergeRemotePreservedFiles(local, snapshot, config);
|
|
1042
956
|
}
|
|
1043
|
-
async function readRemoteSnapshot(backend, config, signal, options = {}) {
|
|
1044
|
-
const head = await backend.readHead(signal);
|
|
1045
|
-
if (!head) return { head: void 0, snapshot: void 0, selectionState: void 0 };
|
|
1046
|
-
const snapshot = await readSnapshotForHead(backend, head, signal);
|
|
1047
|
-
const selectionState = inspectRemoteSelection(config.include, snapshot);
|
|
1048
|
-
if (!options.allowSelectionDifference && selectionState.kind === "different") {
|
|
1049
|
-
const configIdentity = syncConfigReviewFingerprint(config);
|
|
1050
|
-
throw remoteSelectionMismatch(config, selectionState.include, configIdentity);
|
|
1051
|
-
}
|
|
1052
|
-
return {
|
|
1053
|
-
head,
|
|
1054
|
-
snapshot: filterSnapshotForConfigPolicy(snapshot, config),
|
|
1055
|
-
selectionState
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
957
|
async function confirmPush(ctx, options, config, backend, local, upload, head, remote, title = snapshotIncludesSessions(upload) ? "Push pi settings and sessions?" : "Push pi settings?") {
|
|
1059
958
|
throwIfAborted(options.signal);
|
|
1060
959
|
if (options.yes) return true;
|
|
@@ -1075,10 +974,6 @@ async function confirmPush(ctx, options, config, backend, local, upload, head, r
|
|
|
1075
974
|
ctx.ui.notify("Push cancelled.", "info");
|
|
1076
975
|
return false;
|
|
1077
976
|
}
|
|
1078
|
-
function throwIfAborted(signal) {
|
|
1079
|
-
if (!signal?.aborted) return;
|
|
1080
|
-
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
1081
|
-
}
|
|
1082
977
|
function sameRemoteHead(backend, left, right) {
|
|
1083
978
|
if (!left || !right) return left === right;
|
|
1084
979
|
return backend.sameRevision(left.revision, right.revision);
|
|
@@ -1096,6 +991,193 @@ async function backupLocal(profile, options = {}, signal) {
|
|
|
1096
991
|
await fs2.writeFile(backupPath, encoded, { signal });
|
|
1097
992
|
return backupPath;
|
|
1098
993
|
}
|
|
994
|
+
|
|
995
|
+
// src/sync/sync-queries.ts
|
|
996
|
+
var STATUS_KEY2 = "sync";
|
|
997
|
+
var DEFAULT_PROFILE = "default";
|
|
998
|
+
async function status(ctx, options, factory = createSyncBackend) {
|
|
999
|
+
const config = await loadConfig(options.setup);
|
|
1000
|
+
throwIfAborted(options.signal);
|
|
1001
|
+
ctx.ui.setStatus(STATUS_KEY2, `checking ${config.setupName}`);
|
|
1002
|
+
const { head, selectionState, localChanged, remoteChanged, localFiles, destination, capability } = await inspectSync(config, snapshotOptionsForContext(ctx, config), options.signal, factory);
|
|
1003
|
+
throwIfAborted(options.signal);
|
|
1004
|
+
const remoteText = head ? `remote: ${head.snapshotId} from ${head.machine} at ${head.createdAt}` : "remote: empty";
|
|
1005
|
+
const warnings = syncSessionsWarnings(config);
|
|
1006
|
+
ctx.ui.setStatus(STATUS_KEY2, void 0);
|
|
1007
|
+
ctx.ui.notify(
|
|
1008
|
+
[
|
|
1009
|
+
`sync setup: ${config.setupName}`,
|
|
1010
|
+
`storage connection: ${config.connectionName}`,
|
|
1011
|
+
`storage location: ${safeTerminalText(destination)}`,
|
|
1012
|
+
`publication safety: ${publicationCapabilityDescription(capability)}`,
|
|
1013
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
1014
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
1015
|
+
remoteText,
|
|
1016
|
+
formatRemoteSelectionStatus(selectionState),
|
|
1017
|
+
`local files: ${localFiles}`,
|
|
1018
|
+
`local changed since last sync: ${localChanged ? "yes" : "no"}`,
|
|
1019
|
+
`remote changed since last sync: ${remoteChanged ? "yes" : "no"}`,
|
|
1020
|
+
...warnings
|
|
1021
|
+
].join("\n"),
|
|
1022
|
+
localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0 ? "warning" : "info"
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
async function diff(ctx, options, factory = createSyncBackend) {
|
|
1026
|
+
const config = await loadConfig(options.setup);
|
|
1027
|
+
throwIfAborted(options.signal);
|
|
1028
|
+
ctx.ui.setStatus(STATUS_KEY2, `checking ${config.setupName}`);
|
|
1029
|
+
const backend = await factory(config);
|
|
1030
|
+
const local = await createSnapshot(
|
|
1031
|
+
config.snapshotIdentity,
|
|
1032
|
+
snapshotOptionsForContext(ctx, config)
|
|
1033
|
+
);
|
|
1034
|
+
throwIfAborted(options.signal);
|
|
1035
|
+
const { snapshot: remote, selectionState } = await readRemoteSnapshot(
|
|
1036
|
+
backend,
|
|
1037
|
+
config,
|
|
1038
|
+
options.signal,
|
|
1039
|
+
{ allowSelectionDifference: true }
|
|
1040
|
+
);
|
|
1041
|
+
throwIfAborted(options.signal);
|
|
1042
|
+
ctx.ui.setStatus(STATUS_KEY2, void 0);
|
|
1043
|
+
const warnings = syncSessionsWarnings(config);
|
|
1044
|
+
const header = [
|
|
1045
|
+
`sync setup: ${config.setupName}`,
|
|
1046
|
+
`storage connection: ${config.connectionName}`,
|
|
1047
|
+
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
1048
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
1049
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
1050
|
+
formatRemoteSelectionStatus(selectionState),
|
|
1051
|
+
...warnings
|
|
1052
|
+
].join("\n");
|
|
1053
|
+
const level = warnings.length > 0 || selectionState?.kind === "different" ? "warning" : "info";
|
|
1054
|
+
if (!remote) {
|
|
1055
|
+
ctx.ui.notify(
|
|
1056
|
+
`${header}
|
|
1057
|
+
|
|
1058
|
+
${formatSnapshotOnlyDiff("Remote is empty. Local push would upload", local)}`,
|
|
1059
|
+
level
|
|
1060
|
+
);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
ctx.ui.notify(`${header}
|
|
1064
|
+
|
|
1065
|
+
${formatDiff(local, remote)}`, level);
|
|
1066
|
+
}
|
|
1067
|
+
async function doctor(ctx, options, factory = createSyncBackend) {
|
|
1068
|
+
const messages = [];
|
|
1069
|
+
let level = "info";
|
|
1070
|
+
let snapshotOptions = {};
|
|
1071
|
+
let profile = DEFAULT_PROFILE;
|
|
1072
|
+
let backend;
|
|
1073
|
+
let backendSummary = [];
|
|
1074
|
+
try {
|
|
1075
|
+
const config = await loadConfig(options.setup);
|
|
1076
|
+
throwIfAborted(options.signal);
|
|
1077
|
+
backend = await factory(config);
|
|
1078
|
+
profile = config.snapshotIdentity;
|
|
1079
|
+
snapshotOptions = snapshotOptionsForContext(ctx, config);
|
|
1080
|
+
messages.push(
|
|
1081
|
+
`config: ok (sync setup ${config.setupName})`,
|
|
1082
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
1083
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`
|
|
1084
|
+
);
|
|
1085
|
+
backendSummary = [
|
|
1086
|
+
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
1087
|
+
`publication safety: ${publicationCapabilityDescription(backend.capability)}`
|
|
1088
|
+
];
|
|
1089
|
+
const warnings = syncSessionsWarnings(config);
|
|
1090
|
+
if (warnings.length > 0) {
|
|
1091
|
+
level = "warning";
|
|
1092
|
+
messages.push(...warnings);
|
|
1093
|
+
}
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
throwIfAborted(options.signal);
|
|
1096
|
+
level = "warning";
|
|
1097
|
+
messages.push(`config: ${errorMessage(error)}`);
|
|
1098
|
+
}
|
|
1099
|
+
const local = await createSnapshot(profile, snapshotOptions);
|
|
1100
|
+
throwIfAborted(options.signal);
|
|
1101
|
+
const secrets = scanSnapshot(local);
|
|
1102
|
+
if (secrets.length > 0) {
|
|
1103
|
+
level = "warning";
|
|
1104
|
+
messages.push("secret scan: possible secrets found:");
|
|
1105
|
+
messages.push(...secrets.map((secret) => `- ${secret}`));
|
|
1106
|
+
} else {
|
|
1107
|
+
messages.push(`secret scan: ok (${local.files.length} files checked)`);
|
|
1108
|
+
}
|
|
1109
|
+
const lock = await inspectLock();
|
|
1110
|
+
throwIfAborted(options.signal);
|
|
1111
|
+
if (lock.status === "valid" && isStaleLock(lock.lock)) {
|
|
1112
|
+
level = "warning";
|
|
1113
|
+
messages.push(
|
|
1114
|
+
`lock: stale (pid ${lock.lock.pid}); run /sync unlock --stale after verifying no sync is running`
|
|
1115
|
+
);
|
|
1116
|
+
} else if (lock.status === "valid") {
|
|
1117
|
+
messages.push(`lock: held by pid ${lock.lock.pid} since ${lock.lock.startedAt}`);
|
|
1118
|
+
} else if (lock.status === "unreadable") {
|
|
1119
|
+
level = "warning";
|
|
1120
|
+
messages.push(
|
|
1121
|
+
"lock: unreadable; use /sync unlock --stale only after verifying no sync is running"
|
|
1122
|
+
);
|
|
1123
|
+
} else if (await isLockGuardHeld()) {
|
|
1124
|
+
throwIfAborted(options.signal);
|
|
1125
|
+
level = "warning";
|
|
1126
|
+
messages.push("lock: guard active while metadata is missing or still being initialized");
|
|
1127
|
+
} else {
|
|
1128
|
+
messages.push("lock: free");
|
|
1129
|
+
}
|
|
1130
|
+
if (backend) {
|
|
1131
|
+
messages.push(...backendSummary);
|
|
1132
|
+
const diagnostics = await backend.diagnose(options.signal);
|
|
1133
|
+
throwIfAborted(options.signal);
|
|
1134
|
+
for (const diagnostic of diagnostics) {
|
|
1135
|
+
messages.push(diagnostic.message);
|
|
1136
|
+
if (diagnostic.level !== "info") level = "warning";
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
ctx.ui.notify(messages.join("\n"), level);
|
|
1140
|
+
}
|
|
1141
|
+
async function history(ctx, options, factory = createSyncBackend) {
|
|
1142
|
+
const config = await loadConfig(options.setup);
|
|
1143
|
+
throwIfAborted(options.signal);
|
|
1144
|
+
const backend = await factory(config);
|
|
1145
|
+
const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
|
|
1146
|
+
throwIfAborted(options.signal);
|
|
1147
|
+
if (snapshots.length === 0) {
|
|
1148
|
+
ctx.ui.notify("No remote pi-sync history found.", "info");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const currentSnapshot = snapshots[0]?.snapshotId;
|
|
1152
|
+
if (ctx.mode === "tui") {
|
|
1153
|
+
const labels = snapshots.map(
|
|
1154
|
+
(item, index2) => `${index2 + 1}. ${item.createdAt} \xB7 ${safeTerminalText(item.machine)} \xB7 ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " \xB7 sessions" : ""}`
|
|
1155
|
+
);
|
|
1156
|
+
const selected = await ctx.ui.select(
|
|
1157
|
+
`History for sync setup \u201C${safeTerminalText(config.setupName)}\u201D
|
|
1158
|
+
|
|
1159
|
+
Choose a snapshot to preview rollback.`,
|
|
1160
|
+
[...labels, "Back"]
|
|
1161
|
+
);
|
|
1162
|
+
if (!selected || selected === "Back") return;
|
|
1163
|
+
throwIfAborted(options.signal);
|
|
1164
|
+
const index = labels.indexOf(selected);
|
|
1165
|
+
const snapshot = snapshots[index];
|
|
1166
|
+
if (!snapshot) return;
|
|
1167
|
+
await withLock(
|
|
1168
|
+
"rollback",
|
|
1169
|
+
() => rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
|
|
1170
|
+
backendIdentity: backend.identity,
|
|
1171
|
+
setup: config.setupName
|
|
1172
|
+
})
|
|
1173
|
+
);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
ctx.ui.notify(
|
|
1177
|
+
snapshots.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`).join("\n"),
|
|
1178
|
+
"info"
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1099
1181
|
export {
|
|
1100
1182
|
PublicationStatePersistenceError,
|
|
1101
1183
|
RollbackPublicationError,
|
|
@@ -1109,4 +1191,4 @@ export {
|
|
|
1109
1191
|
status,
|
|
1110
1192
|
syncBoth
|
|
1111
1193
|
};
|
|
1112
|
-
//# sourceMappingURL=sync-operations-
|
|
1194
|
+
//# sourceMappingURL=sync-operations-ONJ27QRL.ts.map
|