@theholocron/kyber 5.0.0-alpha.341 → 5.0.0-alpha.342
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 -11
- package/dist/menu.d.mts +46 -29
- package/dist/menu.mjs +28 -10
- package/dist/observability.d.mts +156 -0
- package/dist/observability.mjs +254 -0
- package/dist/testing.mjs +1 -1
- package/dist/ui.d.mts +18 -1
- package/dist/ui.mjs +12 -1
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -10,16 +10,17 @@ Published, so a second CLI can import it. `@theholocron/cli` is its first consum
|
|
|
10
10
|
|
|
11
11
|
## Subpaths
|
|
12
12
|
|
|
13
|
-
| Subpath
|
|
14
|
-
|
|
|
15
|
-
| `/ui`
|
|
16
|
-
| `/notifier`
|
|
17
|
-
| `/secrets`
|
|
18
|
-
| `/prompts`
|
|
19
|
-
| `/menu`
|
|
20
|
-
| `/testing`
|
|
21
|
-
| `/
|
|
22
|
-
| `/
|
|
13
|
+
| Subpath | What it gives a CLI |
|
|
14
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `/ui` | Status glyphs and colours, padded status rows, a depth-limited JSON view, a spinner that only shows on a terminal (and `createRetryNotice(subscribe)`, which says on it when a request is retried, given the HTTP client's retry hook), wait-for-Enter, open-in-file-browser, the error for "a prompt was needed but there is no terminal", and the cleaning of error text before it reaches a terminal (`describeError`, `commandFailureMessage`, `logReason`, `sanitizeDetail`, `stripControl`). |
|
|
16
|
+
| `/notifier` | A cached, best-effort "a newer version exists" notice for an npm package: `checkForUpdates({ name, currentVersion, cacheDir })` returns a function that prints a boxed notice to stderr, or `null`. Silent in CI and with `NO_UPDATE_NOTIFIER`. |
|
|
17
|
+
| `/secrets` | Handling tokens safely, with no terminal UI: owner-only (`0600`) files, shredding a file that held tokens, "does git ignore this path", dotenv lines that survive quoting, reading a `.env` at the repo root, the clipboard, the OS keyring (`createKeyring({ service })`, best-effort, `@napi-rs/keyring` optional), an in-process store for credentials a provider returned, `--token` / `--token vendor=value` parsing, and resolving a token from the flag, the environment, that store and the keyring (`createResolveToken`, `createFeatureResolver`, `AuthError`). |
|
|
18
|
+
| `/prompts` | A searchable menu (`pickMenuItem`), and prompting for the positionals and options a command left out (`promptForPositionals`, `promptForOptions`): required options always asked, the rest behind one checkbox, mutually exclusive `modes`, secret options masked, and a value that is asked last (`askLast`). They take small structural types (`PositionalCommand`, `OptionsCommand`), not the CLI's command registry. |
|
|
19
|
+
| `/menu` | The interactive menu of a CLI, driven by data: `launchMenu(entries, argv, { bin, envPrefix })` shows a searchable, nested picker over a list of `MenuEntry`, asks for the positionals and options the command needs, then spawns the CLI again with the answers. A command that supports `--dry-run` is previewed first and then confirmed (`--skip-dry-run` opts out); a preview that reports `DRY_RUN_NOOP_EXIT_CODE` skips the question. Secret options never reach argv: they travel as `<envPrefix>_<OPTION>` env vars, and a preview child gets `dryRunPreviewEnvVar(envPrefix)`. `createMenu({ bin, envPrefix })` binds it once (returning `launchMenu(entries, argv, options?)` and `dryRunPreviewEnvVar`). Also `menuItems`, `buildChildArgv`, `forwardedFlags`, `secretEnv`, `shouldPreviewDryRun`. |
|
|
20
|
+
| `/testing` | A test harness for a CLI: `createCliRunner` runs a built binary in a fresh directory with an environment built from nothing, `runCommand` runs one yargs command module through a real parse, `ensureBuilt` is a Vitest global setup that builds only when `dist` is missing or stale, and `hermeticCreateRequire` resolves modules only from one directory. |
|
|
21
|
+
| `/observability` | The logging and telemetry wiring of a CLI: `createCliLogger({ envPrefix, resolveAxiom? })` returns a memoized root logger (`buildCliLogger`, `getLogger`, `reportError`, `getRunId`, `resolveLogLevel`) whose level comes from `--log` / `--verbose` / `--quiet`, then `<PREFIX>_LOG_LEVEL`, then the config file, and is quiet on a terminal unless asked; `createCliTelemetry({ envPrefix, dsn, posthogKey, posthogHost })` returns Sentry error reporting and PostHog analytics behind `<PREFIX>_TELEMETRY=false`. `@theholocron/observability` is an optional peer. |
|
|
22
|
+
| `/config` | A `<name>.config.{ts,js,mjs,cjs,json}` for a new CLI: `createConfigSystem({ name, derive?, resolve, merge })` returns `defineConfig`, `loadConfig` (walks up to `.git`) and `compose()` (dedupe by `id`, all unmet `requires` in one `ConfigError`, per-key merge). Strategies: `lastWins`, `shallowMerge`, `union`, `mergeByKey`. `@theholocron/datapad` is an optional peer. |
|
|
23
|
+
| `/flags` | The options every command of a tool takes: `addGlobalOptions(parser, { describe?, cwd? })` declares `--dry-run`, a repeatable `--token` and `--cwd` once on the root; `tokenContext(values, report)` parses the `--token` values and reports a bad one without a stack trace; `groupMenu(parent, open)` is the hidden default command that makes a bare group (`tool repo`) do something useful; `InferArgs` types a command's handler arguments from its builder. |
|
|
23
24
|
|
|
24
25
|
## Usage
|
|
25
26
|
|
|
@@ -49,7 +50,7 @@ const tokens = tokenContext(argv.token, console.error); // null (and exit code 1
|
|
|
49
50
|
const keyring = createKeyring({ service: "com.example.tool" }); // best-effort: never throws
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
Each subpath's heavy libraries (`chalk`, `ora`, `@inquirer/prompts`, `yargs`, `dotenv`, `clipboardy`, `@napi-rs/keyring`, `@theholocron/datapad`) are
|
|
53
|
+
Each subpath's heavy libraries (`chalk`, `ora`, `@inquirer/prompts`, `yargs`, `dotenv`, `clipboardy`, `@napi-rs/keyring`, `@theholocron/datapad`, `@theholocron/observability`) are
|
|
53
54
|
optional peer dependencies: a CLI installs the ones for the subpaths it imports.
|
|
54
55
|
|
|
55
56
|
`createProgress(print, enabled?, listen?)` takes an optional `listen` hook that can change the spinner's text while a step
|
package/dist/menu.d.mts
CHANGED
|
@@ -75,35 +75,6 @@ interface MenuEntry {
|
|
|
75
75
|
* shape identically with no need to know which kind of command it came from.
|
|
76
76
|
*/
|
|
77
77
|
export declare function buildChildArgv(entry: MenuEntry, positionals: string[], resolved: ResolvedOptions, parentArgv: Record<string, unknown>, dryRunOverride?: boolean): string[];
|
|
78
|
-
/**
|
|
79
|
-
* Reserved exit code a dry-run-capable handler may use — only when the
|
|
80
|
-
* {@link dryRunPreviewEnvVar} is set — to report "ran clean, nothing would
|
|
81
|
-
* change." {@link launchMenu} treats it as success but skips the "proceed with
|
|
82
|
-
* the real run?" question, since there's nothing for the real run to do
|
|
83
|
-
* either.
|
|
84
|
-
*/
|
|
85
|
-
export declare const DRY_RUN_NOOP_EXIT_CODE = 2;
|
|
86
|
-
/**
|
|
87
|
-
* The env var {@link launchMenu} sets on a dry-run preview child (`<ENV_PREFIX>_DRY_RUN_PREVIEW`), so its
|
|
88
|
-
* handler knows this specific `--dry-run` came from the menu's own preview-then-ask flow, not a direct
|
|
89
|
-
* `<bin> <cmd> --dry-run` invocation. A handler that supports it uses this — never bare `argv.dryRun` — to
|
|
90
|
-
* decide whether to report {@link DRY_RUN_NOOP_EXIT_CODE}: that exit code only has special meaning to
|
|
91
|
-
* `launchMenu` itself, so a direct/scripted `--dry-run` call must never produce it — doing so unconditionally
|
|
92
|
-
* would silently change the public exit-code contract for a successful dry run.
|
|
93
|
-
*/
|
|
94
|
-
export declare function dryRunPreviewEnvVar(envPrefix: string): string;
|
|
95
|
-
/**
|
|
96
|
-
* `--token` (repeatable), `--org`, `--cwd`, `--dry-run` — the global flags a
|
|
97
|
-
* menu-launched child should inherit from the parent invocation. Everything
|
|
98
|
-
* else (command-specific options) was never captured by the top-level `$0`
|
|
99
|
-
* handler in the first place, so there's nothing else to forward.
|
|
100
|
-
*
|
|
101
|
-
* `dryRunOverride`, when passed, wins over `argv.dryRun` — {@link launchMenu}'s
|
|
102
|
-
* dry-run offer needs to force `--dry-run` on for the preview spawn and
|
|
103
|
-
* force it back off for the real-run spawn that follows, regardless of
|
|
104
|
-
* what was on the original invocation.
|
|
105
|
-
*/
|
|
106
|
-
export declare function forwardedFlags(argv: Record<string, unknown>, dryRunOverride?: boolean): string[];
|
|
107
78
|
/** How {@link launchMenu} is bound to one particular CLI. */
|
|
108
79
|
interface LaunchMenuConfig {
|
|
109
80
|
/** The CLI's binary name, used in the default non-interactive hint (`Run \`<bin> --help\` …`). */
|
|
@@ -143,6 +114,52 @@ interface LaunchMenuConfig {
|
|
|
143
114
|
* @param config - Binds the menu to one CLI: its binary name, env prefix and optional messages.
|
|
144
115
|
*/
|
|
145
116
|
export declare function launchMenu(entries: MenuEntry[], parentArgv: Record<string, unknown>, config: LaunchMenuConfig): Promise<void>;
|
|
117
|
+
/** The per-call knobs of a bound menu: everything in {@link LaunchMenuConfig} except what {@link createMenu} already fixed. */
|
|
118
|
+
export type BoundMenuOptions = Omit<LaunchMenuConfig, "bin" | "envPrefix">;
|
|
119
|
+
/** A menu engine bound to one CLI by {@link createMenu}. */
|
|
120
|
+
export interface Menu {
|
|
121
|
+
/** {@link launchMenu} with the CLI's `bin` and `envPrefix` already applied. */
|
|
122
|
+
launchMenu: (entries: MenuEntry[], parentArgv: Record<string, unknown>, options?: BoundMenuOptions) => Promise<void>;
|
|
123
|
+
/** The env var a dry-run preview child gets (`<envPrefix>_DRY_RUN_PREVIEW`); see {@link dryRunPreviewEnvVar}. */
|
|
124
|
+
dryRunPreviewEnvVar: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Bind the menu engine to one CLI once, so callers stop repeating `bin` and `envPrefix`: the returned
|
|
128
|
+
* `launchMenu(entries, parentArgv, options?)` is {@link launchMenu} with both applied, and `dryRunPreviewEnvVar` is the
|
|
129
|
+
* matching env var name a dry-run preview child receives.
|
|
130
|
+
*
|
|
131
|
+
* @param binding - The CLI's binary name and env prefix.
|
|
132
|
+
*/
|
|
133
|
+
export declare function createMenu({ bin, envPrefix }: Pick<LaunchMenuConfig, "bin" | "envPrefix">): Menu;
|
|
134
|
+
/**
|
|
135
|
+
* Reserved exit code a dry-run-capable handler may use — only when the
|
|
136
|
+
* {@link dryRunPreviewEnvVar} is set — to report "ran clean, nothing would
|
|
137
|
+
* change." {@link launchMenu} treats it as success but skips the "proceed with
|
|
138
|
+
* the real run?" question, since there's nothing for the real run to do
|
|
139
|
+
* either.
|
|
140
|
+
*/
|
|
141
|
+
export declare const DRY_RUN_NOOP_EXIT_CODE = 2;
|
|
142
|
+
/**
|
|
143
|
+
* The env var {@link launchMenu} sets on a dry-run preview child (`<ENV_PREFIX>_DRY_RUN_PREVIEW`), so its
|
|
144
|
+
* handler knows this specific `--dry-run` came from the menu's own preview-then-ask flow, not a direct
|
|
145
|
+
* `<bin> <cmd> --dry-run` invocation. A handler that supports it uses this — never bare `argv.dryRun` — to
|
|
146
|
+
* decide whether to report {@link DRY_RUN_NOOP_EXIT_CODE}: that exit code only has special meaning to
|
|
147
|
+
* `launchMenu` itself, so a direct/scripted `--dry-run` call must never produce it — doing so unconditionally
|
|
148
|
+
* would silently change the public exit-code contract for a successful dry run.
|
|
149
|
+
*/
|
|
150
|
+
export declare function dryRunPreviewEnvVar(envPrefix: string): string;
|
|
151
|
+
/**
|
|
152
|
+
* `--token` (repeatable), `--org`, `--cwd`, `--dry-run` — the global flags a
|
|
153
|
+
* menu-launched child should inherit from the parent invocation. Everything
|
|
154
|
+
* else (command-specific options) was never captured by the top-level `$0`
|
|
155
|
+
* handler in the first place, so there's nothing else to forward.
|
|
156
|
+
*
|
|
157
|
+
* `dryRunOverride`, when passed, wins over `argv.dryRun` — {@link launchMenu}'s
|
|
158
|
+
* dry-run offer needs to force `--dry-run` on for the preview spawn and
|
|
159
|
+
* force it back off for the real-run spawn that follows, regardless of
|
|
160
|
+
* what was on the original invocation.
|
|
161
|
+
*/
|
|
162
|
+
export declare function forwardedFlags(argv: Record<string, unknown>, dryRunOverride?: boolean): string[];
|
|
146
163
|
/** Prefix of a menu item's `value` that opens a submenu rather than running a command. */
|
|
147
164
|
export declare const GROUP = "group:";
|
|
148
165
|
/** The "← back" item's value; only offered below the first level. */
|
package/dist/menu.mjs
CHANGED
|
@@ -75,15 +75,6 @@ function buildChildArgv(entry, positionals, resolved, parentArgv, dryRunOverride
|
|
|
75
75
|
...forwardedFlags(parentArgv, dryRunOverride)
|
|
76
76
|
];
|
|
77
77
|
}
|
|
78
|
-
// src/menu/dry-run-noop-exit-code/dry-run-noop-exit-code.ts
|
|
79
|
-
/**
|
|
80
|
-
* Reserved exit code a dry-run-capable handler may use — only when the
|
|
81
|
-
* {@link dryRunPreviewEnvVar} is set — to report "ran clean, nothing would
|
|
82
|
-
* change." {@link launchMenu} treats it as success but skips the "proceed with
|
|
83
|
-
* the real run?" question, since there's nothing for the real run to do
|
|
84
|
-
* either.
|
|
85
|
-
*/
|
|
86
|
-
const DRY_RUN_NOOP_EXIT_CODE = 2;
|
|
87
78
|
// src/menu/dry-run-preview-env-var/dry-run-preview-env-var.ts
|
|
88
79
|
/**
|
|
89
80
|
* The env var {@link launchMenu} sets on a dry-run preview child (`<ENV_PREFIX>_DRY_RUN_PREVIEW`), so its
|
|
@@ -96,6 +87,15 @@ const DRY_RUN_NOOP_EXIT_CODE = 2;
|
|
|
96
87
|
function dryRunPreviewEnvVar(envPrefix) {
|
|
97
88
|
return `${envPrefix}_DRY_RUN_PREVIEW`;
|
|
98
89
|
}
|
|
90
|
+
// src/menu/dry-run-noop-exit-code/dry-run-noop-exit-code.ts
|
|
91
|
+
/**
|
|
92
|
+
* Reserved exit code a dry-run-capable handler may use — only when the
|
|
93
|
+
* {@link dryRunPreviewEnvVar} is set — to report "ran clean, nothing would
|
|
94
|
+
* change." {@link launchMenu} treats it as success but skips the "proceed with
|
|
95
|
+
* the real run?" question, since there's nothing for the real run to do
|
|
96
|
+
* either.
|
|
97
|
+
*/
|
|
98
|
+
const DRY_RUN_NOOP_EXIT_CODE = 2;
|
|
99
99
|
// src/menu/menu-items/menu-items.ts
|
|
100
100
|
/** Prefix of a menu item's `value` that opens a submenu rather than running a command. */
|
|
101
101
|
const GROUP = "group:";
|
|
@@ -301,4 +301,22 @@ async function launchMenu(entries, parentArgv, config) {
|
|
|
301
301
|
const forReal = await resolveForRealSpawn(resolvedOptions);
|
|
302
302
|
process.exitCode = await spawnChild(buildChildArgv(picked, positionals, forReal, parentArgv), secretEnv(forReal, envPrefix));
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
// src/menu/create-menu/create-menu.ts
|
|
305
|
+
/**
|
|
306
|
+
* Bind the menu engine to one CLI once, so callers stop repeating `bin` and `envPrefix`: the returned
|
|
307
|
+
* `launchMenu(entries, parentArgv, options?)` is {@link launchMenu} with both applied, and `dryRunPreviewEnvVar` is the
|
|
308
|
+
* matching env var name a dry-run preview child receives.
|
|
309
|
+
*
|
|
310
|
+
* @param binding - The CLI's binary name and env prefix.
|
|
311
|
+
*/
|
|
312
|
+
function createMenu({ bin, envPrefix }) {
|
|
313
|
+
return {
|
|
314
|
+
launchMenu: (entries, parentArgv, options = {}) => launchMenu(entries, parentArgv, {
|
|
315
|
+
bin,
|
|
316
|
+
envPrefix,
|
|
317
|
+
...options
|
|
318
|
+
}),
|
|
319
|
+
dryRunPreviewEnvVar: dryRunPreviewEnvVar(envPrefix)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export { BACK, DRY_RUN_NOOP_EXIT_CODE, GROUP, buildChildArgv, createMenu, dryRunPreviewEnvVar, forwardedFlags, launchMenu, menuItems, secretEnv, shouldPreviewDryRun };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { AxiomTransportConfig } from "@theholocron/observability/logger";
|
|
2
|
+
import { LogLevel, Logger } from "@theholocron/observability/core";
|
|
3
|
+
/** The global logging flags a CLI parses (`--log`, `--verbose`, `--quiet`). */
|
|
4
|
+
interface LogFlags {
|
|
5
|
+
verbose?: boolean;
|
|
6
|
+
quiet?: boolean;
|
|
7
|
+
log?: LogLevel;
|
|
8
|
+
}
|
|
9
|
+
/** What a handler can supply once the CLI's config file is loaded. */
|
|
10
|
+
interface BuildCliLoggerOpts {
|
|
11
|
+
/** Active command name, bound on the root as `command` for every line. */
|
|
12
|
+
command?: string;
|
|
13
|
+
/** The config file's log level, from a handler that has loaded a config. */
|
|
14
|
+
configLevel?: LogLevel;
|
|
15
|
+
/** The config file's non-secret Axiom dataset name. */
|
|
16
|
+
configAxiomDataset?: string;
|
|
17
|
+
/** The resolved org, for a namespaced credential lookup. */
|
|
18
|
+
org?: string;
|
|
19
|
+
}
|
|
20
|
+
/** The command name in effect, resolved for {@link CreateCliLoggerOptions.resolveAxiom}. */
|
|
21
|
+
interface CliLoggerContext {
|
|
22
|
+
/** `opts.command`, else the command name remembered from an earlier build. */
|
|
23
|
+
command: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
interface CreateCliLoggerOptions {
|
|
26
|
+
/** Upper-case env prefix of the CLI, e.g. `"HOLOCRON"` (reads `<PREFIX>_LOG_LEVEL`). */
|
|
27
|
+
envPrefix: string;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve Axiom credentials for a build. Defaults to the package's
|
|
30
|
+
* `resolveAxiomFromEnv`. Return `undefined` to ship nowhere.
|
|
31
|
+
*/
|
|
32
|
+
resolveAxiom?: (opts: BuildCliLoggerOpts, ctx: CliLoggerContext) => AxiomTransportConfig | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** The bound logging functions a CLI re-exports. */
|
|
35
|
+
interface CliLogger {
|
|
36
|
+
/**
|
|
37
|
+
* Build (or return the memoized) process-wide root logger. Rebuilt at most
|
|
38
|
+
* once more when a handler supplies config the flag/env-only first pass could
|
|
39
|
+
* not have known (the level, or a new Axiom dataset).
|
|
40
|
+
*/
|
|
41
|
+
buildCliLogger: (argv: LogFlags, opts?: BuildCliLoggerOpts) => {
|
|
42
|
+
logger: Logger;
|
|
43
|
+
runId: string;
|
|
44
|
+
};
|
|
45
|
+
/** Lazily-memoized root `Logger` for call sites with no `argv` in scope. */
|
|
46
|
+
getLogger: () => Logger;
|
|
47
|
+
/** Print a fatal error with `style.fail` and also log it at error level. */
|
|
48
|
+
reportError: (message: string, log?: Logger) => void;
|
|
49
|
+
/** The current root logger's correlation id, if a root has been built. */
|
|
50
|
+
getRunId: () => string | undefined;
|
|
51
|
+
/** Test hook: drop the memoized root so the next call rebuilds it. */
|
|
52
|
+
resetCliLogger: () => void;
|
|
53
|
+
/** {@link resolveLogLevel} bound to this CLI's env prefix. */
|
|
54
|
+
resolveLogLevel: (argv: LogFlags, configLevel?: LogLevel) => LogLevel | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a CLI's logging functions around a memoized root `Logger`.
|
|
58
|
+
*
|
|
59
|
+
* Level comes from `--log` / `--verbose` / `--quiet`, then
|
|
60
|
+
* `<PREFIX>_LOG_LEVEL`, then the config file. Console output is quiet unless
|
|
61
|
+
* CI or a flag asks for it. `--quiet` and `--log` also turn the progress
|
|
62
|
+
* spinner off (`allowProgress`), as they own the terminal.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const { buildCliLogger, getLogger, reportError } = createCliLogger({ envPrefix: "ACME" });
|
|
66
|
+
*/
|
|
67
|
+
export declare function createCliLogger(options: CreateCliLoggerOptions): CliLogger;
|
|
68
|
+
/** The `telemetry` block of a CLI's config file: the committed override layer. */
|
|
69
|
+
interface TelemetryConfig {
|
|
70
|
+
/** `false` installs the no-op sinks for the whole run. Default on. */
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
/** `"none"` disables usage analytics but leaves error reporting on. Default `"posthog"`. */
|
|
73
|
+
analytics?: "posthog" | "none";
|
|
74
|
+
}
|
|
75
|
+
interface CreateCliTelemetryOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Upper-case env prefix of the CLI, e.g. `"HOLOCRON"`. Reads
|
|
78
|
+
* `<PREFIX>_TELEMETRY` (`false` opts out), the legacy `NO_<PREFIX>_TELEMETRY`
|
|
79
|
+
* and `<PREFIX>_ORG`; the Sentry release is `<prefix lower-cased>@<version>`.
|
|
80
|
+
*/
|
|
81
|
+
envPrefix: string;
|
|
82
|
+
/** Resolve the Sentry DSN at `init` time. `""` disables error reporting. */
|
|
83
|
+
dsn: () => string;
|
|
84
|
+
/** Resolve the PostHog project key at `init` time. `""` disables usage analytics. */
|
|
85
|
+
posthogKey: () => string;
|
|
86
|
+
/** Resolve the PostHog ingest host at `init` time. */
|
|
87
|
+
posthogHost: () => string;
|
|
88
|
+
/** The logger's correlation id, attached to events as `runId`. */
|
|
89
|
+
getRunId?: () => string | undefined;
|
|
90
|
+
}
|
|
91
|
+
/** The bound telemetry functions a CLI re-exports. */
|
|
92
|
+
interface CliTelemetry {
|
|
93
|
+
/** Install the live sinks unless the opt-out is set or a credential is empty. */
|
|
94
|
+
init: (version: string) => void;
|
|
95
|
+
/** Apply the config block: it only ever narrows what `init` turned on. */
|
|
96
|
+
applyConfig: (cfg: TelemetryConfig | undefined) => void;
|
|
97
|
+
/** Capture a usage event; properties run through the secret scrubber. */
|
|
98
|
+
event: (name: string, properties?: Record<string, unknown>) => void;
|
|
99
|
+
/** Start a command span; call the returned function with the outcome. */
|
|
100
|
+
startCommand: (name: string) => (ok: boolean) => void;
|
|
101
|
+
/** Report an error to the error sink. */
|
|
102
|
+
captureException: (err: unknown) => void;
|
|
103
|
+
/** End the error-reporting session. */
|
|
104
|
+
endSession: () => void;
|
|
105
|
+
/** Drain both sinks and return to the no-ops. */
|
|
106
|
+
flush: () => Promise<void>;
|
|
107
|
+
/** Test hook: drop sinks and command state back to the no-op defaults. */
|
|
108
|
+
resetTelemetry: () => void;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create a CLI's self-telemetry: error reporting (Sentry) and usage analytics
|
|
112
|
+
* (PostHog) behind the `ErrorSink` / `AnalyticsSink` seam of
|
|
113
|
+
* `@theholocron/observability`. No-op sinks are installed until `init` finds
|
|
114
|
+
* telemetry enabled, and the single kill switch is `<PREFIX>_TELEMETRY=false`
|
|
115
|
+
* (legacy alias `NO_<PREFIX>_TELEMETRY`). The credentials are passed in, since
|
|
116
|
+
* this reads no environment for them.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const telemetry = createCliTelemetry({
|
|
120
|
+
* envPrefix: "ACME",
|
|
121
|
+
* dsn: () => "https://key@o1.ingest.sentry.io/1",
|
|
122
|
+
* posthogKey: () => "phc_key",
|
|
123
|
+
* posthogHost: () => "https://us.i.posthog.com",
|
|
124
|
+
* });
|
|
125
|
+
*/
|
|
126
|
+
export declare function createCliTelemetry(options: CreateCliTelemetryOptions): CliTelemetry;
|
|
127
|
+
/**
|
|
128
|
+
* Resolve whether human-visible console output (pretty or NDJSON) should run
|
|
129
|
+
* at all, independent of level: the `@theholocron/observability`
|
|
130
|
+
* `consoleOutput` override.
|
|
131
|
+
*
|
|
132
|
+
* Local and interactive runs are quiet by default: structured logger output
|
|
133
|
+
* stays off regardless of level, unless the user asked to see it with
|
|
134
|
+
* `--log <level>`, `--verbose` or `--quiet`. In CI it returns `undefined`,
|
|
135
|
+
* which keeps `createLogger`'s own `ci`/`tty` auto-detection untouched.
|
|
136
|
+
*
|
|
137
|
+
* @param argv - The parsed global logging flags.
|
|
138
|
+
*/
|
|
139
|
+
export declare function resolveConsoleOutput(argv: LogFlags): boolean | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
142
|
+
*
|
|
143
|
+
* 1. `--log <level>` 2. `--verbose` → `"debug"` 3. `--quiet` → `"error"`
|
|
144
|
+
* 4. `<PREFIX>_LOG_LEVEL` env var
|
|
145
|
+
* 5. the config file's log level (`configLevel`)
|
|
146
|
+
*
|
|
147
|
+
* Returns `undefined` when nothing applies, so `createLogger` falls back to
|
|
148
|
+
* `"info"`. Resolving the whole chain here (rather than passing `configLevel`
|
|
149
|
+
* straight through) keeps config below the env var.
|
|
150
|
+
*
|
|
151
|
+
* @param envPrefix - Upper-case env prefix of the CLI, e.g. `"HOLOCRON"`.
|
|
152
|
+
* @param argv - The parsed global logging flags.
|
|
153
|
+
* @param configLevel - The level from the CLI's config file, if one was loaded.
|
|
154
|
+
*/
|
|
155
|
+
export declare function resolveLogLevel(envPrefix: string, argv: LogFlags, configLevel?: LogLevel): LogLevel | undefined;
|
|
156
|
+
export type { BuildCliLoggerOpts, CliLogger, CliLoggerContext, CliTelemetry, CreateCliLoggerOptions, CreateCliTelemetryOptions, LogFlags, TelemetryConfig };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { allowProgress, style } from "./ui.mjs";
|
|
2
|
+
import { createLogger, isCI, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
|
|
3
|
+
import { createEnvLookup } from "@theholocron/env-utils/lookup";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { hostname, userInfo } from "node:os";
|
|
6
|
+
import { PostHogSink } from "@theholocron/observability/analytics";
|
|
7
|
+
import { NoopAnalyticsSink, NoopErrorSink, redactObject } from "@theholocron/observability/core";
|
|
8
|
+
import { SentrySink } from "@theholocron/observability/errors";
|
|
9
|
+
// src/observability/resolve-console-output/resolve-console-output.ts
|
|
10
|
+
/**
|
|
11
|
+
* Resolve whether human-visible console output (pretty or NDJSON) should run
|
|
12
|
+
* at all, independent of level: the `@theholocron/observability`
|
|
13
|
+
* `consoleOutput` override.
|
|
14
|
+
*
|
|
15
|
+
* Local and interactive runs are quiet by default: structured logger output
|
|
16
|
+
* stays off regardless of level, unless the user asked to see it with
|
|
17
|
+
* `--log <level>`, `--verbose` or `--quiet`. In CI it returns `undefined`,
|
|
18
|
+
* which keeps `createLogger`'s own `ci`/`tty` auto-detection untouched.
|
|
19
|
+
*
|
|
20
|
+
* @param argv - The parsed global logging flags.
|
|
21
|
+
*/
|
|
22
|
+
function resolveConsoleOutput(argv) {
|
|
23
|
+
if (argv.log || argv.verbose || argv.quiet) return true;
|
|
24
|
+
return isCI() ? void 0 : false;
|
|
25
|
+
}
|
|
26
|
+
// src/observability/resolve-log-level/resolve-log-level.ts
|
|
27
|
+
const env$1 = createEnvLookup();
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the explicit level to hand to `createLogger`, in priority order:
|
|
30
|
+
*
|
|
31
|
+
* 1. `--log <level>` 2. `--verbose` → `"debug"` 3. `--quiet` → `"error"`
|
|
32
|
+
* 4. `<PREFIX>_LOG_LEVEL` env var
|
|
33
|
+
* 5. the config file's log level (`configLevel`)
|
|
34
|
+
*
|
|
35
|
+
* Returns `undefined` when nothing applies, so `createLogger` falls back to
|
|
36
|
+
* `"info"`. Resolving the whole chain here (rather than passing `configLevel`
|
|
37
|
+
* straight through) keeps config below the env var.
|
|
38
|
+
*
|
|
39
|
+
* @param envPrefix - Upper-case env prefix of the CLI, e.g. `"HOLOCRON"`.
|
|
40
|
+
* @param argv - The parsed global logging flags.
|
|
41
|
+
* @param configLevel - The level from the CLI's config file, if one was loaded.
|
|
42
|
+
*/
|
|
43
|
+
function resolveLogLevel(envPrefix, argv, configLevel) {
|
|
44
|
+
if (argv.log) return argv.log;
|
|
45
|
+
if (argv.verbose) return "debug";
|
|
46
|
+
if (argv.quiet) return "error";
|
|
47
|
+
return parseLogLevel(env$1.get(`${envPrefix}_LOG_LEVEL`)) ?? configLevel;
|
|
48
|
+
}
|
|
49
|
+
// src/observability/create-cli-logger/create-cli-logger.ts
|
|
50
|
+
/**
|
|
51
|
+
* Create a CLI's logging functions around a memoized root `Logger`.
|
|
52
|
+
*
|
|
53
|
+
* Level comes from `--log` / `--verbose` / `--quiet`, then
|
|
54
|
+
* `<PREFIX>_LOG_LEVEL`, then the config file. Console output is quiet unless
|
|
55
|
+
* CI or a flag asks for it. `--quiet` and `--log` also turn the progress
|
|
56
|
+
* spinner off (`allowProgress`), as they own the terminal.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const { buildCliLogger, getLogger, reportError } = createCliLogger({ envPrefix: "ACME" });
|
|
60
|
+
*/
|
|
61
|
+
function createCliLogger(options) {
|
|
62
|
+
const { envPrefix, resolveAxiom = () => resolveAxiomFromEnv() } = options;
|
|
63
|
+
let root;
|
|
64
|
+
let rootLevel;
|
|
65
|
+
let rootCommand;
|
|
66
|
+
let rootAxiomKey;
|
|
67
|
+
const levelFor = (argv, configLevel) => resolveLogLevel(envPrefix, argv, configLevel);
|
|
68
|
+
function buildCliLogger(argv, opts = {}) {
|
|
69
|
+
const { command, configLevel } = opts;
|
|
70
|
+
allowProgress(!argv.quiet && !argv.log);
|
|
71
|
+
if (command) rootCommand = command;
|
|
72
|
+
const level = levelFor(argv, configLevel);
|
|
73
|
+
const consoleOutput = resolveConsoleOutput(argv);
|
|
74
|
+
const axiom = resolveAxiom(opts, { command: command ?? rootCommand });
|
|
75
|
+
const axiomKey = axiom?.dataset;
|
|
76
|
+
const rebuildForConfig = configLevel !== void 0 && level !== rootLevel && !argv.verbose && !argv.quiet && !argv.log;
|
|
77
|
+
if (!root || rebuildForConfig || axiomKey !== void 0 && axiomKey !== rootAxiomKey) {
|
|
78
|
+
const built = createLogger({
|
|
79
|
+
...level ? { level } : {},
|
|
80
|
+
...axiom ? { axiom } : {},
|
|
81
|
+
...consoleOutput !== void 0 ? { consoleOutput } : {}
|
|
82
|
+
});
|
|
83
|
+
root = {
|
|
84
|
+
logger: rootCommand ? built.logger.child({ command: rootCommand }) : built.logger,
|
|
85
|
+
runId: built.runId
|
|
86
|
+
};
|
|
87
|
+
rootLevel = level;
|
|
88
|
+
rootAxiomKey = axiomKey;
|
|
89
|
+
}
|
|
90
|
+
return root;
|
|
91
|
+
}
|
|
92
|
+
function getLogger() {
|
|
93
|
+
return (root ??= createLogger({ consoleOutput: resolveConsoleOutput({}) })).logger;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
buildCliLogger,
|
|
97
|
+
getLogger,
|
|
98
|
+
reportError(message, log = getLogger()) {
|
|
99
|
+
console.error(style.fail(message));
|
|
100
|
+
log.error(message);
|
|
101
|
+
},
|
|
102
|
+
getRunId: () => root?.runId,
|
|
103
|
+
resetCliLogger() {
|
|
104
|
+
root = void 0;
|
|
105
|
+
rootLevel = void 0;
|
|
106
|
+
rootCommand = void 0;
|
|
107
|
+
rootAxiomKey = void 0;
|
|
108
|
+
},
|
|
109
|
+
resolveLogLevel: levelFor
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// src/observability/create-cli-telemetry/create-cli-telemetry.ts
|
|
113
|
+
const env = createEnvLookup();
|
|
114
|
+
/**
|
|
115
|
+
* Anonymous, stable per-machine id: `sha256(hostname + username)` truncated.
|
|
116
|
+
* One-way, so no raw hostname or username is ever transmitted.
|
|
117
|
+
*/
|
|
118
|
+
function machineId() {
|
|
119
|
+
try {
|
|
120
|
+
return createHash("sha256").update(`${hostname()} ${userInfo().username}`).digest("hex").slice(0, 32);
|
|
121
|
+
} catch {
|
|
122
|
+
return "unknown";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a CLI's self-telemetry: error reporting (Sentry) and usage analytics
|
|
127
|
+
* (PostHog) behind the `ErrorSink` / `AnalyticsSink` seam of
|
|
128
|
+
* `@theholocron/observability`. No-op sinks are installed until `init` finds
|
|
129
|
+
* telemetry enabled, and the single kill switch is `<PREFIX>_TELEMETRY=false`
|
|
130
|
+
* (legacy alias `NO_<PREFIX>_TELEMETRY`). The credentials are passed in, since
|
|
131
|
+
* this reads no environment for them.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* const telemetry = createCliTelemetry({
|
|
135
|
+
* envPrefix: "ACME",
|
|
136
|
+
* dsn: () => "https://key@o1.ingest.sentry.io/1",
|
|
137
|
+
* posthogKey: () => "phc_key",
|
|
138
|
+
* posthogHost: () => "https://us.i.posthog.com",
|
|
139
|
+
* });
|
|
140
|
+
*/
|
|
141
|
+
function createCliTelemetry(options) {
|
|
142
|
+
const { envPrefix, dsn, posthogKey, posthogHost, getRunId = () => void 0 } = options;
|
|
143
|
+
let errors = new NoopErrorSink();
|
|
144
|
+
let analytics = new NoopAnalyticsSink();
|
|
145
|
+
let commandStartedAt = 0;
|
|
146
|
+
let currentCommand = "unknown";
|
|
147
|
+
let lastErrorType;
|
|
148
|
+
let currentSpan;
|
|
149
|
+
const isEnabled = () => env.get(`${envPrefix}_TELEMETRY`) !== "false" && !env.get(`NO_${envPrefix}_TELEMETRY`);
|
|
150
|
+
function baseProps() {
|
|
151
|
+
const runId = getRunId();
|
|
152
|
+
return {
|
|
153
|
+
command: currentCommand,
|
|
154
|
+
ci: Boolean(env.get("CI")),
|
|
155
|
+
...runId ? { runId } : {}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function event(name, properties = {}) {
|
|
159
|
+
analytics.capture(machineId(), name, redactObject({
|
|
160
|
+
...baseProps(),
|
|
161
|
+
...properties
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
function resetSinks() {
|
|
165
|
+
errors = new NoopErrorSink();
|
|
166
|
+
analytics = new NoopAnalyticsSink();
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
init(version) {
|
|
170
|
+
const resolvedDsn = dsn();
|
|
171
|
+
if (isEnabled() && resolvedDsn !== "") {
|
|
172
|
+
errors = new SentrySink();
|
|
173
|
+
errors.init({
|
|
174
|
+
dsn: resolvedDsn,
|
|
175
|
+
release: `${envPrefix.toLowerCase()}@${version}`,
|
|
176
|
+
environment: env.get("CI") ? "ci" : "local",
|
|
177
|
+
tags: {
|
|
178
|
+
os: process.platform,
|
|
179
|
+
node: process.version,
|
|
180
|
+
ci: String(Boolean(env.get("CI")))
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const resolvedKey = posthogKey();
|
|
185
|
+
if (isEnabled() && resolvedKey !== "") {
|
|
186
|
+
analytics = new PostHogSink({
|
|
187
|
+
key: resolvedKey,
|
|
188
|
+
host: posthogHost()
|
|
189
|
+
});
|
|
190
|
+
const org = env.get(`${envPrefix}_ORG`);
|
|
191
|
+
analytics.identify(machineId(), {
|
|
192
|
+
ci: Boolean(env.get("CI")),
|
|
193
|
+
os: process.platform,
|
|
194
|
+
node: process.version,
|
|
195
|
+
cli: version,
|
|
196
|
+
...org ? { org } : {}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
applyConfig(cfg) {
|
|
201
|
+
if (!cfg) return;
|
|
202
|
+
const drain = (p) => {
|
|
203
|
+
p.catch(() => {});
|
|
204
|
+
};
|
|
205
|
+
if (cfg.enabled === false) {
|
|
206
|
+
drain(errors.flush());
|
|
207
|
+
drain(analytics.shutdown());
|
|
208
|
+
resetSinks();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (cfg.analytics === "none") {
|
|
212
|
+
drain(analytics.shutdown());
|
|
213
|
+
analytics = new NoopAnalyticsSink();
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
event,
|
|
217
|
+
startCommand(name) {
|
|
218
|
+
currentCommand = name;
|
|
219
|
+
commandStartedAt = Date.now();
|
|
220
|
+
lastErrorType = void 0;
|
|
221
|
+
currentSpan = errors.startSpan(name);
|
|
222
|
+
event("command_started", { dry_run: process.argv.includes("--dry-run") });
|
|
223
|
+
return (ok) => {
|
|
224
|
+
currentSpan?.setStatus(ok);
|
|
225
|
+
currentSpan?.end();
|
|
226
|
+
event(ok ? "command_completed" : "command_failed", {
|
|
227
|
+
status: ok ? "ok" : "fail",
|
|
228
|
+
duration_ms: Date.now() - commandStartedAt,
|
|
229
|
+
...ok || !lastErrorType ? {} : { error_type: lastErrorType }
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
captureException(err) {
|
|
234
|
+
lastErrorType = err instanceof Error ? err.constructor.name : "Error";
|
|
235
|
+
errors.captureException(err);
|
|
236
|
+
},
|
|
237
|
+
endSession() {
|
|
238
|
+
errors.endSession();
|
|
239
|
+
},
|
|
240
|
+
async flush() {
|
|
241
|
+
await errors.flush();
|
|
242
|
+
await analytics.shutdown();
|
|
243
|
+
resetSinks();
|
|
244
|
+
},
|
|
245
|
+
resetTelemetry() {
|
|
246
|
+
resetSinks();
|
|
247
|
+
commandStartedAt = 0;
|
|
248
|
+
currentCommand = "unknown";
|
|
249
|
+
lastErrorType = void 0;
|
|
250
|
+
currentSpan = void 0;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export { createCliLogger, createCliTelemetry, resolveConsoleOutput, resolveLogLevel };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFileSync, spawn } from "node:child_process";
|
|
2
2
|
import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join, sep } from "node:path";
|
|
4
|
-
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
5
4
|
import { tmpdir } from "node:os";
|
|
5
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import yargs from "yargs";
|
|
8
8
|
// src/testing/cli-runner/cli-runner.ts
|
package/dist/ui.d.mts
CHANGED
|
@@ -132,6 +132,23 @@ export declare function progressEnabled(): boolean;
|
|
|
132
132
|
export declare function createProgress(print: (line: string) => void, enabled?: () => boolean, listen?: ProgressListener): Progress;
|
|
133
133
|
/** `fn` under a cleared-when-done spinner named `label`, on a terminal only. */
|
|
134
134
|
export declare function withProgress<T>(label: string, fn: () => Promise<T>, listen?: ProgressListener): Promise<T>;
|
|
135
|
+
/** What {@link createRetryNotice} reads from one retry. Structurally compatible with `@theholocron/http-client`'s `RetryEvent`. */
|
|
136
|
+
interface RetryNoticeEvent {
|
|
137
|
+
/** The service being called, when the client knows it. */
|
|
138
|
+
vendor?: string | undefined;
|
|
139
|
+
/** The attempt about to be made. */
|
|
140
|
+
attempt: number;
|
|
141
|
+
/** How many attempts there are in all. */
|
|
142
|
+
of: number;
|
|
143
|
+
}
|
|
144
|
+
/** Registers a handler for retries and returns a function that removes it. */
|
|
145
|
+
type RetrySubscribe = (handler: (event: RetryNoticeEvent) => void) => () => void;
|
|
146
|
+
/**
|
|
147
|
+
* Builds a {@link ProgressListener} that says on the spinner when a request is being sent again, so a slow step is
|
|
148
|
+
* not mistaken for a stuck one. `subscribe` is the HTTP client's retry hook (e.g. `onRetry`), which keeps kyber free
|
|
149
|
+
* of any client dependency.
|
|
150
|
+
*/
|
|
151
|
+
export declare function createRetryNotice(subscribe: RetrySubscribe): ProgressListener;
|
|
135
152
|
/**
|
|
136
153
|
* {@link stripControl}, then cut to 200 characters with an ellipsis. For a vendor's response body or any other text of
|
|
137
154
|
* unknown size that lands in the terminal.
|
|
@@ -185,4 +202,4 @@ export declare const style: {
|
|
|
185
202
|
* streams are parameters so a test can answer for the user.
|
|
186
203
|
*/
|
|
187
204
|
export declare function waitForEnter(message: string, input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): Promise<"enter" | "closed">;
|
|
188
|
-
export type { Container, Key, Line, Path, Progress, ProgressListener, RenderOptions, RowStatus, ViewJsonOptions, ViewState };
|
|
205
|
+
export type { Container, Key, Line, Path, Progress, ProgressListener, RenderOptions, RetryNoticeEvent, RetrySubscribe, RowStatus, ViewJsonOptions, ViewState };
|
package/dist/ui.mjs
CHANGED
|
@@ -391,6 +391,17 @@ function createProgress(print, enabled = progressEnabled, listen) {
|
|
|
391
391
|
function withProgress(label, fn, listen) {
|
|
392
392
|
return createProgress(() => {}, progressEnabled, listen).run(label, fn);
|
|
393
393
|
}
|
|
394
|
+
// src/ui/retry-notice/retry-notice.ts
|
|
395
|
+
/**
|
|
396
|
+
* Builds a {@link ProgressListener} that says on the spinner when a request is being sent again, so a slow step is
|
|
397
|
+
* not mistaken for a stuck one. `subscribe` is the HTTP client's retry hook (e.g. `onRetry`), which keeps kyber free
|
|
398
|
+
* of any client dependency.
|
|
399
|
+
*/
|
|
400
|
+
function createRetryNotice(subscribe) {
|
|
401
|
+
return (label, setText) => subscribe((e) => {
|
|
402
|
+
setText(`${label} (retrying ${e.vendor ?? "the request"}, attempt ${e.attempt} of ${e.of})`);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
394
405
|
// src/ui/style/style.ts
|
|
395
406
|
/** Formats a message with a status marker and colour: one helper per kind of line a command prints. */
|
|
396
407
|
const style = {
|
|
@@ -453,4 +464,4 @@ function waitForEnter(message, input = process.stdin, output = process.stderr) {
|
|
|
453
464
|
});
|
|
454
465
|
});
|
|
455
466
|
}
|
|
456
|
-
export { NonInteractiveError, allowProgress, commandFailureMessage, createProgress, describeError, expandToDepth, logReason, openPath, pad, progressEnabled, reduceKey, renderTree, sanitizeDetail, setProgressListener, statusRow, stripControl, style, viewJson, waitForEnter, withProgress, withSpinner };
|
|
467
|
+
export { NonInteractiveError, allowProgress, commandFailureMessage, createProgress, createRetryNotice, describeError, expandToDepth, logReason, openPath, pad, progressEnabled, reduceKey, renderTree, sanitizeDetail, setProgressListener, statusRow, stripControl, style, viewJson, waitForEnter, withProgress, withSpinner };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/kyber",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.342",
|
|
4
4
|
"description": "The reusable parts of a command-line tool: terminal output, prompts, the interactive menu, the update notifier, safe handling of tokens, a test harness and the global flags.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"import": "./dist/notifier.mjs",
|
|
41
41
|
"default": "./dist/notifier.mjs"
|
|
42
42
|
},
|
|
43
|
+
"./observability": {
|
|
44
|
+
"types": "./dist/observability.d.mts",
|
|
45
|
+
"import": "./dist/observability.mjs",
|
|
46
|
+
"default": "./dist/observability.mjs"
|
|
47
|
+
},
|
|
43
48
|
"./prompts": {
|
|
44
49
|
"types": "./dist/prompts.d.mts",
|
|
45
50
|
"import": "./dist/prompts.mjs",
|
|
@@ -64,6 +69,9 @@
|
|
|
64
69
|
"files": [
|
|
65
70
|
"dist"
|
|
66
71
|
],
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@theholocron/env-utils": "^1.17.0-alpha.1"
|
|
74
|
+
},
|
|
67
75
|
"devDependencies": {
|
|
68
76
|
"@inquirer/prompts": "^8.7.5",
|
|
69
77
|
"@inquirer/search": "^4.3.6",
|
|
@@ -82,11 +90,12 @@
|
|
|
82
90
|
"typescript": "^5.9.3",
|
|
83
91
|
"vitest": "^5.0.5",
|
|
84
92
|
"yargs": "^18.1.0",
|
|
85
|
-
"@theholocron/
|
|
93
|
+
"@theholocron/tsconfig": "^8.9.0-alpha.8",
|
|
94
|
+
"@theholocron/eslint-config": "^8.9.0-alpha.8",
|
|
95
|
+
"@theholocron/observability": "0.7.0-alpha.1",
|
|
86
96
|
"@theholocron/tsdown-config": "^8.9.0-alpha.8",
|
|
87
97
|
"@theholocron/vitest-config": "^8.9.0-alpha.8",
|
|
88
|
-
"@theholocron/
|
|
89
|
-
"@theholocron/tsconfig": "^8.9.0-alpha.8"
|
|
98
|
+
"@theholocron/datapad": "5.0.0-alpha.342"
|
|
90
99
|
},
|
|
91
100
|
"peerDependencies": {
|
|
92
101
|
"@inquirer/prompts": "^8.0.0",
|
|
@@ -97,7 +106,8 @@
|
|
|
97
106
|
"dotenv": "^17.0.0",
|
|
98
107
|
"ora": "^9.0.0",
|
|
99
108
|
"yargs": "^18.0.0",
|
|
100
|
-
"@theholocron/datapad": "^5.0.0-alpha.
|
|
109
|
+
"@theholocron/datapad": "^5.0.0-alpha.342",
|
|
110
|
+
"@theholocron/observability": "^0.7.0-alpha.1"
|
|
101
111
|
},
|
|
102
112
|
"peerDependenciesMeta": {
|
|
103
113
|
"@inquirer/prompts": {
|
|
@@ -112,6 +122,9 @@
|
|
|
112
122
|
"@theholocron/datapad": {
|
|
113
123
|
"optional": true
|
|
114
124
|
},
|
|
125
|
+
"@theholocron/observability": {
|
|
126
|
+
"optional": true
|
|
127
|
+
},
|
|
115
128
|
"chalk": {
|
|
116
129
|
"optional": true
|
|
117
130
|
},
|