@telepath-computer/stash 0.2.4 → 0.3.2
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 +27 -14
- package/dist/cli-main.d.ts +5 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli-main.js +326 -167
- package/dist/cli-main.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/local-config.d.ts +10 -0
- package/dist/local-config.d.ts.map +1 -0
- package/dist/local-config.js +34 -0
- package/dist/local-config.js.map +1 -0
- package/dist/migrations.d.ts +3 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +54 -0
- package/dist/migrations.js.map +1 -0
- package/dist/providers/github-provider.d.ts +2 -0
- package/dist/providers/github-provider.d.ts.map +1 -1
- package/dist/providers/github-provider.js +14 -3
- package/dist/providers/github-provider.js.map +1 -1
- package/dist/stash.d.ts +2 -1
- package/dist/stash.d.ts.map +1 -1
- package/dist/stash.js +26 -16
- package/dist/stash.js.map +1 -1
- package/package.json +2 -2
package/dist/cli-main.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { access, constants, readFile } from "node:fs/promises";
|
|
2
|
+
import { access, constants, readFile, rm } from "node:fs/promises";
|
|
3
3
|
import { basename, delimiter, join, resolve } from "node:path";
|
|
4
4
|
import { Daemon, UnsupportedPlatformError } from "@rupertsworld/daemon";
|
|
5
5
|
import { Command, Option } from "commander";
|
|
6
6
|
import { input, password } from "@inquirer/prompts";
|
|
7
7
|
import { addBackgroundStash, getBackgroundStashes, getProviderConfig, readGlobalConfig, removeBackgroundStash, setProviderConfig, writeGlobalConfig, } from "./global-config.js";
|
|
8
|
+
import { readLocalConfig, writeLocalConfig } from "./local-config.js";
|
|
8
9
|
import { runDaemon } from "./daemon.js";
|
|
10
|
+
import { needsMigration } from "./migrations.js";
|
|
9
11
|
import { providers } from "./providers/index.js";
|
|
10
12
|
import { Stash } from "./stash.js";
|
|
11
13
|
import { createColors } from "./ui/color.js";
|
|
@@ -15,6 +17,13 @@ import { SyncRenderer } from "./ui/sync-renderer.js";
|
|
|
15
17
|
import { watch as watchStash } from "./watch.js";
|
|
16
18
|
const SERVICE_NAME = "stash-background";
|
|
17
19
|
const SERVICE_DESCRIPTION = "Stash background sync";
|
|
20
|
+
const STASH_CONFIG_KEYS = new Set(["allow-git"]);
|
|
21
|
+
function isStashConfigKey(key) {
|
|
22
|
+
return STASH_CONFIG_KEYS.has(key);
|
|
23
|
+
}
|
|
24
|
+
function isStashDirectory(dir) {
|
|
25
|
+
return existsSync(join(dir, ".stash"));
|
|
26
|
+
}
|
|
18
27
|
function getProvider(name) {
|
|
19
28
|
const provider = providers[name];
|
|
20
29
|
if (!provider) {
|
|
@@ -37,12 +46,32 @@ function writeLine(stream, text) {
|
|
|
37
46
|
function isUnsupportedPlatformError(error) {
|
|
38
47
|
return error instanceof UnsupportedPlatformError;
|
|
39
48
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
async function bounceDaemonIfMigrationNeeded(dir, getService) {
|
|
50
|
+
if (!isStashDirectory(dir) || !needsMigration(dir)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const service = await getService();
|
|
55
|
+
const status = await service.status();
|
|
56
|
+
if (!status.running) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
await service.uninstall();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function resolveServiceLaunch(argv = process.argv) {
|
|
67
|
+
const invokedPath = argv[1];
|
|
68
|
+
if (invokedPath) {
|
|
69
|
+
const candidate = resolve(invokedPath);
|
|
44
70
|
if (await isExecutable(candidate)) {
|
|
45
|
-
return candidate;
|
|
71
|
+
return { command: candidate, args: ["daemon"] };
|
|
72
|
+
}
|
|
73
|
+
if (existsSync(candidate)) {
|
|
74
|
+
return { command: process.execPath, args: [candidate, "daemon"] };
|
|
46
75
|
}
|
|
47
76
|
}
|
|
48
77
|
for (const pathEntry of (process.env.PATH ?? "").split(delimiter)) {
|
|
@@ -51,10 +80,10 @@ export async function resolveStashCommand() {
|
|
|
51
80
|
}
|
|
52
81
|
const candidate = join(pathEntry, "stash");
|
|
53
82
|
if (await isExecutable(candidate)) {
|
|
54
|
-
return candidate;
|
|
83
|
+
return { command: candidate, args: ["daemon"] };
|
|
55
84
|
}
|
|
56
85
|
}
|
|
57
|
-
throw new Error("Could not
|
|
86
|
+
throw new Error("Could not resolve a command to run `stash daemon`");
|
|
58
87
|
}
|
|
59
88
|
async function promptField(field) {
|
|
60
89
|
if (field.secret) {
|
|
@@ -84,23 +113,30 @@ async function readBackgroundStatus(dir) {
|
|
|
84
113
|
}
|
|
85
114
|
return JSON.parse(await readFile(statusPath, "utf8"));
|
|
86
115
|
}
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
function renderGitWarning(stdout) {
|
|
117
|
+
const { dim, yellow } = createColors(stdout);
|
|
118
|
+
writeLine(stdout, `${yellow("Warning:")} ${dim("This directory contains .git. Stash will not sync until you either:")}`);
|
|
119
|
+
writeLine(stdout, dim(" - remove .git, or"));
|
|
120
|
+
writeLine(stdout, dim(' - run `stash config set allow-git true` (see "Using stash with git" in the README)'));
|
|
121
|
+
}
|
|
122
|
+
async function warnIfGitSyncBlocked(dir, stdout) {
|
|
123
|
+
if (!existsSync(join(dir, ".git"))) {
|
|
89
124
|
return;
|
|
90
125
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
writeLine(stderr, "warning: background service is not installed");
|
|
95
|
-
}
|
|
126
|
+
const localConfig = await readLocalConfig(dir);
|
|
127
|
+
if (localConfig["allow-git"] === true) {
|
|
128
|
+
return;
|
|
96
129
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
renderGitWarning(stdout);
|
|
131
|
+
}
|
|
132
|
+
function parseConfigValue(key, value) {
|
|
133
|
+
if (value === "true") {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (value === "false") {
|
|
137
|
+
return false;
|
|
103
138
|
}
|
|
139
|
+
throw new Error(`Invalid value for ${key}: expected true or false`);
|
|
104
140
|
}
|
|
105
141
|
async function runSetup(providerName, valuesFromCli, deps, stdout) {
|
|
106
142
|
const provider = getProvider(providerName);
|
|
@@ -110,22 +146,8 @@ async function runSetup(providerName, valuesFromCli, deps, stdout) {
|
|
|
110
146
|
await deps.writeGlobalConfig(globalConfig);
|
|
111
147
|
writeLine(stdout, `Configured ${providerName}.`);
|
|
112
148
|
}
|
|
113
|
-
async function
|
|
114
|
-
const
|
|
115
|
-
const alreadyInitialized = existsSync(join(dir, ".stash"));
|
|
116
|
-
await Stash.init(dir, await readConfig());
|
|
117
|
-
writeLine(stdout, alreadyInitialized ? "Already initialized." : "Initialized stash.");
|
|
118
|
-
}
|
|
119
|
-
async function registerBackgroundStash(dir, readConfig, writeConfig, serviceHandle, stderr) {
|
|
120
|
-
const globalConfig = await readConfig();
|
|
121
|
-
const stash = await Stash.load(dir, globalConfig);
|
|
122
|
-
if (Object.keys(stash.connections).length === 0) {
|
|
123
|
-
writeLine(stderr, "warning: this stash won't sync until a provider is connected");
|
|
124
|
-
}
|
|
125
|
-
await writeConfig(addBackgroundStash(globalConfig, dir));
|
|
126
|
-
await warnIfServiceUnavailable(serviceHandle, stderr);
|
|
127
|
-
}
|
|
128
|
-
async function runConnect(providerName, valuesFromCli, deps, stdout, stderr) {
|
|
149
|
+
async function runConnect(providerName, valuesFromCli, deps, stdout) {
|
|
150
|
+
const { dim, green } = createColors(stdout);
|
|
129
151
|
const provider = getProvider(providerName);
|
|
130
152
|
let globalConfig = await deps.readGlobalConfig();
|
|
131
153
|
const setupValues = await collectFields(provider.spec.setup, valuesFromCli, getProviderConfig(globalConfig, providerName));
|
|
@@ -135,14 +157,47 @@ async function runConnect(providerName, valuesFromCli, deps, stdout, stderr) {
|
|
|
135
157
|
const stash = await Stash.init(dir, globalConfig);
|
|
136
158
|
const connectValues = await collectFields(provider.spec.connect, valuesFromCli);
|
|
137
159
|
await stash.connect(providerName, connectValues);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
160
|
+
globalConfig = await deps.readGlobalConfig();
|
|
161
|
+
await deps.writeGlobalConfig(addBackgroundStash(globalConfig, resolve(dir)));
|
|
141
162
|
writeLine(stdout, `Connected ${providerName}.`);
|
|
163
|
+
await warnIfGitSyncBlocked(dir, stdout);
|
|
164
|
+
if (!deps.getService) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const status = await (await deps.getService()).status();
|
|
169
|
+
if (status.running) {
|
|
170
|
+
writeLine(stdout, `${green("Background sync is on")} ${dim("·")} ${dim("This stash is now syncing automatically")}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof Error &&
|
|
175
|
+
error.message === "Could not resolve a command to run `stash daemon`") {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!isUnsupportedPlatformError(error)) {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
142
182
|
}
|
|
143
|
-
async function runDisconnect(providerName, cwd, readConfig, stdout) {
|
|
144
|
-
const
|
|
183
|
+
async function runDisconnect(providerName, cwd, readConfig, writeConfig, stdout) {
|
|
184
|
+
const dir = cwd();
|
|
185
|
+
const globalConfig = await readConfig();
|
|
186
|
+
const stash = await Stash.load(dir, globalConfig);
|
|
187
|
+
if (!providerName) {
|
|
188
|
+
for (const name of Object.keys(stash.connections)) {
|
|
189
|
+
await stash.disconnect(name);
|
|
190
|
+
}
|
|
191
|
+
await writeConfig(removeBackgroundStash(globalConfig, resolve(dir)));
|
|
192
|
+
await rm(join(dir, ".stash"), { recursive: true, force: true });
|
|
193
|
+
writeLine(stdout, "Disconnected stash.");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
145
196
|
await stash.disconnect(providerName);
|
|
197
|
+
if (Object.keys(stash.connections).length === 0) {
|
|
198
|
+
await writeConfig(removeBackgroundStash(globalConfig, resolve(dir)));
|
|
199
|
+
await rm(join(dir, ".stash"), { recursive: true, force: true });
|
|
200
|
+
}
|
|
146
201
|
writeLine(stdout, `Disconnected ${providerName}.`);
|
|
147
202
|
}
|
|
148
203
|
async function runSync(cwd, readConfig) {
|
|
@@ -188,14 +243,116 @@ function formatChangeParts(status) {
|
|
|
188
243
|
parts.push(`${status.deleted.length} deleted`);
|
|
189
244
|
return parts;
|
|
190
245
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
246
|
+
function formatStashCount(count) {
|
|
247
|
+
return `${count} ${count === 1 ? "stash" : "stashes"}`;
|
|
248
|
+
}
|
|
249
|
+
async function runStatusAll(readConfig, serviceHandle, stdout) {
|
|
250
|
+
const { dim, green, red } = createColors(stdout);
|
|
251
|
+
const globalConfig = await readConfig();
|
|
252
|
+
const stashes = getBackgroundStashes(globalConfig);
|
|
253
|
+
if (stashes.length === 0) {
|
|
254
|
+
writeLine(stdout, "No stashes connected yet — run `stash connect <provider>` in a directory to add one");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const current = await serviceHandle.status();
|
|
259
|
+
if (current.running) {
|
|
260
|
+
writeLine(stdout, `${green("Background sync is on")} ${dim("·")} ${dim(`watching ${formatStashCount(stashes.length)}`)}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
writeLine(stdout, red("Background sync is off"));
|
|
264
|
+
writeLine(stdout, dim(`Run \`stash start\` to resume syncing ${formatStashCount(stashes.length)}`));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (isUnsupportedPlatformError(error)) {
|
|
269
|
+
writeLine(stdout, red("Background sync is not supported on this platform"));
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
writeLine(stdout, "");
|
|
276
|
+
for (const dir of stashes) {
|
|
277
|
+
const title = basename(dir);
|
|
278
|
+
if (!existsSync(dir)) {
|
|
279
|
+
writeLine(stdout, `${red("✗")} ${title}`);
|
|
280
|
+
writeLine(stdout, dim(` ${dir}`));
|
|
281
|
+
writeLine(stdout, dim(" Directory not found"));
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (!isStashDirectory(dir)) {
|
|
285
|
+
writeLine(stdout, `${red("✗")} ${title}`);
|
|
286
|
+
writeLine(stdout, dim(` ${dir}`));
|
|
287
|
+
writeLine(stdout, dim(" Not a stash"));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const stash = await Stash.load(dir, globalConfig);
|
|
291
|
+
const persistedStatus = await readBackgroundStatus(dir);
|
|
292
|
+
const localStatus = stash.status();
|
|
293
|
+
const connectionNames = Object.keys(stash.connections);
|
|
294
|
+
if (persistedStatus?.kind === "error") {
|
|
295
|
+
writeLine(stdout, `${red("✗")} ${title}`);
|
|
296
|
+
writeLine(stdout, dim(` ${dir}`));
|
|
297
|
+
for (const providerName of connectionNames) {
|
|
298
|
+
const conn = stash.connections[providerName];
|
|
299
|
+
const label = conn.repo ?? Object.values(conn).join(", ");
|
|
300
|
+
writeLine(stdout, ` ${providerName} ${label} ${dim("·")} ${red(persistedStatus.error ?? "unknown error")}`);
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
writeLine(stdout, `${green("●")} ${title}`);
|
|
305
|
+
writeLine(stdout, dim(` ${dir}`));
|
|
306
|
+
for (const providerName of connectionNames) {
|
|
307
|
+
const conn = stash.connections[providerName];
|
|
308
|
+
const label = conn.repo ?? Object.values(conn).join(", ");
|
|
309
|
+
if (persistedStatus === null || localStatus.lastSync === null) {
|
|
310
|
+
writeLine(stdout, ` ${providerName} ${label} ${dim("·")} ${dim("Waiting for first sync")}`);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const parts = formatChangeParts(localStatus);
|
|
314
|
+
if (parts.length > 0) {
|
|
315
|
+
writeLine(stdout, ` ${providerName} ${label} ${dim("·")} Local changes: ${parts.join(", ")} ${dim("·")} ${dim(`synced ${formatTimeAgo(localStatus.lastSync)}`)}`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
writeLine(stdout, ` ${providerName} ${label} ${dim("·")} ${dim(`Up to date · synced ${formatTimeAgo(localStatus.lastSync)}`)}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function runStatus(cwd, readConfig, serviceHandle, stdout) {
|
|
324
|
+
const { dim, green, yellow, red } = createColors(stdout);
|
|
325
|
+
const dir = cwd();
|
|
326
|
+
if (!isStashDirectory(dir)) {
|
|
327
|
+
throw new Error("Not in a stash directory — run `stash status --all` to view all stashes");
|
|
328
|
+
}
|
|
329
|
+
const globalConfig = await readConfig();
|
|
330
|
+
const stash = await Stash.load(dir, globalConfig);
|
|
194
331
|
const connectionNames = Object.keys(stash.connections);
|
|
195
332
|
if (connectionNames.length === 0) {
|
|
196
|
-
writeLine(stdout, dim("
|
|
333
|
+
writeLine(stdout, dim("No connections — run `stash connect <provider>` to get started"));
|
|
197
334
|
return;
|
|
198
335
|
}
|
|
336
|
+
if (getBackgroundStashes(globalConfig).includes(resolve(dir))) {
|
|
337
|
+
try {
|
|
338
|
+
const current = await serviceHandle.status();
|
|
339
|
+
if (current.running) {
|
|
340
|
+
writeLine(stdout, `${green("Background sync is on")} ${dim("·")} ${dim("Use `stash status --all` to view all stashes")}`);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
writeLine(stdout, `${red("Background sync is off")} ${dim("·")} ${dim("Run `stash start` to keep connected stashes in sync")}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
if (isUnsupportedPlatformError(error)) {
|
|
348
|
+
writeLine(stdout, `${red("Background sync is not supported on this platform")}`);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
writeLine(stdout, "");
|
|
355
|
+
}
|
|
199
356
|
const status = stash.status();
|
|
200
357
|
const parts = formatChangeParts(status);
|
|
201
358
|
for (const name of connectionNames) {
|
|
@@ -206,118 +363,102 @@ async function runStatus(cwd, readConfig, stdout) {
|
|
|
206
363
|
if (status.lastSync) {
|
|
207
364
|
const ago = formatTimeAgo(status.lastSync);
|
|
208
365
|
if (parts.length > 0) {
|
|
209
|
-
writeLine(stdout, ` ${parts.join(", ")} ${dim("·")} ${dim(`synced ${ago}`)}`);
|
|
366
|
+
writeLine(stdout, ` Local changes: ${parts.join(", ")} ${dim("·")} ${dim(`synced ${ago}`)}`);
|
|
210
367
|
}
|
|
211
368
|
else {
|
|
212
|
-
writeLine(stdout, dim(`
|
|
369
|
+
writeLine(stdout, dim(` Up to date · synced ${ago}`));
|
|
213
370
|
}
|
|
214
371
|
}
|
|
215
372
|
else if (parts.length > 0) {
|
|
216
|
-
writeLine(stdout, ` ${parts.join(", ")} ${dim("·")} ${dim("
|
|
373
|
+
writeLine(stdout, ` Local changes: ${parts.join(", ")} ${dim("·")} ${dim("Never synced")}`);
|
|
217
374
|
}
|
|
218
375
|
else {
|
|
219
|
-
writeLine(stdout, dim("
|
|
376
|
+
writeLine(stdout, dim(" Never synced"));
|
|
220
377
|
}
|
|
221
378
|
}
|
|
222
379
|
}
|
|
223
|
-
async function
|
|
224
|
-
await serviceHandle.install();
|
|
225
|
-
writeLine(stdout, "Installed background service.");
|
|
226
|
-
}
|
|
227
|
-
async function runBackgroundUninstall(serviceHandle, stdout) {
|
|
228
|
-
await serviceHandle.uninstall();
|
|
229
|
-
writeLine(stdout, "Uninstalled background service.");
|
|
230
|
-
}
|
|
231
|
-
async function runBackgroundAdd(dirArg, cwd, readConfig, writeConfig, serviceHandle, stdout, stderr) {
|
|
232
|
-
const dir = resolve(dirArg ?? cwd());
|
|
233
|
-
await registerBackgroundStash(dir, readConfig, writeConfig, serviceHandle, stderr);
|
|
234
|
-
writeLine(stdout, `Registered ${dir} for background sync.`);
|
|
235
|
-
}
|
|
236
|
-
async function runBackgroundRemove(dirArg, cwd, readConfig, writeConfig, stdout) {
|
|
237
|
-
const dir = resolve(dirArg ?? cwd());
|
|
238
|
-
const globalConfig = await readConfig();
|
|
239
|
-
await writeConfig(removeBackgroundStash(globalConfig, dir));
|
|
240
|
-
writeLine(stdout, `Removed ${dir} from background sync.`);
|
|
241
|
-
}
|
|
242
|
-
async function runBackgroundStatus(readConfig, serviceHandle, stdout) {
|
|
380
|
+
async function runStart(serviceHandle, readConfig, stdout) {
|
|
243
381
|
const { dim, green, red } = createColors(stdout);
|
|
244
|
-
let serviceRunning = false;
|
|
245
|
-
let serviceMessage = null;
|
|
246
382
|
try {
|
|
247
383
|
const current = await serviceHandle.status();
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
else if (current.running) {
|
|
252
|
-
serviceRunning = true;
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
serviceMessage = red("background service is stopped — run `stash background install` to restart");
|
|
384
|
+
if (current.installed && current.running) {
|
|
385
|
+
writeLine(stdout, green("Background sync is already running"));
|
|
386
|
+
return;
|
|
256
387
|
}
|
|
257
388
|
}
|
|
258
389
|
catch (error) {
|
|
259
390
|
if (isUnsupportedPlatformError(error)) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
throw error;
|
|
391
|
+
writeLine(stdout, red("Background sync is not supported on this platform"));
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
return;
|
|
264
394
|
}
|
|
395
|
+
throw error;
|
|
265
396
|
}
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
397
|
+
await serviceHandle.install();
|
|
398
|
+
const count = getBackgroundStashes(await readConfig()).length;
|
|
399
|
+
writeLine(stdout, green("Background sync is on"));
|
|
400
|
+
writeLine(stdout, dim(`Watching ${formatStashCount(count)} · starts on startup`));
|
|
401
|
+
}
|
|
402
|
+
async function runStop(serviceHandle, readConfig, stdout) {
|
|
403
|
+
const { dim, red } = createColors(stdout);
|
|
404
|
+
try {
|
|
405
|
+
const current = await serviceHandle.status();
|
|
406
|
+
if (!current.installed && !current.running) {
|
|
407
|
+
writeLine(stdout, red("Background sync is not running"));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
270
410
|
}
|
|
271
|
-
|
|
272
|
-
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (isUnsupportedPlatformError(error)) {
|
|
413
|
+
writeLine(stdout, red("Background sync is not running"));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
throw error;
|
|
273
417
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
418
|
+
await serviceHandle.uninstall();
|
|
419
|
+
const count = getBackgroundStashes(await readConfig()).length;
|
|
420
|
+
writeLine(stdout, red("Background sync is off"));
|
|
421
|
+
writeLine(stdout, dim(`Run \`stash start\` to resume syncing ${formatStashCount(count)}`));
|
|
422
|
+
}
|
|
423
|
+
async function ensureInitializedStash(cwd, readConfig) {
|
|
424
|
+
const dir = cwd();
|
|
425
|
+
await Stash.load(dir, await readConfig());
|
|
426
|
+
return dir;
|
|
427
|
+
}
|
|
428
|
+
async function runConfigSet(key, value, cwd, readConfig, stdout) {
|
|
429
|
+
if (!isStashConfigKey(key)) {
|
|
430
|
+
throw new Error(`Unknown config key: ${key}`);
|
|
277
431
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
if (status.kind === "error") {
|
|
292
|
-
writeLine(stdout, `${red("✗")} ${dir}`);
|
|
293
|
-
const ago = status.lastSync ? formatTimeAgo(new Date(status.lastSync)) : null;
|
|
294
|
-
const errorMsg = status.error ?? "unknown error";
|
|
295
|
-
writeLine(stdout, ago ? ` ${red(errorMsg)} ${dim("·")} ${dim(`synced ${ago}`)}` : ` ${red(errorMsg)}`);
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
const ago = status.lastSync ? formatTimeAgo(new Date(status.lastSync)) : null;
|
|
299
|
-
if (status.summary) {
|
|
300
|
-
writeLine(stdout, `${green("●")} ${dir}`);
|
|
301
|
-
writeLine(stdout, ` ${status.summary} ${dim("·")} ${dim(`synced ${ago ?? "unknown"}`)}`);
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
writeLine(stdout, `${green("●")} ${dir}`);
|
|
305
|
-
writeLine(stdout, dim(` up to date · synced ${ago ?? "unknown"}`));
|
|
306
|
-
}
|
|
432
|
+
const dir = await ensureInitializedStash(cwd, readConfig);
|
|
433
|
+
const currentConfig = await readLocalConfig(dir);
|
|
434
|
+
const nextConfig = {
|
|
435
|
+
...currentConfig,
|
|
436
|
+
[key]: parseConfigValue(key, value),
|
|
437
|
+
};
|
|
438
|
+
await writeLocalConfig(dir, nextConfig);
|
|
439
|
+
writeLine(stdout, `${key}=${String(nextConfig[key])}`);
|
|
440
|
+
}
|
|
441
|
+
async function runConfigGet(key, cwd, readConfig, stdout) {
|
|
442
|
+
if (!isStashConfigKey(key)) {
|
|
443
|
+
throw new Error(`Unknown config key: ${key}`);
|
|
307
444
|
}
|
|
445
|
+
const dir = await ensureInitializedStash(cwd, readConfig);
|
|
446
|
+
const localConfig = await readLocalConfig(dir);
|
|
447
|
+
const value = localConfig[key];
|
|
448
|
+
writeLine(stdout, value === undefined ? "" : String(value));
|
|
308
449
|
}
|
|
309
450
|
function addFieldOptions(command, fields) {
|
|
310
451
|
for (const field of fields) {
|
|
311
452
|
command.addOption(new Option(`--${field.name} <value>`, field.label));
|
|
312
453
|
}
|
|
313
454
|
}
|
|
314
|
-
async function createDefaultService() {
|
|
315
|
-
const
|
|
455
|
+
async function createDefaultService(argv = process.argv) {
|
|
456
|
+
const launch = await resolveServiceLaunch(argv);
|
|
316
457
|
return new Daemon({
|
|
317
458
|
name: SERVICE_NAME,
|
|
318
459
|
description: SERVICE_DESCRIPTION,
|
|
319
|
-
command,
|
|
320
|
-
args:
|
|
460
|
+
command: launch.command,
|
|
461
|
+
args: launch.args,
|
|
321
462
|
env: process.env.PATH ? { PATH: process.env.PATH } : undefined,
|
|
322
463
|
});
|
|
323
464
|
}
|
|
@@ -326,10 +467,25 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
326
467
|
const readConfig = deps.readGlobalConfig ?? readGlobalConfig;
|
|
327
468
|
const writeConfig = deps.writeGlobalConfig ?? writeGlobalConfig;
|
|
328
469
|
const serviceHandle = deps.service;
|
|
329
|
-
const getService = async () => serviceHandle ?? (await createDefaultService());
|
|
470
|
+
const getService = async () => serviceHandle ?? (await createDefaultService(argv));
|
|
330
471
|
const runDaemonCommand = deps.runDaemon ?? runDaemon;
|
|
331
472
|
const stdout = deps.stdout ?? process.stdout;
|
|
332
473
|
const stderr = deps.stderr ?? process.stderr;
|
|
474
|
+
const dirsToCheck = [cwd()];
|
|
475
|
+
try {
|
|
476
|
+
const config = await readConfig();
|
|
477
|
+
dirsToCheck.push(...getBackgroundStashes(config));
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
// Global config unreadable — skip migration check.
|
|
481
|
+
}
|
|
482
|
+
let daemonBounced = false;
|
|
483
|
+
for (const dir of dirsToCheck) {
|
|
484
|
+
if (await bounceDaemonIfMigrationNeeded(dir, getService)) {
|
|
485
|
+
daemonBounced = true;
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
333
489
|
const program = new Command()
|
|
334
490
|
.name("stash")
|
|
335
491
|
.description("Conflict-free synced folders")
|
|
@@ -354,30 +510,22 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
354
510
|
.command("connect")
|
|
355
511
|
.description("Connect this stash to a provider")
|
|
356
512
|
.argument("<provider>", "Provider name")
|
|
357
|
-
.option("--background", "Register this stash for background syncing")
|
|
358
513
|
.allowUnknownOption(true)
|
|
359
514
|
.action(async (providerName, _opts, command) => {
|
|
360
515
|
const opts = command.opts();
|
|
361
|
-
const svc = opts.background === true ? await getService() : undefined;
|
|
362
516
|
await runConnect(providerName, opts, {
|
|
363
517
|
cwd,
|
|
364
518
|
readGlobalConfig: readConfig,
|
|
365
519
|
writeGlobalConfig: writeConfig,
|
|
366
|
-
|
|
367
|
-
}, stdout
|
|
520
|
+
getService,
|
|
521
|
+
}, stdout);
|
|
368
522
|
});
|
|
369
523
|
program
|
|
370
524
|
.command("disconnect")
|
|
371
525
|
.description("Disconnect provider from this stash")
|
|
372
|
-
.argument("
|
|
526
|
+
.argument("[provider]", "Provider name")
|
|
373
527
|
.action(async (providerName) => {
|
|
374
|
-
await runDisconnect(providerName, cwd, readConfig, stdout);
|
|
375
|
-
});
|
|
376
|
-
program
|
|
377
|
-
.command("init")
|
|
378
|
-
.description("Initialize the current directory as a stash")
|
|
379
|
-
.action(() => {
|
|
380
|
-
return runInit(cwd, readConfig, stdout);
|
|
528
|
+
await runDisconnect(providerName, cwd, readConfig, writeConfig, stdout);
|
|
381
529
|
});
|
|
382
530
|
program
|
|
383
531
|
.command("sync")
|
|
@@ -390,35 +538,37 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
390
538
|
program
|
|
391
539
|
.command("status")
|
|
392
540
|
.description("Show local stash status")
|
|
393
|
-
.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
.description("Remove the background service")
|
|
402
|
-
.action(async () => runBackgroundUninstall(await getService(), stdout));
|
|
403
|
-
backgroundCommand
|
|
404
|
-
.command("add")
|
|
405
|
-
.description("Register a stash for background syncing")
|
|
406
|
-
.argument("[dir]", "Stash directory")
|
|
407
|
-
.action(async (dirArg) => {
|
|
408
|
-
const svc = await getService().catch(() => undefined);
|
|
409
|
-
await runBackgroundAdd(dirArg, cwd, readConfig, writeConfig, svc, stdout, stderr);
|
|
541
|
+
.option("--all", "Show all connected stashes")
|
|
542
|
+
.action(async (_opts, command) => {
|
|
543
|
+
const opts = command.opts();
|
|
544
|
+
if (opts.all) {
|
|
545
|
+
await runStatusAll(readConfig, await getService(), stdout);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
await runStatus(cwd, readConfig, await getService(), stdout);
|
|
410
549
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.
|
|
414
|
-
.
|
|
415
|
-
.
|
|
416
|
-
|
|
417
|
-
.
|
|
418
|
-
|
|
419
|
-
.
|
|
420
|
-
|
|
421
|
-
.
|
|
550
|
+
const configCommand = program.command("config").description("Manage stash config");
|
|
551
|
+
configCommand
|
|
552
|
+
.command("set")
|
|
553
|
+
.description("Set a stash config value")
|
|
554
|
+
.argument("<key>", "Config key")
|
|
555
|
+
.argument("<value>", "Config value")
|
|
556
|
+
.action((key, value) => runConfigSet(key, value, cwd, readConfig, stdout));
|
|
557
|
+
configCommand
|
|
558
|
+
.command("get")
|
|
559
|
+
.description("Get a stash config value")
|
|
560
|
+
.argument("<key>", "Config key")
|
|
561
|
+
.action((key) => runConfigGet(key, cwd, readConfig, stdout));
|
|
562
|
+
program
|
|
563
|
+
.command("start")
|
|
564
|
+
.description("Start background sync")
|
|
565
|
+
.action(async () => runStart(await getService(), readConfig, stdout));
|
|
566
|
+
program
|
|
567
|
+
.command("stop")
|
|
568
|
+
.description("Stop background sync")
|
|
569
|
+
.action(async () => runStop(await getService(), readConfig, stdout));
|
|
570
|
+
program
|
|
571
|
+
.command("daemon", { hidden: true })
|
|
422
572
|
.description("Run the background daemon")
|
|
423
573
|
.action(() => runDaemonCommand());
|
|
424
574
|
const commandName = argv[2];
|
|
@@ -439,5 +589,14 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
439
589
|
return;
|
|
440
590
|
}
|
|
441
591
|
await program.parseAsync(argv);
|
|
592
|
+
const skipRestart = commandName === "stop" || commandName === "daemon";
|
|
593
|
+
if (daemonBounced && !skipRestart) {
|
|
594
|
+
try {
|
|
595
|
+
await (await getService()).install();
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
// Best-effort restart — service may be unsupported or unavailable.
|
|
599
|
+
}
|
|
600
|
+
}
|
|
442
601
|
}
|
|
443
602
|
//# sourceMappingURL=cli-main.js.map
|