@narumitw/pi-sync 0.50.3 → 0.51.1
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
|
@@ -6,87 +6,87 @@ import { safeTerminalText } from "../terminal-text.js";
|
|
|
6
6
|
import { promptTextInput } from "./text-input.js";
|
|
7
7
|
|
|
8
8
|
export async function readSetupConnection(name: string, signal?: AbortSignal) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const connection = (await readLocalConfigObject())?.storageConnections[name];
|
|
10
|
+
signal?.throwIfAborted();
|
|
11
|
+
if (!connection) throw new Error("Storage connection changed; reopen setup.");
|
|
12
|
+
return connection;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const AUTOMATIC_SYNC_DESCRIPTION =
|
|
16
|
-
|
|
16
|
+
"Check the current setup in the background at startup; review changes in /sync before transferring. At shutdown, automatically push selected content only when sessions are included. No startup dialogs.";
|
|
17
17
|
|
|
18
18
|
export async function chooseSetupContent(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const choice = await ctx.ui.select(
|
|
20
|
+
"Choose included content\n\nRecommended: Pi settings, instructions, skills, prompts, themes, and extensions.\nMinimal: settings.json and AGENTS.md.\nReview the exact paths before saving.",
|
|
21
|
+
["Recommended Pi settings", "Minimal settings", "Cancel"],
|
|
22
|
+
{ signal },
|
|
23
|
+
);
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
if (!choice || choice === "Cancel") return undefined;
|
|
26
|
+
return choice === "Minimal settings" ? ["settings.json", "AGENTS.md"] : [...DEFAULT_SYNC_INCLUDE];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export async function chooseAutomaticSync(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
const choice = await ctx.ui.select(
|
|
31
|
+
`Automatic sync for this setup\n\n${AUTOMATIC_SYNC_DESCRIPTION}`,
|
|
32
|
+
["Keep automatic sync off", "Enable automatic sync", "Cancel"],
|
|
33
|
+
{ signal },
|
|
34
|
+
);
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
return !choice || choice === "Cancel" ? undefined : choice === "Enable automatic sync";
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export async function chooseSessions(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
const choice = await ctx.ui.select(
|
|
41
|
+
"Session conversations\n\nSessions can contain prompts, tool output, paths, screenshots, and secrets.",
|
|
42
|
+
["Keep sessions off (recommended)", "Include session conversations", "Cancel"],
|
|
43
|
+
{ signal },
|
|
44
|
+
);
|
|
45
|
+
signal?.throwIfAborted();
|
|
46
|
+
if (!choice || choice === "Cancel") return undefined;
|
|
47
|
+
if (choice !== "Include session conversations") return false;
|
|
48
|
+
const confirmed = await ctx.ui.confirm(
|
|
49
|
+
"Include session conversations?",
|
|
50
|
+
"I understand that session JSONL can contain prompts, tool output, paths, screenshots, and secrets.",
|
|
51
|
+
{ signal },
|
|
52
|
+
);
|
|
53
|
+
signal?.throwIfAborted();
|
|
54
|
+
return confirmed ? true : undefined;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function includedContentLines(include: readonly string[], sessions = false) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
const paths = [...include, ...(sessions && !include.includes("sessions") ? ["sessions"] : [])];
|
|
59
|
+
return [
|
|
60
|
+
`Included content: ${paths.length} paths`,
|
|
61
|
+
...paths.map((item) => ` ${safeTerminalText(item)}`),
|
|
62
|
+
`Sessions: ${sessions ? "On — privacy warning acknowledged" : "Off"}`,
|
|
63
|
+
];
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export async function promptResourceName(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
ctx: ExtensionCommandContext,
|
|
68
|
+
kind: "sync setup" | "storage connection",
|
|
69
|
+
defaultValue: string,
|
|
70
|
+
signal?: AbortSignal,
|
|
71
|
+
literalBrackets = false,
|
|
72
72
|
) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
73
|
+
return promptTextInput(
|
|
74
|
+
ctx,
|
|
75
|
+
`Name this ${kind}\n\nA local label, such as home or work.`,
|
|
76
|
+
{
|
|
77
|
+
defaultValue,
|
|
78
|
+
rejectPlaceholders: !literalBrackets,
|
|
79
|
+
validate: async (name) => {
|
|
80
|
+
validateConfigName(name, kind);
|
|
81
|
+
const settings = await readLocalConfigObject();
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
const names = kind === "sync setup" ? settings?.syncSetups : settings?.storageConnections;
|
|
84
|
+
if (names && Object.hasOwn(names, name)) {
|
|
85
|
+
throw new Error(`A ${kind} with this name already exists. Choose a different name.`);
|
|
86
|
+
}
|
|
87
|
+
return name;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
signal,
|
|
91
|
+
);
|
|
92
92
|
}
|
|
@@ -4,67 +4,66 @@ import { syncErrorGuidance } from "../../sync/sync-error-guidance.js";
|
|
|
4
4
|
|
|
5
5
|
/** A settings draft is committed only after review; retryable I/O failures retain the same draft. */
|
|
6
6
|
export async function saveReviewedDraft(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
ctx: ExtensionCommandContext,
|
|
8
|
+
title: string,
|
|
9
|
+
lines: readonly string[],
|
|
10
|
+
label: string,
|
|
11
|
+
save: (signal: AbortSignal) => Promise<unknown>,
|
|
12
|
+
signal?: AbortSignal,
|
|
13
13
|
) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
14
|
+
let saved = false;
|
|
15
|
+
let failure: unknown;
|
|
16
|
+
let failureText: string | undefined;
|
|
17
|
+
const menu = defineMenu<undefined, "review", "save", ExtensionCommandContext>({
|
|
18
|
+
start: "review",
|
|
19
|
+
screens: {
|
|
20
|
+
review: () => ({
|
|
21
|
+
kind: "review",
|
|
22
|
+
title,
|
|
23
|
+
content: [...(failureText ? [failureText, ""] : []), ...lines].join("\n"),
|
|
24
|
+
lines: ["Review before saving. Back cancels this draft."],
|
|
25
|
+
format: { kind: "text" },
|
|
26
|
+
viewportSize: "adaptive",
|
|
27
|
+
confirm: { id: "save", label, action: "save" },
|
|
28
|
+
hint: "back",
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
actions: {
|
|
32
|
+
save: async ({ signal: actionSignal }) => {
|
|
33
|
+
try {
|
|
34
|
+
actionSignal.throwIfAborted();
|
|
35
|
+
await save(actionSignal);
|
|
36
|
+
saved = true;
|
|
37
|
+
return { kind: "close" };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (actionSignal.aborted) return { kind: "close" };
|
|
40
|
+
// Do not silently rebase a stale review or overwrite an invalid settings file.
|
|
41
|
+
// The caller must reopen current settings for those failures.
|
|
42
|
+
if (!retryableSettingsError(error)) {
|
|
43
|
+
failure = error;
|
|
44
|
+
return { kind: "close" };
|
|
45
|
+
}
|
|
46
|
+
failureText = `Not saved. ${syncErrorGuidance(error)}\nYour draft is retained. Fix the problem, then save again, or cancel.`;
|
|
47
|
+
ctx.ui.notify(failureText, "error");
|
|
48
|
+
return { kind: "stay" };
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const result = await runMenu(ctx, menu, {
|
|
54
|
+
getState: () => undefined,
|
|
55
|
+
signal,
|
|
56
|
+
isCurrent: () => !signal?.aborted,
|
|
57
|
+
});
|
|
58
|
+
if (signal?.aborted) return false;
|
|
59
|
+
if (failure) throw failure;
|
|
60
|
+
if (result.kind === "error") throw result.error;
|
|
61
|
+
return saved;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function retryableSettingsError(error: unknown) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
);
|
|
65
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
66
|
+
return (
|
|
67
|
+
code !== undefined && ["EACCES", "EPERM", "ENOSPC", "EBUSY", "EIO", "EMFILE", "ENFILE", "EROFS"].includes(code)
|
|
68
|
+
);
|
|
70
69
|
}
|
|
@@ -13,169 +13,165 @@ import { backendStorageDescription } from "../manager-state.js";
|
|
|
13
13
|
import { safeTerminalText } from "../terminal-text.js";
|
|
14
14
|
|
|
15
15
|
async function selectSetupForSwitch(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
ctx: ExtensionCommandContext,
|
|
17
|
+
raw: Record<string, unknown>,
|
|
18
|
+
targets: Record<string, unknown>,
|
|
19
|
+
active: string | undefined,
|
|
20
|
+
signal?: AbortSignal,
|
|
21
21
|
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return selectedName;
|
|
22
|
+
let selectedName: string | undefined;
|
|
23
|
+
const nameById = new Map<string, string>();
|
|
24
|
+
const profiles = ownRecord(raw.storageConnections);
|
|
25
|
+
const menu = defineMenu<undefined, "setups", "select", ExtensionCommandContext>({
|
|
26
|
+
start: "setups",
|
|
27
|
+
screens: {
|
|
28
|
+
setups: () => ({
|
|
29
|
+
kind: "actions",
|
|
30
|
+
title: "Switch sync setup",
|
|
31
|
+
lines: [`Current sync setup: ${safeTerminalText(active ?? "none")}`],
|
|
32
|
+
items: Object.keys(targets)
|
|
33
|
+
.sort((left, right) => left.localeCompare(right))
|
|
34
|
+
.map((candidate, index) => {
|
|
35
|
+
const target = ownRecord(targets[candidate]);
|
|
36
|
+
const storage = ownRecord(target?.storage);
|
|
37
|
+
const profileName = typeof storage?.connection === "string" ? storage.connection : undefined;
|
|
38
|
+
const profile = profileName && profiles ? ownRecord(profiles[profileName]) : undefined;
|
|
39
|
+
const location = profile
|
|
40
|
+
? profile.type === "git"
|
|
41
|
+
? `${String(storage?.branch ?? "missing branch")}:${String(storage?.path ?? "missing path")}`
|
|
42
|
+
: profile.type === "s3"
|
|
43
|
+
? `${String(storage?.bucket ?? "missing bucket")}/${String(storage?.path ?? "missing path")}`
|
|
44
|
+
: String(storage?.path ?? "missing path")
|
|
45
|
+
: `invalid: missing connection ${profileName ?? "reference"}`;
|
|
46
|
+
const id = `setup:${index}`;
|
|
47
|
+
nameById.set(id, candidate);
|
|
48
|
+
return {
|
|
49
|
+
id,
|
|
50
|
+
label: `${safeTerminalText(candidate)}${candidate === active ? " (current)" : ""}`,
|
|
51
|
+
description: `${safeTerminalText(profileName ?? "unknown")} · ${safeTerminalText(location)}`,
|
|
52
|
+
action: "select" as const,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
hint: "close",
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
actions: {
|
|
59
|
+
select: async ({ itemId }) => {
|
|
60
|
+
selectedName = nameById.get(itemId);
|
|
61
|
+
return { kind: "close" };
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
await runMenu(ctx, menu, {
|
|
66
|
+
getState: () => undefined,
|
|
67
|
+
signal,
|
|
68
|
+
isCurrent: () => !signal?.aborted,
|
|
69
|
+
});
|
|
70
|
+
return selectedName;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
export async function showSetupSwitcher(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
ctx: ExtensionCommandContext,
|
|
75
|
+
runRoute: RunRoute,
|
|
76
|
+
selectedName?: string,
|
|
77
|
+
signal?: AbortSignal,
|
|
79
78
|
) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
79
|
+
const raw = await readLocalConfigObject();
|
|
80
|
+
if (signal?.aborted) return false;
|
|
81
|
+
if (raw?.version !== 3) {
|
|
82
|
+
ctx.ui.notify("Add a second sync setup before switching setups.", "info");
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const targets = ownRecord(raw.syncSetups);
|
|
86
|
+
if (!targets) {
|
|
87
|
+
ctx.ui.notify("No sync setups are configured.", "warning");
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const active = typeof raw.activeSyncSetup === "string" ? raw.activeSyncSetup : undefined;
|
|
91
|
+
let name = selectedName;
|
|
92
|
+
if (!name) {
|
|
93
|
+
name = await selectSetupForSwitch(ctx, raw, targets, active, signal);
|
|
94
|
+
if (!name) return false;
|
|
95
|
+
}
|
|
96
|
+
if (!name || !Object.hasOwn(targets, name)) {
|
|
97
|
+
ctx.ui.notify(`Sync setup “${safeTerminalText(name ?? "unknown")}” no longer exists.`, "warning");
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (name === active) {
|
|
101
|
+
ctx.ui.notify(`Sync setup “${safeTerminalText(name)}” is already current.`, "info");
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
let config: AnySyncConfig;
|
|
105
|
+
try {
|
|
106
|
+
config = await loadConfig(name);
|
|
107
|
+
if (signal?.aborted) return false;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
ctx.ui.notify(
|
|
110
|
+
`Cannot use sync setup “${safeTerminalText(name)}”: ${safeTerminalText(errorMessage(error))}`,
|
|
111
|
+
"error",
|
|
112
|
+
);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const onSwitch = await loadOnSwitch();
|
|
116
|
+
if (signal?.aborted) return false;
|
|
117
|
+
const switchEffect =
|
|
118
|
+
onSwitch === "ask-before-pull"
|
|
119
|
+
? "After switching, pi-sync will ask whether to review a pull for this setup."
|
|
120
|
+
: onSwitch === "pull-after-switch"
|
|
121
|
+
? "After switching, pi-sync will check this setup and show exact changes before applying them."
|
|
122
|
+
: "After switching, pi-sync will not pull or modify synced files.";
|
|
123
|
+
const confirmed = await ctx.ui.confirm(
|
|
124
|
+
"Switch sync setup?",
|
|
125
|
+
[
|
|
126
|
+
`From: ${safeTerminalText(active ?? "none")}`,
|
|
127
|
+
`To: ${safeTerminalText(name)}`,
|
|
128
|
+
`Storage: ${backendStorageDescription(config)}`,
|
|
129
|
+
`Included content: ${config.include.length} paths`,
|
|
130
|
+
`Automatic sync: ${automaticSyncSummary(config.automatic)} · Sessions: ${config.include.includes("sessions") ? "On" : "Off"}`,
|
|
131
|
+
"",
|
|
132
|
+
switchEffect,
|
|
133
|
+
].join("\n"),
|
|
134
|
+
{ signal },
|
|
135
|
+
);
|
|
136
|
+
if (signal?.aborted || !confirmed) return false;
|
|
137
|
+
try {
|
|
138
|
+
let pullClosed = false;
|
|
139
|
+
const result = await useSyncSetup(
|
|
140
|
+
ctx,
|
|
141
|
+
name,
|
|
142
|
+
async (selectedTarget) => {
|
|
143
|
+
const pullResult = await runCancellableOperation(
|
|
144
|
+
ctx,
|
|
145
|
+
`Pulling sync setup “${safeTerminalText(name)}”…`,
|
|
146
|
+
"pull",
|
|
147
|
+
runRoute,
|
|
148
|
+
{
|
|
149
|
+
commitAware: true,
|
|
150
|
+
cancelledMessage: null,
|
|
151
|
+
target: selectedTarget,
|
|
152
|
+
signal,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
const disposition = await dispatchManagerResult(ctx, pullResult, "pull", runRoute, signal);
|
|
156
|
+
if (pullResult.kind === "closed" || pullResult.kind.endsWith("required")) {
|
|
157
|
+
pullClosed = disposition.kind === "close";
|
|
158
|
+
}
|
|
159
|
+
if (disposition.appliedRoute === "pull") return "applied";
|
|
160
|
+
if (pullResult.kind === "completed") return pullResult.outcome;
|
|
161
|
+
return pullResult.kind === "cancelled" ? "cancelled" : undefined;
|
|
162
|
+
},
|
|
163
|
+
onSwitch,
|
|
164
|
+
signal,
|
|
165
|
+
syncConfigReviewIdentity(config),
|
|
166
|
+
);
|
|
167
|
+
if (pullClosed) return "closed";
|
|
168
|
+
return result.pullApplied ? "pull-attempted" : "switched";
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (signal?.aborted) return false;
|
|
171
|
+
ctx.ui.notify(
|
|
172
|
+
`Sync setup “${safeTerminalText(name)}” was not switched: ${safeTerminalText(errorMessage(error))}`,
|
|
173
|
+
"error",
|
|
174
|
+
);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
181
177
|
}
|