@tailglow/cli 0.9.6 → 0.9.15
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/package.json +2 -2
- package/src/commands.generated.js +144 -128
- package/src/config-command.js +7 -0
- package/src/config.js +33 -0
- package/src/docs-command.js +6 -0
- package/src/docs.generated.js +21 -21
- package/src/flags.js +15 -0
- package/src/help.js +4 -0
- package/src/import-command.js +111 -10
- package/src/import-file.js +47 -0
- package/src/import-worker.generated.js +5 -1
- package/src/import.generated.js +5 -1
- package/src/index.js +34 -0
- package/src/login-command.js +118 -6
- package/src/output.js +13 -2
- package/src/request.js +18 -0
- package/src/update-check.js +22 -0
package/src/config.js
CHANGED
|
@@ -71,6 +71,7 @@ export function readCliVersion() {
|
|
|
71
71
|
version_read ??= fs
|
|
72
72
|
.readFile(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8")
|
|
73
73
|
.then((manifest) => String(JSON.parse(manifest).version));
|
|
74
|
+
|
|
74
75
|
return version_read;
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -87,7 +88,9 @@ export function readCliVersion() {
|
|
|
87
88
|
export async function loadConfig(path = CLI_CONFIGS.CONFIG_PATH) {
|
|
88
89
|
try {
|
|
89
90
|
const parsed = JSON.parse(await fs.readFile(path, "utf-8"));
|
|
91
|
+
|
|
90
92
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
93
|
+
|
|
91
94
|
return parsed;
|
|
92
95
|
} catch {
|
|
93
96
|
return {};
|
|
@@ -107,15 +110,18 @@ export async function loadConfig(path = CLI_CONFIGS.CONFIG_PATH) {
|
|
|
107
110
|
export async function loadConfigForWrite(path = CLI_CONFIGS.CONFIG_PATH) {
|
|
108
111
|
/** @type {string} */
|
|
109
112
|
let contents;
|
|
113
|
+
|
|
110
114
|
try {
|
|
111
115
|
contents = await fs.readFile(path, "utf-8");
|
|
112
116
|
} catch (err) {
|
|
113
117
|
if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") return {};
|
|
118
|
+
|
|
114
119
|
throw new Error(`Cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
/** @type {unknown} */
|
|
118
123
|
let parsed;
|
|
124
|
+
|
|
119
125
|
try {
|
|
120
126
|
parsed = JSON.parse(contents);
|
|
121
127
|
} catch {
|
|
@@ -148,15 +154,18 @@ export async function loadConfigForWrite(path = CLI_CONFIGS.CONFIG_PATH) {
|
|
|
148
154
|
*/
|
|
149
155
|
export async function saveConfig(values, path = CLI_CONFIGS.CONFIG_PATH) {
|
|
150
156
|
const directory = dirname(path);
|
|
157
|
+
|
|
151
158
|
await fs.mkdir(directory, { recursive: true, mode: CLI_CONFIGS.DIRECTORY_MODE });
|
|
152
159
|
await fs.chmod(directory, CLI_CONFIGS.DIRECTORY_MODE);
|
|
153
160
|
|
|
154
161
|
const staged = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
162
|
+
|
|
155
163
|
try {
|
|
156
164
|
await fs.writeFile(staged, `${JSON.stringify(values, null, 2)}\n`, {
|
|
157
165
|
mode: CLI_CONFIGS.FILE_MODE,
|
|
158
166
|
flag: "wx"
|
|
159
167
|
});
|
|
168
|
+
|
|
160
169
|
await fs.chmod(staged, CLI_CONFIGS.FILE_MODE);
|
|
161
170
|
await fs.rename(staged, path);
|
|
162
171
|
} catch (err) {
|
|
@@ -197,9 +206,11 @@ export async function withConfigLock(
|
|
|
197
206
|
) {
|
|
198
207
|
const lock_path = `${path}.lock`;
|
|
199
208
|
const token = `${process.pid}:${randomUUID()}`;
|
|
209
|
+
|
|
200
210
|
await fs.mkdir(dirname(path), { recursive: true, mode: CLI_CONFIGS.DIRECTORY_MODE });
|
|
201
211
|
|
|
202
212
|
const started_at = Date.now();
|
|
213
|
+
|
|
203
214
|
while (true) {
|
|
204
215
|
try {
|
|
205
216
|
await fs.writeFile(lock_path, token, { flag: "wx", mode: CLI_CONFIGS.FILE_MODE });
|
|
@@ -212,6 +223,7 @@ export async function withConfigLock(
|
|
|
212
223
|
`If no tglow is running, delete ${lock_path} and try again.`
|
|
213
224
|
);
|
|
214
225
|
}
|
|
226
|
+
|
|
215
227
|
await new Promise((resolve) => setTimeout(resolve, CLI_CONFIGS.LOCK_RETRY_MS));
|
|
216
228
|
}
|
|
217
229
|
}
|
|
@@ -233,6 +245,7 @@ export async function withConfigLock(
|
|
|
233
245
|
export async function updateConfig(mutate, path = CLI_CONFIGS.CONFIG_PATH) {
|
|
234
246
|
return withConfigLock(async () => {
|
|
235
247
|
const next = mutate(await loadConfigForWrite(path));
|
|
248
|
+
|
|
236
249
|
await saveConfig(next, path);
|
|
237
250
|
return next;
|
|
238
251
|
}, path);
|
|
@@ -244,6 +257,7 @@ export async function updateConfig(mutate, path = CLI_CONFIGS.CONFIG_PATH) {
|
|
|
244
257
|
*/
|
|
245
258
|
export function redactSetting(key, value) {
|
|
246
259
|
if (!SECRET_KEYS.has(key)) return value;
|
|
260
|
+
|
|
247
261
|
return value.length <= 8 ? "********" : `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
248
262
|
}
|
|
249
263
|
|
|
@@ -260,12 +274,17 @@ function isNonEmptyString(value) {
|
|
|
260
274
|
*/
|
|
261
275
|
export function readStoredLogin(config) {
|
|
262
276
|
const login = config.login;
|
|
277
|
+
|
|
263
278
|
if (!login || typeof login !== "object" || Array.isArray(login)) return null;
|
|
279
|
+
|
|
264
280
|
const fields = /** @type {Record<string, unknown>} */ (login);
|
|
281
|
+
|
|
265
282
|
if (fields.type !== "session" && fields.type !== "api_key") return null;
|
|
283
|
+
|
|
266
284
|
for (const name of ["secret", "id", "api_url", "team_id", "user_id", "email"]) {
|
|
267
285
|
if (!isNonEmptyString(fields[name])) return null;
|
|
268
286
|
}
|
|
287
|
+
|
|
269
288
|
return /** @type {import("./types.d.ts").StoredLogin} */ (login);
|
|
270
289
|
}
|
|
271
290
|
|
|
@@ -277,13 +296,19 @@ export function readStoredLogin(config) {
|
|
|
277
296
|
*/
|
|
278
297
|
export function readPendingLogin(config) {
|
|
279
298
|
const pending = config.pending_login;
|
|
299
|
+
|
|
280
300
|
if (!pending || typeof pending !== "object" || Array.isArray(pending)) return null;
|
|
301
|
+
|
|
281
302
|
const fields = /** @type {Record<string, unknown>} */ (pending);
|
|
303
|
+
|
|
282
304
|
if (fields.method !== "account" && fields.method !== "api_key") return null;
|
|
305
|
+
|
|
283
306
|
for (const name of ["id", "poll_secret", "user_code", "verification_url", "api_url"]) {
|
|
284
307
|
if (!isNonEmptyString(fields[name])) return null;
|
|
285
308
|
}
|
|
309
|
+
|
|
286
310
|
if (typeof fields.started_at !== "number") return null;
|
|
311
|
+
|
|
287
312
|
return /** @type {import("./types.d.ts").PendingLogin} */ (pending);
|
|
288
313
|
}
|
|
289
314
|
|
|
@@ -304,12 +329,15 @@ export function readPendingLogin(config) {
|
|
|
304
329
|
*/
|
|
305
330
|
export function resolveSetting(name, { flags = {}, env = process.env, config = {} } = {}) {
|
|
306
331
|
const flag = flags[name.replaceAll("_", "-")] ?? flags[name];
|
|
332
|
+
|
|
307
333
|
if (typeof flag === "string" && flag.length > 0) return flag;
|
|
308
334
|
|
|
309
335
|
const from_env = env[`TAILGLOW_${name.toUpperCase()}`];
|
|
336
|
+
|
|
310
337
|
if (typeof from_env === "string" && from_env.length > 0) return from_env;
|
|
311
338
|
|
|
312
339
|
const stored = config[name];
|
|
340
|
+
|
|
313
341
|
return typeof stored === "string" && stored.length > 0 ? stored : null;
|
|
314
342
|
}
|
|
315
343
|
|
|
@@ -355,18 +383,22 @@ export function isSameApiUrl(first, second) {
|
|
|
355
383
|
*/
|
|
356
384
|
export function resolveCredential({ flags = {}, env = process.env, config = {} } = {}) {
|
|
357
385
|
const flag = flags["api-key"];
|
|
386
|
+
|
|
358
387
|
if (isNonEmptyString(flag)) return { secret: String(flag), source: "flag", login: null };
|
|
359
388
|
|
|
360
389
|
const from_env = env.TAILGLOW_API_KEY;
|
|
390
|
+
|
|
361
391
|
if (isNonEmptyString(from_env)) return { secret: String(from_env), source: "env", login: null };
|
|
362
392
|
|
|
363
393
|
const login = readStoredLogin(config);
|
|
364
394
|
const api_url = resolveApiUrl({ flags, env, config });
|
|
395
|
+
|
|
365
396
|
if (login && isSameApiUrl(login.api_url, api_url)) {
|
|
366
397
|
return { secret: login.secret, source: "login", login };
|
|
367
398
|
}
|
|
368
399
|
|
|
369
400
|
const stored = config.api_key;
|
|
401
|
+
|
|
370
402
|
if (isNonEmptyString(stored)) return { secret: String(stored), source: "config", login: null };
|
|
371
403
|
|
|
372
404
|
if (login) {
|
|
@@ -376,6 +408,7 @@ export function resolveCredential({ flags = {}, env = process.env, config = {} }
|
|
|
376
408
|
`${api_url}\`.`
|
|
377
409
|
);
|
|
378
410
|
}
|
|
411
|
+
|
|
379
412
|
throw new Error(
|
|
380
413
|
"Not signed in. Run `tglow login`, or set TAILGLOW_API_KEY to an API key.\n" +
|
|
381
414
|
"Create a key in the dashboard under Team settings."
|
package/src/docs-command.js
CHANGED
|
@@ -28,11 +28,13 @@ export function toDocsPath(reference) {
|
|
|
28
28
|
/** @param {string} path */
|
|
29
29
|
function findSimilarPaths(path) {
|
|
30
30
|
const name = (path.split("/").pop() ?? path).toLowerCase();
|
|
31
|
+
|
|
31
32
|
if (!name) return [];
|
|
32
33
|
|
|
33
34
|
return Object.keys(DOCS_PAGES)
|
|
34
35
|
.filter((candidate) => {
|
|
35
36
|
const candidate_name = (candidate.split("/").pop() ?? candidate).toLowerCase();
|
|
37
|
+
|
|
36
38
|
return candidate_name.includes(name) || name.includes(candidate_name);
|
|
37
39
|
})
|
|
38
40
|
.slice(0, 5);
|
|
@@ -70,12 +72,14 @@ export function runDocsCommand(words, flags) {
|
|
|
70
72
|
const json = Boolean(flags.json);
|
|
71
73
|
|
|
72
74
|
const unknown = Object.keys(flags).filter((flag) => !RESERVED_FLAGS.has(flag));
|
|
75
|
+
|
|
73
76
|
if (unknown.length > 0) {
|
|
74
77
|
return fail(
|
|
75
78
|
`Unknown flag${unknown.length === 1 ? "" : "s"}: ${unknown.map((flag) => `--${flag}`).join(", ")}.`,
|
|
76
79
|
json
|
|
77
80
|
);
|
|
78
81
|
}
|
|
82
|
+
|
|
79
83
|
if (rest.length > 0) {
|
|
80
84
|
return fail(`Unexpected argument${rest.length === 1 ? "" : "s"}: ${rest.join(", ")}.`, json);
|
|
81
85
|
}
|
|
@@ -129,10 +133,12 @@ export function runDocsCommand(words, flags) {
|
|
|
129
133
|
similar.length > 0
|
|
130
134
|
? ` Did you mean ${similar.map((candidate) => `\`${candidate}\``).join(", ")}?`
|
|
131
135
|
: "";
|
|
136
|
+
|
|
132
137
|
return fail(`No docs page "${reference}".${hint} Run \`tglow docs\` for the index.`, json);
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
const url = `${CLI_CONFIGS.DOCS_URL}/${path}.md`;
|
|
141
|
+
|
|
136
142
|
if (json) return { output: JSON.stringify({ path, url, content }, null, 2), code: 0 };
|
|
137
143
|
|
|
138
144
|
return { output: `${content.trimEnd()}\n\nLatest version online: ${url}`, code: 0 };
|