@narumitw/pi-sync 0.49.10 → 0.49.11
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 +10 -1
- package/dist/chunks/chunk-3GRWMWUZ.ts +213 -0
- package/dist/chunks/chunk-3GRWMWUZ.ts.map +7 -0
- package/dist/chunks/chunk-3NDD5QPM.ts +203 -0
- package/dist/chunks/chunk-3NDD5QPM.ts.map +7 -0
- package/dist/chunks/chunk-4F3W4CSC.ts +195 -0
- package/dist/chunks/chunk-4F3W4CSC.ts.map +7 -0
- package/dist/chunks/chunk-4U6AFLJB.ts +221 -0
- package/dist/chunks/chunk-4U6AFLJB.ts.map +7 -0
- package/dist/chunks/chunk-5MQ76BK5.ts +34 -0
- package/dist/chunks/chunk-5MQ76BK5.ts.map +7 -0
- package/dist/chunks/chunk-APZVBSM6.ts +1238 -0
- package/dist/chunks/chunk-APZVBSM6.ts.map +7 -0
- package/dist/chunks/chunk-BHJUISB4.ts +134 -0
- package/dist/chunks/chunk-BHJUISB4.ts.map +7 -0
- package/dist/chunks/chunk-D3S54OXI.ts +653 -0
- package/dist/chunks/chunk-D3S54OXI.ts.map +7 -0
- package/dist/chunks/chunk-EWX3TPLQ.ts +356 -0
- package/dist/chunks/chunk-EWX3TPLQ.ts.map +7 -0
- package/dist/chunks/chunk-F5QL2GPY.ts +341 -0
- package/dist/chunks/chunk-F5QL2GPY.ts.map +7 -0
- package/dist/chunks/chunk-FR6CVXAL.ts +81 -0
- package/dist/chunks/chunk-FR6CVXAL.ts.map +7 -0
- package/dist/chunks/chunk-HGV24P4T.ts +53 -0
- package/dist/chunks/chunk-HGV24P4T.ts.map +7 -0
- package/dist/chunks/chunk-JNOOSPEW.ts +82 -0
- package/dist/chunks/chunk-JNOOSPEW.ts.map +7 -0
- package/dist/chunks/chunk-LJDSHNS3.ts +259 -0
- package/dist/chunks/chunk-LJDSHNS3.ts.map +7 -0
- package/dist/chunks/chunk-RGJ6SOYR.ts +128 -0
- package/dist/chunks/chunk-RGJ6SOYR.ts.map +7 -0
- package/dist/chunks/chunk-W64NEHXT.ts +167 -0
- package/dist/chunks/chunk-W64NEHXT.ts.map +7 -0
- package/dist/chunks/chunk-YQ6UW7IF.ts +394 -0
- package/dist/chunks/chunk-YQ6UW7IF.ts.map +7 -0
- package/dist/chunks/file-selection-4UABOP44.ts +290 -0
- package/dist/chunks/file-selection-4UABOP44.ts.map +7 -0
- package/dist/chunks/git-backend-E3UOUUOM.ts +1104 -0
- package/dist/chunks/git-backend-E3UOUUOM.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-YKBLZ7KL.ts +18 -0
- package/dist/chunks/manager-result-dispatcher-YKBLZ7KL.ts.map +7 -0
- package/dist/chunks/manager-ui-7E25VWUP.ts +2754 -0
- package/dist/chunks/manager-ui-7E25VWUP.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-J6OW5DKZ.ts +17 -0
- package/dist/chunks/remote-selection-ui-J6OW5DKZ.ts.map +7 -0
- package/dist/chunks/s3-backend-3QZGO2EF.ts +680 -0
- package/dist/chunks/s3-backend-3QZGO2EF.ts.map +7 -0
- package/dist/chunks/setup-switch-X3W3FALZ.ts +27 -0
- package/dist/chunks/setup-switch-X3W3FALZ.ts.map +7 -0
- package/dist/chunks/snapshot-WBSV4MQG.ts +45 -0
- package/dist/chunks/snapshot-WBSV4MQG.ts.map +7 -0
- package/dist/chunks/sync-operations-MPAFVVHA.ts +1112 -0
- package/dist/chunks/sync-operations-MPAFVVHA.ts.map +7 -0
- package/dist/chunks/sync-state-36H7ULG2.ts +41 -0
- package/dist/chunks/sync-state-36H7ULG2.ts.map +7 -0
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts +869 -0
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +7 -0
- package/dist/index.ts +668 -0
- package/dist/index.ts.map +7 -0
- package/package.json +10 -6
package/dist/index.ts
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
createSyncAttentionController,
|
|
5
|
+
syncAttentionMatchesConfig
|
|
6
|
+
} from "./chunks/chunk-JNOOSPEW.js";
|
|
7
|
+
import {
|
|
8
|
+
completeSyncArguments,
|
|
9
|
+
parseOptions,
|
|
10
|
+
resolveSyncCommand,
|
|
11
|
+
setSyncSetupCompletions,
|
|
12
|
+
splitArgs,
|
|
13
|
+
usage,
|
|
14
|
+
validateCommandOptions
|
|
15
|
+
} from "./chunks/chunk-4U6AFLJB.js";
|
|
16
|
+
import {
|
|
17
|
+
recoverSnapshotTransactionsOnStartup
|
|
18
|
+
} from "./chunks/chunk-3NDD5QPM.js";
|
|
19
|
+
import {
|
|
20
|
+
unlock,
|
|
21
|
+
withLock
|
|
22
|
+
} from "./chunks/chunk-LJDSHNS3.js";
|
|
23
|
+
import {
|
|
24
|
+
SetupPullRequiresUiError,
|
|
25
|
+
errorMessage,
|
|
26
|
+
isSyncDecisionRequiredError
|
|
27
|
+
} from "./chunks/chunk-W64NEHXT.js";
|
|
28
|
+
import "./chunks/chunk-RGJ6SOYR.js";
|
|
29
|
+
import "./chunks/chunk-EWX3TPLQ.js";
|
|
30
|
+
import {
|
|
31
|
+
activeLocalConfigPath,
|
|
32
|
+
configuredSyncSetupNames,
|
|
33
|
+
consumeLocalConfigMigrationNotice,
|
|
34
|
+
createLocalConfigDocument,
|
|
35
|
+
ensureStateDir,
|
|
36
|
+
isMissingConfigError,
|
|
37
|
+
loadConfig,
|
|
38
|
+
loadPartialConfig,
|
|
39
|
+
localConfigPath,
|
|
40
|
+
localConfigTemplate,
|
|
41
|
+
readLocalConfigObject,
|
|
42
|
+
readStateForConfig,
|
|
43
|
+
sessionTokenWarnings,
|
|
44
|
+
syncSessionsWarnings
|
|
45
|
+
} from "./chunks/chunk-APZVBSM6.js";
|
|
46
|
+
import {
|
|
47
|
+
migrateLegacyStateDirectory,
|
|
48
|
+
stateDirectoryMigrationNotice,
|
|
49
|
+
withStateDirectoryAccess
|
|
50
|
+
} from "./chunks/chunk-F5QL2GPY.js";
|
|
51
|
+
import {
|
|
52
|
+
RemoteSelectionMismatchError,
|
|
53
|
+
formatRemoteSelectionMismatch
|
|
54
|
+
} from "./chunks/chunk-YQ6UW7IF.js";
|
|
55
|
+
|
|
56
|
+
// src/sync-extension.ts
|
|
57
|
+
var STATUS_KEY = "sync";
|
|
58
|
+
var AUTO_SYNC_OPTIONS = {
|
|
59
|
+
yes: true,
|
|
60
|
+
force: false,
|
|
61
|
+
stale: false,
|
|
62
|
+
silent: true,
|
|
63
|
+
reload: false,
|
|
64
|
+
auto: true,
|
|
65
|
+
args: []
|
|
66
|
+
};
|
|
67
|
+
function sync(pi, dependencies = {}) {
|
|
68
|
+
const loaders = {
|
|
69
|
+
setupSwitch: cachedModuleLoader(
|
|
70
|
+
dependencies.loadSetupSwitch ?? (() => import("./chunks/setup-switch-X3W3FALZ.js"))
|
|
71
|
+
),
|
|
72
|
+
snapshot: cachedModuleLoader(dependencies.loadSnapshot ?? (() => import("./chunks/snapshot-WBSV4MQG.js"))),
|
|
73
|
+
syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import("./chunks/sync-state-36H7ULG2.js"))),
|
|
74
|
+
operations: cachedModuleLoader(
|
|
75
|
+
dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-MPAFVVHA.js"))
|
|
76
|
+
)
|
|
77
|
+
};
|
|
78
|
+
const attention = createSyncAttentionController();
|
|
79
|
+
let sessionAbort = new AbortController();
|
|
80
|
+
let shutdownAbort;
|
|
81
|
+
pi.registerCommand("sync", {
|
|
82
|
+
description: "Sync Pi settings through Git, WebDAV, R2, or S3-compatible storage",
|
|
83
|
+
getArgumentCompletions: completeSyncArguments,
|
|
84
|
+
handler: async (args, ctx) => {
|
|
85
|
+
if (!ctx.hasUI) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"/sync requires TUI or RPC mode so results and safety prompts are observable."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const run = () => handleCommand(args, ctx, sessionAbort.signal, loaders, attention);
|
|
91
|
+
if (splitArgs(args)[0] === "migrate-state") await run();
|
|
92
|
+
else await withStateDirectoryAccess(run);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
96
|
+
shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
|
|
97
|
+
shutdownAbort = void 0;
|
|
98
|
+
sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
|
|
99
|
+
sessionAbort = new AbortController();
|
|
100
|
+
const signal = sessionAbort.signal;
|
|
101
|
+
attention.reset(ctx);
|
|
102
|
+
let decision;
|
|
103
|
+
try {
|
|
104
|
+
decision = await withStateDirectoryAccess(() => startSession(ctx, signal, loaders));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (signal.aborted) return;
|
|
107
|
+
ctx.ui.notify(`pi-sync state access failed: ${errorMessage(error)}`, "error");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!decision || signal.aborted) return;
|
|
111
|
+
attention.set(decision, "sync");
|
|
112
|
+
if (ctx.mode !== "tui") {
|
|
113
|
+
ctx.ui.notify(
|
|
114
|
+
`pi-sync auto sync skipped: ${formatRemoteSelectionMismatch(
|
|
115
|
+
decision.setupName,
|
|
116
|
+
decision.localInclude,
|
|
117
|
+
decision.remoteInclude
|
|
118
|
+
)}
|
|
119
|
+
RPC review is read-only.`,
|
|
120
|
+
"warning"
|
|
121
|
+
);
|
|
122
|
+
} else if (attention.markOffered()) {
|
|
123
|
+
await resolveSelectionAttention(ctx, attention, signal, loaders, {
|
|
124
|
+
cancelLabel: "Later",
|
|
125
|
+
withStateAccess: withStateDirectoryAccess
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (!signal.aborted) attention.publish(ctx);
|
|
129
|
+
});
|
|
130
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
131
|
+
sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
|
|
132
|
+
attention.reset(ctx);
|
|
133
|
+
shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
|
|
134
|
+
const controller = new AbortController();
|
|
135
|
+
shutdownAbort = controller;
|
|
136
|
+
const signal = combineSignals(controller.signal, AbortSignal.timeout(3e4));
|
|
137
|
+
const reason = typeof event === "object" && event ? event.reason : void 0;
|
|
138
|
+
try {
|
|
139
|
+
if (reason !== "reload") {
|
|
140
|
+
await withStateDirectoryAccess(async () => {
|
|
141
|
+
if (signal.aborted) return;
|
|
142
|
+
await autoPushSessions(ctx, signal, loaders);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (!signal.aborted) {
|
|
147
|
+
ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
|
|
148
|
+
}
|
|
149
|
+
} finally {
|
|
150
|
+
if (shutdownAbort === controller) shutdownAbort = void 0;
|
|
151
|
+
}
|
|
152
|
+
if (signal.aborted) return;
|
|
153
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function startSession(ctx, signal, loaders) {
|
|
157
|
+
if (signal.aborted) return;
|
|
158
|
+
try {
|
|
159
|
+
const migrationNotice2 = stateDirectoryMigrationNotice();
|
|
160
|
+
if (migrationNotice2) ctx.ui.notify(migrationNotice2, "warning");
|
|
161
|
+
} catch (error) {
|
|
162
|
+
ctx.ui.notify(`pi-sync state directory requires attention: ${errorMessage(error)}`, "error");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await recoverSnapshotTransactionsOnStartup();
|
|
167
|
+
if (signal.aborted) return;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (signal.aborted) return;
|
|
170
|
+
ctx.ui.notify(`pi-sync recovery required: ${errorMessage(error)}`, "error");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
setSyncSetupCompletions(await configuredSyncSetupNames());
|
|
175
|
+
if (signal.aborted) return;
|
|
176
|
+
} catch {
|
|
177
|
+
if (signal.aborted) return;
|
|
178
|
+
setSyncSetupCompletions([]);
|
|
179
|
+
}
|
|
180
|
+
const migrationNotice = consumeLocalConfigMigrationNotice();
|
|
181
|
+
if (migrationNotice) ctx.ui.notify(migrationNotice, "warning");
|
|
182
|
+
if (signal.aborted) return;
|
|
183
|
+
return autoSync(ctx, signal, loaders);
|
|
184
|
+
}
|
|
185
|
+
async function handleCommand(rawArgs, ctx, sessionSignal, loaders, attention) {
|
|
186
|
+
if (!rawArgs.trim()) {
|
|
187
|
+
try {
|
|
188
|
+
const { showSyncManager } = await import("./chunks/manager-ui-7E25VWUP.js");
|
|
189
|
+
if (sessionSignal.aborted) return;
|
|
190
|
+
await showSyncManager(
|
|
191
|
+
ctx,
|
|
192
|
+
(route, signal, onCommit, target) => executeCommand(
|
|
193
|
+
route,
|
|
194
|
+
ctx,
|
|
195
|
+
combineSignals(sessionSignal, signal),
|
|
196
|
+
loaders,
|
|
197
|
+
onCommit,
|
|
198
|
+
target
|
|
199
|
+
),
|
|
200
|
+
sessionSignal,
|
|
201
|
+
{
|
|
202
|
+
getAttention: () => attention.current(),
|
|
203
|
+
onSelectionResolved: (expected) => {
|
|
204
|
+
if (attention.current() === expected) attention.clear(ctx);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (sessionSignal.aborted) return;
|
|
210
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
211
|
+
ctx.ui.notify(errorMessage(error), "error");
|
|
212
|
+
}
|
|
213
|
+
if (!sessionSignal.aborted) attention.publish(ctx);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const result = await executeCommand(rawArgs, ctx, sessionSignal, loaders);
|
|
217
|
+
if (result.kind === "decision-required") {
|
|
218
|
+
ctx.ui.notify(result.decision.directMessage, "error");
|
|
219
|
+
} else if (result.kind === "remote-selection-required") {
|
|
220
|
+
const origin = directSelectionOrigin(rawArgs);
|
|
221
|
+
if (origin) attention.set(result.decision, origin);
|
|
222
|
+
const deterministic = splitArgs(rawArgs).some((arg) => arg === "--yes" || arg === "-y");
|
|
223
|
+
if (origin && ctx.mode === "tui" && !deterministic) {
|
|
224
|
+
await resolveSelectionAttention(ctx, attention, sessionSignal, loaders);
|
|
225
|
+
} else {
|
|
226
|
+
ctx.ui.notify(
|
|
227
|
+
formatRemoteSelectionMismatch(
|
|
228
|
+
result.decision.setupName,
|
|
229
|
+
result.decision.localInclude,
|
|
230
|
+
result.decision.remoteInclude
|
|
231
|
+
),
|
|
232
|
+
"error"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
await clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, sessionSignal);
|
|
237
|
+
await reconcileSelectionAttention(ctx, attention, sessionSignal);
|
|
238
|
+
if (!sessionSignal.aborted) attention.publish(ctx);
|
|
239
|
+
}
|
|
240
|
+
async function clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, signal) {
|
|
241
|
+
if (result.kind !== "completed" || result.outcome === "cancelled" || signal.aborted) return;
|
|
242
|
+
const [command, ...rest] = splitArgs(rawArgs);
|
|
243
|
+
if (command !== "sync" && command !== "pull" && command !== "push") return;
|
|
244
|
+
const current = attention.current();
|
|
245
|
+
if (!current) return;
|
|
246
|
+
try {
|
|
247
|
+
const options = parseOptions(rest);
|
|
248
|
+
const setupName = options.setup ?? (await loadConfig()).setupName;
|
|
249
|
+
if (signal.aborted || attention.current() !== current) return;
|
|
250
|
+
if (current.decision.setupName === setupName) attention.clear(ctx);
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function reconcileSelectionAttention(ctx, attention, signal) {
|
|
255
|
+
const current = attention.current();
|
|
256
|
+
if (!current || signal.aborted) return;
|
|
257
|
+
try {
|
|
258
|
+
const config = await loadConfig(current.decision.setupName);
|
|
259
|
+
if (signal.aborted || attention.current() !== current) return;
|
|
260
|
+
if (!syncAttentionMatchesConfig(current, config)) attention.clear(ctx);
|
|
261
|
+
} catch {
|
|
262
|
+
if (!signal.aborted && attention.current() === current) attention.clear(ctx);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function directSelectionOrigin(rawArgs) {
|
|
266
|
+
const command = splitArgs(rawArgs)[0];
|
|
267
|
+
return command === "sync" || command === "pull" || command === "push" ? command : void 0;
|
|
268
|
+
}
|
|
269
|
+
async function resolveSelectionAttention(ctx, attention, signal, loaders, options = {}) {
|
|
270
|
+
const current = attention.current();
|
|
271
|
+
if (!current || signal.aborted) return;
|
|
272
|
+
const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-YKBLZ7KL.js");
|
|
273
|
+
if (signal.aborted || attention.current() !== current) return;
|
|
274
|
+
await dispatchManagerResult(
|
|
275
|
+
ctx,
|
|
276
|
+
{ kind: "remote-selection-required", decision: current.decision },
|
|
277
|
+
current.origin,
|
|
278
|
+
(route, actionSignal, onCommit, target) => {
|
|
279
|
+
const execute = () => executeRecoveryCommand(
|
|
280
|
+
route,
|
|
281
|
+
ctx,
|
|
282
|
+
combineSignals(signal, actionSignal),
|
|
283
|
+
loaders,
|
|
284
|
+
onCommit,
|
|
285
|
+
target
|
|
286
|
+
);
|
|
287
|
+
return options.withStateAccess ? options.withStateAccess(execute) : execute();
|
|
288
|
+
},
|
|
289
|
+
signal,
|
|
290
|
+
{
|
|
291
|
+
cancelLabel: options.cancelLabel,
|
|
292
|
+
withStateAccess: options.withStateAccess,
|
|
293
|
+
onSelectionResolved: () => {
|
|
294
|
+
if (attention.current() === current) attention.clear(ctx);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
async function executeRecoveryCommand(rawArgs, ctx, signal, loaders, onCommit, setup) {
|
|
300
|
+
try {
|
|
301
|
+
const [subcommand, ...rest] = splitArgs(rawArgs);
|
|
302
|
+
if (subcommand !== "sync" && subcommand !== "pull" && subcommand !== "push") {
|
|
303
|
+
throw new Error(`Unsupported sync recovery route: ${subcommand ?? "missing"}`);
|
|
304
|
+
}
|
|
305
|
+
const options = parseOptions(rest);
|
|
306
|
+
if (setup !== void 0) options.setup = setup;
|
|
307
|
+
if (signal) options.signal = signal;
|
|
308
|
+
if (onCommit) options.onCommit = onCommit;
|
|
309
|
+
options.reload = false;
|
|
310
|
+
options.auto = false;
|
|
311
|
+
validateCommandOptions(subcommand, options);
|
|
312
|
+
const operations = await loaders.operations();
|
|
313
|
+
throwIfAborted(options.signal);
|
|
314
|
+
if (subcommand === "push") {
|
|
315
|
+
const outcome = await withLock("push", () => operations.push(ctx, options));
|
|
316
|
+
return { kind: "completed", ...outcome ? { outcome } : {} };
|
|
317
|
+
}
|
|
318
|
+
if (subcommand === "pull") {
|
|
319
|
+
const outcome = await withLock("pull", () => operations.pull(ctx, options));
|
|
320
|
+
return { kind: "completed", ...outcome ? { outcome } : {} };
|
|
321
|
+
}
|
|
322
|
+
await withLock("sync", () => operations.syncBoth(ctx, options));
|
|
323
|
+
return { kind: "completed" };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (signal?.aborted) return { kind: "failed" };
|
|
326
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
327
|
+
if (error instanceof RemoteSelectionMismatchError) {
|
|
328
|
+
return { kind: "remote-selection-required", decision: error.decision };
|
|
329
|
+
}
|
|
330
|
+
if (isSyncDecisionRequiredError(error)) {
|
|
331
|
+
return { kind: "decision-required", decision: error.decision };
|
|
332
|
+
}
|
|
333
|
+
ctx.ui.notify(errorMessage(error), "error");
|
|
334
|
+
return { kind: "failed" };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function executeCommand(rawArgs, ctx, signal, loaders, onCommit, setup) {
|
|
338
|
+
try {
|
|
339
|
+
const command = await resolveSyncCommand(rawArgs, ctx);
|
|
340
|
+
if (signal?.aborted || !command) return { kind: "completed" };
|
|
341
|
+
const { subcommand, rest } = command;
|
|
342
|
+
const options = parseOptions(rest);
|
|
343
|
+
if (setup !== void 0) options.setup = setup;
|
|
344
|
+
if (signal) options.signal = signal;
|
|
345
|
+
if (onCommit) options.onCommit = onCommit;
|
|
346
|
+
validateCommandOptions(subcommand, options);
|
|
347
|
+
switch (subcommand) {
|
|
348
|
+
case "help":
|
|
349
|
+
ctx.ui.notify(usage(), "info");
|
|
350
|
+
return { kind: "completed" };
|
|
351
|
+
case "use": {
|
|
352
|
+
const { useSyncSetup } = await loaders.setupSwitch();
|
|
353
|
+
throwIfAborted(options.signal);
|
|
354
|
+
await useSyncSetup(
|
|
355
|
+
ctx,
|
|
356
|
+
options.args[0] ?? "",
|
|
357
|
+
async (selectedSetup) => {
|
|
358
|
+
const operations = await loaders.operations();
|
|
359
|
+
throwIfAborted(options.signal);
|
|
360
|
+
return withLock(
|
|
361
|
+
"pull",
|
|
362
|
+
() => operations.pull(ctx, { ...options, setup: selectedSetup })
|
|
363
|
+
);
|
|
364
|
+
},
|
|
365
|
+
void 0,
|
|
366
|
+
options.signal
|
|
367
|
+
);
|
|
368
|
+
return { kind: "completed" };
|
|
369
|
+
}
|
|
370
|
+
case "init":
|
|
371
|
+
await initConfig(ctx, signal);
|
|
372
|
+
return { kind: "completed" };
|
|
373
|
+
case "config":
|
|
374
|
+
await showConfig(ctx, options);
|
|
375
|
+
return { kind: "completed" };
|
|
376
|
+
case "files": {
|
|
377
|
+
const { showFileSelection } = await import("./chunks/file-selection-4UABOP44.js");
|
|
378
|
+
throwIfAborted(options.signal);
|
|
379
|
+
await showFileSelection(ctx, options.setup, options.signal);
|
|
380
|
+
return { kind: "completed" };
|
|
381
|
+
}
|
|
382
|
+
case "status": {
|
|
383
|
+
const operations = await loaders.operations();
|
|
384
|
+
throwIfAborted(options.signal);
|
|
385
|
+
await operations.status(ctx, options);
|
|
386
|
+
return { kind: "completed" };
|
|
387
|
+
}
|
|
388
|
+
case "diff": {
|
|
389
|
+
const operations = await loaders.operations();
|
|
390
|
+
throwIfAborted(options.signal);
|
|
391
|
+
await operations.diff(ctx, options);
|
|
392
|
+
return { kind: "completed" };
|
|
393
|
+
}
|
|
394
|
+
case "doctor": {
|
|
395
|
+
const operations = await loaders.operations();
|
|
396
|
+
throwIfAborted(options.signal);
|
|
397
|
+
await operations.doctor(ctx, options);
|
|
398
|
+
return { kind: "completed" };
|
|
399
|
+
}
|
|
400
|
+
case "push": {
|
|
401
|
+
const operations = await loaders.operations();
|
|
402
|
+
throwIfAborted(options.signal);
|
|
403
|
+
const outcome = await withLock("push", () => operations.push(ctx, options));
|
|
404
|
+
return { kind: "completed", ...outcome ? { outcome } : {} };
|
|
405
|
+
}
|
|
406
|
+
case "pull": {
|
|
407
|
+
const operations = await loaders.operations();
|
|
408
|
+
throwIfAborted(options.signal);
|
|
409
|
+
const outcome = await withLock("pull", () => operations.pull(ctx, options));
|
|
410
|
+
return { kind: "completed", ...outcome ? { outcome } : {} };
|
|
411
|
+
}
|
|
412
|
+
case "sync": {
|
|
413
|
+
const operations = await loaders.operations();
|
|
414
|
+
throwIfAborted(options.signal);
|
|
415
|
+
await withLock("sync", () => operations.syncBoth(ctx, options));
|
|
416
|
+
return { kind: "completed" };
|
|
417
|
+
}
|
|
418
|
+
case "history": {
|
|
419
|
+
const operations = await loaders.operations();
|
|
420
|
+
throwIfAborted(options.signal);
|
|
421
|
+
await operations.history(ctx, options);
|
|
422
|
+
return { kind: "completed" };
|
|
423
|
+
}
|
|
424
|
+
case "rollback": {
|
|
425
|
+
const operations = await loaders.operations();
|
|
426
|
+
throwIfAborted(options.signal);
|
|
427
|
+
await withLock("rollback", () => operations.rollback(ctx, options));
|
|
428
|
+
return { kind: "completed" };
|
|
429
|
+
}
|
|
430
|
+
case "migrate-state":
|
|
431
|
+
await migrateStateDirectory(ctx, options);
|
|
432
|
+
return { kind: "completed" };
|
|
433
|
+
case "unlock":
|
|
434
|
+
await unlock(ctx, options);
|
|
435
|
+
return { kind: "completed" };
|
|
436
|
+
default:
|
|
437
|
+
ctx.ui.notify(`Unknown /sync command: ${subcommand}
|
|
438
|
+
|
|
439
|
+
${usage()}`, "warning");
|
|
440
|
+
return { kind: "failed" };
|
|
441
|
+
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (signal?.aborted) return { kind: "failed" };
|
|
444
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
445
|
+
if (error instanceof SetupPullRequiresUiError) throw error;
|
|
446
|
+
if (error instanceof RemoteSelectionMismatchError) {
|
|
447
|
+
return { kind: "remote-selection-required", decision: error.decision };
|
|
448
|
+
}
|
|
449
|
+
if (isSyncDecisionRequiredError(error)) {
|
|
450
|
+
return { kind: "decision-required", decision: error.decision };
|
|
451
|
+
}
|
|
452
|
+
ctx.ui.notify(errorMessage(error), "error");
|
|
453
|
+
return { kind: "failed" };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function migrateStateDirectory(ctx, options) {
|
|
457
|
+
const notice = stateDirectoryMigrationNotice();
|
|
458
|
+
if (!notice) {
|
|
459
|
+
ctx.ui.notify("pi-sync already uses the canonical pi-sync/ state directory.", "info");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (!options.yes && !await ctx.ui.confirm(
|
|
463
|
+
"Migrate pi-sync state directory",
|
|
464
|
+
"Confirm that every other Pi process is closed. pi-sync will atomically rename .pisync/ to pi-sync/ without merging or deleting either root.",
|
|
465
|
+
{ signal: options.signal }
|
|
466
|
+
)) {
|
|
467
|
+
ctx.ui.notify("pi-sync state migration cancelled.", "info");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
throwIfAborted(options.signal);
|
|
471
|
+
const result = await migrateLegacyStateDirectory();
|
|
472
|
+
throwIfAborted(options.signal);
|
|
473
|
+
if (result.status === "ready") {
|
|
474
|
+
ctx.ui.notify("pi-sync already uses the canonical pi-sync/ state directory.", "info");
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
ctx.ui.notify(result.message, result.status === "migrated" ? "info" : "warning");
|
|
478
|
+
}
|
|
479
|
+
async function autoSync(ctx, signal, loaders) {
|
|
480
|
+
try {
|
|
481
|
+
const partial = await loadPartialConfig();
|
|
482
|
+
throwIfAborted(signal);
|
|
483
|
+
if (!partial.automatic) return;
|
|
484
|
+
await ensureStateDir();
|
|
485
|
+
throwIfAborted(signal);
|
|
486
|
+
await loadConfig();
|
|
487
|
+
throwIfAborted(signal);
|
|
488
|
+
const operations = await loaders.operations();
|
|
489
|
+
throwIfAborted(signal);
|
|
490
|
+
await withLock("auto-sync", () => {
|
|
491
|
+
throwIfAborted(signal);
|
|
492
|
+
return operations.syncBoth(ctx, { ...AUTO_SYNC_OPTIONS, signal });
|
|
493
|
+
});
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (signal.aborted || isMissingConfigError(error)) return;
|
|
496
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
497
|
+
if (error instanceof RemoteSelectionMismatchError) return error.decision;
|
|
498
|
+
ctx.ui.notify(`pi-sync auto sync skipped: ${errorMessage(error)}`, "warning");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async function autoPushSessions(ctx, signal, loaders) {
|
|
502
|
+
try {
|
|
503
|
+
const partial = await loadPartialConfig();
|
|
504
|
+
throwIfAborted(signal);
|
|
505
|
+
if (!partial.automatic) return;
|
|
506
|
+
if (!partial.include.includes("sessions")) return;
|
|
507
|
+
await ensureStateDir();
|
|
508
|
+
throwIfAborted(signal);
|
|
509
|
+
const config = await loadConfig();
|
|
510
|
+
throwIfAborted(signal);
|
|
511
|
+
if (!config.include.includes("sessions")) return;
|
|
512
|
+
const [operations, snapshotModule, syncStateModule] = await Promise.all([
|
|
513
|
+
loaders.operations(),
|
|
514
|
+
loaders.snapshot(),
|
|
515
|
+
loaders.syncState()
|
|
516
|
+
]);
|
|
517
|
+
throwIfAborted(signal);
|
|
518
|
+
await withLock("auto-session-push", async () => {
|
|
519
|
+
throwIfAborted(signal);
|
|
520
|
+
const state = await readStateForConfig(config);
|
|
521
|
+
throwIfAborted(signal);
|
|
522
|
+
const local = await snapshotModule.createSnapshot(
|
|
523
|
+
config.snapshotIdentity,
|
|
524
|
+
snapshotOptionsForContext(ctx, config)
|
|
525
|
+
);
|
|
526
|
+
throwIfAborted(signal);
|
|
527
|
+
if (!syncStateModule.hasLocalChanges(local, state, config)) return;
|
|
528
|
+
await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });
|
|
529
|
+
});
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (signal.aborted || isMissingConfigError(error)) return;
|
|
532
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
533
|
+
ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async function initConfig(ctx, signal) {
|
|
537
|
+
const configPath = localConfigPath();
|
|
538
|
+
if (await readLocalConfigObject()) {
|
|
539
|
+
ctx.ui.notify(`Config already exists: ${await activeLocalConfigPath()}`, "info");
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (ctx.mode === "tui") {
|
|
543
|
+
const { showSetupWizard } = await import("./chunks/manager-ui-7E25VWUP.js");
|
|
544
|
+
throwIfAborted(signal);
|
|
545
|
+
await showSetupWizard(ctx, signal);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
await createLocalConfigDocument(localConfigTemplate());
|
|
549
|
+
ctx.ui.notify(
|
|
550
|
+
`Created ${configPath}. Add a storage connection and sync setup before syncing.`,
|
|
551
|
+
"info"
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
async function showConfig(ctx, options) {
|
|
555
|
+
const config = await loadConfig(options.setup);
|
|
556
|
+
const warnings = [
|
|
557
|
+
...config.backend.type === "s3" ? sessionTokenWarnings(config.backend.profile) : [],
|
|
558
|
+
...syncSessionsWarnings(config)
|
|
559
|
+
];
|
|
560
|
+
const storageLines = configStorageLines(config);
|
|
561
|
+
ctx.ui.notify(
|
|
562
|
+
[
|
|
563
|
+
"pi-sync config:",
|
|
564
|
+
`sync setup: ${config.setupName}`,
|
|
565
|
+
`storage connection: ${config.connectionName}`,
|
|
566
|
+
...storageLines,
|
|
567
|
+
`storage path: ${config.storagePath}`,
|
|
568
|
+
`automatic sync: ${config.automatic ? "enabled" : "disabled"}`,
|
|
569
|
+
`included content: ${config.include.join(", ") || "none"}`,
|
|
570
|
+
`sessions: ${config.include.includes("sessions") ? "included" : "not included"}`,
|
|
571
|
+
`settings file: ${localConfigPath()}`,
|
|
572
|
+
...warnings
|
|
573
|
+
].join("\n"),
|
|
574
|
+
warnings.length > 0 ? "warning" : "info"
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
function configStorageLines(config) {
|
|
578
|
+
switch (config.backend.type) {
|
|
579
|
+
case "git":
|
|
580
|
+
return [
|
|
581
|
+
"kind: git",
|
|
582
|
+
`remote: ${displayGitRemote(config.backend.profile.remote)}`,
|
|
583
|
+
"authentication: existing Git/SSH credentials (not stored)",
|
|
584
|
+
`branch: ${config.backend.destination.branch}`
|
|
585
|
+
];
|
|
586
|
+
case "webdav":
|
|
587
|
+
return [
|
|
588
|
+
"kind: webdav",
|
|
589
|
+
`url: ${displayWebDavUrl(config.backend.profile.url, config.backend.profile.username)}`,
|
|
590
|
+
"username: configured (value hidden)",
|
|
591
|
+
"password: configured"
|
|
592
|
+
];
|
|
593
|
+
case "s3":
|
|
594
|
+
return [
|
|
595
|
+
"kind: s3",
|
|
596
|
+
`endpoint: ${config.backend.profile.endpoint}`,
|
|
597
|
+
`bucket: ${config.backend.destination.bucket}`,
|
|
598
|
+
`region: ${config.backend.profile.region}`,
|
|
599
|
+
"access key id: configured",
|
|
600
|
+
"secret access key: configured",
|
|
601
|
+
`session token: ${config.backend.profile.sessionToken ? "configured" : "not configured"}`
|
|
602
|
+
];
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function displayGitRemote(value) {
|
|
606
|
+
if (!value) return "missing";
|
|
607
|
+
try {
|
|
608
|
+
const url = new URL(value);
|
|
609
|
+
url.username = "";
|
|
610
|
+
url.password = "";
|
|
611
|
+
url.search = "";
|
|
612
|
+
url.hash = "";
|
|
613
|
+
return url.toString();
|
|
614
|
+
} catch {
|
|
615
|
+
return value.replace(/^(?:[^@\s]+@)?(?<host>[^:]+):.+$/u, "$<host>:\u2026");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function displayWebDavUrl(value, username) {
|
|
619
|
+
if (!value) return "missing";
|
|
620
|
+
try {
|
|
621
|
+
const url = new URL(value);
|
|
622
|
+
url.username = "";
|
|
623
|
+
url.password = "";
|
|
624
|
+
url.search = "";
|
|
625
|
+
url.hash = "";
|
|
626
|
+
return username ? `${url.origin}/\u2026` : `${url.origin}${url.pathname}`;
|
|
627
|
+
} catch {
|
|
628
|
+
return "invalid (value hidden)";
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function throwIfAborted(signal) {
|
|
632
|
+
if (!signal?.aborted) return;
|
|
633
|
+
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
634
|
+
}
|
|
635
|
+
function combineSignals(primary, secondary) {
|
|
636
|
+
return secondary ? AbortSignal.any([primary, secondary]) : primary;
|
|
637
|
+
}
|
|
638
|
+
function snapshotOptionsForContext(ctx, config) {
|
|
639
|
+
return {
|
|
640
|
+
include: config.include,
|
|
641
|
+
sessionDir: sessionDirFromContext(ctx)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function sessionDirFromContext(ctx) {
|
|
645
|
+
const manager = ctx.sessionManager;
|
|
646
|
+
const usesDefaultSessionDir = manager.usesDefaultSessionDir;
|
|
647
|
+
if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
const getSessionDir = manager.getSessionDir;
|
|
651
|
+
return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
|
|
652
|
+
}
|
|
653
|
+
function cachedModuleLoader(load) {
|
|
654
|
+
let pending;
|
|
655
|
+
return () => {
|
|
656
|
+
if (!pending) {
|
|
657
|
+
pending = load().catch((error) => {
|
|
658
|
+
pending = void 0;
|
|
659
|
+
throw error;
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
return pending;
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
export {
|
|
666
|
+
sync as default
|
|
667
|
+
};
|
|
668
|
+
//# sourceMappingURL=index.ts.map
|