@theholocron/cli 3.61.0 → 3.62.1
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/dist/cli.mjs +166 -92
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -9,9 +9,9 @@ import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1, cr
|
|
|
9
9
|
import { createEnvLookup } from "@theholocron/env-utils";
|
|
10
10
|
import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
|
|
12
13
|
import ora from "ora";
|
|
13
14
|
import chalk from "chalk";
|
|
14
|
-
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/logger";
|
|
15
15
|
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
@@ -288,6 +288,94 @@ function resolveConfig(raw) {
|
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
290
|
//#endregion
|
|
291
|
+
//#region src/logger.ts
|
|
292
|
+
/**
|
|
293
|
+
* CLI-side wiring for `@theholocron/logger`.
|
|
294
|
+
*
|
|
295
|
+
* `logger` is the operational-output channel — internal state, debug
|
|
296
|
+
* traces, errors, structured context that routes to Axiom. It runs in
|
|
297
|
+
* parallel to `print` (user-facing UX output) and does not replace it.
|
|
298
|
+
*/
|
|
299
|
+
/**
|
|
300
|
+
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
301
|
+
*
|
|
302
|
+
* 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
|
|
303
|
+
* 3. `HOLOCRON_LOG_LEVEL` env var
|
|
304
|
+
* 4. `holocron.config` `log.level` (`configLevel`)
|
|
305
|
+
*
|
|
306
|
+
* Returns `undefined` when nothing applies — `createLogger` then defaults
|
|
307
|
+
* to `"info"`. Resolving the full chain here (rather than passing
|
|
308
|
+
* `configLevel` straight through) keeps config below the env var.
|
|
309
|
+
*/
|
|
310
|
+
function resolveLogLevel(argv, configLevel) {
|
|
311
|
+
if (argv.verbose) return "debug";
|
|
312
|
+
if (argv.quiet) return "error";
|
|
313
|
+
return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
|
|
314
|
+
}
|
|
315
|
+
let root;
|
|
316
|
+
let rootLevel;
|
|
317
|
+
let rootCommand;
|
|
318
|
+
let rootAxiomKey;
|
|
319
|
+
/**
|
|
320
|
+
* Resolve Axiom credentials for the CLI. Env vars win — same contract as
|
|
321
|
+
* `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
|
|
322
|
+
* bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
|
|
323
|
+
* dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
|
|
324
|
+
* `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
|
|
325
|
+
* token and a dataset are found.
|
|
326
|
+
*/
|
|
327
|
+
function resolveCliAxiom(opts) {
|
|
328
|
+
const fromEnv = resolveAxiomFromEnv();
|
|
329
|
+
if (fromEnv) return fromEnv;
|
|
330
|
+
const dataset = env.get("HOLOCRON_AXIOM_DATASET") || env.get("AXIOM_DATASET") || opts.configAxiomDataset;
|
|
331
|
+
if (!dataset) return void 0;
|
|
332
|
+
const org = opts.org ?? env.get("HOLOCRON_ORG");
|
|
333
|
+
const token = (org ? getToken(`axiom.${org}`) : null) ?? getToken("axiom");
|
|
334
|
+
return token ? {
|
|
335
|
+
dataset,
|
|
336
|
+
token
|
|
337
|
+
} : void 0;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* The process-wide root logger. Built once (from `cli.ts`'s middleware,
|
|
341
|
+
* with the command name + flags + env). Rebuilt at most once more when a
|
|
342
|
+
* command's handler supplies `holocron.config` context the flag/env-only
|
|
343
|
+
* first pass could not have known — `log.level` (unless `--verbose` /
|
|
344
|
+
* `--quiet` already fixed it) or `log.axiom.dataset` + the resolved org
|
|
345
|
+
* for a keyring-backed Axiom transport. That rebuild generates a fresh
|
|
346
|
+
* `runId`, which is harmless: nothing logs between the middleware and the
|
|
347
|
+
* handler.
|
|
348
|
+
*/
|
|
349
|
+
function buildCliLogger(argv, opts = {}) {
|
|
350
|
+
const { command, configLevel } = opts;
|
|
351
|
+
if (command) rootCommand = command;
|
|
352
|
+
const level = resolveLogLevel(argv, configLevel);
|
|
353
|
+
const axiom = resolveCliAxiom(opts);
|
|
354
|
+
const axiomKey = axiom?.dataset;
|
|
355
|
+
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
|
|
356
|
+
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
357
|
+
const built = createLogger({
|
|
358
|
+
...level ? { level } : {},
|
|
359
|
+
...axiom ? { axiom } : {}
|
|
360
|
+
});
|
|
361
|
+
root = {
|
|
362
|
+
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
363
|
+
runId: built.runId
|
|
364
|
+
};
|
|
365
|
+
rootLevel = level;
|
|
366
|
+
rootAxiomKey = axiomKey;
|
|
367
|
+
}
|
|
368
|
+
return root;
|
|
369
|
+
}
|
|
370
|
+
/** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
|
|
371
|
+
function getLogger() {
|
|
372
|
+
return (root ??= createLogger()).logger;
|
|
373
|
+
}
|
|
374
|
+
/** The current root logger's correlation id, if a root has been built. */
|
|
375
|
+
function getRunId() {
|
|
376
|
+
return root?.runId;
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
291
379
|
//#region src/ui/progress.ts
|
|
292
380
|
/**
|
|
293
381
|
* Runs `fn`, showing an ora spinner for its duration in TTY environments.
|
|
@@ -356,6 +444,7 @@ function resolveAuthSetToken(input) {
|
|
|
356
444
|
}
|
|
357
445
|
async function runAuthSet(input) {
|
|
358
446
|
const print = input.print ?? ((l) => console.log(l));
|
|
447
|
+
const logger = input.logger ?? getLogger();
|
|
359
448
|
const importer = input.importer ?? defaultImporter$1;
|
|
360
449
|
const { provider } = input;
|
|
361
450
|
const keyringKey = input.org ? `${provider}.${input.org}` : provider;
|
|
@@ -374,6 +463,11 @@ async function runAuthSet(input) {
|
|
|
374
463
|
const hint = await tryLoadHint(importer, packageName);
|
|
375
464
|
if (hint) print(style.hint(` hint: ${hint}`));
|
|
376
465
|
}
|
|
466
|
+
logger.warn({
|
|
467
|
+
provider,
|
|
468
|
+
keyringKey,
|
|
469
|
+
reason: "no token supplied"
|
|
470
|
+
}, `auth set: ${keyringKey}`);
|
|
377
471
|
return {
|
|
378
472
|
status: "fail",
|
|
379
473
|
message: "no token supplied"
|
|
@@ -387,6 +481,12 @@ async function runAuthSet(input) {
|
|
|
387
481
|
if (!verified.ok) {
|
|
388
482
|
print(style.fail(`token rejected by ${provider}: ${verified.message}`));
|
|
389
483
|
if (module.AUTH_HINT) print(style.hint(` hint: ${module.AUTH_HINT}`));
|
|
484
|
+
logger.warn({
|
|
485
|
+
provider,
|
|
486
|
+
keyringKey,
|
|
487
|
+
verified: false,
|
|
488
|
+
reason: verified.message
|
|
489
|
+
}, `auth set: ${keyringKey}`);
|
|
390
490
|
return {
|
|
391
491
|
status: "fail",
|
|
392
492
|
message: verified.message
|
|
@@ -401,12 +501,24 @@ async function runAuthSet(input) {
|
|
|
401
501
|
}
|
|
402
502
|
if (!setToken(keyringKey, token)) {
|
|
403
503
|
print(style.fail(`keyring unavailable — token not stored. Use env vars instead.`));
|
|
504
|
+
logger.warn({
|
|
505
|
+
provider,
|
|
506
|
+
keyringKey,
|
|
507
|
+
reason: "keyring unavailable"
|
|
508
|
+
}, `auth set: ${keyringKey}`);
|
|
404
509
|
return {
|
|
405
510
|
status: "fail",
|
|
406
511
|
message: "keyring unavailable"
|
|
407
512
|
};
|
|
408
513
|
}
|
|
409
514
|
print(style.success(`stored ${keyringKey} token${subject ? ` (${subject})` : ""}`));
|
|
515
|
+
logger.info({
|
|
516
|
+
provider,
|
|
517
|
+
keyringKey,
|
|
518
|
+
verified: subject !== void 0,
|
|
519
|
+
...subject ? { subject } : {},
|
|
520
|
+
status: "ok"
|
|
521
|
+
}, `auth set: ${keyringKey}`);
|
|
410
522
|
return {
|
|
411
523
|
status: "ok",
|
|
412
524
|
...subject ? { message: subject } : {}
|
|
@@ -414,11 +526,20 @@ async function runAuthSet(input) {
|
|
|
414
526
|
}
|
|
415
527
|
function runAuthUnset(input) {
|
|
416
528
|
const print = input.print ?? ((l) => console.log(l));
|
|
529
|
+
const logger = input.logger ?? getLogger();
|
|
417
530
|
if (deleteToken(input.provider)) {
|
|
418
531
|
print(style.success(`removed ${input.provider} token`));
|
|
532
|
+
logger.info({
|
|
533
|
+
provider: input.provider,
|
|
534
|
+
status: "ok"
|
|
535
|
+
}, `auth unset: ${input.provider}`);
|
|
419
536
|
return { status: "ok" };
|
|
420
537
|
}
|
|
421
538
|
print(style.dim(`no stored token for ${input.provider}`));
|
|
539
|
+
logger.info({
|
|
540
|
+
provider: input.provider,
|
|
541
|
+
status: "skip"
|
|
542
|
+
}, `auth unset: ${input.provider}`);
|
|
422
543
|
return {
|
|
423
544
|
status: "skip",
|
|
424
545
|
message: "nothing to remove"
|
|
@@ -426,12 +547,19 @@ function runAuthUnset(input) {
|
|
|
426
547
|
}
|
|
427
548
|
async function runAuthCheck(input) {
|
|
428
549
|
const print = input.print ?? ((l) => console.log(l));
|
|
550
|
+
const logger = input.logger ?? getLogger();
|
|
429
551
|
const importer = input.importer ?? defaultImporter$1;
|
|
430
552
|
const { provider } = input;
|
|
431
553
|
const keyringKey = input.org ? `${provider}.${input.org}` : provider;
|
|
432
554
|
const token = getToken(keyringKey);
|
|
433
555
|
if (!token) {
|
|
434
556
|
print(style.dim(`no stored token for ${keyringKey}`));
|
|
557
|
+
logger.info({
|
|
558
|
+
provider,
|
|
559
|
+
keyringKey,
|
|
560
|
+
status: "skip",
|
|
561
|
+
reason: "no stored token"
|
|
562
|
+
}, `auth check: ${keyringKey}`);
|
|
435
563
|
return {
|
|
436
564
|
status: "skip",
|
|
437
565
|
message: "no stored token"
|
|
@@ -458,6 +586,13 @@ async function runAuthCheck(input) {
|
|
|
458
586
|
const verified = input.showSpinner !== false ? await withSpinner(`Verifying ${keyringKey} token…`, verify) : await verify();
|
|
459
587
|
if (verified.ok) {
|
|
460
588
|
print(style.success(`${keyringKey}: ok — ${verified.subject}`));
|
|
589
|
+
logger.info({
|
|
590
|
+
provider,
|
|
591
|
+
keyringKey,
|
|
592
|
+
verified: true,
|
|
593
|
+
subject: verified.subject,
|
|
594
|
+
status: "ok"
|
|
595
|
+
}, `auth check: ${keyringKey}`);
|
|
461
596
|
return {
|
|
462
597
|
status: "ok",
|
|
463
598
|
message: verified.subject
|
|
@@ -465,6 +600,13 @@ async function runAuthCheck(input) {
|
|
|
465
600
|
}
|
|
466
601
|
print(style.fail(`${keyringKey}: rejected — ${verified.message}`));
|
|
467
602
|
if (module.AUTH_HINT) print(style.hint(` hint: ${module.AUTH_HINT}`));
|
|
603
|
+
logger.warn({
|
|
604
|
+
provider,
|
|
605
|
+
keyringKey,
|
|
606
|
+
verified: false,
|
|
607
|
+
reason: verified.message,
|
|
608
|
+
status: "fail"
|
|
609
|
+
}, `auth check: ${keyringKey}`);
|
|
468
610
|
return {
|
|
469
611
|
status: "fail",
|
|
470
612
|
message: verified.message
|
|
@@ -472,6 +614,12 @@ async function runAuthCheck(input) {
|
|
|
472
614
|
} catch (err) {
|
|
473
615
|
const msg = err instanceof Error ? err.message : String(err);
|
|
474
616
|
print(style.fail(`${keyringKey}: cannot verify — ${msg}`));
|
|
617
|
+
logger.warn({
|
|
618
|
+
provider,
|
|
619
|
+
keyringKey,
|
|
620
|
+
reason: msg,
|
|
621
|
+
status: "fail"
|
|
622
|
+
}, `auth check: ${keyringKey}`);
|
|
475
623
|
return {
|
|
476
624
|
status: "fail",
|
|
477
625
|
message: msg
|
|
@@ -480,28 +628,42 @@ async function runAuthCheck(input) {
|
|
|
480
628
|
}
|
|
481
629
|
async function runAuthList(input = {}) {
|
|
482
630
|
const print = input.print ?? ((l) => console.log(l));
|
|
631
|
+
const logger = input.logger ?? getLogger();
|
|
483
632
|
const importer = input.importer ?? defaultImporter$1;
|
|
484
633
|
const providers = listStoredProviders();
|
|
485
634
|
if (providers.length === 0) {
|
|
486
635
|
print(style.dim("no stored tokens."));
|
|
487
636
|
print(style.hint("run: holocron auth set <provider> <token>"));
|
|
637
|
+
logger.info({ providers: 0 }, "auth list: done");
|
|
488
638
|
return {
|
|
489
639
|
status: "ok",
|
|
490
640
|
message: "none"
|
|
491
641
|
};
|
|
492
642
|
}
|
|
643
|
+
let ok = 0;
|
|
644
|
+
let fail = 0;
|
|
493
645
|
for (const provider of providers.sort()) {
|
|
494
646
|
const check = await runAuthCheck({
|
|
495
647
|
provider,
|
|
496
648
|
importer,
|
|
497
649
|
print: () => {},
|
|
650
|
+
logger,
|
|
498
651
|
showSpinner: false
|
|
499
652
|
});
|
|
500
653
|
const label = `${provider}${check.message ? ` — ${check.message}` : ""}`;
|
|
501
|
-
if (check.status === "ok")
|
|
502
|
-
|
|
503
|
-
|
|
654
|
+
if (check.status === "ok") {
|
|
655
|
+
ok += 1;
|
|
656
|
+
print(` ${style.success(label)}`);
|
|
657
|
+
} else if (check.status === "fail") {
|
|
658
|
+
fail += 1;
|
|
659
|
+
print(` ${style.fail(label)}`);
|
|
660
|
+
} else print(` ${style.dim(`· ${label}`)}`);
|
|
504
661
|
}
|
|
662
|
+
logger.info({
|
|
663
|
+
providers: providers.length,
|
|
664
|
+
ok,
|
|
665
|
+
fail
|
|
666
|
+
}, "auth list: done");
|
|
505
667
|
return { status: "ok" };
|
|
506
668
|
}
|
|
507
669
|
async function tryLoadHint(importer, packageName) {
|
|
@@ -512,94 +674,6 @@ async function tryLoadHint(importer, packageName) {
|
|
|
512
674
|
}
|
|
513
675
|
}
|
|
514
676
|
//#endregion
|
|
515
|
-
//#region src/logger.ts
|
|
516
|
-
/**
|
|
517
|
-
* CLI-side wiring for `@theholocron/logger`.
|
|
518
|
-
*
|
|
519
|
-
* `logger` is the operational-output channel — internal state, debug
|
|
520
|
-
* traces, errors, structured context that routes to Axiom. It runs in
|
|
521
|
-
* parallel to `print` (user-facing UX output) and does not replace it.
|
|
522
|
-
*/
|
|
523
|
-
/**
|
|
524
|
-
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
525
|
-
*
|
|
526
|
-
* 1. `--verbose` → `"debug"` 2. `--quiet` → `"error"`
|
|
527
|
-
* 3. `HOLOCRON_LOG_LEVEL` env var
|
|
528
|
-
* 4. `holocron.config` `log.level` (`configLevel`)
|
|
529
|
-
*
|
|
530
|
-
* Returns `undefined` when nothing applies — `createLogger` then defaults
|
|
531
|
-
* to `"info"`. Resolving the full chain here (rather than passing
|
|
532
|
-
* `configLevel` straight through) keeps config below the env var.
|
|
533
|
-
*/
|
|
534
|
-
function resolveLogLevel(argv, configLevel) {
|
|
535
|
-
if (argv.verbose) return "debug";
|
|
536
|
-
if (argv.quiet) return "error";
|
|
537
|
-
return parseLogLevel(env.get("HOLOCRON_LOG_LEVEL")) ?? configLevel;
|
|
538
|
-
}
|
|
539
|
-
let root;
|
|
540
|
-
let rootLevel;
|
|
541
|
-
let rootCommand;
|
|
542
|
-
let rootAxiomKey;
|
|
543
|
-
/**
|
|
544
|
-
* Resolve Axiom credentials for the CLI. Env vars win — same contract as
|
|
545
|
-
* `@theholocron/logger`'s `resolveAxiomFromEnv`. Failing that, the CLI-only
|
|
546
|
-
* bridge pairs the OS-keyring token (`axiom.<org>` then bare `axiom`) with a
|
|
547
|
-
* dataset from `HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` or
|
|
548
|
-
* `holocron.config` `log.axiom.dataset`. Returns `undefined` unless both a
|
|
549
|
-
* token and a dataset are found.
|
|
550
|
-
*/
|
|
551
|
-
function resolveCliAxiom(opts) {
|
|
552
|
-
const fromEnv = resolveAxiomFromEnv();
|
|
553
|
-
if (fromEnv) return fromEnv;
|
|
554
|
-
const dataset = env.get("HOLOCRON_AXIOM_DATASET") || env.get("AXIOM_DATASET") || opts.configAxiomDataset;
|
|
555
|
-
if (!dataset) return void 0;
|
|
556
|
-
const org = opts.org ?? env.get("HOLOCRON_ORG");
|
|
557
|
-
const token = (org ? getToken(`axiom.${org}`) : null) ?? getToken("axiom");
|
|
558
|
-
return token ? {
|
|
559
|
-
dataset,
|
|
560
|
-
token
|
|
561
|
-
} : void 0;
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* The process-wide root logger. Built once (from `cli.ts`'s middleware,
|
|
565
|
-
* with the command name + flags + env). Rebuilt at most once more when a
|
|
566
|
-
* command's handler supplies `holocron.config` context the flag/env-only
|
|
567
|
-
* first pass could not have known — `log.level` (unless `--verbose` /
|
|
568
|
-
* `--quiet` already fixed it) or `log.axiom.dataset` + the resolved org
|
|
569
|
-
* for a keyring-backed Axiom transport. That rebuild generates a fresh
|
|
570
|
-
* `runId`, which is harmless: nothing logs between the middleware and the
|
|
571
|
-
* handler.
|
|
572
|
-
*/
|
|
573
|
-
function buildCliLogger(argv, opts = {}) {
|
|
574
|
-
const { command, configLevel } = opts;
|
|
575
|
-
if (command) rootCommand = command;
|
|
576
|
-
const level = resolveLogLevel(argv, configLevel);
|
|
577
|
-
const axiom = resolveCliAxiom(opts);
|
|
578
|
-
const axiomKey = axiom?.dataset;
|
|
579
|
-
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet;
|
|
580
|
-
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
581
|
-
const built = createLogger({
|
|
582
|
-
...level ? { level } : {},
|
|
583
|
-
...axiom ? { axiom } : {}
|
|
584
|
-
});
|
|
585
|
-
root = {
|
|
586
|
-
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
587
|
-
runId: built.runId
|
|
588
|
-
};
|
|
589
|
-
rootLevel = level;
|
|
590
|
-
rootAxiomKey = axiomKey;
|
|
591
|
-
}
|
|
592
|
-
return root;
|
|
593
|
-
}
|
|
594
|
-
/** Lazily-memoized `Logger` for module-level call sites with no `argv` in scope. */
|
|
595
|
-
function getLogger() {
|
|
596
|
-
return (root ??= createLogger()).logger;
|
|
597
|
-
}
|
|
598
|
-
/** The current root logger's correlation id, if a root has been built. */
|
|
599
|
-
function getRunId() {
|
|
600
|
-
return root?.runId;
|
|
601
|
-
}
|
|
602
|
-
//#endregion
|
|
603
677
|
//#region src/plugin/loader.ts
|
|
604
678
|
/**
|
|
605
679
|
* `PluginLoader` — loads provider plugins per the resolved config and
|