@theholocron/kyber 5.0.0-alpha.340 → 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 +23 -10
- package/dist/config.d.mts +81 -0
- package/dist/config.mjs +111 -0
- package/dist/index-DpXxO_wH.d.mts +214 -0
- package/dist/menu.d.mts +200 -0
- package/dist/menu.mjs +322 -0
- package/dist/notifier.mjs +1 -1
- package/dist/observability.d.mts +156 -0
- package/dist/observability.mjs +254 -0
- package/dist/prompts.d.mts +2 -214
- package/dist/prompts.mjs +1 -1
- package/dist/secrets.d.mts +81 -1
- package/dist/secrets.mjs +74 -2
- package/dist/testing.mjs +2 -2
- package/dist/ui.d.mts +57 -3
- package/dist/ui.mjs +80 -4
- package/package.json +33 -5
|
@@ -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/prompts.d.mts
CHANGED
|
@@ -1,214 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/** The label shown in the picker. */
|
|
4
|
-
name: string;
|
|
5
|
-
/** What the picker resolves to: a command name, `group:<path>` to open a submenu, or `back`. */
|
|
6
|
-
value: string;
|
|
7
|
-
/** One line of help shown beside the label. */
|
|
8
|
-
description: string;
|
|
9
|
-
/** Lower-cased text the search box matches against (a group includes everything under it). */
|
|
10
|
-
haystack: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Searchable autocomplete over one menu level's `items`. `select()` would work too
|
|
14
|
-
* at these list sizes, but `search()` degrades gracefully as the surface grows and
|
|
15
|
-
* costs nothing when it doesn't. Resolves to the picked item's `value`.
|
|
16
|
-
*/
|
|
17
|
-
export declare function pickMenuItem(items: MenuItem[], message?: string): Promise<string>;
|
|
18
|
-
/**
|
|
19
|
-
* `@inquirer/search`'s `source` callback, factored out as a plain function —
|
|
20
|
-
* unit-testable directly instead of only through a mocked `search()` call.
|
|
21
|
-
* Empty/undefined `term` (nothing typed yet) returns every item.
|
|
22
|
-
*/
|
|
23
|
-
export declare function searchChoices(items: MenuItem[], term: string | undefined): Array<{
|
|
24
|
-
name: string;
|
|
25
|
-
value: string;
|
|
26
|
-
description: string;
|
|
27
|
-
short: string;
|
|
28
|
-
}>;
|
|
29
|
-
/** A resolved option answer — the shapes `buildChildArgv`/`secretEnv` turn into argv tokens or env entries. */
|
|
30
|
-
type OptionAnswer = string | boolean | string[];
|
|
31
|
-
/** One option to offer: how to ask for it and how the answer is turned into a flag. */
|
|
32
|
-
interface OptionPrompt {
|
|
33
|
-
/** The yargs option's camelCased argv key, e.g. `"otp"`. */
|
|
34
|
-
key: string;
|
|
35
|
-
/** The literal flag as typed at the CLI, e.g. `"--otp"`. */
|
|
36
|
-
flag: string;
|
|
37
|
-
/** The kind of prompt: a yes/no, free text, a pick from `choices`, or masked text. */
|
|
38
|
-
type: "confirm" | "input" | "select" | "password";
|
|
39
|
-
/** The question shown to the user. */
|
|
40
|
-
message: string;
|
|
41
|
-
/**
|
|
42
|
-
* Required when `type === "select"`. A plain list for a fixed set, or a
|
|
43
|
-
* thunk for a set resolved at prompt time — same shape as
|
|
44
|
-
* `PositionalPrompt.choices`.
|
|
45
|
-
*/
|
|
46
|
-
choices?: string[] | (() => string[]);
|
|
47
|
-
/** The answer proposed when the user just presses Enter. */
|
|
48
|
-
default?: string | boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Collected with a masked `password()` prompt and delivered to the
|
|
51
|
-
* child through an environment variable instead of argv (`ps` and
|
|
52
|
-
* shell history expose argv) — never written to the dry-run preview
|
|
53
|
-
* echo, telemetry, or logs. Always paired with `type: "password"`.
|
|
54
|
-
*/
|
|
55
|
-
secret?: boolean;
|
|
56
|
-
/**
|
|
57
|
-
* Always included, like a required positional — never behind the
|
|
58
|
-
* multi-select, and never subject to `when`. Combines with `askLast`
|
|
59
|
-
* cleanly: `required` only decides whether this sits behind the
|
|
60
|
-
* checkbox, not when its value gets collected.
|
|
61
|
-
*/
|
|
62
|
-
required?: boolean;
|
|
63
|
-
/** Boolean whose yargs default is `true` — answering "no" emits `--no-<flag>` instead of omitting the flag. */
|
|
64
|
-
negatable?: boolean;
|
|
65
|
-
/** Array option taking exactly `arity` values (e.g. `--sync-trust <old> <new>`) — prompted once per value. */
|
|
66
|
-
arity?: number;
|
|
67
|
-
/**
|
|
68
|
-
* Defer collecting this option's value until right before the real
|
|
69
|
-
* (non-dry-run) spawn, instead of upfront with everything else
|
|
70
|
-
* (holocron#1052 follow-up) — for a time-sensitive secret (an OTP code)
|
|
71
|
-
* that can go stale if typed too early: a dry-run preview and a human
|
|
72
|
-
* confirming "proceed with the real run?" can both take a while, and
|
|
73
|
-
* neither one actually needs the value anyway. Works whether this
|
|
74
|
-
* option also has `required: true` (always included, no checkbox pick
|
|
75
|
-
* needed) or not (offered through the checkbox as usual) — `askLast`
|
|
76
|
-
* only ever changes *when* the value is collected, never whether it's
|
|
77
|
-
* included. See `promptForDeferredOptions`.
|
|
78
|
-
*/
|
|
79
|
-
askLast?: boolean;
|
|
80
|
-
/**
|
|
81
|
-
* Evaluated against answers collected so far, in declaration order —
|
|
82
|
-
* `false` skips prompting (and so emitting) this option entirely, as if
|
|
83
|
-
* it had never been selected. Not applied to `required` options. For a
|
|
84
|
-
* command whose options have real either/or relationships, prefer
|
|
85
|
-
* {@link OptionMode} instead — nothing contingent is ever shown in the
|
|
86
|
-
* first place, rather than silently dropped after being picked.
|
|
87
|
-
*/
|
|
88
|
-
when?: (answers: Readonly<Record<string, OptionAnswer>>) => boolean;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* One of a command's mutually exclusive workflows (holocron#1052 follow-up)
|
|
92
|
-
* — `package publish`'s steady-state publish, `--initial` bootstrap, and
|
|
93
|
-
* `--sync-trust` maintenance mode are three different things to do, not
|
|
94
|
-
* three independent flags. The mode picked first decides which options are
|
|
95
|
-
* even offered, so nothing contingent on another mode is ever shown.
|
|
96
|
-
*/
|
|
97
|
-
interface OptionMode {
|
|
98
|
-
/** Unique within the command's `modes` — identifies the pick, never shown. */
|
|
99
|
-
key: string;
|
|
100
|
-
/** Shown as this mode's choice label in the "what would you like to do?" prompt — keep it short; put caveats in `description` instead. */
|
|
101
|
-
message: string;
|
|
102
|
-
/**
|
|
103
|
-
* Longer explanation shown as helper text under this choice only while
|
|
104
|
-
* it's highlighted (`select()`'s own per-choice `description`) — the
|
|
105
|
-
* place for a caveat ("use only for a package's very first publish")
|
|
106
|
-
* that would make `message` too long to scan at a glance.
|
|
107
|
-
*/
|
|
108
|
-
description?: string;
|
|
109
|
-
/**
|
|
110
|
-
* Emitted unconditionally once this mode is picked, before any of its
|
|
111
|
-
* own options — e.g. `"--initial"` for a bootstrap-publish mode. Omit
|
|
112
|
-
* for a mode that implies no flag of its own (steady-state here: no
|
|
113
|
-
* flag means "neither of the others").
|
|
114
|
-
*/
|
|
115
|
-
flag?: string;
|
|
116
|
-
/** This mode's own options, prompted exactly like a flat command's `options` — required ones always asked, the rest behind one checkbox. */
|
|
117
|
-
options?: OptionPrompt[];
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* `promptForOptions`' result: every {@link OptionPrompt} relevant to
|
|
121
|
-
* rendering, in order, paired with the answers collected for them.
|
|
122
|
-
* `options` is the flat `OptionsCommand.options` list directly for a
|
|
123
|
-
* flat command, or the picked mode's own `options` (plus a synthetic
|
|
124
|
-
* entry for its `flag`, when it has one) for a `modes`-based command —
|
|
125
|
-
* `buildChildArgv`/`secretEnv` render either shape identically, with no
|
|
126
|
-
* need to know which kind of command produced it.
|
|
127
|
-
*/
|
|
128
|
-
interface ResolvedOptions {
|
|
129
|
-
/** The options that apply, in the order they were asked. */
|
|
130
|
-
options: OptionPrompt[];
|
|
131
|
-
/** What was answered, by each option's `key`. */
|
|
132
|
-
answers: Record<string, OptionAnswer>;
|
|
133
|
-
/** Picked `askLast: true` options with no value yet — resolve via `promptForDeferredOptions` right before the real spawn. Undefined/empty is the common case: nothing deferred. */
|
|
134
|
-
deferred?: OptionPrompt[];
|
|
135
|
-
}
|
|
136
|
-
/** The part of a command that {@link promptForOptions} needs: its name and the options (or modes) to offer. */
|
|
137
|
-
interface OptionsCommand {
|
|
138
|
-
/** Full command name as typed at the CLI, e.g. `"package publish"`. */
|
|
139
|
-
name: string;
|
|
140
|
-
/** Declared options, in order. Undefined/empty means nothing to offer — no prompt at all. Ignored when `modes` is set. */
|
|
141
|
-
options?: OptionPrompt[];
|
|
142
|
-
/**
|
|
143
|
-
* Mutually exclusive workflows — when set, a mode is picked first and
|
|
144
|
-
* `options` above is ignored entirely; only the picked mode's own
|
|
145
|
-
* options are then offered. Use this only for a command whose options
|
|
146
|
-
* have a real either/or relationship; plain `options` covers
|
|
147
|
-
* everything else and stays the simpler default.
|
|
148
|
-
*/
|
|
149
|
-
modes?: OptionMode[];
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Resolves `entry`'s options into a {@link ResolvedOptions} ready for
|
|
153
|
-
* `buildChildArgv`/`secretEnv` — delegating to {@link promptForModes} when
|
|
154
|
-
* `entry.modes` is set, or treating `entry.options` as a flat list
|
|
155
|
-
* otherwise. See `promptForFlatOptions` for the flat-list prompting
|
|
156
|
-
* algorithm itself. `result.deferred` still needs
|
|
157
|
-
* {@link promptForDeferredOptions} before the real spawn.
|
|
158
|
-
*/
|
|
159
|
-
export declare function promptForOptions(entry: OptionsCommand, argv: Record<string, unknown>): Promise<ResolvedOptions>;
|
|
160
|
-
/**
|
|
161
|
-
* Collects a value for each of `deferred` — the `askLast: true` options
|
|
162
|
-
* `promptForOptions` deliberately left unresolved. Call this as the very
|
|
163
|
-
* last thing before the real (non-dry-run) spawn, never before: a dry-run
|
|
164
|
-
* preview never reaches the real npm call these options exist for (an OTP
|
|
165
|
-
* is never checked during `--dry-run`), and a time-sensitive code typed
|
|
166
|
-
* any earlier — before a dry-run preview runs, before "proceed with the
|
|
167
|
-
* real run?" waits on a human — risks going stale before it's actually
|
|
168
|
-
* used. A no-op (no prompt at all) when `deferred` is empty.
|
|
169
|
-
*/
|
|
170
|
-
export declare function promptForDeferredOptions(deferred: OptionPrompt[]): Promise<Record<string, OptionAnswer>>;
|
|
171
|
-
/** One positional argument to ask for when it was not given on the command line. */
|
|
172
|
-
interface PositionalPrompt {
|
|
173
|
-
/** The yargs positional's camelCased argv key (e.g. `"newVersion"`). */
|
|
174
|
-
key: string;
|
|
175
|
-
/**
|
|
176
|
-
* The literal token in the command string, when it differs from `key`
|
|
177
|
-
* (yargs positionals are kebab-case there — `<new-version>` — but
|
|
178
|
-
* camelCased in argv). Shown in the non-interactive fallback message.
|
|
179
|
-
* Defaults to `key`.
|
|
180
|
-
*/
|
|
181
|
-
cliArg?: string;
|
|
182
|
-
/** Label shown to the user. */
|
|
183
|
-
message: string;
|
|
184
|
-
/** Free text, or a pick from `choices`. */
|
|
185
|
-
type: "input" | "select";
|
|
186
|
-
/**
|
|
187
|
-
* Required when `type === "select"`. A plain list for a fixed set, or a
|
|
188
|
-
* thunk for a set resolved at prompt time (e.g. `auth unset`'s provider
|
|
189
|
-
* choices are whatever currently has a stored token — there's no fixed
|
|
190
|
-
* provider list, providers are open-ended plugin packages).
|
|
191
|
-
*/
|
|
192
|
-
choices?: string[] | (() => string[]);
|
|
193
|
-
/** Optional `input()` validation — e.g. numeric positionals. */
|
|
194
|
-
validate?: (value: string) => boolean | string;
|
|
195
|
-
}
|
|
196
|
-
/** The part of a command that {@link promptForPositionals} needs: its name and the positionals to ask for. */
|
|
197
|
-
interface PositionalCommand {
|
|
198
|
-
/** Full command name as typed at the CLI, e.g. `"auth set"`. */
|
|
199
|
-
name: string;
|
|
200
|
-
/** Required positionals only, in order. */
|
|
201
|
-
positionals: PositionalPrompt[];
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* For each of `entry.positionals` not already present in `argv`, prompts for
|
|
205
|
-
* it (`input()` or `select()`, per the positional's `type`) and returns the
|
|
206
|
-
* resolved values in positional order — ready to append to a spawned child's
|
|
207
|
-
* argv, or to use directly in the current handler (Layer 3).
|
|
208
|
-
*
|
|
209
|
-
* Throws {@link NonInteractiveError} the first time it would need to prompt
|
|
210
|
-
* on a non-TTY stdin, and again if a `select` positional's choices resolve
|
|
211
|
-
* empty (nothing to pick from — e.g. `auth unset` with no stored tokens).
|
|
212
|
-
*/
|
|
213
|
-
export declare function promptForPositionals(entry: PositionalCommand, argv: Record<string, unknown>): Promise<string[]>;
|
|
214
|
-
export type { MenuItem, OptionAnswer, OptionMode, OptionPrompt, OptionsCommand, PositionalCommand, PositionalPrompt, ResolvedOptions };
|
|
1
|
+
import { a as promptForOptions, c as OptionPrompt, d as pickMenuItem, f as searchChoices, i as promptForDeferredOptions, l as OptionsCommand, n as PositionalCommand, o as OptionAnswer, p as MenuItem, r as PositionalPrompt, s as OptionMode, t as promptForPositionals, u as ResolvedOptions } from "./index-DpXxO_wH.mjs";
|
|
2
|
+
export { type MenuItem, type OptionAnswer, type OptionMode, type OptionPrompt, type OptionsCommand, type PositionalCommand, type PositionalPrompt, type ResolvedOptions, pickMenuItem, promptForDeferredOptions, promptForOptions, promptForPositionals, searchChoices };
|
package/dist/prompts.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as NonInteractiveError } from "./non-interactive-error-Mm_Mn79M.mjs";
|
|
2
|
-
import search from "@inquirer/search";
|
|
3
2
|
import { checkbox, confirm, input, password, select } from "@inquirer/prompts";
|
|
3
|
+
import search from "@inquirer/search";
|
|
4
4
|
// src/prompts/menu/menu.ts
|
|
5
5
|
/**
|
|
6
6
|
* Searchable autocomplete over one menu level's `items`. `select()` would work too
|