vibeusage 0.2.21 → 0.2.23
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 +306 -173
- package/README.old.md +324 -0
- package/README.zh-CN.md +304 -188
- package/package.json +32 -32
- package/src/cli.js +37 -37
- package/src/commands/activate-if-needed.js +41 -0
- package/src/commands/diagnostics.js +8 -9
- package/src/commands/doctor.js +31 -26
- package/src/commands/init.js +285 -218
- package/src/commands/status.js +86 -83
- package/src/commands/sync.js +178 -130
- package/src/commands/uninstall.js +66 -62
- package/src/lib/activation-check.js +341 -0
- package/src/lib/browser-auth.js +52 -54
- package/src/lib/claude-config.js +25 -25
- package/src/lib/cli-ui.js +35 -35
- package/src/lib/codex-config.js +40 -36
- package/src/lib/debug-flags.js +2 -2
- package/src/lib/diagnostics.js +70 -57
- package/src/lib/doctor.js +139 -132
- package/src/lib/fs.js +17 -17
- package/src/lib/gemini-config.js +44 -40
- package/src/lib/init-flow.js +16 -22
- package/src/lib/insforge-client.js +10 -10
- package/src/lib/insforge.js +9 -3
- package/src/lib/openclaw-hook.js +89 -66
- package/src/lib/openclaw-session-plugin.js +116 -92
- package/src/lib/opencode-config.js +31 -32
- package/src/lib/opencode-usage-audit.js +34 -31
- package/src/lib/progress.js +12 -13
- package/src/lib/project-usage-purge.js +23 -17
- package/src/lib/prompt.js +8 -4
- package/src/lib/rollout.js +342 -241
- package/src/lib/runtime-config.js +34 -22
- package/src/lib/subscriptions.js +94 -92
- package/src/lib/tracker-paths.js +6 -6
- package/src/lib/upload-throttle.js +35 -16
- package/src/lib/uploader.js +33 -29
- package/src/lib/vibeusage-api.js +72 -56
- package/src/lib/vibeusage-public-repo.js +41 -24
package/src/lib/doctor.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const { constants } = require(
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
const { constants } = require("node:fs");
|
|
3
3
|
|
|
4
|
-
const { readJsonStrict } = require(
|
|
4
|
+
const { readJsonStrict } = require("./fs");
|
|
5
5
|
|
|
6
6
|
async function buildDoctorReport({
|
|
7
7
|
runtime = {},
|
|
8
8
|
diagnostics = null,
|
|
9
9
|
fetch = globalThis.fetch,
|
|
10
10
|
now = () => new Date(),
|
|
11
|
-
paths = {}
|
|
11
|
+
paths = {},
|
|
12
12
|
} = {}) {
|
|
13
13
|
const checks = [];
|
|
14
14
|
|
|
@@ -38,96 +38,103 @@ async function buildDoctorReport({
|
|
|
38
38
|
ok: summary.critical === 0,
|
|
39
39
|
summary,
|
|
40
40
|
checks,
|
|
41
|
-
diagnostics
|
|
41
|
+
diagnostics,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function buildRuntimeChecks(runtime = {}) {
|
|
46
46
|
const checks = [];
|
|
47
|
-
const baseUrl =
|
|
48
|
-
|
|
47
|
+
const baseUrl =
|
|
48
|
+
typeof runtime.baseUrl === "string" && runtime.baseUrl.trim() ? runtime.baseUrl.trim() : null;
|
|
49
|
+
const deviceToken =
|
|
50
|
+
typeof runtime.deviceToken === "string" && runtime.deviceToken.trim() ? "set" : "unset";
|
|
49
51
|
const dashboardUrl =
|
|
50
|
-
typeof runtime.dashboardUrl ===
|
|
51
|
-
|
|
52
|
+
typeof runtime.dashboardUrl === "string" && runtime.dashboardUrl.trim()
|
|
53
|
+
? runtime.dashboardUrl.trim()
|
|
54
|
+
: null;
|
|
55
|
+
const httpTimeoutMs = Number.isFinite(Number(runtime.httpTimeoutMs))
|
|
56
|
+
? Number(runtime.httpTimeoutMs)
|
|
57
|
+
: null;
|
|
52
58
|
const debug = Boolean(runtime.debug);
|
|
53
|
-
const insforgeAnonKey =
|
|
59
|
+
const insforgeAnonKey =
|
|
60
|
+
typeof runtime.insforgeAnonKey === "string" && runtime.insforgeAnonKey.trim() ? "set" : "unset";
|
|
54
61
|
const autoRetryNoSpawn = Boolean(runtime.autoRetryNoSpawn);
|
|
55
62
|
|
|
56
63
|
checks.push({
|
|
57
|
-
id:
|
|
58
|
-
status: baseUrl ?
|
|
59
|
-
detail: baseUrl ?
|
|
64
|
+
id: "runtime.base_url",
|
|
65
|
+
status: baseUrl ? "ok" : "fail",
|
|
66
|
+
detail: baseUrl ? "base_url set" : "base_url missing",
|
|
60
67
|
critical: false,
|
|
61
68
|
meta: {
|
|
62
69
|
base_url: baseUrl,
|
|
63
|
-
source: runtime?.sources?.baseUrl || null
|
|
64
|
-
}
|
|
70
|
+
source: runtime?.sources?.baseUrl || null,
|
|
71
|
+
},
|
|
65
72
|
});
|
|
66
73
|
|
|
67
74
|
checks.push({
|
|
68
|
-
id:
|
|
69
|
-
status: deviceToken ===
|
|
70
|
-
detail: deviceToken ===
|
|
75
|
+
id: "runtime.device_token",
|
|
76
|
+
status: deviceToken === "set" ? "ok" : "warn",
|
|
77
|
+
detail: deviceToken === "set" ? "device token set" : "device token missing",
|
|
71
78
|
critical: false,
|
|
72
79
|
meta: {
|
|
73
80
|
device_token: deviceToken,
|
|
74
|
-
source: runtime?.sources?.deviceToken || null
|
|
75
|
-
}
|
|
81
|
+
source: runtime?.sources?.deviceToken || null,
|
|
82
|
+
},
|
|
76
83
|
});
|
|
77
84
|
|
|
78
85
|
checks.push({
|
|
79
|
-
id:
|
|
80
|
-
status:
|
|
81
|
-
detail: dashboardUrl ?
|
|
86
|
+
id: "runtime.dashboard_url",
|
|
87
|
+
status: "ok",
|
|
88
|
+
detail: dashboardUrl ? "dashboard_url set" : "dashboard_url unset",
|
|
82
89
|
critical: false,
|
|
83
90
|
meta: {
|
|
84
91
|
dashboard_url: dashboardUrl,
|
|
85
|
-
source: runtime?.sources?.dashboardUrl || null
|
|
86
|
-
}
|
|
92
|
+
source: runtime?.sources?.dashboardUrl || null,
|
|
93
|
+
},
|
|
87
94
|
});
|
|
88
95
|
|
|
89
96
|
checks.push({
|
|
90
|
-
id:
|
|
91
|
-
status:
|
|
92
|
-
detail:
|
|
97
|
+
id: "runtime.http_timeout_ms",
|
|
98
|
+
status: "ok",
|
|
99
|
+
detail: "http timeout resolved",
|
|
93
100
|
critical: false,
|
|
94
101
|
meta: {
|
|
95
102
|
http_timeout_ms: httpTimeoutMs,
|
|
96
|
-
source: runtime?.sources?.httpTimeoutMs || null
|
|
97
|
-
}
|
|
103
|
+
source: runtime?.sources?.httpTimeoutMs || null,
|
|
104
|
+
},
|
|
98
105
|
});
|
|
99
106
|
|
|
100
107
|
checks.push({
|
|
101
|
-
id:
|
|
102
|
-
status:
|
|
103
|
-
detail: debug ?
|
|
108
|
+
id: "runtime.debug",
|
|
109
|
+
status: "ok",
|
|
110
|
+
detail: debug ? "debug enabled" : "debug disabled",
|
|
104
111
|
critical: false,
|
|
105
112
|
meta: {
|
|
106
113
|
debug,
|
|
107
|
-
source: runtime?.sources?.debug || null
|
|
108
|
-
}
|
|
114
|
+
source: runtime?.sources?.debug || null,
|
|
115
|
+
},
|
|
109
116
|
});
|
|
110
117
|
|
|
111
118
|
checks.push({
|
|
112
|
-
id:
|
|
113
|
-
status:
|
|
114
|
-
detail: insforgeAnonKey ===
|
|
119
|
+
id: "runtime.insforge_anon_key",
|
|
120
|
+
status: "ok",
|
|
121
|
+
detail: insforgeAnonKey === "set" ? "anon key set" : "anon key unset",
|
|
115
122
|
critical: false,
|
|
116
123
|
meta: {
|
|
117
124
|
anon_key: insforgeAnonKey,
|
|
118
|
-
source: runtime?.sources?.insforgeAnonKey || null
|
|
119
|
-
}
|
|
125
|
+
source: runtime?.sources?.insforgeAnonKey || null,
|
|
126
|
+
},
|
|
120
127
|
});
|
|
121
128
|
|
|
122
129
|
checks.push({
|
|
123
|
-
id:
|
|
124
|
-
status:
|
|
125
|
-
detail: autoRetryNoSpawn ?
|
|
130
|
+
id: "runtime.auto_retry_no_spawn",
|
|
131
|
+
status: "ok",
|
|
132
|
+
detail: autoRetryNoSpawn ? "auto retry spawn disabled" : "auto retry spawn enabled",
|
|
126
133
|
critical: false,
|
|
127
134
|
meta: {
|
|
128
135
|
auto_retry_no_spawn: autoRetryNoSpawn,
|
|
129
|
-
source: runtime?.sources?.autoRetryNoSpawn || null
|
|
130
|
-
}
|
|
136
|
+
source: runtime?.sources?.autoRetryNoSpawn || null,
|
|
137
|
+
},
|
|
131
138
|
});
|
|
132
139
|
|
|
133
140
|
return checks;
|
|
@@ -138,85 +145,85 @@ async function checkTrackerDir(trackerDir) {
|
|
|
138
145
|
const st = await fs.stat(trackerDir);
|
|
139
146
|
if (!st.isDirectory()) {
|
|
140
147
|
return {
|
|
141
|
-
id:
|
|
142
|
-
status:
|
|
143
|
-
detail:
|
|
148
|
+
id: "fs.tracker_dir",
|
|
149
|
+
status: "fail",
|
|
150
|
+
detail: "tracker dir is not a directory",
|
|
144
151
|
critical: true,
|
|
145
|
-
meta: { path: trackerDir }
|
|
152
|
+
meta: { path: trackerDir },
|
|
146
153
|
};
|
|
147
154
|
}
|
|
148
155
|
await fs.access(trackerDir, constants.R_OK);
|
|
149
156
|
return {
|
|
150
|
-
id:
|
|
151
|
-
status:
|
|
152
|
-
detail:
|
|
157
|
+
id: "fs.tracker_dir",
|
|
158
|
+
status: "ok",
|
|
159
|
+
detail: "tracker dir readable",
|
|
153
160
|
critical: false,
|
|
154
|
-
meta: { path: trackerDir }
|
|
161
|
+
meta: { path: trackerDir },
|
|
155
162
|
};
|
|
156
163
|
} catch (err) {
|
|
157
|
-
if (err && (err.code ===
|
|
164
|
+
if (err && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
158
165
|
return {
|
|
159
|
-
id:
|
|
160
|
-
status:
|
|
161
|
-
detail:
|
|
166
|
+
id: "fs.tracker_dir",
|
|
167
|
+
status: "warn",
|
|
168
|
+
detail: "tracker dir missing",
|
|
162
169
|
critical: false,
|
|
163
|
-
meta: { path: trackerDir }
|
|
170
|
+
meta: { path: trackerDir },
|
|
164
171
|
};
|
|
165
172
|
}
|
|
166
|
-
if (err && (err.code ===
|
|
173
|
+
if (err && (err.code === "EACCES" || err.code === "EPERM")) {
|
|
167
174
|
return {
|
|
168
|
-
id:
|
|
169
|
-
status:
|
|
170
|
-
detail:
|
|
175
|
+
id: "fs.tracker_dir",
|
|
176
|
+
status: "fail",
|
|
177
|
+
detail: "tracker dir permission denied",
|
|
171
178
|
critical: true,
|
|
172
|
-
meta: { path: trackerDir, code: err.code }
|
|
179
|
+
meta: { path: trackerDir, code: err.code },
|
|
173
180
|
};
|
|
174
181
|
}
|
|
175
182
|
return {
|
|
176
|
-
id:
|
|
177
|
-
status:
|
|
178
|
-
detail:
|
|
183
|
+
id: "fs.tracker_dir",
|
|
184
|
+
status: "fail",
|
|
185
|
+
detail: "tracker dir error",
|
|
179
186
|
critical: true,
|
|
180
|
-
meta: { path: trackerDir, code: err?.code ||
|
|
187
|
+
meta: { path: trackerDir, code: err?.code || "error" },
|
|
181
188
|
};
|
|
182
189
|
}
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
async function checkConfigJson(configPath) {
|
|
186
193
|
const res = await readJsonStrict(configPath);
|
|
187
|
-
if (res.status ===
|
|
194
|
+
if (res.status === "ok") {
|
|
188
195
|
return {
|
|
189
|
-
id:
|
|
190
|
-
status:
|
|
191
|
-
detail:
|
|
196
|
+
id: "fs.config_json",
|
|
197
|
+
status: "ok",
|
|
198
|
+
detail: "config.json readable",
|
|
192
199
|
critical: false,
|
|
193
|
-
meta: { path: configPath }
|
|
200
|
+
meta: { path: configPath },
|
|
194
201
|
};
|
|
195
202
|
}
|
|
196
|
-
if (res.status ===
|
|
203
|
+
if (res.status === "missing") {
|
|
197
204
|
return {
|
|
198
|
-
id:
|
|
199
|
-
status:
|
|
200
|
-
detail:
|
|
205
|
+
id: "fs.config_json",
|
|
206
|
+
status: "warn",
|
|
207
|
+
detail: "config.json missing",
|
|
201
208
|
critical: false,
|
|
202
|
-
meta: { path: configPath }
|
|
209
|
+
meta: { path: configPath },
|
|
203
210
|
};
|
|
204
211
|
}
|
|
205
|
-
if (res.status ===
|
|
212
|
+
if (res.status === "invalid") {
|
|
206
213
|
return {
|
|
207
|
-
id:
|
|
208
|
-
status:
|
|
209
|
-
detail:
|
|
214
|
+
id: "fs.config_json",
|
|
215
|
+
status: "fail",
|
|
216
|
+
detail: "config.json invalid",
|
|
210
217
|
critical: true,
|
|
211
|
-
meta: { path: configPath }
|
|
218
|
+
meta: { path: configPath },
|
|
212
219
|
};
|
|
213
220
|
}
|
|
214
221
|
return {
|
|
215
|
-
id:
|
|
216
|
-
status:
|
|
217
|
-
detail:
|
|
222
|
+
id: "fs.config_json",
|
|
223
|
+
status: "fail",
|
|
224
|
+
detail: "config.json read error",
|
|
218
225
|
critical: true,
|
|
219
|
-
meta: { path: configPath }
|
|
226
|
+
meta: { path: configPath },
|
|
220
227
|
};
|
|
221
228
|
}
|
|
222
229
|
|
|
@@ -225,31 +232,31 @@ async function checkCliEntrypoint(cliPath) {
|
|
|
225
232
|
const st = await fs.stat(cliPath);
|
|
226
233
|
if (!st.isFile()) {
|
|
227
234
|
return {
|
|
228
|
-
id:
|
|
229
|
-
status:
|
|
230
|
-
detail:
|
|
235
|
+
id: "cli.entrypoint",
|
|
236
|
+
status: "fail",
|
|
237
|
+
detail: "cli entrypoint is not a file",
|
|
231
238
|
critical: false,
|
|
232
|
-
meta: { path: cliPath }
|
|
239
|
+
meta: { path: cliPath },
|
|
233
240
|
};
|
|
234
241
|
}
|
|
235
242
|
await fs.access(cliPath, constants.R_OK);
|
|
236
|
-
if (process.platform !==
|
|
243
|
+
if (process.platform !== "win32") {
|
|
237
244
|
await fs.access(cliPath, constants.X_OK);
|
|
238
245
|
}
|
|
239
246
|
return {
|
|
240
|
-
id:
|
|
241
|
-
status:
|
|
242
|
-
detail:
|
|
247
|
+
id: "cli.entrypoint",
|
|
248
|
+
status: "ok",
|
|
249
|
+
detail: "cli entrypoint readable",
|
|
243
250
|
critical: false,
|
|
244
|
-
meta: { path: cliPath }
|
|
251
|
+
meta: { path: cliPath },
|
|
245
252
|
};
|
|
246
253
|
} catch (err) {
|
|
247
254
|
return {
|
|
248
|
-
id:
|
|
249
|
-
status:
|
|
250
|
-
detail:
|
|
255
|
+
id: "cli.entrypoint",
|
|
256
|
+
status: "fail",
|
|
257
|
+
detail: "cli entrypoint not accessible",
|
|
251
258
|
critical: false,
|
|
252
|
-
meta: { path: cliPath, code: err?.code ||
|
|
259
|
+
meta: { path: cliPath, code: err?.code || "error" },
|
|
253
260
|
};
|
|
254
261
|
}
|
|
255
262
|
}
|
|
@@ -257,42 +264,42 @@ async function checkCliEntrypoint(cliPath) {
|
|
|
257
264
|
async function checkNetwork({ baseUrl, fetch }) {
|
|
258
265
|
if (!baseUrl) {
|
|
259
266
|
return {
|
|
260
|
-
id:
|
|
261
|
-
status:
|
|
262
|
-
detail:
|
|
267
|
+
id: "network.base_url",
|
|
268
|
+
status: "warn",
|
|
269
|
+
detail: "base_url missing (skipped)",
|
|
263
270
|
critical: false,
|
|
264
|
-
meta: { base_url: null }
|
|
271
|
+
meta: { base_url: null },
|
|
265
272
|
};
|
|
266
273
|
}
|
|
267
274
|
|
|
268
275
|
const start = Date.now();
|
|
269
276
|
try {
|
|
270
|
-
if (typeof fetch !==
|
|
271
|
-
const res = await fetch(baseUrl, { method:
|
|
277
|
+
if (typeof fetch !== "function") throw new Error("Missing fetch");
|
|
278
|
+
const res = await fetch(baseUrl, { method: "GET" });
|
|
272
279
|
const latency = Date.now() - start;
|
|
273
280
|
return {
|
|
274
|
-
id:
|
|
275
|
-
status:
|
|
281
|
+
id: "network.base_url",
|
|
282
|
+
status: "ok",
|
|
276
283
|
detail: `HTTP ${res.status} (reachable)`,
|
|
277
284
|
critical: false,
|
|
278
285
|
meta: {
|
|
279
286
|
status_code: res.status,
|
|
280
287
|
latency_ms: latency,
|
|
281
|
-
base_url: baseUrl
|
|
282
|
-
}
|
|
288
|
+
base_url: baseUrl,
|
|
289
|
+
},
|
|
283
290
|
};
|
|
284
291
|
} catch (err) {
|
|
285
292
|
const latency = Date.now() - start;
|
|
286
293
|
return {
|
|
287
|
-
id:
|
|
288
|
-
status:
|
|
289
|
-
detail:
|
|
294
|
+
id: "network.base_url",
|
|
295
|
+
status: "fail",
|
|
296
|
+
detail: "Network error",
|
|
290
297
|
critical: false,
|
|
291
298
|
meta: {
|
|
292
299
|
error: err?.message || String(err),
|
|
293
300
|
latency_ms: latency,
|
|
294
|
-
base_url: baseUrl
|
|
295
|
-
}
|
|
301
|
+
base_url: baseUrl,
|
|
302
|
+
},
|
|
296
303
|
};
|
|
297
304
|
}
|
|
298
305
|
}
|
|
@@ -302,28 +309,28 @@ function buildDiagnosticsChecks(diagnostics) {
|
|
|
302
309
|
const notify = diagnostics?.notify || {};
|
|
303
310
|
const notifyConfigured = Boolean(
|
|
304
311
|
notify.codex_notify_configured ||
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
312
|
+
notify.every_code_notify_configured ||
|
|
313
|
+
notify.claude_hook_configured ||
|
|
314
|
+
notify.gemini_hook_configured ||
|
|
315
|
+
notify.opencode_plugin_configured ||
|
|
316
|
+
notify.openclaw_hook_configured,
|
|
310
317
|
);
|
|
311
318
|
|
|
312
319
|
checks.push({
|
|
313
|
-
id:
|
|
314
|
-
status: notifyConfigured ?
|
|
315
|
-
detail: notifyConfigured ?
|
|
320
|
+
id: "notify.configured",
|
|
321
|
+
status: notifyConfigured ? "ok" : "warn",
|
|
322
|
+
detail: notifyConfigured ? "notify configured" : "notify not configured",
|
|
316
323
|
critical: false,
|
|
317
|
-
meta: { configured: notifyConfigured }
|
|
324
|
+
meta: { configured: notifyConfigured },
|
|
318
325
|
});
|
|
319
326
|
|
|
320
327
|
const uploadError = diagnostics?.upload?.last_error || null;
|
|
321
328
|
checks.push({
|
|
322
|
-
id:
|
|
323
|
-
status: uploadError ?
|
|
324
|
-
detail: uploadError ?
|
|
329
|
+
id: "upload.last_error",
|
|
330
|
+
status: uploadError ? "warn" : "ok",
|
|
331
|
+
detail: uploadError ? "last upload error present" : "no upload errors",
|
|
325
332
|
critical: false,
|
|
326
|
-
meta: { last_error: uploadError ? uploadError.message || null : null }
|
|
333
|
+
meta: { last_error: uploadError ? uploadError.message || null : null },
|
|
327
334
|
});
|
|
328
335
|
|
|
329
336
|
return checks;
|
|
@@ -332,11 +339,11 @@ function buildDiagnosticsChecks(diagnostics) {
|
|
|
332
339
|
function summarizeChecks(checks = []) {
|
|
333
340
|
const summary = { ok: 0, warn: 0, fail: 0, critical: 0 };
|
|
334
341
|
for (const check of checks) {
|
|
335
|
-
if (!check || typeof check.status !==
|
|
336
|
-
if (check.status ===
|
|
337
|
-
else if (check.status ===
|
|
338
|
-
else if (check.status ===
|
|
339
|
-
if (check.status ===
|
|
342
|
+
if (!check || typeof check.status !== "string") continue;
|
|
343
|
+
if (check.status === "ok") summary.ok += 1;
|
|
344
|
+
else if (check.status === "warn") summary.warn += 1;
|
|
345
|
+
else if (check.status === "fail") summary.fail += 1;
|
|
346
|
+
if (check.status === "fail" && check.critical) summary.critical += 1;
|
|
340
347
|
}
|
|
341
348
|
return summary;
|
|
342
349
|
}
|
package/src/lib/fs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
const path = require("node:path");
|
|
3
3
|
|
|
4
4
|
async function ensureDir(p) {
|
|
5
5
|
await fs.mkdir(p, { recursive: true });
|
|
@@ -9,13 +9,13 @@ async function writeFileAtomic(filePath, content) {
|
|
|
9
9
|
const dir = path.dirname(filePath);
|
|
10
10
|
await ensureDir(dir);
|
|
11
11
|
const tmp = `${filePath}.tmp.${Date.now()}`;
|
|
12
|
-
await fs.writeFile(tmp, content, { encoding:
|
|
12
|
+
await fs.writeFile(tmp, content, { encoding: "utf8" });
|
|
13
13
|
await fs.rename(tmp, filePath);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async function readJson(filePath) {
|
|
17
17
|
try {
|
|
18
|
-
const raw = await fs.readFile(filePath,
|
|
18
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
19
19
|
return JSON.parse(raw);
|
|
20
20
|
} catch (_e) {
|
|
21
21
|
return null;
|
|
@@ -24,21 +24,21 @@ async function readJson(filePath) {
|
|
|
24
24
|
|
|
25
25
|
async function readJsonStrict(filePath) {
|
|
26
26
|
try {
|
|
27
|
-
const raw = await fs.readFile(filePath,
|
|
28
|
-
return { status:
|
|
27
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
28
|
+
return { status: "ok", value: JSON.parse(raw), error: null };
|
|
29
29
|
} catch (err) {
|
|
30
|
-
if (err && (err.code ===
|
|
31
|
-
return { status:
|
|
30
|
+
if (err && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
31
|
+
return { status: "missing", value: null, error: err };
|
|
32
32
|
}
|
|
33
|
-
if (err && err.name ===
|
|
34
|
-
return { status:
|
|
33
|
+
if (err && err.name === "SyntaxError") {
|
|
34
|
+
return { status: "invalid", value: null, error: err };
|
|
35
35
|
}
|
|
36
|
-
return { status:
|
|
36
|
+
return { status: "error", value: null, error: err };
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function writeJson(filePath, obj) {
|
|
41
|
-
await writeFileAtomic(filePath, JSON.stringify(obj, null, 2) +
|
|
41
|
+
await writeFileAtomic(filePath, JSON.stringify(obj, null, 2) + "\n");
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async function chmod600IfPossible(filePath) {
|
|
@@ -49,16 +49,16 @@ async function chmod600IfPossible(filePath) {
|
|
|
49
49
|
|
|
50
50
|
async function openLock(lockPath, { quietIfLocked }) {
|
|
51
51
|
try {
|
|
52
|
-
const handle = await fs.open(lockPath,
|
|
52
|
+
const handle = await fs.open(lockPath, "wx");
|
|
53
53
|
return {
|
|
54
54
|
async release() {
|
|
55
55
|
await handle.close().catch(() => {});
|
|
56
|
-
}
|
|
56
|
+
},
|
|
57
57
|
};
|
|
58
58
|
} catch (e) {
|
|
59
|
-
if (e && e.code ===
|
|
59
|
+
if (e && e.code === "EEXIST") {
|
|
60
60
|
if (!quietIfLocked) {
|
|
61
|
-
process.stdout.write(
|
|
61
|
+
process.stdout.write("Another sync is already running.\n");
|
|
62
62
|
}
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
@@ -73,5 +73,5 @@ module.exports = {
|
|
|
73
73
|
readJsonStrict,
|
|
74
74
|
writeJson,
|
|
75
75
|
chmod600IfPossible,
|
|
76
|
-
openLock
|
|
76
|
+
openLock,
|
|
77
77
|
};
|