@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
|
@@ -14,114 +14,104 @@ import { promptResourceName } from "./setup-prompts.js";
|
|
|
14
14
|
import { showSetupSwitcher } from "./setup-switcher.js";
|
|
15
15
|
import { showAddWebDavTarget, showEditWebDavTarget } from "./webdav-ui.js";
|
|
16
16
|
|
|
17
|
-
export async function showSyncSetupManager(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
signal,
|
|
40
|
-
);
|
|
17
|
+
export async function showSyncSetupManager(ctx: ExtensionCommandContext, runRoute: RunRoute, signal?: AbortSignal) {
|
|
18
|
+
return showSyncSetups(
|
|
19
|
+
ctx,
|
|
20
|
+
{
|
|
21
|
+
add: async (setupSignal) => {
|
|
22
|
+
await showAddTarget(ctx, setupSignal);
|
|
23
|
+
},
|
|
24
|
+
edit: async (name, setupSignal) => {
|
|
25
|
+
await showEditTarget(ctx, name, setupSignal);
|
|
26
|
+
},
|
|
27
|
+
makeCurrent: async (name, setupSignal) => {
|
|
28
|
+
const result = await showSetupSwitcher(ctx, runRoute, name, setupSignal);
|
|
29
|
+
return result === "pull-attempted" || result === "closed" ? "exit" : undefined;
|
|
30
|
+
},
|
|
31
|
+
remove: async (name, setupSignal) => {
|
|
32
|
+
await showRemoveTarget(ctx, name, setupSignal);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
signal,
|
|
36
|
+
);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
async function showAddTarget(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
await showAddS3Target(ctx, raw, profile, name, signal);
|
|
40
|
+
let raw = await readLocalConfigObject();
|
|
41
|
+
if (signal?.aborted) return;
|
|
42
|
+
if (!raw) return void ctx.ui.notify("Set up the first sync setup before adding another.", "info");
|
|
43
|
+
if (raw.version !== 3) {
|
|
44
|
+
ctx.ui.notify("Version 1 and version 2 settings are unsupported and are never migrated.", "error");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let profiles = ownRecord(raw.storageConnections) ?? {};
|
|
48
|
+
const name = await promptResourceName(ctx, "sync setup", "work", signal);
|
|
49
|
+
if (!name) return;
|
|
50
|
+
const createConnection = "Add a new storage connection…";
|
|
51
|
+
let profile = await ctx.ui.select(
|
|
52
|
+
"Choose a storage connection\n\nReuse a server address and sign-in details. A new connection is saved separately and remains if you cancel this setup.",
|
|
53
|
+
[...Object.keys(profiles).sort(), createConnection, "Cancel"],
|
|
54
|
+
{ signal },
|
|
55
|
+
);
|
|
56
|
+
if (signal?.aborted || !profile || profile === "Cancel") return;
|
|
57
|
+
if (profile === createConnection) {
|
|
58
|
+
const previousNames = new Set(Object.keys(profiles));
|
|
59
|
+
if (!(await showAddStorageConnection(ctx, signal))) return;
|
|
60
|
+
if (signal?.aborted) return;
|
|
61
|
+
raw = (await readLocalConfigObject()) ?? raw;
|
|
62
|
+
if (signal?.aborted) return;
|
|
63
|
+
profiles = ownRecord(raw.storageConnections) ?? {};
|
|
64
|
+
profile = Object.keys(profiles).find((candidate) => !previousNames.has(candidate));
|
|
65
|
+
if (!profile) return;
|
|
66
|
+
ctx.ui.notify(
|
|
67
|
+
`Connection “${safeTerminalText(profile)}” saved. Cancelling setup will keep this connection.`,
|
|
68
|
+
"info",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const storageKind = ownRecord(profiles[profile])?.type;
|
|
72
|
+
if (storageKind === "webdav") {
|
|
73
|
+
const saved = await showAddWebDavTarget(ctx, name, profile, signal);
|
|
74
|
+
if (signal?.aborted) return;
|
|
75
|
+
if (saved) await refreshTargetCompletions();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (storageKind === "git") {
|
|
79
|
+
const saved = await showAddGitTarget(ctx, name, profile, signal);
|
|
80
|
+
if (signal?.aborted) return;
|
|
81
|
+
if (saved) await refreshTargetCompletions();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await showAddS3Target(ctx, raw, profile, name, signal);
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
async function showEditTarget(ctx: ExtensionCommandContext, name: string, signal?: AbortSignal) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
const partial = await loadPartialConfig(name);
|
|
89
|
+
if (signal?.aborted) return;
|
|
90
|
+
if (!partial.setupName) {
|
|
91
|
+
ctx.ui.notify("Create version 3 settings before editing a named sync setup.", "info");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (partial.storageKind === "webdav") {
|
|
95
|
+
await showEditWebDavTarget(ctx, partial, signal);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (partial.storageKind === "git") {
|
|
99
|
+
await showEditGitTarget(ctx, partial, signal);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await showEditS3Target(ctx, partial, signal);
|
|
110
103
|
}
|
|
111
104
|
|
|
112
105
|
async function showRemoveTarget(ctx: ExtensionCommandContext, name: string, signal?: AbortSignal) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`Removed sync setup “${safeTerminalText(name)}”; remote data was not deleted.`,
|
|
125
|
-
"info",
|
|
126
|
-
);
|
|
106
|
+
const confirmed = await ctx.ui.confirm(
|
|
107
|
+
"Remove sync setup?",
|
|
108
|
+
`Remove local sync setup “${safeTerminalText(name)}”? Remote data and history are not deleted.`,
|
|
109
|
+
{ signal },
|
|
110
|
+
);
|
|
111
|
+
if (signal?.aborted || !confirmed) return;
|
|
112
|
+
await removeSyncSetup(name, signal);
|
|
113
|
+
if (signal?.aborted) return;
|
|
114
|
+
await refreshTargetCompletions();
|
|
115
|
+
if (signal?.aborted) return;
|
|
116
|
+
ctx.ui.notify(`Removed sync setup “${safeTerminalText(name)}”; remote data was not deleted.`, "info");
|
|
127
117
|
}
|
|
@@ -1,167 +1,151 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { normalizeGitBranch } from "../../backends/git/git-config.js";
|
|
3
3
|
import { readLocalConfigObject } from "../../settings/settings-store.js";
|
|
4
|
-
import type {
|
|
5
|
-
StorageConnectionSettings,
|
|
6
|
-
SyncSetupSettings,
|
|
7
|
-
} from "../../settings/settings-types.js";
|
|
4
|
+
import type { StorageConnectionSettings, SyncSetupSettings } from "../../settings/settings-types.js";
|
|
8
5
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
effectiveSyncSetupRemoteIdentity,
|
|
7
|
+
normalizeStoragePath,
|
|
8
|
+
ownRecord,
|
|
12
9
|
} from "../../settings/settings-validation.js";
|
|
13
10
|
import { errorMessage } from "../../sync/sync-errors.js";
|
|
14
11
|
import { safeTerminalText } from "../terminal-text.js";
|
|
15
|
-
import {
|
|
16
|
-
promptTextInput,
|
|
17
|
-
requiredExistingBucket,
|
|
18
|
-
requiredInput,
|
|
19
|
-
requiredValueInput,
|
|
20
|
-
} from "./text-input.js";
|
|
12
|
+
import { promptTextInput, requiredExistingBucket, requiredInput, requiredValueInput } from "./text-input.js";
|
|
21
13
|
|
|
22
14
|
interface ChosenRemoteLocation {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
connectionName: string;
|
|
16
|
+
bucket: string;
|
|
17
|
+
path: string;
|
|
26
18
|
}
|
|
27
19
|
|
|
28
20
|
export async function chooseInitialRemoteLocation(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
ctx: ExtensionCommandContext,
|
|
22
|
+
_preset: string,
|
|
23
|
+
setupName: string,
|
|
24
|
+
signal?: AbortSignal,
|
|
33
25
|
): Promise<ChosenRemoteLocation | undefined> {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return bucket ? { connectionName: setupName, bucket, path: "./" } : undefined;
|
|
26
|
+
const choice = await ctx.ui.select(
|
|
27
|
+
"Choose storage location\n\nUse an existing bucket. ./ stores snapshots at the bucket root; a custom path keeps them in a separate folder.",
|
|
28
|
+
["Use an existing bucket at ./", "Customize remote location", "Cancel"],
|
|
29
|
+
{ signal },
|
|
30
|
+
);
|
|
31
|
+
if (signal?.aborted || !choice || choice === "Cancel") return undefined;
|
|
32
|
+
if (choice === "Customize remote location") return chooseCustomRemoteLocation(ctx, setupName, signal);
|
|
33
|
+
const bucket = await requiredExistingBucket(ctx, "pi-sync", signal);
|
|
34
|
+
return bucket ? { connectionName: setupName, bucket, path: "./" } : undefined;
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
export async function chooseAdditionalRemoteLocation(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
ctx: ExtensionCommandContext,
|
|
39
|
+
settings: Record<string, unknown>,
|
|
40
|
+
connectionName: string,
|
|
41
|
+
setupName: string,
|
|
42
|
+
signal?: AbortSignal,
|
|
52
43
|
): Promise<Omit<ChosenRemoteLocation, "connectionName"> | undefined> {
|
|
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
|
-
}
|
|
44
|
+
const setups = ownRecord(settings.syncSetups) ?? {};
|
|
45
|
+
const currentSetup = typeof settings.activeSyncSetup === "string" ? settings.activeSyncSetup : undefined;
|
|
46
|
+
const candidates = Object.entries(setups)
|
|
47
|
+
.map(([name, value]) => ({ name, storage: ownRecord(ownRecord(value)?.storage) }))
|
|
48
|
+
.filter(
|
|
49
|
+
(item): item is { name: string; storage: Record<string, unknown> } =>
|
|
50
|
+
item.storage?.connection === connectionName && typeof item.storage.bucket === "string",
|
|
51
|
+
);
|
|
52
|
+
const source =
|
|
53
|
+
candidates.find((item) => item.name === currentSetup) ??
|
|
54
|
+
candidates.sort((left, right) => left.name.localeCompare(right.name))[0];
|
|
55
|
+
if (source) {
|
|
56
|
+
const suggestedPath = "./";
|
|
57
|
+
const sameBucketLabel = `Same bucket as “${safeTerminalText(source.name)}”`;
|
|
58
|
+
const choice = await ctx.ui.select(
|
|
59
|
+
[
|
|
60
|
+
`Storage location for “${safeTerminalText(setupName)}”`,
|
|
61
|
+
"",
|
|
62
|
+
`Existing bucket: ${safeTerminalText(String(source.storage.bucket))}`,
|
|
63
|
+
`Remote path: ${safeTerminalText(suggestedPath)}`,
|
|
64
|
+
"./ uses the bucket root. Use a different path or bucket for independent setups.",
|
|
65
|
+
].join("\n"),
|
|
66
|
+
[sameBucketLabel, "Use a different bucket", "Customize remote location", "Cancel"],
|
|
67
|
+
{ signal },
|
|
68
|
+
);
|
|
69
|
+
if (signal?.aborted || !choice || choice === "Cancel") return undefined;
|
|
70
|
+
if (choice === sameBucketLabel) {
|
|
71
|
+
return { bucket: String(source.storage.bucket), path: suggestedPath };
|
|
72
|
+
}
|
|
73
|
+
if (choice === "Use a different bucket") {
|
|
74
|
+
const bucket = await requiredExistingBucket(ctx, "pi-sync", signal);
|
|
75
|
+
return bucket ? { bucket, path: "./" } : undefined;
|
|
76
|
+
}
|
|
77
|
+
const custom = await chooseCustomRemoteLocation(ctx, connectionName, signal);
|
|
78
|
+
return custom ? { bucket: custom.bucket, path: custom.path } : undefined;
|
|
79
|
+
}
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
const location = await chooseInitialRemoteLocation(ctx, "S3", setupName, signal);
|
|
82
|
+
return location ? { bucket: location.bucket, path: location.path } : undefined;
|
|
93
83
|
}
|
|
94
84
|
|
|
95
85
|
async function chooseCustomRemoteLocation(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
ctx: ExtensionCommandContext,
|
|
87
|
+
connectionName: string,
|
|
88
|
+
signal?: AbortSignal,
|
|
99
89
|
): Promise<ChosenRemoteLocation | undefined> {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
90
|
+
const bucket = await requiredExistingBucket(ctx, "pi-sync", signal);
|
|
91
|
+
if (!bucket) return undefined;
|
|
92
|
+
const storagePath = await requiredInput(
|
|
93
|
+
ctx,
|
|
94
|
+
"Storage path\n\nPath inside the bucket, not your local filesystem.\n./ uses the bucket root. Use different paths for independent setups.",
|
|
95
|
+
"./",
|
|
96
|
+
signal,
|
|
97
|
+
normalizeStoragePath,
|
|
98
|
+
);
|
|
99
|
+
if (!storagePath) return undefined;
|
|
100
|
+
return { connectionName, bucket, path: normalizeStoragePath(storagePath) };
|
|
111
101
|
}
|
|
112
102
|
|
|
113
103
|
/** Early UI check only; the settings writer still validates uniqueness under its lock. */
|
|
114
104
|
export async function promptAvailableSetupStorage<T extends SyncSetupSettings["storage"]>(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
ctx: ExtensionCommandContext,
|
|
106
|
+
initial: T,
|
|
107
|
+
signal?: AbortSignal,
|
|
118
108
|
): Promise<T | undefined> {
|
|
119
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return undefined;
|
|
109
|
+
let storage = initial;
|
|
110
|
+
while (!signal?.aborted) {
|
|
111
|
+
const settings = await readLocalConfigObject();
|
|
112
|
+
if (signal?.aborted) return undefined;
|
|
113
|
+
const connection = settings?.storageConnections[storage.connection];
|
|
114
|
+
if (!settings || !connection) throw new Error("Storage connection changed; reopen setup.");
|
|
115
|
+
const identity = setupPublicationIdentity(storage, connection);
|
|
116
|
+
const occupied = Object.entries(settings.syncSetups).find(
|
|
117
|
+
([, setup]) =>
|
|
118
|
+
setupPublicationIdentity(setup.storage, settings.storageConnections[setup.storage.connection]) === identity,
|
|
119
|
+
);
|
|
120
|
+
if (!occupied) return storage;
|
|
121
|
+
const git = connection.type === "git";
|
|
122
|
+
const message = `${git ? "Git branch" : "Storage location"} is already used by “${safeTerminalText(occupied[0])}”. ${git ? "Use a different branch; pi-sync owns the entire branch." : "Enter a different storage path, or cancel to choose another connection or bucket."}`;
|
|
123
|
+
ctx.ui.notify(message, "warning");
|
|
124
|
+
const title = `${git ? "Git branch for the new setup" : "Storage path for the new setup"}\n\n${message}`;
|
|
125
|
+
const example = git ? "pi-sync/work" : "backups/work";
|
|
126
|
+
const value =
|
|
127
|
+
connection.type === "webdav"
|
|
128
|
+
? await promptTextInput(ctx, title, { example }, signal)
|
|
129
|
+
: await requiredValueInput(ctx, title, example, signal);
|
|
130
|
+
if (signal?.aborted || value === undefined) return undefined;
|
|
131
|
+
try {
|
|
132
|
+
storage = git
|
|
133
|
+
? { ...storage, branch: normalizeGitBranch(value) }
|
|
134
|
+
: { ...storage, path: normalizeStoragePath(value) };
|
|
135
|
+
} catch (error) {
|
|
136
|
+
ctx.ui.notify(safeTerminalText(errorMessage(error)), "warning");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
153
140
|
}
|
|
154
141
|
|
|
155
|
-
function setupPublicationIdentity(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
connection,
|
|
166
|
-
);
|
|
142
|
+
function setupPublicationIdentity(storage: SyncSetupSettings["storage"], connection: StorageConnectionSettings) {
|
|
143
|
+
// Git publications own a complete branch tree; changing only its directory is not isolation.
|
|
144
|
+
return effectiveSyncSetupRemoteIdentity(
|
|
145
|
+
{
|
|
146
|
+
storage: connection.type === "git" ? { ...storage, path: "./" } : storage,
|
|
147
|
+
sync: { include: [], automatic: false },
|
|
148
|
+
},
|
|
149
|
+
connection,
|
|
150
|
+
);
|
|
167
151
|
}
|
|
@@ -4,31 +4,31 @@ import { errorMessage } from "../../sync/sync-errors.js";
|
|
|
4
4
|
import { safeTerminalText } from "../terminal-text.js";
|
|
5
5
|
|
|
6
6
|
export async function promptInitialSetupName(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
7
|
+
while (!signal?.aborted) {
|
|
8
|
+
const hint = "For example: home or work. Leave blank for default.";
|
|
9
|
+
// Pi styles the whole input title as accent; give only the guidance a muted role.
|
|
10
|
+
const guidance = ctx.mode === "tui" ? ctx.ui.theme.fg("muted", hint) : hint;
|
|
11
|
+
// This compact prompt owns its default hint; the general helper would repeat it.
|
|
12
|
+
const value = await ctx.ui.input(`Sync setup name\n${guidance}`, undefined, { signal });
|
|
13
|
+
if (signal?.aborted) {
|
|
14
|
+
throw signal.reason instanceof Error
|
|
15
|
+
? signal.reason
|
|
16
|
+
: new DOMException("The operation was aborted", "AbortError");
|
|
17
|
+
}
|
|
18
|
+
if (value === undefined) return undefined;
|
|
19
|
+
const name = value.trim() || "default";
|
|
20
|
+
try {
|
|
21
|
+
if (name.includes("<") || name.includes(">")) {
|
|
22
|
+
throw new Error("Replace example placeholders with your own name.");
|
|
23
|
+
}
|
|
24
|
+
validateConfigName(name, "sync setup");
|
|
25
|
+
return name;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
ctx.ui.notify(
|
|
28
|
+
`This name cannot be used for the sync setup. ${safeTerminalText(errorMessage(error))} Enter another name (for example, default).`,
|
|
29
|
+
"warning",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
34
|
}
|