@narumitw/pi-sync 0.50.3 → 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-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
- 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-YHEMENKV.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-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
- 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-IPAOAWZA.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-JUJZYNNM.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 +89 -101
- 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 +197 -205
- 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-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-DHVFS4QK.ts.map +0 -7
- package/dist/chunks/chunk-ETTRJDNP.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-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-YDWUZVPN.ts.map +0 -7
- package/dist/chunks/chunk-YHEMENKV.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-ASKGOSW5.ts +0 -22
- package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
- package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
- package/dist/chunks/setup-wizard-JUJZYNNM.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-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-QDNR7WEI.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
|
@@ -4,100 +4,95 @@ import type { CommandOptions } from "../commands/command-types.js";
|
|
|
4
4
|
import { loadConfig, loadPartialConfig } from "../settings/config.js";
|
|
5
5
|
import { isMissingConfigError } from "../settings/config-errors.js";
|
|
6
6
|
import { consumeLocalConfigMigrationNotice } from "../settings/config-file.js";
|
|
7
|
-
import {
|
|
7
|
+
import { readLocalConfigObject } from "../settings/settings-store.js";
|
|
8
8
|
import { snapshotOptionsForContext } from "../snapshot/session-paths.js";
|
|
9
9
|
import { recoverSnapshotTransactionsOnStartup } from "../snapshot/snapshot-transaction.js";
|
|
10
10
|
import { withLock } from "../state/lock.js";
|
|
11
11
|
import { stateDirectoryMigrationNotice } from "../state/state-directory.js";
|
|
12
12
|
import { ensureStateDir, readStateForConfig } from "../state/sync-state-store.js";
|
|
13
|
+
import { configureSyncStatus, setSyncStatus } from "../ui/sync-status.js";
|
|
13
14
|
import { safeTerminalText } from "../ui/terminal-text.js";
|
|
14
15
|
import { throwIfAborted } from "./signals.js";
|
|
15
16
|
import { errorMessage } from "./sync-errors.js";
|
|
16
17
|
import type { SyncLoaders } from "./sync-loaders.js";
|
|
17
18
|
|
|
18
19
|
const AUTO_SYNC_OPTIONS: CommandOptions = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
yes: true,
|
|
21
|
+
force: false,
|
|
22
|
+
stale: false,
|
|
23
|
+
silent: true,
|
|
24
|
+
reload: false,
|
|
25
|
+
auto: true,
|
|
26
|
+
args: [],
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
const STATUS_KEY = "sync";
|
|
29
|
-
|
|
30
29
|
export async function startSession(ctx: ExtensionContext, signal: AbortSignal) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
30
|
+
throwIfAborted(signal);
|
|
31
|
+
const stateNotice = stateDirectoryMigrationNotice();
|
|
32
|
+
if (stateNotice && ctx.hasUI) ctx.ui.notify(stateNotice, "warning");
|
|
33
|
+
// Recovery can restore managed files: finish it before Pi accepts user edits.
|
|
34
|
+
// Unlike remote inspection, it must never be detached behind startup.
|
|
35
|
+
await recoverSnapshotTransactionsOnStartup();
|
|
36
|
+
throwIfAborted(signal);
|
|
37
|
+
try {
|
|
38
|
+
const settings = await readLocalConfigObject();
|
|
39
|
+
throwIfAborted(signal);
|
|
40
|
+
const names = settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];
|
|
41
|
+
setSyncSetupCompletions(names);
|
|
42
|
+
configureSyncStatus(ctx, settings?.showStatus ?? true);
|
|
43
|
+
} catch {
|
|
44
|
+
if (signal.aborted) return;
|
|
45
|
+
setSyncSetupCompletions([]);
|
|
46
|
+
}
|
|
47
|
+
const migrationNotice = consumeLocalConfigMigrationNotice();
|
|
48
|
+
if (migrationNotice && ctx.hasUI) ctx.ui.notify(migrationNotice, "warning");
|
|
49
|
+
throwIfAborted(signal);
|
|
50
|
+
if (!ctx.hasUI) return;
|
|
51
|
+
try {
|
|
52
|
+
const config = await loadConfig();
|
|
53
|
+
throwIfAborted(signal);
|
|
54
|
+
configureSyncStatus(ctx, config.showStatus);
|
|
55
|
+
return config.automatic ? config : undefined;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throwIfAborted(signal);
|
|
58
|
+
if (!isMissingConfigError(error)) {
|
|
59
|
+
ctx.ui.notify(`pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}`, "warning");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
export async function autoPushSessions(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (signal.aborted || isMissingConfigError(error)) return;
|
|
100
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
101
|
-
ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
|
|
102
|
-
}
|
|
64
|
+
export async function autoPushSessions(ctx: ExtensionContext, signal: AbortSignal, loaders: SyncLoaders) {
|
|
65
|
+
try {
|
|
66
|
+
const partial = await loadPartialConfig();
|
|
67
|
+
throwIfAborted(signal);
|
|
68
|
+
if (!partial.automatic) return;
|
|
69
|
+
if (!partial.include.includes("sessions")) return;
|
|
70
|
+
await ensureStateDir();
|
|
71
|
+
throwIfAborted(signal);
|
|
72
|
+
const config = await loadConfig();
|
|
73
|
+
throwIfAborted(signal);
|
|
74
|
+
if (!config.include.includes("sessions")) return;
|
|
75
|
+
const [operations, snapshotModule, syncStateModule] = await Promise.all([
|
|
76
|
+
loaders.operations(),
|
|
77
|
+
loaders.snapshot(),
|
|
78
|
+
loaders.syncState(),
|
|
79
|
+
]);
|
|
80
|
+
throwIfAborted(signal);
|
|
81
|
+
await withLock("auto-session-push", async () => {
|
|
82
|
+
throwIfAborted(signal);
|
|
83
|
+
const state = await readStateForConfig(config);
|
|
84
|
+
throwIfAborted(signal);
|
|
85
|
+
const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {
|
|
86
|
+
...snapshotOptionsForContext(ctx, config),
|
|
87
|
+
signal,
|
|
88
|
+
});
|
|
89
|
+
throwIfAborted(signal);
|
|
90
|
+
if (!syncStateModule.hasLocalChanges(local, state, config)) return;
|
|
91
|
+
await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (signal.aborted || isMissingConfigError(error)) return;
|
|
95
|
+
setSyncStatus(ctx, undefined);
|
|
96
|
+
ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
|
|
97
|
+
}
|
|
103
98
|
}
|
|
@@ -5,79 +5,71 @@ import { filterSnapshotForConfigPolicy } from "../snapshot/snapshot.js";
|
|
|
5
5
|
import type { Snapshot } from "../snapshot/snapshot-types.js";
|
|
6
6
|
import { safeTerminalText } from "../ui/terminal-text.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
inspectRemoteSelection,
|
|
9
|
+
type RemoteSelectionState,
|
|
10
|
+
remoteSelectionMismatch,
|
|
11
|
+
sameSyncInclude,
|
|
12
|
+
snapshotSelectionInclude,
|
|
13
13
|
} from "./sync-policy.js";
|
|
14
14
|
|
|
15
|
-
export async function readSnapshotForHead(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
if (head.selection) {
|
|
32
|
-
const snapshotInclude = snapshotSelectionInclude(snapshot);
|
|
33
|
-
if (!snapshotInclude || !sameSyncInclude(head.selection.include, snapshotInclude)) {
|
|
34
|
-
throw new Error("Remote head selection does not match its immutable snapshot.");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return snapshot;
|
|
15
|
+
export async function readSnapshotForHead(backend: SyncBackend, head: RemoteHead, signal?: AbortSignal) {
|
|
16
|
+
const snapshot = await backend.readSnapshot(head.snapshotRef, signal);
|
|
17
|
+
if (signal?.aborted) {
|
|
18
|
+
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
19
|
+
}
|
|
20
|
+
if (snapshot.id !== head.snapshotId) {
|
|
21
|
+
throw new Error(`Remote head ${head.snapshotId} resolved to unexpected snapshot ${snapshot.id}.`);
|
|
22
|
+
}
|
|
23
|
+
if (head.selection) {
|
|
24
|
+
const snapshotInclude = snapshotSelectionInclude(snapshot);
|
|
25
|
+
if (!snapshotInclude || !sameSyncInclude(head.selection.include, snapshotInclude)) {
|
|
26
|
+
throw new Error("Remote head selection does not match its immutable snapshot.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return snapshot;
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
export function requireCompatibleRemoteSelection(config: AnySyncConfig, snapshot: Snapshot) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
const state = inspectRemoteSelection(config.include, snapshot);
|
|
34
|
+
if (state.kind === "different") {
|
|
35
|
+
throw remoteSelectionMismatch(config, state.include, syncConfigReviewFingerprint(config));
|
|
36
|
+
}
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
export function formatRemoteSelectionStatus(state: RemoteSelectionState | undefined) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
if (!state) return "remote included content: unavailable (remote is empty)";
|
|
41
|
+
if (state.kind === "same") return "remote included content: matches this setup";
|
|
42
|
+
if (state.kind === "legacy") {
|
|
43
|
+
return `remote included content: unavailable in legacy snapshot (${state.discovered.length} path${state.discovered.length === 1 ? "" : "s"} discovered, partial)`;
|
|
44
|
+
}
|
|
45
|
+
return [
|
|
46
|
+
"remote included content: differs from this setup",
|
|
47
|
+
`remote-only selection: ${safeList(state.remoteOnly)}`,
|
|
48
|
+
`local-only selection: ${safeList(state.localOnly)}`,
|
|
49
|
+
].join("\n");
|
|
58
50
|
}
|
|
59
51
|
|
|
60
52
|
function safeList(values: readonly string[]) {
|
|
61
|
-
|
|
53
|
+
return values.length > 0 ? values.map(safeTerminalText).join(", ") : "none";
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
export async function readRemoteSnapshot(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
backend: SyncBackend,
|
|
58
|
+
config: AnySyncConfig,
|
|
59
|
+
signal?: AbortSignal,
|
|
60
|
+
options: { allowSelectionDifference?: boolean } = {},
|
|
69
61
|
) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
const head = await backend.readHead(signal);
|
|
63
|
+
if (!head) return { head: undefined, snapshot: undefined, selectionState: undefined };
|
|
64
|
+
const snapshot = await readSnapshotForHead(backend, head, signal);
|
|
65
|
+
const selectionState = inspectRemoteSelection(config.include, snapshot);
|
|
66
|
+
if (!options.allowSelectionDifference && selectionState.kind === "different") {
|
|
67
|
+
const configIdentity = syncConfigReviewFingerprint(config);
|
|
68
|
+
throw remoteSelectionMismatch(config, selectionState.include, configIdentity);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
head,
|
|
72
|
+
snapshot: filterSnapshotForConfigPolicy(snapshot, config),
|
|
73
|
+
selectionState,
|
|
74
|
+
};
|
|
83
75
|
}
|
package/src/sync/setup-switch.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { setSyncSetupCompletions } from "../commands/command.js";
|
|
3
|
-
import {
|
|
4
|
-
loadConfig,
|
|
5
|
-
syncConfigReviewIdentity,
|
|
6
|
-
syncSetupReviewIdentity,
|
|
7
|
-
} from "../settings/config.js";
|
|
3
|
+
import { loadConfig, syncConfigReviewIdentity, syncSetupReviewIdentity } from "../settings/config.js";
|
|
8
4
|
import { configuredSyncSetupNames, updateLocalConfig } from "../settings/settings-store.js";
|
|
9
5
|
import type { OnSwitchAction } from "../settings/settings-types.js";
|
|
10
6
|
import { normalizeOnSwitch } from "../settings/settings-validation.js";
|
|
@@ -14,137 +10,126 @@ import { SetupPullRequiresUiError } from "./sync-errors.js";
|
|
|
14
10
|
export { SetupPullRequiresUiError } from "./sync-errors.js";
|
|
15
11
|
|
|
16
12
|
export const SETUP_SWITCH_ACTION_OPTIONS: ReadonlyArray<{
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
label: string;
|
|
14
|
+
value: OnSwitchAction;
|
|
19
15
|
}> = [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
{ label: "Ask before pull", value: "ask-before-pull" },
|
|
17
|
+
{ label: "Start pull", value: "pull-after-switch" },
|
|
18
|
+
{ label: "Switch only", value: "switch-only" },
|
|
23
19
|
];
|
|
24
20
|
|
|
25
21
|
export function setupSwitchActionLabel(action: OnSwitchAction) {
|
|
26
|
-
|
|
22
|
+
return SETUP_SWITCH_ACTION_OPTIONS.find((option) => option.value === action)?.label ?? action;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
export function setupSwitchActionFromLabel(label: string): OnSwitchAction | undefined {
|
|
30
|
-
|
|
26
|
+
return SETUP_SWITCH_ACTION_OPTIONS.find((option) => option.label === label)?.value;
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
export async function saveOnSwitch(action: OnSwitchAction, signal?: AbortSignal) {
|
|
34
|
-
|
|
30
|
+
await updateLocalConfig((settings) => ({ ...settings, onSwitch: action }), signal);
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
export type SetupPullOutcome = "applied" | "cancelled";
|
|
38
34
|
|
|
39
35
|
export interface SetupSwitchResult {
|
|
40
|
-
|
|
36
|
+
pullApplied: boolean;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
export async function useSyncSetup(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
ctx: ExtensionCommandContext,
|
|
41
|
+
name: string,
|
|
42
|
+
pullCurrentSetup?: (setup: string) => Promise<SetupPullOutcome | undefined>,
|
|
43
|
+
expectedAction?: OnSwitchAction,
|
|
44
|
+
signal?: AbortSignal,
|
|
45
|
+
expectedSetupIdentity?: string,
|
|
50
46
|
): Promise<SetupSwitchResult> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
setSyncSetupCompletions(await configuredSyncSetupNames());
|
|
98
|
-
throwIfAborted(signal);
|
|
47
|
+
const normalized = name.trim();
|
|
48
|
+
if (!normalized) throw new Error("Usage: /sync use <setup>");
|
|
49
|
+
const loadedConfig = await loadConfig(normalized);
|
|
50
|
+
const reviewedSetupIdentity = expectedSetupIdentity ?? syncConfigReviewIdentity(loadedConfig);
|
|
51
|
+
throwIfAborted(signal);
|
|
52
|
+
const switchResult: { action: OnSwitchAction; switched: boolean } = {
|
|
53
|
+
action: "ask-before-pull",
|
|
54
|
+
switched: false,
|
|
55
|
+
};
|
|
56
|
+
await updateLocalConfig((current) => {
|
|
57
|
+
throwIfAborted(signal);
|
|
58
|
+
const setup = current.syncSetups[normalized];
|
|
59
|
+
if (!setup) {
|
|
60
|
+
throw new Error(`Sync setup “${safeTerminalText(normalized)}” no longer exists.`);
|
|
61
|
+
}
|
|
62
|
+
const connectionName = setup.storage.connection;
|
|
63
|
+
const connection = current.storageConnections[connectionName];
|
|
64
|
+
if (
|
|
65
|
+
!connection ||
|
|
66
|
+
syncSetupReviewIdentity(normalized, setup, connectionName, connection) !== reviewedSetupIdentity
|
|
67
|
+
) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Sync setup “${safeTerminalText(normalized)}” changed while the switch preview was open; reopen it and review the current destination.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
switchResult.action = normalizeOnSwitch(current.onSwitch);
|
|
73
|
+
if (expectedAction !== undefined && switchResult.action !== expectedAction) {
|
|
74
|
+
throw new Error("Setup-switch behavior changed while the preview was open; reopen it and retry.");
|
|
75
|
+
}
|
|
76
|
+
if (switchResult.action === "pull-after-switch" && !ctx.hasUI) {
|
|
77
|
+
throw new SetupPullRequiresUiError(
|
|
78
|
+
`Automatic setup pulls require interactive confirmation; sync setup “${safeTerminalText(normalized)}” was not switched. Use TUI or RPC mode.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (current.activeSyncSetup === normalized) return current;
|
|
82
|
+
switchResult.switched = true;
|
|
83
|
+
return { ...current, activeSyncSetup: normalized };
|
|
84
|
+
}, signal);
|
|
85
|
+
throwIfAborted(signal);
|
|
86
|
+
if (!switchResult.switched) {
|
|
87
|
+
ctx.ui.notify(`Sync setup “${safeTerminalText(normalized)}” is already current.`, "info");
|
|
88
|
+
return { pullApplied: false };
|
|
89
|
+
}
|
|
90
|
+
setSyncSetupCompletions(await configuredSyncSetupNames());
|
|
91
|
+
throwIfAborted(signal);
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return { pullApplied: false };
|
|
124
|
-
}
|
|
125
|
-
}
|
|
93
|
+
if (switchResult.action === "switch-only") {
|
|
94
|
+
ctx.ui.notify(`Switched to “${safeTerminalText(normalized)}”. No files were pulled.`, "info");
|
|
95
|
+
return { pullApplied: false };
|
|
96
|
+
}
|
|
97
|
+
if (switchResult.action === "ask-before-pull") {
|
|
98
|
+
if (ctx.mode !== "tui") {
|
|
99
|
+
ctx.ui.notify(
|
|
100
|
+
`Switched to “${safeTerminalText(normalized)}”. No files were pulled because confirmation requires TUI mode; run /sync pull to apply this setup.`,
|
|
101
|
+
"info",
|
|
102
|
+
);
|
|
103
|
+
return { pullApplied: false };
|
|
104
|
+
}
|
|
105
|
+
const confirmed = await ctx.ui.confirm(
|
|
106
|
+
`Review a pull for sync setup “${safeTerminalText(normalized)}” now?`,
|
|
107
|
+
"pi-sync will check the remote snapshot and show the exact local writes and deletions before applying anything.",
|
|
108
|
+
{ signal },
|
|
109
|
+
);
|
|
110
|
+
throwIfAborted(signal);
|
|
111
|
+
if (!confirmed) {
|
|
112
|
+
ctx.ui.notify(`Switched to “${safeTerminalText(normalized)}”; files were not pulled.`, "info");
|
|
113
|
+
return { pullApplied: false };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
return { pullApplied: pullOutcome === "applied" };
|
|
117
|
+
ctx.ui.notify(`Switched to “${safeTerminalText(normalized)}”. Checking remote files for a reviewed pull…`, "info");
|
|
118
|
+
if (!pullCurrentSetup) {
|
|
119
|
+
throw new Error(`Switched to “${safeTerminalText(normalized)}”, but pull is unavailable.`);
|
|
120
|
+
}
|
|
121
|
+
const pullOutcome = await pullCurrentSetup(normalized);
|
|
122
|
+
throwIfAborted(signal);
|
|
123
|
+
if (pullOutcome === "cancelled") {
|
|
124
|
+
ctx.ui.notify(
|
|
125
|
+
`Pull cancelled; sync setup “${safeTerminalText(normalized)}” remains current and synced files were not changed.`,
|
|
126
|
+
"info",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return { pullApplied: pullOutcome === "applied" };
|
|
143
130
|
}
|
|
144
131
|
|
|
145
132
|
function throwIfAborted(signal?: AbortSignal) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
? signal.reason
|
|
149
|
-
: new DOMException("The operation was aborted", "AbortError");
|
|
133
|
+
if (!signal?.aborted) return;
|
|
134
|
+
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
150
135
|
}
|
package/src/sync/signals.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
export function throwIfAborted(signal?: AbortSignal) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
? signal.reason
|
|
5
|
-
: new DOMException("The operation was aborted", "AbortError");
|
|
2
|
+
if (!signal?.aborted) return;
|
|
3
|
+
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
6
4
|
}
|
|
7
5
|
|
|
8
6
|
export function combineSignals(primary: AbortSignal, secondary?: AbortSignal) {
|
|
9
|
-
|
|
7
|
+
return secondary ? AbortSignal.any([primary, secondary]) : primary;
|
|
10
8
|
}
|