@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.
@@ -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 };
@@ -1,214 +1,2 @@
1
- /** A row in one level of a nested menu: a command to run, a group to open, or "back". */
2
- interface MenuItem {
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