@narumitw/pi-sync 0.50.2 → 0.51.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 +4 -1
- package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
- package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
- package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
- package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
- package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
- package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
- package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
- package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
- package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
- package/dist/chunks/chunk-6546TS54.ts.map +7 -0
- package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
- package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
- package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
- package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
- package/dist/chunks/{chunk-4CHTZMEO.ts → chunk-7KHKWL3Q.ts} +37 -55
- package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
- package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
- package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
- package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
- package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
- package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
- package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
- package/dist/chunks/{chunk-G4RNQQZS.ts → chunk-E42GXD6D.ts} +13 -19
- package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
- package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
- package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
- package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
- package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
- package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
- package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
- package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
- package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
- package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
- package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
- package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
- package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
- package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
- package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
- package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
- package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
- package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
- package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
- package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
- package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
- package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
- package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
- package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
- package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
- package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
- package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
- package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
- package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
- package/dist/chunks/{chunk-KW7YQEZN.ts → chunk-UA7RXOIT.ts} +48 -100
- package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
- package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
- package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
- package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
- package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
- package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
- package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
- package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
- package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
- package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
- package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
- package/dist/chunks/{manager-ui-5KLKVMRC.ts → manager-ui-763NEKJY.ts} +124 -251
- package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
- package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
- package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
- package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
- package/dist/chunks/{setup-wizard-IUB6ESEU.ts → setup-wizard-YIEWGX32.ts} +12 -12
- package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
- package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
- package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
- package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
- package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
- package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
- package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
- package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
- package/dist/index.ts +49 -74
- package/dist/index.ts.map +3 -3
- package/docs/settings.md +9 -0
- package/package.json +61 -62
- package/src/backends/backend-factory.ts +15 -15
- package/src/backends/backend-identity.ts +31 -31
- package/src/backends/backend-types.ts +42 -42
- package/src/backends/git/git-backend.ts +714 -788
- package/src/backends/git/git-config.ts +104 -111
- package/src/backends/git/git-runner.ts +230 -247
- package/src/backends/git/git-storage.ts +228 -245
- package/src/backends/s3/s3-backend.ts +408 -441
- package/src/backends/s3/s3-client.ts +288 -333
- package/src/backends/sync-backend.ts +65 -65
- package/src/backends/webdav/webdav-backend.ts +542 -589
- package/src/backends/webdav/webdav-client.ts +292 -316
- package/src/backends/webdav/webdav-config.ts +57 -68
- package/src/commands/command-execution.ts +270 -289
- package/src/commands/command-handler.ts +160 -163
- package/src/commands/command-types.ts +13 -13
- package/src/commands/command.ts +190 -196
- package/src/commands/setup-completions.ts +1 -1
- package/src/paths.ts +47 -47
- package/src/settings/config-errors.ts +4 -5
- package/src/settings/config-file.ts +473 -506
- package/src/settings/config.ts +171 -174
- package/src/settings/settings-management.ts +191 -216
- package/src/settings/settings-store.ts +63 -77
- package/src/settings/settings-types.ts +78 -75
- package/src/settings/settings-validation.ts +328 -364
- package/src/snapshot/session-paths.ts +41 -55
- package/src/snapshot/snapshot-apply.ts +197 -229
- package/src/snapshot/snapshot-codec.ts +29 -31
- package/src/snapshot/snapshot-paths.ts +4 -6
- package/src/snapshot/snapshot-transaction.ts +161 -170
- package/src/snapshot/snapshot-types.ts +24 -24
- package/src/snapshot/snapshot.ts +310 -343
- package/src/state/json-file.ts +11 -11
- package/src/state/lock.ts +226 -242
- package/src/state/lockfile-fs.ts +20 -20
- package/src/state/operation-availability.ts +36 -41
- package/src/state/state-directory.ts +171 -173
- package/src/state/state-types.ts +15 -15
- package/src/state/sync-state-store.ts +23 -23
- package/src/sync/automatic-sync.ts +75 -80
- package/src/sync/remote-snapshot.ts +52 -60
- package/src/sync/setup-switch.ts +99 -114
- package/src/sync/signals.ts +3 -5
- package/src/sync/startup-check.ts +97 -111
- package/src/sync/sync-decision.ts +76 -80
- package/src/sync/sync-error-guidance.ts +41 -48
- package/src/sync/sync-errors.ts +10 -10
- package/src/sync/sync-inspection.ts +29 -33
- package/src/sync/sync-loaders.ts +28 -36
- package/src/sync/sync-mutations.ts +510 -622
- package/src/sync/sync-operations.ts +7 -7
- package/src/sync/sync-policy.ts +296 -326
- package/src/sync/sync-queries.ts +175 -194
- package/src/sync/sync-state.ts +74 -100
- package/src/sync-extension.ts +89 -101
- package/src/sync.ts +26 -26
- package/src/types.ts +37 -37
- package/src/ui/automatic-sync-summary.ts +1 -3
- package/src/ui/cancellable-operation.ts +92 -112
- package/src/ui/file-selection.ts +250 -274
- package/src/ui/manager-attention.ts +51 -63
- package/src/ui/manager-recovery.ts +48 -57
- package/src/ui/manager-result-dispatcher.ts +62 -76
- package/src/ui/manager-state.ts +200 -232
- package/src/ui/manager-ui.ts +264 -362
- package/src/ui/remote-selection-ui.ts +547 -588
- package/src/ui/secret-input.ts +198 -214
- package/src/ui/settings-ui.ts +175 -172
- package/src/ui/setup/git-ui.ts +232 -261
- package/src/ui/setup/s3-credentials-ui.ts +58 -65
- package/src/ui/setup/s3-ui.ts +171 -195
- package/src/ui/setup/setup-actions.ts +91 -101
- package/src/ui/setup/setup-location-ui.ts +121 -137
- package/src/ui/setup/setup-name-ui.ts +27 -27
- package/src/ui/setup/setup-prompts.ts +65 -65
- package/src/ui/setup/setup-review.ts +58 -59
- package/src/ui/setup/setup-switcher.ts +156 -160
- package/src/ui/setup/setup-wizard.ts +28 -28
- package/src/ui/setup/text-input.ts +54 -58
- package/src/ui/setup/webdav-ui.ts +246 -293
- package/src/ui/storage-connections-ui.ts +317 -369
- package/src/ui/sync-attention.ts +148 -164
- package/src/ui/sync-format.ts +110 -121
- package/src/ui/sync-resolution-ui.ts +147 -153
- package/src/ui/sync-setups-ui.ts +218 -238
- package/src/ui/sync-status.ts +17 -0
- package/src/ui/terminal-text.ts +2 -2
- package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
- package/dist/chunks/chunk-4CHTZMEO.ts.map +0 -7
- package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
- package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
- package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
- package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
- package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
- package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
- package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
- package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
- package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
- package/dist/chunks/chunk-G4RNQQZS.ts.map +0 -7
- package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
- package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
- package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
- package/dist/chunks/chunk-KW7YQEZN.ts.map +0 -7
- package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
- package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
- package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
- package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
- package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
- package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
- package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
- package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
- package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
- package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
- package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
- package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
- package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
- package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
- package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-FKIBM3GO.ts +0 -22
- package/dist/chunks/manager-ui-5KLKVMRC.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-D7RBRJNL.ts +0 -21
- package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
- package/dist/chunks/setup-wizard-IUB6ESEU.ts.map +0 -7
- package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
- package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
- package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
- /package/dist/chunks/{manager-result-dispatcher-FKIBM3GO.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-D7RBRJNL.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
- /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
- /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
package/src/sync/sync-queries.ts
CHANGED
|
@@ -7,11 +7,8 @@ import { snapshotOptionsForContext } from "../snapshot/session-paths.js";
|
|
|
7
7
|
import { createSnapshot, scanSnapshot } from "../snapshot/snapshot.js";
|
|
8
8
|
import type { SnapshotOptions } from "../snapshot/snapshot-types.js";
|
|
9
9
|
import { inspectLock, isLockGuardHeld, isStaleLock, withLock } from "../state/lock.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
formatSnapshotOnlyDiff,
|
|
13
|
-
publicationCapabilityDescription,
|
|
14
|
-
} from "../ui/sync-format.js";
|
|
10
|
+
import { formatDiff, formatSnapshotOnlyDiff, publicationCapabilityDescription } from "../ui/sync-format.js";
|
|
11
|
+
import { setSyncStatus } from "../ui/sync-status.js";
|
|
15
12
|
import { safeTerminalText } from "../ui/terminal-text.js";
|
|
16
13
|
import { formatRemoteSelectionStatus, readRemoteSnapshot } from "./remote-snapshot.js";
|
|
17
14
|
import { throwIfAborted } from "./signals.js";
|
|
@@ -19,217 +16,201 @@ import { errorMessage } from "./sync-errors.js";
|
|
|
19
16
|
import { inspectSync } from "./sync-inspection.js";
|
|
20
17
|
import { rollback } from "./sync-mutations.js";
|
|
21
18
|
|
|
22
|
-
const STATUS_KEY = "sync";
|
|
23
19
|
const DEFAULT_PROFILE = "default";
|
|
24
20
|
|
|
25
21
|
export async function status(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
ctx: ExtensionCommandContext,
|
|
23
|
+
options: CommandOptions,
|
|
24
|
+
factory: SyncBackendFactory = createSyncBackend,
|
|
29
25
|
) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const config = await loadConfig(options.setup);
|
|
27
|
+
throwIfAborted(options.signal);
|
|
28
|
+
setSyncStatus(ctx, `checking ${config.setupName}`);
|
|
29
|
+
const { head, selectionState, localChanged, remoteChanged, localFiles, destination, capability } = await inspectSync(
|
|
30
|
+
config,
|
|
31
|
+
snapshotOptionsForContext(ctx, config),
|
|
32
|
+
options.signal,
|
|
33
|
+
factory,
|
|
34
|
+
);
|
|
35
|
+
throwIfAborted(options.signal);
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0
|
|
58
|
-
? "warning"
|
|
59
|
-
: "info",
|
|
60
|
-
);
|
|
37
|
+
const remoteText = head ? `remote: ${head.snapshotId} from ${head.machine} at ${head.createdAt}` : "remote: empty";
|
|
38
|
+
const warnings = syncSessionsWarnings(config);
|
|
39
|
+
setSyncStatus(ctx, undefined);
|
|
40
|
+
ctx.ui.notify(
|
|
41
|
+
[
|
|
42
|
+
`sync setup: ${config.setupName}`,
|
|
43
|
+
`storage connection: ${config.connectionName}`,
|
|
44
|
+
`storage location: ${safeTerminalText(destination)}`,
|
|
45
|
+
`publication safety: ${publicationCapabilityDescription(capability)}`,
|
|
46
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
47
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
48
|
+
remoteText,
|
|
49
|
+
formatRemoteSelectionStatus(selectionState),
|
|
50
|
+
`local files: ${localFiles}`,
|
|
51
|
+
`local changed since last sync: ${localChanged ? "yes" : "no"}`,
|
|
52
|
+
`remote changed since last sync: ${remoteChanged ? "yes" : "no"}`,
|
|
53
|
+
...warnings,
|
|
54
|
+
].join("\n"),
|
|
55
|
+
localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0 ? "warning" : "info",
|
|
56
|
+
);
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
export async function diff(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
ctx: ExtensionCommandContext,
|
|
61
|
+
options: CommandOptions,
|
|
62
|
+
factory: SyncBackendFactory = createSyncBackend,
|
|
67
63
|
) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
config,
|
|
80
|
-
options.signal,
|
|
81
|
-
{ allowSelectionDifference: true },
|
|
82
|
-
);
|
|
83
|
-
throwIfAborted(options.signal);
|
|
84
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
64
|
+
const config = await loadConfig(options.setup);
|
|
65
|
+
throwIfAborted(options.signal);
|
|
66
|
+
setSyncStatus(ctx, `checking ${config.setupName}`);
|
|
67
|
+
const backend = await factory(config);
|
|
68
|
+
const local = await createSnapshot(config.snapshotIdentity, snapshotOptionsForContext(ctx, config));
|
|
69
|
+
throwIfAborted(options.signal);
|
|
70
|
+
const { snapshot: remote, selectionState } = await readRemoteSnapshot(backend, config, options.signal, {
|
|
71
|
+
allowSelectionDifference: true,
|
|
72
|
+
});
|
|
73
|
+
throwIfAborted(options.signal);
|
|
74
|
+
setSyncStatus(ctx, undefined);
|
|
85
75
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
76
|
+
const warnings = syncSessionsWarnings(config);
|
|
77
|
+
const header = [
|
|
78
|
+
`sync setup: ${config.setupName}`,
|
|
79
|
+
`storage connection: ${config.connectionName}`,
|
|
80
|
+
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
81
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
82
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
83
|
+
formatRemoteSelectionStatus(selectionState),
|
|
84
|
+
...warnings,
|
|
85
|
+
].join("\n");
|
|
86
|
+
const level = warnings.length > 0 || selectionState?.kind === "different" ? "warning" : "info";
|
|
87
|
+
if (!remote) {
|
|
88
|
+
ctx.ui.notify(`${header}\n\n${formatSnapshotOnlyDiff("Remote is empty. Local push would upload", local)}`, level);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
104
91
|
|
|
105
|
-
|
|
92
|
+
ctx.ui.notify(`${header}\n\n${formatDiff(local, remote)}`, level);
|
|
106
93
|
}
|
|
107
94
|
|
|
108
95
|
export async function doctor(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
96
|
+
ctx: ExtensionCommandContext,
|
|
97
|
+
options: CommandOptions,
|
|
98
|
+
factory: SyncBackendFactory = createSyncBackend,
|
|
112
99
|
) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
100
|
+
const messages: string[] = [];
|
|
101
|
+
let level: "info" | "warning" = "info";
|
|
102
|
+
let snapshotOptions: SnapshotOptions = {};
|
|
103
|
+
let profile = DEFAULT_PROFILE;
|
|
104
|
+
let backend: SyncBackend | undefined;
|
|
105
|
+
let backendSummary: string[] = [];
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
107
|
+
try {
|
|
108
|
+
const config = await loadConfig(options.setup);
|
|
109
|
+
throwIfAborted(options.signal);
|
|
110
|
+
backend = await factory(config);
|
|
111
|
+
profile = config.snapshotIdentity;
|
|
112
|
+
snapshotOptions = snapshotOptionsForContext(ctx, config);
|
|
113
|
+
messages.push(
|
|
114
|
+
`config: ok (sync setup ${config.setupName})`,
|
|
115
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
116
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
117
|
+
);
|
|
118
|
+
backendSummary = [
|
|
119
|
+
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
120
|
+
`publication safety: ${publicationCapabilityDescription(backend.capability)}`,
|
|
121
|
+
];
|
|
122
|
+
const warnings = syncSessionsWarnings(config);
|
|
123
|
+
if (warnings.length > 0) {
|
|
124
|
+
level = "warning";
|
|
125
|
+
messages.push(...warnings);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throwIfAborted(options.signal);
|
|
129
|
+
level = "warning";
|
|
130
|
+
messages.push(`config: ${errorMessage(error)}`);
|
|
131
|
+
}
|
|
145
132
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
const local = await createSnapshot(profile, snapshotOptions);
|
|
134
|
+
throwIfAborted(options.signal);
|
|
135
|
+
const secrets = scanSnapshot(local);
|
|
136
|
+
if (secrets.length > 0) {
|
|
137
|
+
level = "warning";
|
|
138
|
+
messages.push("secret scan: possible secrets found:");
|
|
139
|
+
messages.push(...secrets.map((secret) => `- ${secret}`));
|
|
140
|
+
} else {
|
|
141
|
+
messages.push(`secret scan: ok (${local.files.length} files checked)`);
|
|
142
|
+
}
|
|
156
143
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
messages.push("lock: guard active while metadata is missing or still being initialized");
|
|
175
|
-
} else {
|
|
176
|
-
messages.push("lock: free");
|
|
177
|
-
}
|
|
144
|
+
const lock = await inspectLock();
|
|
145
|
+
throwIfAborted(options.signal);
|
|
146
|
+
if (lock.status === "valid" && isStaleLock(lock.lock)) {
|
|
147
|
+
level = "warning";
|
|
148
|
+
messages.push(`lock: stale (pid ${lock.lock.pid}); run /sync unlock --stale after verifying no sync is running`);
|
|
149
|
+
} else if (lock.status === "valid") {
|
|
150
|
+
messages.push(`lock: held by pid ${lock.lock.pid} since ${lock.lock.startedAt}`);
|
|
151
|
+
} else if (lock.status === "unreadable") {
|
|
152
|
+
level = "warning";
|
|
153
|
+
messages.push("lock: unreadable; use /sync unlock --stale only after verifying no sync is running");
|
|
154
|
+
} else if (await isLockGuardHeld()) {
|
|
155
|
+
throwIfAborted(options.signal);
|
|
156
|
+
level = "warning";
|
|
157
|
+
messages.push("lock: guard active while metadata is missing or still being initialized");
|
|
158
|
+
} else {
|
|
159
|
+
messages.push("lock: free");
|
|
160
|
+
}
|
|
178
161
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
162
|
+
if (backend) {
|
|
163
|
+
messages.push(...backendSummary);
|
|
164
|
+
const diagnostics = await backend.diagnose(options.signal);
|
|
165
|
+
throwIfAborted(options.signal);
|
|
166
|
+
for (const diagnostic of diagnostics) {
|
|
167
|
+
messages.push(diagnostic.message);
|
|
168
|
+
if (diagnostic.level !== "info") level = "warning";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
ctx.ui.notify(messages.join("\n"), level);
|
|
189
172
|
}
|
|
190
173
|
|
|
191
174
|
export async function history(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
175
|
+
ctx: ExtensionCommandContext,
|
|
176
|
+
options: CommandOptions,
|
|
177
|
+
factory: SyncBackendFactory = createSyncBackend,
|
|
195
178
|
) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
179
|
+
const config = await loadConfig(options.setup);
|
|
180
|
+
throwIfAborted(options.signal);
|
|
181
|
+
const backend = await factory(config);
|
|
182
|
+
const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
|
|
183
|
+
throwIfAborted(options.signal);
|
|
184
|
+
if (snapshots.length === 0) {
|
|
185
|
+
ctx.ui.notify("No remote pi-sync history found.", "info");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
205
188
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"info",
|
|
234
|
-
);
|
|
189
|
+
const currentSnapshot = snapshots[0]?.snapshotId;
|
|
190
|
+
if (ctx.mode === "tui") {
|
|
191
|
+
const labels = snapshots.map(
|
|
192
|
+
(item, index) =>
|
|
193
|
+
`${index + 1}. ${item.createdAt} · ${safeTerminalText(item.machine)} · ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " · sessions" : ""}`,
|
|
194
|
+
);
|
|
195
|
+
const selected = await ctx.ui.select(
|
|
196
|
+
`History for sync setup “${safeTerminalText(config.setupName)}”\n\nChoose a snapshot to preview rollback.`,
|
|
197
|
+
[...labels, "Back"],
|
|
198
|
+
);
|
|
199
|
+
if (!selected || selected === "Back") return;
|
|
200
|
+
throwIfAborted(options.signal);
|
|
201
|
+
const index = labels.indexOf(selected);
|
|
202
|
+
const snapshot = snapshots[index];
|
|
203
|
+
if (!snapshot) return;
|
|
204
|
+
await withLock("rollback", () =>
|
|
205
|
+
rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
|
|
206
|
+
backendIdentity: backend.identity,
|
|
207
|
+
setup: config.setupName,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
ctx.ui.notify(
|
|
213
|
+
snapshots.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`).join("\n"),
|
|
214
|
+
"info",
|
|
215
|
+
);
|
|
235
216
|
}
|
package/src/sync/sync-state.ts
CHANGED
|
@@ -1,168 +1,142 @@
|
|
|
1
1
|
import type { RemoteHead } from "../backends/sync-backend.js";
|
|
2
2
|
import { toPosix } from "../paths.js";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
canonicalSnapshotPathForConfig,
|
|
5
|
+
filterSnapshotForConfigPolicy,
|
|
6
|
+
isConfiguredSnapshotPath,
|
|
7
|
+
isSessionPath,
|
|
8
8
|
} from "../snapshot/snapshot.js";
|
|
9
9
|
import type { Snapshot } from "../snapshot/snapshot-types.js";
|
|
10
10
|
import type { SyncState } from "../state/state-types.js";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
customIncludePathsByLower,
|
|
13
|
+
includeFromSelectionConfig,
|
|
14
|
+
normalizeSyncInclude,
|
|
15
|
+
type SyncSelectionConfig,
|
|
16
16
|
} from "./sync-policy.js";
|
|
17
17
|
|
|
18
18
|
type SyncPolicyConfig = SyncSelectionConfig;
|
|
19
19
|
|
|
20
20
|
export function hasLocalChanges(local: Snapshot, state: SyncState, config: SyncPolicyConfig) {
|
|
21
|
-
|
|
21
|
+
return !sameHashes(fileHashMap(local), stateHashMapForConfig(state, config));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function remoteChangedSinceState(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
head: RemoteHead | undefined,
|
|
26
|
+
state: SyncState,
|
|
27
|
+
config: SyncPolicyConfig,
|
|
28
|
+
sameRevision: (left: string, right: string) => boolean,
|
|
29
29
|
) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
!state.include?.includes("sessions") &&
|
|
38
|
-
head.syncSessions
|
|
39
|
-
);
|
|
30
|
+
if (!head) return Boolean(state.lastAppliedSnapshot);
|
|
31
|
+
if (head.snapshotId !== state.lastAppliedSnapshot) return true;
|
|
32
|
+
if (state.lastRemoteRevision && !sameRevision(head.revision, state.lastRemoteRevision)) return true;
|
|
33
|
+
if (syncIncludeChanged(state, config)) return true;
|
|
34
|
+
return (
|
|
35
|
+
includeFromSelectionConfig(config).includes("sessions") && !state.include?.includes("sessions") && head.syncSessions
|
|
36
|
+
);
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
export function hasRemoteChanges(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
remote: Snapshot,
|
|
41
|
+
state: SyncState,
|
|
42
|
+
config: SyncPolicyConfig,
|
|
43
|
+
ignoredPaths = new Set<string>(),
|
|
47
44
|
) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
filterSnapshotForConfigPolicy(remote, config),
|
|
51
|
-
state,
|
|
52
|
-
config,
|
|
53
|
-
ignoredPaths,
|
|
54
|
-
);
|
|
45
|
+
if (remote.id === state.lastAppliedSnapshot && !syncPolicyChanged(state, config)) return false;
|
|
46
|
+
return !snapshotHashesMatchState(filterSnapshotForConfigPolicy(remote, config), state, config, ignoredPaths);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
export function sameHashes(left: Record<string, string>, right: Record<string, string>) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
51
|
+
for (const key of keys) if (left[key] !== right[key]) return false;
|
|
52
|
+
return true;
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
export function fileHashMap(snapshot: Snapshot) {
|
|
64
|
-
|
|
56
|
+
return Object.fromEntries(snapshot.files.map((file) => [file.path, file.sha256]));
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
function stateHashMapForConfig(state: SyncState, config: SyncPolicyConfig) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
const includePaths = customIncludePathsByLower(includeFromSelectionConfig(config));
|
|
61
|
+
return Object.fromEntries(
|
|
62
|
+
Object.entries(state.lastFileHashes)
|
|
63
|
+
.filter(([filePath]) => isConfiguredSnapshotPath(filePath, config))
|
|
64
|
+
.map(([filePath, hash]) => [canonicalSnapshotPathForConfig(filePath, includePaths), hash]),
|
|
65
|
+
);
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
export function snapshotHashesMatchState(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
snapshot: Snapshot,
|
|
70
|
+
state: SyncState,
|
|
71
|
+
config: SyncPolicyConfig,
|
|
72
|
+
ignoredPaths = new Set<string>(),
|
|
81
73
|
) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
return sameHashes(
|
|
75
|
+
withoutHashPaths(fileHashMap(snapshot), ignoredPaths),
|
|
76
|
+
withoutHashPaths(stateHashMapForConfig(state, config), ignoredPaths),
|
|
77
|
+
);
|
|
86
78
|
}
|
|
87
79
|
|
|
88
80
|
export function snapshotsMatch(left: Snapshot, right: Snapshot) {
|
|
89
|
-
|
|
90
|
-
left.syncSessions === right.syncSessions && sameHashes(fileHashMap(left), fileHashMap(right))
|
|
91
|
-
);
|
|
81
|
+
return left.syncSessions === right.syncSessions && sameHashes(fileHashMap(left), fileHashMap(right));
|
|
92
82
|
}
|
|
93
83
|
|
|
94
84
|
function withoutHashPaths(hashes: Record<string, string>, ignoredPaths: Set<string>) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Object.entries(hashes).filter(([filePath]) => !ignoredPaths.has(toPosix(filePath))),
|
|
98
|
-
);
|
|
85
|
+
if (ignoredPaths.size === 0) return hashes;
|
|
86
|
+
return Object.fromEntries(Object.entries(hashes).filter(([filePath]) => !ignoredPaths.has(toPosix(filePath))));
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
export function syncPolicyChanged(state: SyncState, config: SyncPolicyConfig) {
|
|
102
|
-
|
|
90
|
+
return syncIncludeChanged(state, config);
|
|
103
91
|
}
|
|
104
92
|
|
|
105
93
|
export function shouldRefreshSyncedState(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
remote: Snapshot,
|
|
95
|
+
head: RemoteHead | undefined,
|
|
96
|
+
state: SyncState,
|
|
97
|
+
config: SyncPolicyConfig,
|
|
98
|
+
sameRevision: (left: string, right: string) => boolean,
|
|
111
99
|
) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
syncPolicyChanged(state, config)
|
|
118
|
-
);
|
|
100
|
+
return (
|
|
101
|
+
remote.id !== state.lastAppliedSnapshot ||
|
|
102
|
+
Boolean(head && (!state.lastRemoteRevision || !sameRevision(head.revision, state.lastRemoteRevision))) ||
|
|
103
|
+
syncPolicyChanged(state, config)
|
|
104
|
+
);
|
|
119
105
|
}
|
|
120
106
|
|
|
121
107
|
function syncIncludeChanged(state: SyncState, config: SyncPolicyConfig) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const current = includeFromSelectionConfig(config);
|
|
126
|
-
return stored.length !== current.length || stored.some((item, index) => item !== current[index]);
|
|
108
|
+
const stored = state.include ? normalizeSyncInclude(state.include) : includeFromSelectionConfig(state);
|
|
109
|
+
const current = includeFromSelectionConfig(config);
|
|
110
|
+
return stored.length !== current.length || stored.some((item, index) => item !== current[index]);
|
|
127
111
|
}
|
|
128
112
|
|
|
129
113
|
export function settingsHashMap(snapshot: Snapshot) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.map((file) => [file.path, file.sha256]),
|
|
134
|
-
);
|
|
114
|
+
return Object.fromEntries(
|
|
115
|
+
snapshot.files.filter((file) => !isSessionPath(file.path)).map((file) => [file.path, file.sha256]),
|
|
116
|
+
);
|
|
135
117
|
}
|
|
136
118
|
|
|
137
119
|
export function sessionHashMap(snapshot: Snapshot) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
.map((file) => [file.path, file.sha256]),
|
|
142
|
-
);
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
snapshot.files.filter((file) => isSessionPath(file.path)).map((file) => [file.path, file.sha256]),
|
|
122
|
+
);
|
|
143
123
|
}
|
|
144
124
|
|
|
145
125
|
export function settingsHashMapFromState(state: SyncState) {
|
|
146
|
-
|
|
147
|
-
Object.entries(state.lastFileHashes).filter(([filePath]) => !isSessionPath(filePath)),
|
|
148
|
-
);
|
|
126
|
+
return Object.fromEntries(Object.entries(state.lastFileHashes).filter(([filePath]) => !isSessionPath(filePath)));
|
|
149
127
|
}
|
|
150
128
|
|
|
151
129
|
export function settingsHashesMatchState(remote: Snapshot, state: SyncState) {
|
|
152
|
-
|
|
130
|
+
return sameHashes(settingsHashMap(remote), settingsHashMapFromState(state));
|
|
153
131
|
}
|
|
154
132
|
|
|
155
133
|
export function canPullRemoteSettingsOnFirstSync(local: Snapshot, remote: Snapshot) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
([filePath, hash]) => remoteSettings[filePath] === hash,
|
|
159
|
-
);
|
|
134
|
+
const remoteSettings = settingsHashMap(remote);
|
|
135
|
+
return Object.entries(settingsHashMap(local)).every(([filePath, hash]) => remoteSettings[filePath] === hash);
|
|
160
136
|
}
|
|
161
137
|
|
|
162
138
|
export function canPullRemoteSessionsOnFirstSync(local: Snapshot, remote: Snapshot) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
([filePath, hash]) => remoteSessions[filePath] === hash,
|
|
167
|
-
);
|
|
139
|
+
const localSessions = sessionHashMap(local);
|
|
140
|
+
const remoteSessions = sessionHashMap(remote);
|
|
141
|
+
return Object.entries(localSessions).every(([filePath, hash]) => remoteSessions[filePath] === hash);
|
|
168
142
|
}
|