@narumitw/pi-sync 0.49.4 → 0.49.6
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 +12 -5
- package/package.json +1 -1
- package/src/command.ts +6 -1
- package/src/config.ts +2 -3
- package/src/git-backend.ts +2 -2
- package/src/index.ts +1 -1
- package/src/lock-policy.ts +2 -0
- package/src/lock.ts +5 -43
- package/src/lockfile-fs.ts +25 -0
- package/src/paths.ts +2 -0
- package/src/setup-switch.ts +3 -2
- package/src/snapshot-paths.ts +13 -0
- package/src/snapshot-transaction.ts +1 -1
- package/src/snapshot.ts +2 -12
- package/src/state-directory.ts +184 -0
- package/src/sync-decision.ts +5 -13
- package/src/sync-errors.ts +23 -0
- package/src/sync-extension.ts +567 -0
- package/src/sync-format.ts +3 -4
- package/src/sync.ts +1 -448
package/src/sync.ts
CHANGED
|
@@ -1,451 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
ExtensionContext,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import type { RunRouteResult } from "./cancellable-operation.js";
|
|
7
|
-
import {
|
|
8
|
-
completeSyncArguments,
|
|
9
|
-
parseOptions,
|
|
10
|
-
resolveSyncCommand,
|
|
11
|
-
setSyncSetupCompletions,
|
|
12
|
-
usage,
|
|
13
|
-
validateCommandOptions,
|
|
14
|
-
} from "./command.js";
|
|
15
|
-
import {
|
|
16
|
-
activeLocalConfigPath,
|
|
17
|
-
configuredSyncSetupNames,
|
|
18
|
-
consumeLocalConfigMigrationNotice,
|
|
19
|
-
createLocalConfigDocument,
|
|
20
|
-
ensureStateDir,
|
|
21
|
-
isMissingConfigError,
|
|
22
|
-
loadConfig,
|
|
23
|
-
loadPartialConfig,
|
|
24
|
-
localConfigPath,
|
|
25
|
-
localConfigTemplate,
|
|
26
|
-
readLocalConfigObject,
|
|
27
|
-
readStateForConfig,
|
|
28
|
-
sessionTokenWarnings,
|
|
29
|
-
syncSessionsWarnings,
|
|
30
|
-
} from "./config.js";
|
|
31
|
-
import { unlock, withLock } from "./lock.js";
|
|
32
|
-
import { SetupPullRequiresUiError, useSyncSetup } from "./setup-switch.js";
|
|
33
|
-
import { createSnapshot } from "./snapshot.js";
|
|
34
|
-
import { recoverSnapshotTransactionsOnStartup } from "./snapshot-transaction.js";
|
|
35
|
-
import { isSyncDecisionRequiredError } from "./sync-decision.js";
|
|
36
|
-
import { errorMessage } from "./sync-format.js";
|
|
37
|
-
import { hasLocalChanges } from "./sync-state.js";
|
|
38
|
-
import type { AnySyncConfig, CommandOptions, SnapshotOptions } from "./types.js";
|
|
39
|
-
|
|
40
|
-
const STATUS_KEY = "sync";
|
|
41
|
-
|
|
42
|
-
type SyncOperations = typeof import("./sync-operations.js");
|
|
43
|
-
let syncOperationsPromise: Promise<SyncOperations> | undefined;
|
|
44
|
-
|
|
45
|
-
function loadSyncOperations(): Promise<SyncOperations> {
|
|
46
|
-
if (!syncOperationsPromise) {
|
|
47
|
-
syncOperationsPromise = import("./sync-operations.js").catch((error) => {
|
|
48
|
-
syncOperationsPromise = undefined;
|
|
49
|
-
throw error;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return syncOperationsPromise;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const AUTO_SYNC_OPTIONS: CommandOptions = {
|
|
56
|
-
yes: true,
|
|
57
|
-
force: false,
|
|
58
|
-
stale: false,
|
|
59
|
-
silent: true,
|
|
60
|
-
reload: false,
|
|
61
|
-
auto: true,
|
|
62
|
-
args: [],
|
|
63
|
-
};
|
|
64
|
-
export default function sync(pi: ExtensionAPI) {
|
|
65
|
-
let sessionAbort = new AbortController();
|
|
66
|
-
let shutdownAbort: AbortController | undefined;
|
|
67
|
-
|
|
68
|
-
pi.registerCommand("sync", {
|
|
69
|
-
description: "Sync Pi settings through Git, WebDAV, R2, or S3-compatible storage",
|
|
70
|
-
getArgumentCompletions: completeSyncArguments,
|
|
71
|
-
handler: async (args, ctx) => {
|
|
72
|
-
if (!ctx.hasUI) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
"/sync requires TUI or RPC mode so results and safety prompts are observable.",
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
await handleCommand(args, ctx, sessionAbort.signal);
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
82
|
-
shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
|
|
83
|
-
shutdownAbort = undefined;
|
|
84
|
-
sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
|
|
85
|
-
sessionAbort = new AbortController();
|
|
86
|
-
const signal = sessionAbort.signal;
|
|
87
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
88
|
-
try {
|
|
89
|
-
await recoverSnapshotTransactionsOnStartup();
|
|
90
|
-
if (signal.aborted) return;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (signal.aborted) return;
|
|
93
|
-
ctx.ui.notify(`pi-sync recovery required: ${errorMessage(error)}`, "error");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
setSyncSetupCompletions(await configuredSyncSetupNames());
|
|
98
|
-
if (signal.aborted) return;
|
|
99
|
-
} catch {
|
|
100
|
-
if (signal.aborted) return;
|
|
101
|
-
setSyncSetupCompletions([]);
|
|
102
|
-
}
|
|
103
|
-
const migrationNotice = consumeLocalConfigMigrationNotice();
|
|
104
|
-
if (migrationNotice) ctx.ui.notify(migrationNotice, "warning");
|
|
105
|
-
if (signal.aborted) return;
|
|
106
|
-
await autoSync(ctx, signal);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
pi.on("session_shutdown", async (event, ctx) => {
|
|
110
|
-
sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
|
|
111
|
-
shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
|
|
112
|
-
const controller = new AbortController();
|
|
113
|
-
shutdownAbort = controller;
|
|
114
|
-
const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));
|
|
115
|
-
const reason =
|
|
116
|
-
typeof event === "object" && event ? (event as { reason?: string }).reason : undefined;
|
|
117
|
-
try {
|
|
118
|
-
if (reason !== "reload") await autoPushSessions(ctx, signal);
|
|
119
|
-
} finally {
|
|
120
|
-
if (shutdownAbort === controller) shutdownAbort = undefined;
|
|
121
|
-
}
|
|
122
|
-
if (signal.aborted) return;
|
|
123
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function handleCommand(
|
|
128
|
-
rawArgs: string,
|
|
129
|
-
ctx: ExtensionCommandContext,
|
|
130
|
-
sessionSignal: AbortSignal,
|
|
131
|
-
) {
|
|
132
|
-
if (!rawArgs.trim()) {
|
|
133
|
-
try {
|
|
134
|
-
const { showSyncManager } = await import("./manager-ui.js");
|
|
135
|
-
if (sessionSignal.aborted) return;
|
|
136
|
-
await showSyncManager(
|
|
137
|
-
ctx,
|
|
138
|
-
(route, signal, onCommit, target) =>
|
|
139
|
-
executeCommand(route, ctx, combineSignals(sessionSignal, signal), onCommit, target),
|
|
140
|
-
sessionSignal,
|
|
141
|
-
);
|
|
142
|
-
} catch (error) {
|
|
143
|
-
if (sessionSignal.aborted) return;
|
|
144
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
145
|
-
ctx.ui.notify(errorMessage(error), "error");
|
|
146
|
-
}
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const result = await executeCommand(rawArgs, ctx, sessionSignal);
|
|
150
|
-
if (result.kind === "decision-required") {
|
|
151
|
-
ctx.ui.notify(result.decision.directMessage, "error");
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function executeCommand(
|
|
156
|
-
rawArgs: string,
|
|
157
|
-
ctx: ExtensionCommandContext,
|
|
158
|
-
signal?: AbortSignal,
|
|
159
|
-
onCommit?: () => void,
|
|
160
|
-
setup?: string,
|
|
161
|
-
): Promise<RunRouteResult> {
|
|
162
|
-
try {
|
|
163
|
-
const command = await resolveSyncCommand(rawArgs, ctx);
|
|
164
|
-
if (signal?.aborted || !command) return { kind: "completed" };
|
|
165
|
-
const { subcommand, rest } = command;
|
|
166
|
-
const options = parseOptions(rest);
|
|
167
|
-
if (setup !== undefined) options.setup = setup;
|
|
168
|
-
if (signal) options.signal = signal;
|
|
169
|
-
if (onCommit) options.onCommit = onCommit;
|
|
170
|
-
validateCommandOptions(subcommand, options);
|
|
171
|
-
|
|
172
|
-
switch (subcommand) {
|
|
173
|
-
case "help":
|
|
174
|
-
ctx.ui.notify(usage(), "info");
|
|
175
|
-
return { kind: "completed" };
|
|
176
|
-
case "use":
|
|
177
|
-
await useSyncSetup(
|
|
178
|
-
ctx,
|
|
179
|
-
options.args[0] ?? "",
|
|
180
|
-
async (selectedSetup) => {
|
|
181
|
-
const operations = await loadSyncOperations();
|
|
182
|
-
throwIfAborted(options.signal);
|
|
183
|
-
return withLock("pull", () =>
|
|
184
|
-
operations.pull(ctx, { ...options, setup: selectedSetup }),
|
|
185
|
-
);
|
|
186
|
-
},
|
|
187
|
-
undefined,
|
|
188
|
-
options.signal,
|
|
189
|
-
);
|
|
190
|
-
return { kind: "completed" };
|
|
191
|
-
case "init":
|
|
192
|
-
await initConfig(ctx, signal);
|
|
193
|
-
return { kind: "completed" };
|
|
194
|
-
case "config":
|
|
195
|
-
await showConfig(ctx, options);
|
|
196
|
-
return { kind: "completed" };
|
|
197
|
-
case "files":
|
|
198
|
-
await (await import("./file-selection.js")).showFileSelection(
|
|
199
|
-
ctx,
|
|
200
|
-
options.setup,
|
|
201
|
-
options.signal,
|
|
202
|
-
);
|
|
203
|
-
return { kind: "completed" };
|
|
204
|
-
case "status":
|
|
205
|
-
await (await loadSyncOperations()).status(ctx, options);
|
|
206
|
-
return { kind: "completed" };
|
|
207
|
-
case "diff":
|
|
208
|
-
await (await loadSyncOperations()).diff(ctx, options);
|
|
209
|
-
return { kind: "completed" };
|
|
210
|
-
case "doctor":
|
|
211
|
-
await (await loadSyncOperations()).doctor(ctx, options);
|
|
212
|
-
return { kind: "completed" };
|
|
213
|
-
case "push": {
|
|
214
|
-
const operations = await loadSyncOperations();
|
|
215
|
-
throwIfAborted(options.signal);
|
|
216
|
-
const outcome = await withLock("push", () => operations.push(ctx, options));
|
|
217
|
-
return { kind: "completed", ...(outcome ? { outcome } : {}) };
|
|
218
|
-
}
|
|
219
|
-
case "pull": {
|
|
220
|
-
const operations = await loadSyncOperations();
|
|
221
|
-
throwIfAborted(options.signal);
|
|
222
|
-
const outcome = await withLock("pull", () => operations.pull(ctx, options));
|
|
223
|
-
return { kind: "completed", ...(outcome ? { outcome } : {}) };
|
|
224
|
-
}
|
|
225
|
-
case "sync": {
|
|
226
|
-
const operations = await loadSyncOperations();
|
|
227
|
-
throwIfAborted(options.signal);
|
|
228
|
-
await withLock("sync", () => operations.syncBoth(ctx, options));
|
|
229
|
-
return { kind: "completed" };
|
|
230
|
-
}
|
|
231
|
-
case "history":
|
|
232
|
-
await (await loadSyncOperations()).history(ctx, options);
|
|
233
|
-
return { kind: "completed" };
|
|
234
|
-
case "rollback": {
|
|
235
|
-
const operations = await loadSyncOperations();
|
|
236
|
-
throwIfAborted(options.signal);
|
|
237
|
-
await withLock("rollback", () => operations.rollback(ctx, options));
|
|
238
|
-
return { kind: "completed" };
|
|
239
|
-
}
|
|
240
|
-
case "unlock":
|
|
241
|
-
await unlock(ctx, options);
|
|
242
|
-
return { kind: "completed" };
|
|
243
|
-
default:
|
|
244
|
-
ctx.ui.notify(`Unknown /sync command: ${subcommand}\n\n${usage()}`, "warning");
|
|
245
|
-
return { kind: "failed" };
|
|
246
|
-
}
|
|
247
|
-
} catch (error) {
|
|
248
|
-
if (signal?.aborted) return { kind: "failed" };
|
|
249
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
250
|
-
if (error instanceof SetupPullRequiresUiError) throw error;
|
|
251
|
-
if (isSyncDecisionRequiredError(error)) {
|
|
252
|
-
return { kind: "decision-required", decision: error.decision };
|
|
253
|
-
}
|
|
254
|
-
ctx.ui.notify(errorMessage(error), "error");
|
|
255
|
-
return { kind: "failed" };
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function autoSync(ctx: ExtensionContext, signal: AbortSignal) {
|
|
260
|
-
try {
|
|
261
|
-
const partial = await loadPartialConfig();
|
|
262
|
-
throwIfAborted(signal);
|
|
263
|
-
if (!partial.automatic) return;
|
|
264
|
-
await ensureStateDir();
|
|
265
|
-
throwIfAborted(signal);
|
|
266
|
-
await loadConfig();
|
|
267
|
-
throwIfAborted(signal);
|
|
268
|
-
const operations = await loadSyncOperations();
|
|
269
|
-
throwIfAborted(signal);
|
|
270
|
-
await withLock("auto-sync", () => {
|
|
271
|
-
throwIfAborted(signal);
|
|
272
|
-
return operations.syncBoth(ctx, { ...AUTO_SYNC_OPTIONS, signal });
|
|
273
|
-
});
|
|
274
|
-
} catch (error) {
|
|
275
|
-
if (signal.aborted || isMissingConfigError(error)) return;
|
|
276
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
277
|
-
ctx.ui.notify(`pi-sync auto sync skipped: ${errorMessage(error)}`, "warning");
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async function autoPushSessions(ctx: ExtensionContext, signal: AbortSignal) {
|
|
282
|
-
try {
|
|
283
|
-
const partial = await loadPartialConfig();
|
|
284
|
-
throwIfAborted(signal);
|
|
285
|
-
if (!partial.automatic) return;
|
|
286
|
-
if (!partial.include.includes("sessions")) return;
|
|
287
|
-
await ensureStateDir();
|
|
288
|
-
throwIfAborted(signal);
|
|
289
|
-
const config = await loadConfig();
|
|
290
|
-
throwIfAborted(signal);
|
|
291
|
-
if (!config.include.includes("sessions")) return;
|
|
292
|
-
const operations = await loadSyncOperations();
|
|
293
|
-
throwIfAborted(signal);
|
|
294
|
-
await withLock("auto-session-push", async () => {
|
|
295
|
-
throwIfAborted(signal);
|
|
296
|
-
const state = await readStateForConfig(config);
|
|
297
|
-
throwIfAborted(signal);
|
|
298
|
-
const local = await createSnapshot(
|
|
299
|
-
config.snapshotIdentity,
|
|
300
|
-
snapshotOptionsForContext(ctx, config),
|
|
301
|
-
);
|
|
302
|
-
throwIfAborted(signal);
|
|
303
|
-
if (!hasLocalChanges(local, state, config)) return;
|
|
304
|
-
await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });
|
|
305
|
-
});
|
|
306
|
-
} catch (error) {
|
|
307
|
-
if (signal.aborted || isMissingConfigError(error)) return;
|
|
308
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
309
|
-
ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async function initConfig(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
314
|
-
const configPath = localConfigPath();
|
|
315
|
-
if (await readLocalConfigObject()) {
|
|
316
|
-
ctx.ui.notify(`Config already exists: ${await activeLocalConfigPath()}`, "info");
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (ctx.mode === "tui") {
|
|
321
|
-
const { showSetupWizard } = await import("./manager-ui.js");
|
|
322
|
-
throwIfAborted(signal);
|
|
323
|
-
await showSetupWizard(ctx, signal);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
await createLocalConfigDocument(localConfigTemplate());
|
|
327
|
-
ctx.ui.notify(
|
|
328
|
-
`Created ${configPath}. Add a storage connection and sync setup before syncing.`,
|
|
329
|
-
"info",
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
async function showConfig(ctx: ExtensionCommandContext, options: CommandOptions) {
|
|
334
|
-
const config = await loadConfig(options.setup);
|
|
335
|
-
const warnings = [
|
|
336
|
-
...(config.backend.type === "s3" ? sessionTokenWarnings(config.backend.profile) : []),
|
|
337
|
-
...syncSessionsWarnings(config),
|
|
338
|
-
];
|
|
339
|
-
const storageLines = configStorageLines(config);
|
|
340
|
-
ctx.ui.notify(
|
|
341
|
-
[
|
|
342
|
-
"pi-sync config:",
|
|
343
|
-
`sync setup: ${config.setupName}`,
|
|
344
|
-
`storage connection: ${config.connectionName}`,
|
|
345
|
-
...storageLines,
|
|
346
|
-
`storage path: ${config.storagePath}`,
|
|
347
|
-
`automatic sync: ${config.automatic ? "enabled" : "disabled"}`,
|
|
348
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
349
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "not included"}`,
|
|
350
|
-
`settings file: ${localConfigPath()}`,
|
|
351
|
-
...warnings,
|
|
352
|
-
].join("\n"),
|
|
353
|
-
warnings.length > 0 ? "warning" : "info",
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function configStorageLines(config: AnySyncConfig) {
|
|
358
|
-
switch (config.backend.type) {
|
|
359
|
-
case "git":
|
|
360
|
-
return [
|
|
361
|
-
"kind: git",
|
|
362
|
-
`remote: ${displayGitRemote(config.backend.profile.remote)}`,
|
|
363
|
-
"authentication: existing Git/SSH credentials (not stored)",
|
|
364
|
-
`branch: ${config.backend.destination.branch}`,
|
|
365
|
-
];
|
|
366
|
-
case "webdav":
|
|
367
|
-
return [
|
|
368
|
-
"kind: webdav",
|
|
369
|
-
`url: ${displayWebDavUrl(config.backend.profile.url, config.backend.profile.username)}`,
|
|
370
|
-
"username: configured (value hidden)",
|
|
371
|
-
"password: configured",
|
|
372
|
-
];
|
|
373
|
-
case "s3":
|
|
374
|
-
return [
|
|
375
|
-
"kind: s3",
|
|
376
|
-
`endpoint: ${config.backend.profile.endpoint}`,
|
|
377
|
-
`bucket: ${config.backend.destination.bucket}`,
|
|
378
|
-
`region: ${config.backend.profile.region}`,
|
|
379
|
-
"access key id: configured",
|
|
380
|
-
"secret access key: configured",
|
|
381
|
-
`session token: ${config.backend.profile.sessionToken ? "configured" : "not configured"}`,
|
|
382
|
-
];
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function displayGitRemote(value: string | undefined) {
|
|
387
|
-
if (!value) return "missing";
|
|
388
|
-
try {
|
|
389
|
-
const url = new URL(value);
|
|
390
|
-
url.username = "";
|
|
391
|
-
url.password = "";
|
|
392
|
-
url.search = "";
|
|
393
|
-
url.hash = "";
|
|
394
|
-
return url.toString();
|
|
395
|
-
} catch {
|
|
396
|
-
return value.replace(/^(?:[^@\s]+@)?(?<host>[^:]+):.+$/u, "$<host>:…");
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function displayWebDavUrl(value: string | undefined, username: string | undefined) {
|
|
401
|
-
if (!value) return "missing";
|
|
402
|
-
try {
|
|
403
|
-
const url = new URL(value);
|
|
404
|
-
url.username = "";
|
|
405
|
-
url.password = "";
|
|
406
|
-
url.search = "";
|
|
407
|
-
url.hash = "";
|
|
408
|
-
return username ? `${url.origin}/…` : `${url.origin}${url.pathname}`;
|
|
409
|
-
} catch {
|
|
410
|
-
return "invalid (value hidden)";
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function throwIfAborted(signal?: AbortSignal) {
|
|
415
|
-
if (!signal?.aborted) return;
|
|
416
|
-
throw signal.reason instanceof Error
|
|
417
|
-
? signal.reason
|
|
418
|
-
: new DOMException("The operation was aborted", "AbortError");
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function combineSignals(primary: AbortSignal, secondary?: AbortSignal) {
|
|
422
|
-
return secondary ? AbortSignal.any([primary, secondary]) : primary;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function snapshotOptionsForContext(
|
|
426
|
-
ctx: ExtensionCommandContext | ExtensionContext,
|
|
427
|
-
config: AnySyncConfig,
|
|
428
|
-
): SnapshotOptions {
|
|
429
|
-
return {
|
|
430
|
-
include: config.include,
|
|
431
|
-
sessionDir: sessionDirFromContext(ctx),
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function sessionDirFromContext(ctx: ExtensionCommandContext | ExtensionContext) {
|
|
436
|
-
const manager = ctx.sessionManager as typeof ctx.sessionManager & {
|
|
437
|
-
usesDefaultSessionDir?: () => boolean;
|
|
438
|
-
};
|
|
439
|
-
const usesDefaultSessionDir = manager.usesDefaultSessionDir;
|
|
440
|
-
if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
|
|
441
|
-
return undefined;
|
|
442
|
-
}
|
|
443
|
-
const getSessionDir = manager.getSessionDir;
|
|
444
|
-
return typeof getSessionDir === "function"
|
|
445
|
-
? (getSessionDir.call(manager) as string | undefined)
|
|
446
|
-
: undefined;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
1
|
export { completeSyncArguments, parseOptions, splitArgs } from "./command.js";
|
|
450
2
|
export {
|
|
451
3
|
isCloudflareR2Endpoint,
|
|
@@ -474,6 +26,7 @@ export {
|
|
|
474
26
|
preflightSnapshotApply,
|
|
475
27
|
protectSnapshotApplyPlan,
|
|
476
28
|
} from "./snapshot-apply.js";
|
|
29
|
+
export { default, type SyncDependencies } from "./sync-extension.js";
|
|
477
30
|
export {
|
|
478
31
|
canPullRemoteSessionsOnFirstSync,
|
|
479
32
|
canPullRemoteSettingsOnFirstSync,
|