hacksh 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
+ import { a as createExec, c as HackshPromptError, d as summarizeError, f as summarizeErrors, i as resolveOutputMode, l as HackshUsageError, n as createJsonPrinter, o as HackshExecError, r as createPrintQueue, s as HackshExitError, t as createPrettyPrinter, u as HackshValidationError } from "./pretty-BFub611x.js";
1
2
  import { globalRegistry, safeParseAsync } from "zod/v4/core";
2
- import { inspect, parseArgs as parseArgs$1, stripVTControlCharacters, styleText } from "node:util";
3
- import { spawn } from "node:child_process";
4
- import { emitKeypressEvents } from "node:readline";
5
- import { PassThrough } from "node:stream";
3
+ import { parseArgs as parseArgs$1 } from "node:util";
6
4
  import { readFile, realpath } from "node:fs/promises";
7
5
  import { dirname, join } from "node:path";
6
+ import { emitKeypressEvents } from "node:readline";
7
+ import { PassThrough } from "node:stream";
8
8
  //#region src/options.ts
9
9
  function kebabCase(value) {
10
10
  return value.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
@@ -12,6 +12,9 @@ function kebabCase(value) {
12
12
  function aliasList(value) {
13
13
  return typeof value === "string" ? [value] : value;
14
14
  }
15
+ //#endregion
16
+ //#region src/schema.ts
17
+ const POSITIONALS_KEY = "positionals";
15
18
  function definition(schema) {
16
19
  return schema._zod.def;
17
20
  }
@@ -35,8 +38,23 @@ function readArgInfo(schema) {
35
38
  let defaultValue;
36
39
  let description;
37
40
  let title;
41
+ let alias;
42
+ let valueName;
43
+ let env;
38
44
  while (current) {
39
45
  const metadata = globalRegistry.get(current);
46
+ if (metadata) {
47
+ if (metadata.alias !== void 0 && typeof metadata.alias !== "string" && !(Array.isArray(metadata.alias) && metadata.alias.every((value) => typeof value === "string"))) throw new TypeError("Argument metadata `alias` must be a string or an array of strings");
48
+ if (metadata.env !== void 0 && typeof metadata.env !== "string" && metadata.env !== false) throw new TypeError("Argument metadata `env` must be a string or false");
49
+ for (const key of [
50
+ "valueName",
51
+ "title",
52
+ "description"
53
+ ]) if (metadata[key] !== void 0 && typeof metadata[key] !== "string") throw new TypeError(`Argument metadata \`${key}\` must be a string`);
54
+ alias ??= metadata.alias;
55
+ valueName ??= metadata.valueName;
56
+ env ??= metadata.env;
57
+ }
40
58
  description ??= metadata?.description;
41
59
  title ??= metadata?.title;
42
60
  const def = definition(current);
@@ -51,12 +69,15 @@ function readArgInfo(schema) {
51
69
  defaultValue,
52
70
  description,
53
71
  title,
72
+ alias,
73
+ valueName,
74
+ env,
54
75
  choices: enumChoices(base)
55
76
  };
56
77
  }
57
- /** Read the `_` tuple or array schema that declares positional arguments. */
78
+ /** Read the `positionals` tuple or array schema that declares positional arguments. */
58
79
  function readPositionals(shape) {
59
- const field = shape["_"];
80
+ const field = shape[POSITIONALS_KEY];
60
81
  if (!field) return void 0;
61
82
  const def = definition(unwrapInput(field));
62
83
  let items;
@@ -67,7 +88,7 @@ function readPositionals(shape) {
67
88
  } else if (def.type === "array") {
68
89
  items = [];
69
90
  rest = def.element;
70
- } else throw new TypeError("Positional arguments `_` must use a tuple or array schema");
91
+ } else throw new TypeError("Positional arguments `positionals` must use a tuple or array schema");
71
92
  return {
72
93
  items: items.map((schema, index) => ({
73
94
  schema,
@@ -115,24 +136,31 @@ function defineCommand(def) {
115
136
  let sawOptional = false;
116
137
  for (const { schema, label } of positionals.items) {
117
138
  const info = readArgInfo(schema);
139
+ if (info.alias !== void 0 || info.env !== void 0 || info.valueName !== void 0) throw new TypeError(`Positional argument \`${label}\` cannot have alias, env, or valueName metadata`);
118
140
  if (isArrayInput(schema)) throw new TypeError(`Positional argument \`${label}\` cannot be an array; use a tuple rest`);
119
141
  if (!info.takesValue) throw new TypeError(`Boolean argument \`${label}\` cannot be positional`);
120
142
  if (sawOptional && info.required) throw new TypeError(`Required positional \`${label}\` cannot follow an optional positional`);
121
143
  sawOptional ||= !info.required;
122
144
  }
145
+ if (positionals.rest) {
146
+ const info = readArgInfo(positionals.rest.schema);
147
+ if (info.alias !== void 0 || info.env !== void 0 || info.valueName !== void 0) throw new TypeError(`Positional argument \`${positionals.rest.label}\` cannot have alias, env, or valueName metadata`);
148
+ }
123
149
  if (def.subCommands && Object.keys(def.subCommands).length > 0) throw new TypeError("A command cannot define both positional arguments and subcommands");
124
150
  }
125
151
  const usedAliases = /* @__PURE__ */ new Set();
126
- for (const [key, options] of Object.entries(def.argOptions ?? {})) {
127
- if (key === "_") throw new TypeError("Positional arguments cannot have argument options");
128
- const schema = shape[key];
129
- if (!schema) throw new TypeError(`Unknown argument options key \`${key}\``);
130
- if (options?.env !== void 0) {
152
+ for (const [key, schema] of Object.entries(shape)) {
153
+ const options = readArgInfo(schema);
154
+ if (key === "positionals") {
155
+ if (options.alias !== void 0 || options.env !== void 0 || options.valueName !== void 0) throw new TypeError("Positional arguments cannot have alias, env, or valueName metadata");
156
+ continue;
157
+ }
158
+ if (options.env !== void 0) {
131
159
  if (options.env === "") throw new TypeError(`Environment variable name for \`${key}\` cannot be empty`);
132
160
  if (isArrayInput(schema)) throw new TypeError(`Option \`${key}\` cannot use an environment variable because it accepts an array`);
133
161
  if (isObjectInput(schema)) throw new TypeError(`Option \`${key}\` cannot use an environment variable because it accepts an object`);
134
162
  }
135
- if (options?.alias === void 0) continue;
163
+ if (options.alias === void 0) continue;
136
164
  for (const alias of aliasList(options.alias)) {
137
165
  if (alias.length !== 1) throw new TypeError(`Alias \`${alias}\` must be exactly one character`);
138
166
  if (usedAliases.has(alias)) throw new TypeError(`Duplicate alias \`${alias}\``);
@@ -140,228 +168,12 @@ function defineCommand(def) {
140
168
  }
141
169
  }
142
170
  for (const key of Object.keys(shape)) {
143
- if (key === "help") throw new TypeError("Option `help` conflicts with the builtin --help");
144
- if (key !== "_" && /^no[-A-Z]/.test(key)) throw new TypeError(`Option \`${key}\` conflicts with --no- negation`);
171
+ if (key === "help" || key === "version") throw new TypeError(`Option \`${key}\` conflicts with the builtin --${key}`);
172
+ if (key !== "positionals" && /^no[-A-Z]/.test(key)) throw new TypeError(`Option \`${key}\` conflicts with --no- negation`);
145
173
  }
146
174
  return def;
147
175
  }
148
176
  //#endregion
149
- //#region src/errors.ts
150
- var HackshUsageError = class extends Error {
151
- command;
152
- constructor(message, command, options) {
153
- super(message, options);
154
- this.name = "HackshUsageError";
155
- this.command = command;
156
- }
157
- };
158
- var HackshValidationError = class extends HackshUsageError {
159
- issues;
160
- constructor(command, issues) {
161
- super(`Invalid arguments:\n${issues.map(({ arg, message }) => ` ${arg}: ${message}`).join("\n")}`, command);
162
- this.name = "HackshValidationError";
163
- this.issues = issues;
164
- }
165
- };
166
- /** An input request that could not be completed safely. */
167
- var HackshPromptError = class extends Error {
168
- reason;
169
- prompt;
170
- constructor(reason, prompt, message, options) {
171
- super(message, options);
172
- this.name = "HackshPromptError";
173
- this.reason = reason;
174
- this.prompt = prompt;
175
- }
176
- };
177
- /** An external command that did not succeed; `result` is not enumerable so error summaries omit child output. */
178
- var HackshExecError = class extends Error {
179
- #result;
180
- constructor(message, result, options) {
181
- super(message, options);
182
- this.name = "HackshExecError";
183
- this.#result = result;
184
- }
185
- get result() {
186
- return this.#result;
187
- }
188
- };
189
- /** A requested process exit status; `runMain` exits with it and reports only a non-empty message. */
190
- var HackshExitError = class extends Error {
191
- exitCode;
192
- constructor(exitCode, message, options) {
193
- if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) throw new TypeError("Exit code must be an integer from 0 to 255");
194
- super(message, options);
195
- this.name = "HackshExitError";
196
- this.exitCode = exitCode;
197
- }
198
- };
199
- function safeString$1(value, fallback) {
200
- try {
201
- return String(value);
202
- } catch {
203
- return fallback;
204
- }
205
- }
206
- function readProperty(value, key) {
207
- try {
208
- return Reflect.get(value, key);
209
- } catch {
210
- return "[Unserializable]";
211
- }
212
- }
213
- function enumerableKeys(value) {
214
- try {
215
- return Object.keys(value);
216
- } catch {
217
- return [];
218
- }
219
- }
220
- function hasToJSON(value) {
221
- try {
222
- return typeof Reflect.get(value, "toJSON") === "function";
223
- } catch {
224
- return false;
225
- }
226
- }
227
- function isPlainObject(value) {
228
- try {
229
- const prototype = Object.getPrototypeOf(value);
230
- return prototype === Object.prototype || prototype === null;
231
- } catch {
232
- return false;
233
- }
234
- }
235
- function summarizeIssue({ arg, message, issue }, seen) {
236
- if (!issue) return {
237
- arg,
238
- message
239
- };
240
- const details = {};
241
- for (const key of enumerableKeys(issue)) {
242
- if ([
243
- "code",
244
- "path",
245
- "message",
246
- "inst"
247
- ].includes(key)) continue;
248
- details[key] = summarizeErrors(readProperty(issue, key), seen);
249
- }
250
- const code = readProperty(issue, "code");
251
- const path = readProperty(issue, "path");
252
- return {
253
- arg,
254
- message,
255
- ...typeof code === "string" ? { code } : {},
256
- ...Array.isArray(path) ? { path } : {},
257
- ...Object.keys(details).length === 0 ? {} : { details }
258
- };
259
- }
260
- function errorKind(error) {
261
- if (error instanceof HackshPromptError) return "prompt";
262
- if (error instanceof HackshValidationError) return "validation";
263
- if (error instanceof HackshUsageError) return "usage";
264
- return "error";
265
- }
266
- function summarizeErrorWithSeen(error, seen) {
267
- const summary = {
268
- kind: errorKind(error),
269
- name: safeString$1(readProperty(error, "name"), "Error"),
270
- message: safeString$1(readProperty(error, "message"), "[Unserializable error message]")
271
- };
272
- seen.set(error, summary);
273
- const stack = readProperty(error, "stack");
274
- const cause = readProperty(error, "cause");
275
- if (typeof stack === "string") summary.stack = stack;
276
- if (cause !== void 0) summary.cause = summarizeErrors(cause, seen);
277
- if (error instanceof HackshValidationError) summary.issues = error.issues.map((issue) => summarizeIssue(issue, seen));
278
- const reserved = /* @__PURE__ */ new Set([
279
- "name",
280
- "message",
281
- "stack",
282
- "cause",
283
- "command",
284
- "issues"
285
- ]);
286
- const properties = {};
287
- for (const key of enumerableKeys(error)) {
288
- if (reserved.has(key)) continue;
289
- properties[key] = summarizeErrors(readProperty(error, key), seen);
290
- }
291
- if (Object.keys(properties).length > 0) summary.properties = properties;
292
- return summary;
293
- }
294
- /** Recursively replace Error values with command- and schema-free summaries. */
295
- function summarizeErrors(value, seen = /* @__PURE__ */ new WeakMap()) {
296
- if (typeof value !== "object" || value === null) return value;
297
- const existing = seen.get(value);
298
- if (existing !== void 0) return existing;
299
- if (value instanceof Error) return summarizeErrorWithSeen(value, seen);
300
- if (Array.isArray(value)) {
301
- const result = [];
302
- seen.set(value, result);
303
- let changed = false;
304
- for (const item of value) {
305
- const summarized = summarizeErrors(item, seen);
306
- result.push(summarized);
307
- changed ||= summarized !== item;
308
- }
309
- if (changed) return result;
310
- seen.set(value, value);
311
- return value;
312
- }
313
- if (value instanceof Map) {
314
- const result = /* @__PURE__ */ new Map();
315
- seen.set(value, result);
316
- let changed = false;
317
- for (const [key, item] of value) {
318
- const summarizedKey = summarizeErrors(key, seen);
319
- const summarizedItem = summarizeErrors(item, seen);
320
- result.set(summarizedKey, summarizedItem);
321
- changed ||= summarizedKey !== key || summarizedItem !== item;
322
- }
323
- if (changed) return result;
324
- seen.set(value, value);
325
- return value;
326
- }
327
- if (value instanceof Set) {
328
- const result = /* @__PURE__ */ new Set();
329
- seen.set(value, result);
330
- let changed = false;
331
- for (const item of value) {
332
- const summarized = summarizeErrors(item, seen);
333
- result.add(summarized);
334
- changed ||= summarized !== item;
335
- }
336
- if (changed) return result;
337
- seen.set(value, value);
338
- return value;
339
- }
340
- if (ArrayBuffer.isView(value) || hasToJSON(value) || !isPlainObject(value)) return value;
341
- const result = {};
342
- seen.set(value, result);
343
- let changed = false;
344
- for (const key of enumerableKeys(value)) {
345
- const original = readProperty(value, key);
346
- const summarized = summarizeErrors(original, seen);
347
- result[key] = summarized;
348
- changed ||= summarized !== original;
349
- }
350
- if (changed) return result;
351
- seen.set(value, value);
352
- return value;
353
- }
354
- /** Convert any thrown value into a canonical error description. */
355
- function summarizeError(error) {
356
- if (error instanceof Error) return summarizeErrorWithSeen(error, /* @__PURE__ */ new WeakMap());
357
- return {
358
- kind: "thrown",
359
- name: "NonErrorThrown",
360
- message: safeString$1(error, "[Unserializable thrown value]"),
361
- value: summarizeErrors(error)
362
- };
363
- }
364
- //#endregion
365
177
  //#region src/environment.ts
366
178
  const commandPaths = /* @__PURE__ */ new WeakMap();
367
179
  function registerCommandPath(command, path) {
@@ -378,9 +190,9 @@ function optionEnvNames(command, path) {
378
190
  const names = /* @__PURE__ */ new Map();
379
191
  if (!command.args) return names;
380
192
  for (const [key, schema] of Object.entries(schemaShape(command.args))) {
381
- if (key === "_") continue;
193
+ if (key === "positionals") continue;
382
194
  if (isArrayInput(schema) || isObjectInput(schema)) continue;
383
- const configured = command.argOptions?.[key]?.env;
195
+ const configured = readArgInfo(schema).env;
384
196
  if (configured === false) continue;
385
197
  if (typeof configured === "string") {
386
198
  names.set(key, configured);
@@ -420,13 +232,13 @@ function optionModel(command) {
420
232
  const parserOptions = {};
421
233
  const allAliases = /* @__PURE__ */ new Set();
422
234
  if (command.args) for (const [key, schema] of Object.entries(schemaShape(command.args))) {
423
- if (key === "_") continue;
424
- const alias = command.argOptions?.[key]?.alias;
425
- const aliases = alias ? aliasList(alias) : [];
235
+ if (key === "positionals") continue;
236
+ const info = readArgInfo(schema);
237
+ const aliases = info.alias === void 0 ? [] : aliasList(info.alias);
426
238
  const definition = {
427
239
  key,
428
240
  kebabName: kebabCase(key),
429
- takesValue: readArgInfo(schema).takesValue
241
+ takesValue: info.takesValue
430
242
  };
431
243
  const type = definition.takesValue ? "string" : "boolean";
432
244
  for (const name of /* @__PURE__ */ new Set([key, definition.kebabName])) {
@@ -454,7 +266,7 @@ function commandPositionals(command) {
454
266
  function displayName(command, path) {
455
267
  const [first, index] = path;
456
268
  if (typeof first !== "string") return "arguments";
457
- if (first !== "_") return `--${kebabCase(first)}`;
269
+ if (first !== "positionals") return `--${kebabCase(first)}`;
458
270
  const positionals = commandPositionals(command);
459
271
  if (!positionals || typeof index !== "number") return "arguments";
460
272
  const item = positionals.items[index];
@@ -606,7 +418,7 @@ async function parseCommandArgs(command, rawArgs, tokenized = tokenizeCommandArg
606
418
  const input = {};
607
419
  const positionals = commandPositionals(command);
608
420
  const positionalValues = [];
609
- if (positionals) input["_"] = positionalValues;
421
+ if (positionals) input[POSITIONALS_KEY] = positionalValues;
610
422
  const extraArgs = [];
611
423
  for (const token of tokenized.tokens) if (token.kind === "known-option") input[token.definition.key] = token.value;
612
424
  else if (token.kind === "unknown-option") extraArgs.push(token.value);
@@ -679,803 +491,91 @@ var TerminalLock = class {
679
491
  }
680
492
  };
681
493
  //#endregion
682
- //#region src/exec.ts
683
- const OPTION_KEYS = /* @__PURE__ */ new Set([
684
- "cwd",
685
- "env",
686
- "input",
687
- "reject",
688
- "timeout",
689
- "signal",
690
- "maxBuffer",
691
- "forceKillAfterDelay",
692
- "stdin",
693
- "stdout",
694
- "stderr",
695
- "interactive",
696
- "display",
697
- "redact"
698
- ]);
699
- const MAX_DELAY = 2147483647;
700
- const REFUSAL_MESSAGE = {
701
- "json-output": "Command cannot use the terminal in JSON output mode",
702
- "non-interactive": "Command requires an interactive terminal",
703
- overlapping: "Command cannot use the terminal while it is in use"
704
- };
705
- function isRecord(value) {
706
- return typeof value === "object" && value !== null && !Array.isArray(value);
707
- }
708
- function assertNoNul(value, label) {
709
- if (value.includes("\0")) throw new TypeError(`${label} must not contain NUL characters`);
710
- }
711
- function readInteger(value, label, minimum, maximum) {
712
- if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) throw new TypeError(`${label} must be an integer from ${minimum} to ${maximum}`);
713
- return value;
714
- }
715
- function readEnum(value, label, allowed) {
716
- if (typeof value === "string" && allowed.includes(value)) return value;
717
- throw new TypeError(`Exec option \`${label}\` must be ${allowed.map((item) => JSON.stringify(item)).join(" or ")}`);
718
- }
719
- function readBoolean(value, label) {
720
- if (typeof value !== "boolean") throw new TypeError(`Exec option \`${label}\` must be a boolean`);
721
- return value;
722
- }
723
- function readRedact(value) {
724
- if (!Array.isArray(value)) throw new TypeError("Exec option `redact` must be an array");
725
- for (const item of value) {
726
- if (typeof item !== "string" || item === "") throw new TypeError("Exec option `redact` must contain only non-empty strings");
727
- assertNoNul(item, "Exec option `redact`");
728
- }
729
- return value;
730
- }
731
- function mergeEnvironment(base, overrides) {
732
- if (overrides === void 0 && base === void 0) return void 0;
733
- if (overrides !== void 0 && !isRecord(overrides)) throw new TypeError("Exec option `env` must be an object");
734
- const merged = {};
735
- for (const [key, value] of Object.entries(base ?? process.env)) if (value !== void 0) merged[key] = value;
736
- for (const [key, value] of Object.entries(overrides ?? {})) {
737
- if (key === "" || key.includes("=") || key.includes("\0")) throw new TypeError(`Invalid environment variable name ${JSON.stringify(key)}`);
738
- if (value === void 0) {
739
- delete merged[key];
740
- continue;
741
- }
742
- if (typeof value !== "string") throw new TypeError(`Environment variable ${key} must be a string or undefined`);
743
- assertNoNul(value, `Environment variable ${key}`);
744
- merged[key] = value;
745
- }
746
- return merged;
747
- }
748
- function redactValue(value, secrets) {
749
- let redacted = value;
750
- for (const secret of [...secrets].sort((left, right) => right.length - left.length)) redacted = redacted.split(secret).join("***");
751
- return redacted;
752
- }
753
- function redactCommand(command, secrets) {
754
- if (secrets.length === 0) return command;
755
- return {
756
- file: redactValue(command.file, secrets),
757
- args: command.args.map((arg) => redactValue(arg, secrets)),
758
- cwd: command.cwd === void 0 ? void 0 : redactValue(command.cwd, secrets)
759
- };
760
- }
761
- function redactError(error, secrets) {
762
- const source = error;
763
- const redacted = new Error(redactValue(error.message, secrets));
764
- if (typeof error.stack === "string") redacted.stack = redactValue(error.stack, secrets);
765
- if (source.code !== void 0) redacted.code = source.code;
766
- if (source.errno !== void 0) redacted.errno = source.errno;
767
- if (typeof source.syscall === "string") redacted.syscall = redactValue(source.syscall, secrets);
768
- if (typeof source.path === "string") redacted.path = redactValue(source.path, secrets);
769
- if (Array.isArray(source.spawnargs)) redacted.spawnargs = source.spawnargs.map((arg) => typeof arg === "string" ? redactValue(arg, secrets) : arg);
770
- return redacted;
771
- }
772
- function observedError(plan, error) {
773
- return plan.redact.length === 0 ? error : redactError(error, plan.redact);
774
- }
775
- function parsePlan(file, args, options, context) {
776
- if (typeof file !== "string" || file === "") throw new TypeError("Exec file must be a non-empty string");
777
- assertNoNul(file, "Exec file");
778
- const argv = args === void 0 ? [] : args;
779
- if (!Array.isArray(argv) || !argv.every((arg) => typeof arg === "string")) throw new TypeError("Exec args must be an array of strings");
780
- for (const arg of argv) assertNoNul(arg, "Exec args");
781
- const raw = options === void 0 ? {} : options;
782
- if (!isRecord(raw)) throw new TypeError("Exec options must be an object");
783
- for (const key of Object.keys(raw)) if (!OPTION_KEYS.has(key)) throw new TypeError(`Unknown exec option \`${key}\``);
784
- const { cwd, input, reject, timeout, signal, maxBuffer, forceKillAfterDelay } = raw;
785
- if (cwd !== void 0) {
786
- if (typeof cwd !== "string" || cwd === "") throw new TypeError("Exec option `cwd` must be a non-empty string");
787
- assertNoNul(cwd, "Exec option `cwd`");
788
- }
789
- if (input !== void 0 && typeof input !== "string") throw new TypeError("Exec option `input` must be a string");
790
- if (reject !== void 0 && typeof reject !== "boolean") throw new TypeError("Exec option `reject` must be a boolean");
791
- if (signal !== void 0 && !(signal instanceof AbortSignal)) throw new TypeError("Exec option `signal` must be an AbortSignal");
792
- const stdin = raw.stdin === void 0 ? "pipe" : readEnum(raw.stdin, "stdin", ["pipe", "inherit"]);
793
- const stdout = raw.stdout === void 0 ? "pipe" : readEnum(raw.stdout, "stdout", ["pipe", "inherit"]);
794
- const stderr = raw.stderr === void 0 ? "pipe" : readEnum(raw.stderr, "stderr", ["pipe", "inherit"]);
795
- if (input !== void 0 && stdin === "inherit") throw new TypeError("Exec option `input` cannot be combined with stdin inherit");
796
- const command = {
797
- file,
798
- args: [...argv],
799
- cwd
800
- };
801
- const redact = raw.redact === void 0 ? [] : readRedact(raw.redact);
802
- return {
803
- command,
804
- reported: redactCommand(command, redact),
805
- env: mergeEnvironment(context.env, raw.env),
806
- input,
807
- reject: reject ?? true,
808
- timeout: timeout === void 0 ? void 0 : readInteger(timeout, "Exec option `timeout`", 1, MAX_DELAY),
809
- signal: signal === void 0 ? context.signal : AbortSignal.any([context.signal, signal]),
810
- maxBuffer: maxBuffer === void 0 ? 1e8 : readInteger(maxBuffer, "Exec option `maxBuffer`", 1, Number.MAX_SAFE_INTEGER),
811
- forceKillAfterDelay: forceKillAfterDelay === void 0 ? 5e3 : readInteger(forceKillAfterDelay, "Exec option `forceKillAfterDelay`", 0, MAX_DELAY),
812
- stdin,
813
- stdout,
814
- stderr,
815
- interactive: raw.interactive === void 0 ? false : readBoolean(raw.interactive, "interactive"),
816
- display: raw.display === void 0 ? true : readBoolean(raw.display, "display"),
817
- redact
818
- };
819
- }
820
- function stripFinalNewline(text) {
821
- if (text.endsWith("\r\n")) return text.slice(0, -2);
822
- if (text.endsWith("\n")) return text.slice(0, -1);
823
- return text;
824
- }
825
- function collect(stream, limit, onLimit) {
826
- const chunks = [];
827
- let length = 0;
828
- let exceeded = false;
829
- stream.on("data", (chunk) => {
830
- if (exceeded) return;
831
- const remaining = limit - length;
832
- if (chunk.length <= remaining) {
833
- chunks.push(chunk);
834
- length += chunk.length;
835
- return;
494
+ //#region src/logger.ts
495
+ function interpolate(message, properties) {
496
+ return message.replace(/\{([^{}]+)\}/g, (placeholder, key) => {
497
+ if (!Object.hasOwn(properties, key)) return placeholder;
498
+ try {
499
+ return String(properties[key]);
500
+ } catch {
501
+ return placeholder;
836
502
  }
837
- chunks.push(chunk.subarray(0, remaining));
838
- length = limit;
839
- exceeded = true;
840
- onLimit();
841
503
  });
842
- return () => stripFinalNewline(Buffer.concat(chunks).toString("utf8"));
843
- }
844
- function toResult(command, outcome, stdout, stderr) {
845
- const failed = {
846
- status: "failed",
847
- command,
848
- stdout,
849
- stderr
850
- };
851
- if (outcome.cause) return {
852
- ...failed,
853
- reason: outcome.cause
854
- };
855
- if (outcome.spawnError) return {
856
- ...failed,
857
- reason: "spawn",
858
- error: outcome.spawnError
859
- };
860
- const { code, signal } = outcome.exit ?? {
861
- code: null,
862
- signal: null
863
- };
864
- if (signal !== null) return {
865
- ...failed,
866
- reason: "signal",
867
- signal
868
- };
869
- if (code !== 0) return {
870
- ...failed,
871
- reason: "exit-code",
872
- exitCode: code ?? 1
873
- };
874
- return {
875
- status: "succeeded",
876
- command,
877
- stdout,
878
- stderr,
879
- exitCode: 0
880
- };
881
- }
882
- function isSpawnErrno(error) {
883
- return error instanceof Error && "syscall" in error;
884
- }
885
- function commandStdio(plan) {
886
- return {
887
- stdin: plan.stdin === "inherit" ? "inherit" : plan.input === void 0 ? "ignore" : "input",
888
- stdout: plan.stdout,
889
- stderr: plan.stderr
890
- };
891
504
  }
892
- function usesTerminal(plan, stdio) {
893
- return plan.interactive || stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
894
- }
895
- function startChild(plan) {
896
- const stdio = commandStdio(plan);
897
- try {
898
- return spawn(plan.command.file, plan.command.args, {
899
- cwd: plan.command.cwd,
900
- env: plan.env,
901
- shell: false,
902
- windowsHide: true,
903
- stdio: [
904
- stdio.stdin === "inherit" ? "inherit" : stdio.stdin === "input" ? "pipe" : "ignore",
905
- stdio.stdout,
906
- stdio.stderr
907
- ]
908
- });
909
- } catch (error) {
910
- if (isSpawnErrno(error)) return error;
911
- throw error;
912
- }
913
- }
914
- function runPlan(plan) {
915
- const outcome = {
916
- cause: void 0,
917
- spawnError: void 0,
918
- exit: void 0
919
- };
920
- if (plan.signal.aborted) {
921
- outcome.cause = "aborted";
922
- return Promise.resolve(toResult(plan.reported, outcome, "", ""));
923
- }
924
- const child = startChild(plan);
925
- if (child instanceof Error) {
926
- outcome.spawnError = observedError(plan, child);
927
- return Promise.resolve(toResult(plan.reported, outcome, "", ""));
928
- }
929
- return new Promise((resolve) => {
930
- const stdout = child.stdout;
931
- const stderr = child.stderr;
932
- let settled = false;
933
- let killTimer;
934
- const settle = () => {
935
- if (settled) return;
936
- settled = true;
937
- clearTimeout(timeoutTimer);
938
- clearTimeout(killTimer);
939
- plan.signal.removeEventListener("abort", onAbort);
940
- resolve(toResult(plan.reported, outcome, readStdout(), readStderr()));
941
- };
942
- const release = () => {
943
- stdout?.destroy();
944
- stderr?.destroy();
945
- settle();
505
+ function createBoundLogger(printer, category, boundProperties) {
506
+ function emit(level, kind, input, properties = {}) {
507
+ const rawMessage = input instanceof Error ? input.message : input;
508
+ const mergedProperties = {
509
+ ...boundProperties,
510
+ ...properties
946
511
  };
947
- const terminate = (cause) => {
948
- if (settled || outcome.cause) return;
949
- outcome.cause = cause;
950
- if (outcome.exit) {
951
- release();
952
- return;
512
+ if (input instanceof Error) mergedProperties.error = input;
513
+ const summarizedProperties = summarizeErrors(mergedProperties);
514
+ printer.print({
515
+ type: "log",
516
+ record: {
517
+ level,
518
+ kind,
519
+ category,
520
+ message: interpolate(rawMessage, mergedProperties),
521
+ rawMessage,
522
+ properties: summarizedProperties,
523
+ timestamp: Date.now()
953
524
  }
954
- child.kill("SIGTERM");
955
- killTimer = setTimeout(() => child.kill("SIGKILL"), plan.forceKillAfterDelay);
956
- };
957
- const onAbort = () => terminate("aborted");
958
- const readStdout = stdout ? collect(stdout, plan.maxBuffer, () => terminate("output-limit")) : () => "";
959
- const readStderr = stderr ? collect(stderr, plan.maxBuffer, () => terminate("output-limit")) : () => "";
960
- const timeoutTimer = plan.timeout === void 0 ? void 0 : setTimeout(() => terminate("timeout"), plan.timeout);
961
- plan.signal.addEventListener("abort", onAbort, { once: true });
962
- child.on("error", (error) => {
963
- if (child.pid !== void 0) return;
964
- outcome.spawnError = observedError(plan, error);
965
- release();
966
525
  });
967
- child.on("exit", (code, signal) => {
968
- outcome.exit = {
969
- code,
970
- signal
971
- };
972
- clearTimeout(killTimer);
973
- if (outcome.cause) release();
974
- });
975
- child.on("close", settle);
976
- if (child.stdin) {
977
- child.stdin.on("error", () => void 0);
978
- child.stdin.end(plan.input);
979
- }
980
- });
981
- }
982
- function formatArgument(value) {
983
- return value === "" || /[\s"'\\$`;|&<>*?()[\]{}~#!]/.test(value) ? JSON.stringify(value) : value;
984
- }
985
- function formatExecCommand(command) {
986
- return [command.file, ...command.args].map(formatArgument).join(" ");
987
- }
988
- function failureMessage(plan, result) {
989
- const display = formatExecCommand(plan.reported);
990
- switch (result.reason) {
991
- case "exit-code": return `Command failed with exit code ${result.exitCode}: ${display}`;
992
- case "signal": return `Command was terminated by ${result.signal}: ${display}`;
993
- case "spawn": return `Command could not be started (${result.error.code ?? result.error.message}): ${display}`;
994
- case "timeout": return `Command timed out after ${plan.timeout} ms: ${display}`;
995
- case "aborted": return `Command was aborted: ${display}`;
996
- case "output-limit": return `Command output exceeded ${plan.maxBuffer} bytes: ${display}`;
997
- case "json-output":
998
- case "non-interactive":
999
- case "overlapping": return `${REFUSAL_MESSAGE[result.reason]}: ${display}`;
1000
526
  }
1001
- }
1002
- function emptyFailure(plan, reason) {
1003
527
  return {
1004
- status: "failed",
1005
- command: plan.reported,
1006
- stdout: "",
1007
- stderr: "",
1008
- reason
528
+ category,
529
+ trace: (message, properties) => emit("trace", "message", message, properties),
530
+ debug: (message, properties) => emit("debug", "message", message, properties),
531
+ info: (message, properties) => emit("info", "message", message, properties),
532
+ warn: (message, properties) => emit("warning", "message", message, properties),
533
+ error: (message, properties) => emit("error", "message", message, properties),
534
+ fatal: (message, properties) => emit("fatal", "message", message, properties),
535
+ step: (message, properties) => emit("info", "step", message, properties),
536
+ success: (message, properties) => emit("info", "success", message, properties),
537
+ with: (properties) => createBoundLogger(printer, category, {
538
+ ...boundProperties,
539
+ ...properties
540
+ }),
541
+ getChild: (child) => createBoundLogger(printer, [...category, child], boundProperties)
1009
542
  };
1010
543
  }
1011
- function complete(plan, result) {
1012
- if (result.status === "failed" && plan.reject) throw new HackshExecError(failureMessage(plan, result), result, result.reason === "spawn" ? { cause: result.error } : void 0);
1013
- return result;
1014
- }
1015
- function blockedReason(plan, context, stdio) {
1016
- const inherited = stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
1017
- if (context.mode.format === "json" && inherited) return "json-output";
1018
- if (plan.interactive && !context.mode.interactive) return "non-interactive";
1019
- }
1020
- async function execute(file, args, options, context) {
1021
- const plan = parsePlan(file, args, options, context);
1022
- if (plan.signal.aborted) return complete(plan, emptyFailure(plan, "aborted"));
1023
- const stdio = commandStdio(plan);
1024
- const blocked = blockedReason(plan, context, stdio);
1025
- if (blocked) return complete(plan, emptyFailure(plan, blocked));
1026
- const holdTerminal = usesTerminal(plan, stdio);
1027
- if (holdTerminal && !context.terminal.acquire("exec")) return complete(plan, emptyFailure(plan, "overlapping"));
1028
- try {
1029
- if (plan.display) {
1030
- const { file, args, cwd } = plan.reported;
1031
- await context.print({
1032
- type: "exec",
1033
- phase: "start",
1034
- command: cwd === void 0 ? {
1035
- file,
1036
- args
1037
- } : {
1038
- file,
1039
- args,
1040
- cwd
1041
- },
1042
- stdio,
1043
- interactive: plan.interactive
1044
- });
1045
- }
1046
- return complete(plan, await runPlan(plan));
1047
- } finally {
1048
- if (holdTerminal) context.terminal.release();
1049
- }
1050
- }
1051
- function createExec(context) {
1052
- return ((file, args, options) => execute(file, args, options, context));
544
+ /** Create the logger for one command invocation. */
545
+ function createLogger(printer, category) {
546
+ return createBoundLogger(printer, [...category], {});
1053
547
  }
1054
548
  //#endregion
1055
- //#region src/serialization.ts
1056
- function safeString(value) {
1057
- try {
1058
- return String(value);
1059
- } catch {
1060
- return "[Unserializable]";
1061
- }
1062
- }
1063
- function normalizeProperty(value, key, ancestors) {
1064
- try {
1065
- return normalizeJson(Reflect.get(value, key), ancestors);
1066
- } catch {
1067
- return "[Unserializable]";
1068
- }
1069
- }
1070
- function normalizedKeys(value) {
549
+ //#region src/package-version.ts
550
+ async function findPackageVersion(entry) {
551
+ if (!entry) return void 0;
552
+ let directory;
1071
553
  try {
1072
- return Object.keys(value);
554
+ directory = dirname(await realpath(entry));
1073
555
  } catch {
1074
556
  return;
1075
557
  }
1076
- }
1077
- const noToJson = Symbol("noToJson");
1078
- function normalizedToJson(value, ancestors) {
1079
- let toJSON;
1080
- try {
1081
- toJSON = Reflect.get(value, "toJSON");
1082
- } catch {
1083
- return noToJson;
1084
- }
1085
- if (typeof toJSON !== "function") return noToJson;
1086
- try {
1087
- const result = Reflect.apply(toJSON, value, []);
1088
- return result === value ? noToJson : normalizeJson(result, ancestors);
1089
- } catch {
1090
- return noToJson;
1091
- }
1092
- }
1093
- function isArrayBufferView(value) {
1094
- try {
1095
- return ArrayBuffer.isView(value);
1096
- } catch {
1097
- return false;
1098
- }
1099
- }
1100
- /** Normalize arbitrary event data into values accepted by JSON.stringify. */
1101
- function normalizeJson(value, ancestors = /* @__PURE__ */ new Set()) {
1102
- switch (typeof value) {
1103
- case "string": return stripVTControlCharacters(value);
1104
- case "boolean": return value;
1105
- case "number": return Number.isFinite(value) ? value : null;
1106
- case "bigint": return value.toString();
1107
- case "undefined": return "[undefined]";
1108
- case "symbol": return `[symbol:${stripVTControlCharacters(value.description ?? "")}]`;
1109
- case "function": return `[function:${stripVTControlCharacters(value.name || "anonymous")}]`;
1110
- }
1111
- if (value === null) return null;
1112
- if (ancestors.has(value)) return "[Circular]";
1113
- const nextAncestors = new Set(ancestors).add(value);
1114
- if (value instanceof Error) return normalizeJson(summarizeError(value), nextAncestors);
1115
- const jsonValue = normalizedToJson(value, nextAncestors);
1116
- if (jsonValue !== noToJson) return jsonValue;
1117
- if (isArrayBufferView(value)) {
1118
- if (value instanceof DataView) return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
1119
- try {
1120
- return Array.from(value, (item) => normalizeJson(item, nextAncestors));
1121
- } catch {
1122
- return "[Unserializable]";
1123
- }
1124
- }
1125
- if (value instanceof ArrayBuffer) return Array.from(new Uint8Array(value));
1126
- if (Array.isArray(value)) return Array.from({ length: value.length }, (_, index) => normalizeProperty(value, index, nextAncestors));
1127
- if (value instanceof Map) try {
1128
- return [...value].map(([key, item]) => [normalizeJson(key, nextAncestors), normalizeJson(item, nextAncestors)]);
1129
- } catch {
1130
- return "[Unserializable]";
1131
- }
1132
- if (value instanceof Set) try {
1133
- return [...value].map((item) => normalizeJson(item, nextAncestors));
1134
- } catch {
1135
- return "[Unserializable]";
1136
- }
1137
- const keys = normalizedKeys(value);
1138
- if (!keys) return safeString(value);
1139
- const result = {};
1140
- for (const key of keys) result[stripVTControlCharacters(key)] = normalizeProperty(value, key, nextAncestors);
1141
- return result;
1142
- }
1143
- //#endregion
1144
- //#region src/printer.ts
1145
- function fallbackValue(value) {
1146
- try {
1147
- return String(value);
1148
- } catch {
1149
- return "[Unserializable]";
558
+ for (;;) {
559
+ const version = await readPackageVersion(join(directory, "package.json"));
560
+ if (version !== void 0) return version;
561
+ const parent = dirname(directory);
562
+ if (parent === directory) return void 0;
563
+ directory = parent;
1150
564
  }
1151
565
  }
1152
- function validatePreference(value, source) {
1153
- if (value === void 0) return void 0;
1154
- if (value === "auto" || value === "pretty" || value === "json") return value;
1155
- throw new TypeError(`Invalid ${source} value ${fallbackValue(value)}; expected auto, pretty, or json`);
1156
- }
1157
- function readEnvironment(env, key) {
566
+ async function readPackageVersion(path) {
567
+ let text;
1158
568
  try {
1159
- return env[key];
569
+ text = await readFile(path, "utf8");
1160
570
  } catch {
1161
571
  return;
1162
572
  }
1163
- }
1164
- /** Resolve output behavior once, before command routing or argument parsing. */
1165
- function resolveOutputMode(options = {}) {
1166
- const env = options.env ?? process.env;
1167
- const configured = validatePreference(options.output, "output");
1168
- const requested = validatePreference(readEnvironment(env, "HACKSH_OUTPUT"), "HACKSH_OUTPUT") ?? configured ?? "auto";
1169
- const ci = Boolean(readEnvironment(env, "CI"));
1170
- const format = requested === "auto" ? ci ? "json" : "pretty" : requested;
1171
- const inputIsTTY = options.inputIsTTY ?? process.stdin.isTTY === true;
1172
- const outputIsTTY = options.outputIsTTY ?? process.stdout.isTTY === true;
1173
- return {
1174
- format,
1175
- interactive: format === "pretty" && !ci && inputIsTTY && outputIsTTY,
1176
- color: format === "pretty" && outputIsTTY && !readEnvironment(env, "NO_COLOR")
1177
- };
1178
- }
1179
- function isErrorLevel(level) {
1180
- return level === "warning" || level === "error" || level === "fatal";
1181
- }
1182
- function eventStream(event) {
1183
- if (event.type === "error" || event.type === "exec") return "stderr";
1184
- if (event.type === "usage") return event.stream;
1185
- if (event.type === "log" && isErrorLevel(event.record.level)) return "stderr";
1186
- return "stdout";
1187
- }
1188
- function formatTextEvent(event) {
1189
- switch (event.type) {
1190
- case "usage": return event.text;
1191
- case "version": return event.version;
1192
- case "error": return event.error.kind === "usage" || event.error.kind === "validation" ? event.error.message : `Error: ${event.error.message}`;
1193
- case "log": return event.record.message;
1194
- case "prompt": return event.prompt.message;
1195
- case "exec": return `$ ${formatExecCommand(event.command)}`;
1196
- }
1197
- }
1198
- /** Undecorated console compatibility adapter; automatic output uses the selected format printer. */
1199
- const consolePrinter = { print(event) {
1200
- const text = formatTextEvent(event);
1201
- return eventStream(event) === "stdout" ? console.log(text) : console.error(text);
1202
- } };
1203
- /** Create an undecorated stream-backed compatibility adapter. */
1204
- function createStreamPrinter(options) {
1205
- return { print(event) {
1206
- (eventStream(event) === "stdout" ? options.stdout : options.stderr).write(`${formatTextEvent(event)}\n`);
1207
- } };
1208
- }
1209
- /** Create a one-semantic-event-per-line JSON printer. */
1210
- function createJsonPrinter(options = {}) {
1211
- const stdout = options.stdout ?? process.stdout;
1212
- const stderr = options.stderr ?? process.stderr;
1213
- return { print(event) {
1214
- let serialized;
1215
- try {
1216
- serialized = JSON.stringify(normalizeJson(event));
1217
- } catch {
1218
- serialized = JSON.stringify({
1219
- type: event.type,
1220
- serializationError: "Event data could not be serialized"
1221
- });
1222
- }
1223
- const line = `${serialized}\n`;
1224
- (eventStream(event) === "stdout" ? stdout : stderr).write(line);
1225
- } };
1226
- }
1227
- /** Serialize printer calls and retain failures until the invocation drains its output. */
1228
- function createPrintQueue(target) {
1229
- let pending;
1230
- let failure;
1231
- let failed = false;
1232
- function recordFailure(error) {
1233
- if (!failed) failure = error;
1234
- failed = true;
1235
- }
1236
- function track(execution, retainFailure) {
1237
- const barrier = execution.catch((error) => {
1238
- if (retainFailure) recordFailure(error);
1239
- });
1240
- pending = barrier;
1241
- barrier.then(() => {
1242
- if (pending === barrier) pending = void 0;
1243
- });
1244
- return retainFailure ? barrier : execution;
1245
- }
1246
- function enqueue(event, retainFailure) {
1247
- if (pending) return track(pending.then(() => target.print(event)), retainFailure);
1248
- try {
1249
- const result = target.print(event);
1250
- if (result) return track(Promise.resolve(result), retainFailure);
1251
- } catch (error) {
1252
- if (retainFailure) recordFailure(error);
1253
- else return Promise.reject(error);
1254
- }
1255
- }
1256
- return {
1257
- printer: { print(event) {
1258
- return enqueue(event, true);
1259
- } },
1260
- async printChecked(event) {
1261
- await enqueue(event, false);
1262
- },
1263
- async drain() {
1264
- await pending;
1265
- if (failed) throw failure;
1266
- }
1267
- };
1268
- }
1269
- //#endregion
1270
- //#region src/pretty.ts
1271
- const unicodeSymbols = {
1272
- bar: "│",
1273
- marks: {
1274
- trace: "·",
1275
- debug: "◆",
1276
- info: "●",
1277
- warning: "▲",
1278
- error: "■",
1279
- fatal: "✖",
1280
- step: "◇",
1281
- success: "✔",
1282
- usage: "◆",
1283
- version: "●",
1284
- errorEvent: "■",
1285
- prompt: "?",
1286
- exec: "$"
1287
- }
1288
- };
1289
- const asciiSymbols = {
1290
- bar: "|",
1291
- marks: {
1292
- trace: ".",
1293
- debug: ":",
1294
- info: "i",
1295
- warning: "!",
1296
- error: "x",
1297
- fatal: "X",
1298
- step: ">",
1299
- success: "+",
1300
- usage: "?",
1301
- version: "i",
1302
- errorEvent: "x",
1303
- prompt: "?",
1304
- exec: "$"
1305
- }
1306
- };
1307
- const markStyles = {
1308
- trace: "gray",
1309
- debug: "magenta",
1310
- info: "blue",
1311
- warning: "yellow",
1312
- error: "red",
1313
- fatal: [
1314
- "bgRed",
1315
- "white",
1316
- "bold"
1317
- ],
1318
- step: "cyan",
1319
- success: "green",
1320
- usage: "cyan",
1321
- version: "blue",
1322
- errorEvent: "red",
1323
- prompt: "cyan",
1324
- exec: "gray"
1325
- };
1326
- function safelyInspect(value) {
1327
- if (typeof value === "string") return value;
1328
573
  try {
1329
- return inspect(value, {
1330
- breakLength: 80,
1331
- colors: false,
1332
- compact: false,
1333
- depth: null,
1334
- sorted: true
1335
- });
574
+ const { version } = JSON.parse(text);
575
+ return typeof version === "string" ? version : void 0;
1336
576
  } catch {
1337
- return "[Unserializable]";
1338
- }
1339
- }
1340
- function propertyLines(properties) {
1341
- const lines = [];
1342
- for (const key of Object.keys(properties).sort()) {
1343
- const rendered = ` ${key}: ${safelyInspect(properties[key])}`.split("\n");
1344
- lines.push(rendered[0] ?? "");
1345
- for (const continuation of rendered.slice(1)) lines.push(` ${continuation}`);
1346
- }
1347
- return lines;
1348
- }
1349
- function logLines(record) {
1350
- const category = record.category.length > 0 ? `[${record.category.join(":")}] ` : "";
1351
- const message = record.message.split("\n");
1352
- return [
1353
- `${category}${message[0] ?? ""}`,
1354
- ...message.slice(1),
1355
- ...propertyLines(record.properties)
1356
- ];
1357
- }
1358
- function errorLines(error) {
1359
- const lines = error.message.split("\n");
1360
- const details = {};
1361
- if (error.cause !== void 0) details.cause = error.cause;
1362
- if (error.properties !== void 0) Object.assign(details, error.properties);
1363
- if (error.value !== void 0) details.value = error.value;
1364
- return [...lines, ...propertyLines(details)];
1365
- }
1366
- function eventMark(event) {
1367
- if (event.type === "usage") return "usage";
1368
- if (event.type === "version") return "version";
1369
- if (event.type === "error") return "errorEvent";
1370
- if (event.type === "prompt") return event.prompt.status === "submitted" ? "success" : "prompt";
1371
- if (event.type === "exec") return "exec";
1372
- return event.record.kind === "message" ? event.record.level : event.record.kind;
1373
- }
1374
- function eventLines(event) {
1375
- switch (event.type) {
1376
- case "usage": return event.text.split("\n");
1377
- case "version": return event.version.split("\n");
1378
- case "error": return errorLines(event.error);
1379
- case "log": return logLines(event.record);
1380
- case "prompt": {
1381
- const { prompt } = event;
1382
- if (prompt.status === "submitted") {
1383
- const result = prompt.kind === "select" ? prompt.choices.find(({ value }) => value === prompt.value)?.label ?? prompt.value : prompt.kind === "confirm" ? prompt.value ? "Yes" : "No" : prompt.value;
1384
- return [`${prompt.message}: ${result}`];
1385
- }
1386
- const lines = [prompt.message];
1387
- if (prompt.kind === "select") for (const choice of prompt.choices) lines.push(`${choice.selected ? ">" : " "} ${choice.label}${choice.hint ? ` — ${choice.hint}` : ""}`);
1388
- else if (prompt.kind === "confirm") lines.push(`${prompt.value ? "Yes" : "No"}${prompt.defaultValue === void 0 ? "" : ` (default: ${prompt.defaultValue ? "yes" : "no"})`}`);
1389
- else lines.push(prompt.value || (prompt.placeholder ? `(${prompt.placeholder})` : ""));
1390
- if (prompt.error) lines.push(prompt.error);
1391
- return lines;
1392
- }
1393
- case "exec": {
1394
- const lines = [formatExecCommand(event.command)];
1395
- if (event.command.cwd !== void 0) lines.push(...propertyLines({ cwd: event.command.cwd }));
1396
- return lines;
1397
- }
1398
- }
1399
- }
1400
- function colorize(enabled, style, text) {
1401
- return enabled ? styleText(style, text, { validateStream: false }) : text;
1402
- }
1403
- const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1404
- const combiningMark = /\p{Mark}/u;
1405
- const pictograph = /\p{Extended_Pictographic}/u;
1406
- function isWideCodePoint(codePoint) {
1407
- return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 131072 && codePoint <= 262141);
1408
- }
1409
- function displayWidth(text) {
1410
- let width = 0;
1411
- for (const { segment } of graphemeSegmenter.segment(stripVTControlCharacters(text))) {
1412
- if (segment === " ") {
1413
- width += 8 - width % 8;
1414
- continue;
1415
- }
1416
- if (pictograph.test(segment) || Array.from(segment).some((value) => isWideCodePoint(value.codePointAt(0)))) {
1417
- width += 2;
1418
- continue;
1419
- }
1420
- for (const value of segment) {
1421
- const codePoint = value.codePointAt(0);
1422
- if (codePoint < 32 || codePoint >= 127 && codePoint < 160) continue;
1423
- if (codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039) continue;
1424
- if (!combiningMark.test(value)) width += 1;
1425
- }
1426
- }
1427
- return width;
1428
- }
1429
- function renderedRows(rendered, columns) {
1430
- const width = Number.isInteger(columns) && columns !== void 0 && columns > 0 ? columns : void 0;
1431
- return rendered.split("\n").reduce((rows, line) => {
1432
- if (!width) return rows + 1;
1433
- return rows + Math.max(1, Math.ceil(displayWidth(line) / width));
1434
- }, 0);
1435
- }
1436
- /** Create the fixed human-readable renderer used for pretty output. */
1437
- function createPrettyPrinter(options) {
1438
- const stdout = options.stdout ?? process.stdout;
1439
- const stderr = options.stderr ?? process.stderr;
1440
- const symbols = options.unicode ? unicodeSymbols : asciiSymbols;
1441
- let activePrompt;
1442
- function render(event) {
1443
- const mark = eventMark(event);
1444
- const lines = eventLines(event);
1445
- const firstPrefix = colorize(options.color, markStyles[mark], symbols.marks[mark]);
1446
- const barPrefix = colorize(options.color, "gray", symbols.bar);
1447
- return lines.map((line, index) => `${index === 0 ? firstPrefix : barPrefix}${line ? ` ${line}` : ""}`).join("\n");
1448
- }
1449
- function eraseActivePrompt() {
1450
- return activePrompt ? "\x1B[1A\x1B[2K".repeat(activePrompt.lines) : "";
577
+ return;
1451
578
  }
1452
- return { print(event) {
1453
- const rendered = render(event);
1454
- if (event.type === "prompt") {
1455
- const erase = eraseActivePrompt();
1456
- if (event.prompt.status === "closed") {
1457
- activePrompt = void 0;
1458
- if (erase) stdout.write(erase);
1459
- return;
1460
- }
1461
- if (event.prompt.status === "submitted") activePrompt = void 0;
1462
- else activePrompt = {
1463
- rendered,
1464
- lines: renderedRows(rendered, stdout.columns)
1465
- };
1466
- stdout.write(`${erase}${rendered}\n`);
1467
- return;
1468
- }
1469
- const erase = eraseActivePrompt();
1470
- if (!activePrompt) {
1471
- (eventStream(event) === "stdout" ? stdout : stderr).write(`${rendered}\n`);
1472
- return;
1473
- }
1474
- stdout.write(erase);
1475
- if (event.type === "error") activePrompt = void 0;
1476
- (eventStream(event) === "stdout" ? stdout : stderr).write(`${rendered}\n`);
1477
- if (activePrompt) stdout.write(`${activePrompt.rendered}\n`);
1478
- } };
1479
579
  }
1480
580
  //#endregion
1481
581
  //#region src/prompts.ts
@@ -1761,103 +861,6 @@ var PromptController = class {
1761
861
  }
1762
862
  };
1763
863
  //#endregion
1764
- //#region src/logger.ts
1765
- function interpolate(message, properties) {
1766
- return message.replace(/\{([^{}]+)\}/g, (placeholder, key) => {
1767
- if (!Object.hasOwn(properties, key)) return placeholder;
1768
- try {
1769
- return String(properties[key]);
1770
- } catch {
1771
- return placeholder;
1772
- }
1773
- });
1774
- }
1775
- function createBoundLogger(printer, category, boundProperties, prompts) {
1776
- function emit(level, kind, input, properties = {}) {
1777
- const rawMessage = input instanceof Error ? input.message : input;
1778
- const mergedProperties = {
1779
- ...boundProperties,
1780
- ...properties
1781
- };
1782
- if (input instanceof Error) mergedProperties.error = input;
1783
- const summarizedProperties = summarizeErrors(mergedProperties);
1784
- printer.print({
1785
- type: "log",
1786
- record: {
1787
- level,
1788
- kind,
1789
- category,
1790
- message: interpolate(rawMessage, mergedProperties),
1791
- rawMessage,
1792
- properties: summarizedProperties,
1793
- timestamp: Date.now()
1794
- }
1795
- });
1796
- }
1797
- return {
1798
- category,
1799
- trace: (message, properties) => emit("trace", "message", message, properties),
1800
- debug: (message, properties) => emit("debug", "message", message, properties),
1801
- info: (message, properties) => emit("info", "message", message, properties),
1802
- warn: (message, properties) => emit("warning", "message", message, properties),
1803
- error: (message, properties) => emit("error", "message", message, properties),
1804
- fatal: (message, properties) => emit("fatal", "message", message, properties),
1805
- step: (message, properties) => emit("info", "step", message, properties),
1806
- success: (message, properties) => emit("info", "success", message, properties),
1807
- select: (options) => prompts.select(options),
1808
- confirm: (options) => prompts.confirm(options),
1809
- text: (options) => prompts.text(options),
1810
- with: (properties) => createBoundLogger(printer, category, {
1811
- ...boundProperties,
1812
- ...properties
1813
- }, prompts),
1814
- getChild: (child) => createBoundLogger(printer, [...category, child], boundProperties, prompts)
1815
- };
1816
- }
1817
- /** Create the logger for one command invocation. */
1818
- function createLogger(printer, category, options) {
1819
- const prompts = new PromptController({
1820
- interactive: options.interactive,
1821
- input: options.input,
1822
- print: (event) => options.printPrompt(event),
1823
- terminal: options.terminal,
1824
- signal: options.signal
1825
- });
1826
- return createBoundLogger(printer, [...category], {}, prompts);
1827
- }
1828
- //#endregion
1829
- //#region src/package-version.ts
1830
- async function findPackageVersion(entry) {
1831
- if (!entry) return void 0;
1832
- let directory;
1833
- try {
1834
- directory = dirname(await realpath(entry));
1835
- } catch {
1836
- return;
1837
- }
1838
- for (;;) {
1839
- const version = await readPackageVersion(join(directory, "package.json"));
1840
- if (version !== void 0) return version;
1841
- const parent = dirname(directory);
1842
- if (parent === directory) return void 0;
1843
- directory = parent;
1844
- }
1845
- }
1846
- async function readPackageVersion(path) {
1847
- let text;
1848
- try {
1849
- text = await readFile(path, "utf8");
1850
- } catch {
1851
- return;
1852
- }
1853
- try {
1854
- const { version } = JSON.parse(text);
1855
- return typeof version === "string" ? version : void 0;
1856
- } catch {
1857
- return;
1858
- }
1859
- }
1860
- //#endregion
1861
864
  //#region src/output.ts
1862
865
  function consoleWriter(method, stream) {
1863
866
  return {
@@ -1952,12 +955,11 @@ function summarizeUsage(command) {
1952
955
  });
1953
956
  }
1954
957
  const envNames = optionEnvNames(command, commandEnvPath(command));
1955
- const options = Object.entries(shape).filter(([key]) => key !== "_").map(([key, schema]) => {
958
+ const options = Object.entries(shape).filter(([key]) => key !== POSITIONALS_KEY).map(([key, schema]) => {
1956
959
  const info = readArgInfo(schema);
1957
- const argOptions = command.argOptions?.[key];
1958
- const aliases = argOptions?.alias ? aliasList(argOptions.alias).map((item) => `-${item}`) : [];
960
+ const aliases = info.alias === void 0 ? [] : aliasList(info.alias).map((item) => `-${item}`);
1959
961
  const name = kebabCase(key);
1960
- const valueName = argOptions?.valueName ?? name;
962
+ const valueName = info.valueName ?? name;
1961
963
  const envName = envNames.get(key);
1962
964
  return {
1963
965
  name,
@@ -2016,15 +1018,14 @@ function formatUsage(summary) {
2016
1018
  return lines.join("\n");
2017
1019
  }
2018
1020
  /** Generate usage text without writing to stdout or stderr. */
2019
- async function renderUsage(command) {
1021
+ function renderUsage(command) {
2020
1022
  return formatUsage(summarizeUsage(command));
2021
1023
  }
2022
- /** Generate and write usage text, awaiting asynchronous writers. */
2023
- async function showUsage(command, options = {}) {
1024
+ /** Generate a usage event and await its printer. */
1025
+ async function showUsage(command, options) {
2024
1026
  const usage = summarizeUsage(command);
2025
1027
  const text = formatUsage(usage);
2026
- if (options.write) return options.write(text);
2027
- return (options.printer ?? selectOutputPrinter(options)).print({
1028
+ return options.printer.print({
2028
1029
  type: "usage",
2029
1030
  usage,
2030
1031
  text,
@@ -2079,8 +1080,7 @@ function resolveCommand(root, rawArgs) {
2079
1080
  remaining = remaining.slice(index + 1);
2080
1081
  }
2081
1082
  }
2082
- async function executeCommand(root, argv, displayUsage, output, env, signal, fallbackVersion = async () => void 0) {
2083
- if (root.args && "version" in schemaShape(root.args)) throw new TypeError("Root option `version` conflicts with the builtin --version");
1083
+ async function executeCommand(root, argv, output, env, signal, fallbackVersion = async () => void 0) {
2084
1084
  const { printer } = output;
2085
1085
  const argEnv = env ?? process.env;
2086
1086
  const resolved = resolveCommand(root, argv);
@@ -2093,7 +1093,7 @@ async function executeCommand(root, argv, displayUsage, output, env, signal, fal
2093
1093
  return;
2094
1094
  }
2095
1095
  if (resolved.segments.some(({ tokenized }) => tokenized.help)) {
2096
- await printUsage(displayUsage, resolved.command, printer, "stdout");
1096
+ await showUsage(resolved.command, { printer });
2097
1097
  return;
2098
1098
  }
2099
1099
  for (const parent of resolved.parents) await parseCommandArgs(parent.command, parent.rawArgs, parent.tokenized, argEnv, commandEnvPath(parent.command));
@@ -2102,13 +1102,18 @@ async function executeCommand(root, argv, displayUsage, output, env, signal, fal
2102
1102
  signal.throwIfAborted();
2103
1103
  const category = resolved.segments.flatMap(({ command }) => command.name === void 0 ? [] : [command.name]);
2104
1104
  return resolved.command.run({
2105
- ...parsed,
2106
- cmd: resolved.command,
2107
- rawArgs: resolved.rawArgs,
2108
- logger: createLogger(printer, category, {
1105
+ command: resolved.command,
1106
+ args: parsed.args,
1107
+ input: {
1108
+ argv: resolved.rawArgs,
1109
+ unknown: parsed.extraArgs,
1110
+ passthrough: parsed.passthrough
1111
+ },
1112
+ logger: createLogger(printer, category),
1113
+ prompt: new PromptController({
2109
1114
  interactive: output.interactive,
2110
1115
  input: output.input,
2111
- printPrompt: (event) => output.printChecked(event),
1116
+ print: (event) => output.printChecked(event),
2112
1117
  terminal: output.terminal,
2113
1118
  signal
2114
1119
  }),
@@ -2125,14 +1130,6 @@ async function executeCommand(root, argv, displayUsage, output, env, signal, fal
2125
1130
  signal
2126
1131
  });
2127
1132
  }
2128
- function printUsage(displayUsage, command, printer, stream) {
2129
- return displayUsage(command, { write: (text) => printer.print({
2130
- type: "usage",
2131
- usage: summarizeUsage(command),
2132
- text,
2133
- stream
2134
- }) });
2135
- }
2136
1133
  function prepareOutput(options, env) {
2137
1134
  const input = options.stdin ?? process.stdin;
2138
1135
  const mode = resolveOutputMode({
@@ -2152,7 +1149,7 @@ function prepareOutput(options, env) {
2152
1149
  async function runCommand(root, argv, options = {}) {
2153
1150
  const output = prepareOutput(options, options.env ?? process.env);
2154
1151
  try {
2155
- return await executeCommand(root, argv, options.showUsage ?? showUsage, output, options.env, options.signal ?? new AbortController().signal);
1152
+ return await executeCommand(root, argv, output, options.env, options.signal ?? new AbortController().signal);
2156
1153
  } finally {
2157
1154
  await output.drain();
2158
1155
  }
@@ -2207,16 +1204,18 @@ async function reportOutputSetupError(error, options) {
2207
1204
  });
2208
1205
  return drainAndExit(output);
2209
1206
  }
2210
- async function reportCommandError(error, output, displayUsage) {
1207
+ async function reportCommandError(error, output) {
2211
1208
  if (error instanceof HackshExitError && error.message === "") return;
2212
1209
  await output.printer.print({
2213
1210
  type: "error",
2214
1211
  error: summarizeError(error)
2215
1212
  });
2216
- if (error instanceof HackshUsageError) await printUsage(displayUsage, error.command, output.printer, "stderr");
1213
+ if (error instanceof HackshUsageError) await showUsage(error.command, {
1214
+ printer: output.printer,
1215
+ stream: "stderr"
1216
+ });
2217
1217
  }
2218
1218
  async function runMain(command, options = {}) {
2219
- const displayUsage = options.showUsage ?? showUsage;
2220
1219
  let output;
2221
1220
  try {
2222
1221
  output = prepareOutput(options, options.env ?? process.env);
@@ -2229,14 +1228,14 @@ async function runMain(command, options = {}) {
2229
1228
  return drainAndExit(output, signals.exitCode() ?? code);
2230
1229
  };
2231
1230
  try {
2232
- await executeCommand(command, options.argv ?? process.argv.slice(2), displayUsage, output, options.env, signals.signal, () => findPackageVersion(process.argv[1]));
1231
+ await executeCommand(command, options.argv ?? process.argv.slice(2), output, options.env, signals.signal, () => findPackageVersion(process.argv[1]));
2233
1232
  } catch (error) {
2234
1233
  if (options.onError) {
2235
1234
  await options.onError(error);
2236
1235
  return exit(commandErrorExitCode(error));
2237
1236
  }
2238
1237
  try {
2239
- await reportCommandError(error, output, displayUsage);
1238
+ await reportCommandError(error, output);
2240
1239
  } finally {
2241
1240
  await exit(commandErrorExitCode(error));
2242
1241
  }
@@ -2247,13 +1246,7 @@ async function runMain(command, options = {}) {
2247
1246
  if (signalExitCode !== void 0) return drainAndExit(output, signalExitCode);
2248
1247
  await output.drain();
2249
1248
  }
2250
- function createMain(command, options = {}) {
2251
- return (argv) => runMain(command, argv === void 0 ? options : {
2252
- ...options,
2253
- argv
2254
- });
2255
- }
2256
1249
  //#endregion
2257
- export { HackshExecError, HackshExitError, HackshPromptError, HackshUsageError, HackshValidationError, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
1250
+ export { HackshExecError, HackshExitError, HackshPromptError, HackshUsageError, HackshValidationError, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage, summarizeUsage };
2258
1251
 
2259
1252
  //# sourceMappingURL=index.js.map