hacksh 0.13.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,816 +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
- "dryRun"
699
- ]);
700
- const MAX_DELAY = 2147483647;
701
- const REFUSAL_MESSAGE = {
702
- "json-output": "Command cannot use the terminal in JSON output mode",
703
- "non-interactive": "Command requires an interactive terminal",
704
- overlapping: "Command cannot use the terminal while it is in use"
705
- };
706
- function isRecord(value) {
707
- return typeof value === "object" && value !== null && !Array.isArray(value);
708
- }
709
- function assertNoNul(value, label) {
710
- if (value.includes("\0")) throw new TypeError(`${label} must not contain NUL characters`);
711
- }
712
- function readInteger(value, label, minimum, maximum) {
713
- if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) throw new TypeError(`${label} must be an integer from ${minimum} to ${maximum}`);
714
- return value;
715
- }
716
- function readEnum(value, label, allowed) {
717
- if (typeof value === "string" && allowed.includes(value)) return value;
718
- throw new TypeError(`Exec option \`${label}\` must be ${allowed.map((item) => JSON.stringify(item)).join(" or ")}`);
719
- }
720
- function readBoolean(value, label) {
721
- if (typeof value !== "boolean") throw new TypeError(`Exec option \`${label}\` must be a boolean`);
722
- return value;
723
- }
724
- function readRedact(value) {
725
- if (!Array.isArray(value)) throw new TypeError("Exec option `redact` must be an array");
726
- for (const item of value) {
727
- if (typeof item !== "string" || item === "") throw new TypeError("Exec option `redact` must contain only non-empty strings");
728
- assertNoNul(item, "Exec option `redact`");
729
- }
730
- return value;
731
- }
732
- function mergeEnvironment(base, overrides) {
733
- if (overrides === void 0 && base === void 0) return void 0;
734
- if (overrides !== void 0 && !isRecord(overrides)) throw new TypeError("Exec option `env` must be an object");
735
- const merged = {};
736
- for (const [key, value] of Object.entries(base ?? process.env)) if (value !== void 0) merged[key] = value;
737
- for (const [key, value] of Object.entries(overrides ?? {})) {
738
- if (key === "" || key.includes("=") || key.includes("\0")) throw new TypeError(`Invalid environment variable name ${JSON.stringify(key)}`);
739
- if (value === void 0) {
740
- delete merged[key];
741
- continue;
742
- }
743
- if (typeof value !== "string") throw new TypeError(`Environment variable ${key} must be a string or undefined`);
744
- assertNoNul(value, `Environment variable ${key}`);
745
- merged[key] = value;
746
- }
747
- return merged;
748
- }
749
- function redactValue(value, secrets) {
750
- let redacted = value;
751
- for (const secret of [...secrets].sort((left, right) => right.length - left.length)) redacted = redacted.split(secret).join("***");
752
- return redacted;
753
- }
754
- function redactCommand(command, secrets) {
755
- if (secrets.length === 0) return command;
756
- return {
757
- file: redactValue(command.file, secrets),
758
- args: command.args.map((arg) => redactValue(arg, secrets)),
759
- cwd: command.cwd === void 0 ? void 0 : redactValue(command.cwd, secrets)
760
- };
761
- }
762
- function redactError(error, secrets) {
763
- const source = error;
764
- const redacted = new Error(redactValue(error.message, secrets));
765
- if (typeof error.stack === "string") redacted.stack = redactValue(error.stack, secrets);
766
- if (source.code !== void 0) redacted.code = source.code;
767
- if (source.errno !== void 0) redacted.errno = source.errno;
768
- if (typeof source.syscall === "string") redacted.syscall = redactValue(source.syscall, secrets);
769
- if (typeof source.path === "string") redacted.path = redactValue(source.path, secrets);
770
- if (Array.isArray(source.spawnargs)) redacted.spawnargs = source.spawnargs.map((arg) => typeof arg === "string" ? redactValue(arg, secrets) : arg);
771
- return redacted;
772
- }
773
- function observedError(plan, error) {
774
- return plan.redact.length === 0 ? error : redactError(error, plan.redact);
775
- }
776
- function parsePlan(file, args, options, context) {
777
- if (typeof file !== "string" || file === "") throw new TypeError("Exec file must be a non-empty string");
778
- assertNoNul(file, "Exec file");
779
- const argv = args === void 0 ? [] : args;
780
- if (!Array.isArray(argv) || !argv.every((arg) => typeof arg === "string")) throw new TypeError("Exec args must be an array of strings");
781
- for (const arg of argv) assertNoNul(arg, "Exec args");
782
- const raw = options === void 0 ? {} : options;
783
- if (!isRecord(raw)) throw new TypeError("Exec options must be an object");
784
- for (const key of Object.keys(raw)) if (!OPTION_KEYS.has(key)) throw new TypeError(`Unknown exec option \`${key}\``);
785
- const { cwd, input, reject, timeout, signal, maxBuffer, forceKillAfterDelay } = raw;
786
- if (cwd !== void 0) {
787
- if (typeof cwd !== "string" || cwd === "") throw new TypeError("Exec option `cwd` must be a non-empty string");
788
- assertNoNul(cwd, "Exec option `cwd`");
789
- }
790
- if (input !== void 0 && typeof input !== "string") throw new TypeError("Exec option `input` must be a string");
791
- if (reject !== void 0 && typeof reject !== "boolean") throw new TypeError("Exec option `reject` must be a boolean");
792
- if (signal !== void 0 && !(signal instanceof AbortSignal)) throw new TypeError("Exec option `signal` must be an AbortSignal");
793
- const stdin = raw.stdin === void 0 ? "pipe" : readEnum(raw.stdin, "stdin", ["pipe", "inherit"]);
794
- const stdout = raw.stdout === void 0 ? "pipe" : readEnum(raw.stdout, "stdout", ["pipe", "inherit"]);
795
- const stderr = raw.stderr === void 0 ? "pipe" : readEnum(raw.stderr, "stderr", ["pipe", "inherit"]);
796
- if (input !== void 0 && stdin === "inherit") throw new TypeError("Exec option `input` cannot be combined with stdin inherit");
797
- const command = {
798
- file,
799
- args: [...argv],
800
- cwd
801
- };
802
- const redact = raw.redact === void 0 ? [] : readRedact(raw.redact);
803
- return {
804
- command,
805
- reported: redactCommand(command, redact),
806
- env: mergeEnvironment(context.env, raw.env),
807
- input,
808
- reject: reject ?? true,
809
- timeout: timeout === void 0 ? void 0 : readInteger(timeout, "Exec option `timeout`", 1, MAX_DELAY),
810
- signal: signal === void 0 ? context.signal : AbortSignal.any([context.signal, signal]),
811
- maxBuffer: maxBuffer === void 0 ? 1e8 : readInteger(maxBuffer, "Exec option `maxBuffer`", 1, Number.MAX_SAFE_INTEGER),
812
- forceKillAfterDelay: forceKillAfterDelay === void 0 ? 5e3 : readInteger(forceKillAfterDelay, "Exec option `forceKillAfterDelay`", 0, MAX_DELAY),
813
- stdin,
814
- stdout,
815
- stderr,
816
- interactive: raw.interactive === void 0 ? false : readBoolean(raw.interactive, "interactive"),
817
- display: raw.display === void 0 ? true : readBoolean(raw.display, "display"),
818
- redact,
819
- dryRun: raw.dryRun === void 0 ? false : readBoolean(raw.dryRun, "dryRun")
820
- };
821
- }
822
- function stripFinalNewline(text) {
823
- if (text.endsWith("\r\n")) return text.slice(0, -2);
824
- if (text.endsWith("\n")) return text.slice(0, -1);
825
- return text;
826
- }
827
- function collect(stream, limit, onLimit) {
828
- const chunks = [];
829
- let length = 0;
830
- let exceeded = false;
831
- stream.on("data", (chunk) => {
832
- if (exceeded) return;
833
- const remaining = limit - length;
834
- if (chunk.length <= remaining) {
835
- chunks.push(chunk);
836
- length += chunk.length;
837
- 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;
838
502
  }
839
- chunks.push(chunk.subarray(0, remaining));
840
- length = limit;
841
- exceeded = true;
842
- onLimit();
843
503
  });
844
- return () => stripFinalNewline(Buffer.concat(chunks).toString("utf8"));
845
- }
846
- function toResult(command, outcome, stdout, stderr) {
847
- const failed = {
848
- status: "failed",
849
- command,
850
- stdout,
851
- stderr
852
- };
853
- if (outcome.cause) return {
854
- ...failed,
855
- reason: outcome.cause
856
- };
857
- if (outcome.spawnError) return {
858
- ...failed,
859
- reason: "spawn",
860
- error: outcome.spawnError
861
- };
862
- const { code, signal } = outcome.exit ?? {
863
- code: null,
864
- signal: null
865
- };
866
- if (signal !== null) return {
867
- ...failed,
868
- reason: "signal",
869
- signal
870
- };
871
- if (code !== 0) return {
872
- ...failed,
873
- reason: "exit-code",
874
- exitCode: code ?? 1
875
- };
876
- return {
877
- status: "succeeded",
878
- command,
879
- stdout,
880
- stderr,
881
- exitCode: 0
882
- };
883
- }
884
- function isSpawnErrno(error) {
885
- return error instanceof Error && "syscall" in error;
886
- }
887
- function execEvent(plan, phase) {
888
- const { file, args, cwd } = plan.reported;
889
- return {
890
- type: "exec",
891
- phase,
892
- command: cwd === void 0 ? {
893
- file,
894
- args
895
- } : {
896
- file,
897
- args,
898
- cwd
899
- },
900
- stdio: commandStdio(plan),
901
- interactive: plan.interactive
902
- };
903
- }
904
- function commandStdio(plan) {
905
- return {
906
- stdin: plan.stdin === "inherit" ? "inherit" : plan.input === void 0 ? "ignore" : "input",
907
- stdout: plan.stdout,
908
- stderr: plan.stderr
909
- };
910
504
  }
911
- function usesTerminal(plan, stdio) {
912
- return plan.interactive || stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
913
- }
914
- function startChild(plan) {
915
- const stdio = commandStdio(plan);
916
- try {
917
- return spawn(plan.command.file, plan.command.args, {
918
- cwd: plan.command.cwd,
919
- env: plan.env,
920
- shell: false,
921
- windowsHide: true,
922
- stdio: [
923
- stdio.stdin === "inherit" ? "inherit" : stdio.stdin === "input" ? "pipe" : "ignore",
924
- stdio.stdout,
925
- stdio.stderr
926
- ]
927
- });
928
- } catch (error) {
929
- if (isSpawnErrno(error)) return error;
930
- throw error;
931
- }
932
- }
933
- function runPlan(plan) {
934
- const outcome = {
935
- cause: void 0,
936
- spawnError: void 0,
937
- exit: void 0
938
- };
939
- if (plan.signal.aborted) {
940
- outcome.cause = "aborted";
941
- return Promise.resolve(toResult(plan.reported, outcome, "", ""));
942
- }
943
- const child = startChild(plan);
944
- if (child instanceof Error) {
945
- outcome.spawnError = observedError(plan, child);
946
- return Promise.resolve(toResult(plan.reported, outcome, "", ""));
947
- }
948
- return new Promise((resolve) => {
949
- const stdout = child.stdout;
950
- const stderr = child.stderr;
951
- let settled = false;
952
- let killTimer;
953
- const settle = () => {
954
- if (settled) return;
955
- settled = true;
956
- clearTimeout(timeoutTimer);
957
- clearTimeout(killTimer);
958
- plan.signal.removeEventListener("abort", onAbort);
959
- resolve(toResult(plan.reported, outcome, readStdout(), readStderr()));
960
- };
961
- const release = () => {
962
- stdout?.destroy();
963
- stderr?.destroy();
964
- 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
965
511
  };
966
- const terminate = (cause) => {
967
- if (settled || outcome.cause) return;
968
- outcome.cause = cause;
969
- if (outcome.exit) {
970
- release();
971
- 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()
972
524
  }
973
- child.kill("SIGTERM");
974
- killTimer = setTimeout(() => child.kill("SIGKILL"), plan.forceKillAfterDelay);
975
- };
976
- const onAbort = () => terminate("aborted");
977
- const readStdout = stdout ? collect(stdout, plan.maxBuffer, () => terminate("output-limit")) : () => "";
978
- const readStderr = stderr ? collect(stderr, plan.maxBuffer, () => terminate("output-limit")) : () => "";
979
- const timeoutTimer = plan.timeout === void 0 ? void 0 : setTimeout(() => terminate("timeout"), plan.timeout);
980
- plan.signal.addEventListener("abort", onAbort, { once: true });
981
- child.on("error", (error) => {
982
- if (child.pid !== void 0) return;
983
- outcome.spawnError = observedError(plan, error);
984
- release();
985
- });
986
- child.on("exit", (code, signal) => {
987
- outcome.exit = {
988
- code,
989
- signal
990
- };
991
- clearTimeout(killTimer);
992
- if (outcome.cause) release();
993
525
  });
994
- child.on("close", settle);
995
- if (child.stdin) {
996
- child.stdin.on("error", () => void 0);
997
- child.stdin.end(plan.input);
998
- }
999
- });
1000
- }
1001
- function formatArgument(value) {
1002
- return value === "" || /[\s"'\\$`;|&<>*?()[\]{}~#!]/.test(value) ? JSON.stringify(value) : value;
1003
- }
1004
- function formatExecCommand(command) {
1005
- return [command.file, ...command.args].map(formatArgument).join(" ");
1006
- }
1007
- function failureMessage(plan, result) {
1008
- const display = formatExecCommand(plan.reported);
1009
- switch (result.reason) {
1010
- case "exit-code": return `Command failed with exit code ${result.exitCode}: ${display}`;
1011
- case "signal": return `Command was terminated by ${result.signal}: ${display}`;
1012
- case "spawn": return `Command could not be started (${result.error.code ?? result.error.message}): ${display}`;
1013
- case "timeout": return `Command timed out after ${plan.timeout} ms: ${display}`;
1014
- case "aborted": return `Command was aborted: ${display}`;
1015
- case "output-limit": return `Command output exceeded ${plan.maxBuffer} bytes: ${display}`;
1016
- case "json-output":
1017
- case "non-interactive":
1018
- case "overlapping": return `${REFUSAL_MESSAGE[result.reason]}: ${display}`;
1019
526
  }
1020
- }
1021
- function emptyFailure(plan, reason) {
1022
527
  return {
1023
- status: "failed",
1024
- command: plan.reported,
1025
- stdout: "",
1026
- stderr: "",
1027
- 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)
1028
542
  };
1029
543
  }
1030
- function complete(plan, result) {
1031
- if (result.status === "failed" && plan.reject) throw new HackshExecError(failureMessage(plan, result), result, result.reason === "spawn" ? { cause: result.error } : void 0);
1032
- return result;
1033
- }
1034
- function blockedReason(plan, context, stdio) {
1035
- const inherited = stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
1036
- if (context.mode.format === "json" && inherited) return "json-output";
1037
- if (plan.interactive && !context.mode.interactive) return "non-interactive";
1038
- }
1039
- async function execute(file, args, options, context) {
1040
- const plan = parsePlan(file, args, options, context);
1041
- if (plan.signal.aborted) return complete(plan, emptyFailure(plan, "aborted"));
1042
- if (plan.dryRun) {
1043
- if (plan.display) await context.print(execEvent(plan, "skip"));
1044
- return {
1045
- status: "skipped",
1046
- command: plan.reported
1047
- };
1048
- }
1049
- const stdio = commandStdio(plan);
1050
- const blocked = blockedReason(plan, context, stdio);
1051
- if (blocked) return complete(plan, emptyFailure(plan, blocked));
1052
- const holdTerminal = usesTerminal(plan, stdio);
1053
- if (holdTerminal && !context.terminal.acquire("exec")) return complete(plan, emptyFailure(plan, "overlapping"));
1054
- try {
1055
- if (plan.display) await context.print(execEvent(plan, "start"));
1056
- return complete(plan, await runPlan(plan));
1057
- } finally {
1058
- if (holdTerminal) context.terminal.release();
1059
- }
1060
- }
1061
- function createExec(context) {
1062
- 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], {});
1063
547
  }
1064
548
  //#endregion
1065
- //#region src/serialization.ts
1066
- function safeString(value) {
1067
- try {
1068
- return String(value);
1069
- } catch {
1070
- return "[Unserializable]";
1071
- }
1072
- }
1073
- function normalizeProperty(value, key, ancestors) {
1074
- try {
1075
- return normalizeJson(Reflect.get(value, key), ancestors);
1076
- } catch {
1077
- return "[Unserializable]";
1078
- }
1079
- }
1080
- function normalizedKeys(value) {
549
+ //#region src/package-version.ts
550
+ async function findPackageVersion(entry) {
551
+ if (!entry) return void 0;
552
+ let directory;
1081
553
  try {
1082
- return Object.keys(value);
554
+ directory = dirname(await realpath(entry));
1083
555
  } catch {
1084
556
  return;
1085
557
  }
1086
- }
1087
- const noToJson = Symbol("noToJson");
1088
- function normalizedToJson(value, ancestors) {
1089
- let toJSON;
1090
- try {
1091
- toJSON = Reflect.get(value, "toJSON");
1092
- } catch {
1093
- return noToJson;
1094
- }
1095
- if (typeof toJSON !== "function") return noToJson;
1096
- try {
1097
- const result = Reflect.apply(toJSON, value, []);
1098
- return result === value ? noToJson : normalizeJson(result, ancestors);
1099
- } catch {
1100
- return noToJson;
1101
- }
1102
- }
1103
- function isArrayBufferView(value) {
1104
- try {
1105
- return ArrayBuffer.isView(value);
1106
- } catch {
1107
- return false;
1108
- }
1109
- }
1110
- /** Normalize arbitrary event data into values accepted by JSON.stringify. */
1111
- function normalizeJson(value, ancestors = /* @__PURE__ */ new Set()) {
1112
- switch (typeof value) {
1113
- case "string": return stripVTControlCharacters(value);
1114
- case "boolean": return value;
1115
- case "number": return Number.isFinite(value) ? value : null;
1116
- case "bigint": return value.toString();
1117
- case "undefined": return "[undefined]";
1118
- case "symbol": return `[symbol:${stripVTControlCharacters(value.description ?? "")}]`;
1119
- case "function": return `[function:${stripVTControlCharacters(value.name || "anonymous")}]`;
1120
- }
1121
- if (value === null) return null;
1122
- if (ancestors.has(value)) return "[Circular]";
1123
- const nextAncestors = new Set(ancestors).add(value);
1124
- if (value instanceof Error) return normalizeJson(summarizeError(value), nextAncestors);
1125
- const jsonValue = normalizedToJson(value, nextAncestors);
1126
- if (jsonValue !== noToJson) return jsonValue;
1127
- if (isArrayBufferView(value)) {
1128
- if (value instanceof DataView) return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
1129
- try {
1130
- return Array.from(value, (item) => normalizeJson(item, nextAncestors));
1131
- } catch {
1132
- return "[Unserializable]";
1133
- }
1134
- }
1135
- if (value instanceof ArrayBuffer) return Array.from(new Uint8Array(value));
1136
- if (Array.isArray(value)) return Array.from({ length: value.length }, (_, index) => normalizeProperty(value, index, nextAncestors));
1137
- if (value instanceof Map) try {
1138
- return [...value].map(([key, item]) => [normalizeJson(key, nextAncestors), normalizeJson(item, nextAncestors)]);
1139
- } catch {
1140
- return "[Unserializable]";
1141
- }
1142
- if (value instanceof Set) try {
1143
- return [...value].map((item) => normalizeJson(item, nextAncestors));
1144
- } catch {
1145
- return "[Unserializable]";
1146
- }
1147
- const keys = normalizedKeys(value);
1148
- if (!keys) return safeString(value);
1149
- const result = {};
1150
- for (const key of keys) result[stripVTControlCharacters(key)] = normalizeProperty(value, key, nextAncestors);
1151
- return result;
1152
- }
1153
- //#endregion
1154
- //#region src/printer.ts
1155
- function fallbackValue(value) {
1156
- try {
1157
- return String(value);
1158
- } catch {
1159
- 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;
1160
564
  }
1161
565
  }
1162
- function validatePreference(value, source) {
1163
- if (value === void 0) return void 0;
1164
- if (value === "auto" || value === "pretty" || value === "json") return value;
1165
- throw new TypeError(`Invalid ${source} value ${fallbackValue(value)}; expected auto, pretty, or json`);
1166
- }
1167
- function readEnvironment(env, key) {
566
+ async function readPackageVersion(path) {
567
+ let text;
1168
568
  try {
1169
- return env[key];
569
+ text = await readFile(path, "utf8");
1170
570
  } catch {
1171
571
  return;
1172
572
  }
1173
- }
1174
- /** Resolve output behavior once, before command routing or argument parsing. */
1175
- function resolveOutputMode(options = {}) {
1176
- const env = options.env ?? process.env;
1177
- const configured = validatePreference(options.output, "output");
1178
- const requested = validatePreference(readEnvironment(env, "HACKSH_OUTPUT"), "HACKSH_OUTPUT") ?? configured ?? "auto";
1179
- const ci = Boolean(readEnvironment(env, "CI"));
1180
- const format = requested === "auto" ? ci ? "json" : "pretty" : requested;
1181
- const inputIsTTY = options.inputIsTTY ?? process.stdin.isTTY === true;
1182
- const outputIsTTY = options.outputIsTTY ?? process.stdout.isTTY === true;
1183
- return {
1184
- format,
1185
- interactive: format === "pretty" && !ci && inputIsTTY && outputIsTTY,
1186
- color: format === "pretty" && outputIsTTY && !readEnvironment(env, "NO_COLOR")
1187
- };
1188
- }
1189
- function isErrorLevel(level) {
1190
- return level === "warning" || level === "error" || level === "fatal";
1191
- }
1192
- function eventStream(event) {
1193
- if (event.type === "error" || event.type === "exec") return "stderr";
1194
- if (event.type === "usage") return event.stream;
1195
- if (event.type === "log" && isErrorLevel(event.record.level)) return "stderr";
1196
- return "stdout";
1197
- }
1198
- function formatTextEvent(event) {
1199
- switch (event.type) {
1200
- case "usage": return event.text;
1201
- case "version": return event.version;
1202
- case "error": return event.error.kind === "usage" || event.error.kind === "validation" ? event.error.message : `Error: ${event.error.message}`;
1203
- case "log": return event.record.message;
1204
- case "prompt": return event.prompt.message;
1205
- case "exec": return `${event.phase === "skip" ? "#" : "$"} ${formatExecCommand(event.command)}`;
1206
- }
1207
- }
1208
- /** Undecorated console compatibility adapter; automatic output uses the selected format printer. */
1209
- const consolePrinter = { print(event) {
1210
- const text = formatTextEvent(event);
1211
- return eventStream(event) === "stdout" ? console.log(text) : console.error(text);
1212
- } };
1213
- /** Create an undecorated stream-backed compatibility adapter. */
1214
- function createStreamPrinter(options) {
1215
- return { print(event) {
1216
- (eventStream(event) === "stdout" ? options.stdout : options.stderr).write(`${formatTextEvent(event)}\n`);
1217
- } };
1218
- }
1219
- /** Create a one-semantic-event-per-line JSON printer. */
1220
- function createJsonPrinter(options = {}) {
1221
- const stdout = options.stdout ?? process.stdout;
1222
- const stderr = options.stderr ?? process.stderr;
1223
- return { print(event) {
1224
- let serialized;
1225
- try {
1226
- serialized = JSON.stringify(normalizeJson(event));
1227
- } catch {
1228
- serialized = JSON.stringify({
1229
- type: event.type,
1230
- serializationError: "Event data could not be serialized"
1231
- });
1232
- }
1233
- const line = `${serialized}\n`;
1234
- (eventStream(event) === "stdout" ? stdout : stderr).write(line);
1235
- } };
1236
- }
1237
- /** Serialize printer calls and retain failures until the invocation drains its output. */
1238
- function createPrintQueue(target) {
1239
- let pending;
1240
- let failure;
1241
- let failed = false;
1242
- function recordFailure(error) {
1243
- if (!failed) failure = error;
1244
- failed = true;
1245
- }
1246
- function track(execution, retainFailure) {
1247
- const barrier = execution.catch((error) => {
1248
- if (retainFailure) recordFailure(error);
1249
- });
1250
- pending = barrier;
1251
- barrier.then(() => {
1252
- if (pending === barrier) pending = void 0;
1253
- });
1254
- return retainFailure ? barrier : execution;
1255
- }
1256
- function enqueue(event, retainFailure) {
1257
- if (pending) return track(pending.then(() => target.print(event)), retainFailure);
1258
- try {
1259
- const result = target.print(event);
1260
- if (result) return track(Promise.resolve(result), retainFailure);
1261
- } catch (error) {
1262
- if (retainFailure) recordFailure(error);
1263
- else return Promise.reject(error);
1264
- }
1265
- }
1266
- return {
1267
- printer: { print(event) {
1268
- return enqueue(event, true);
1269
- } },
1270
- async printChecked(event) {
1271
- await enqueue(event, false);
1272
- },
1273
- async drain() {
1274
- await pending;
1275
- if (failed) throw failure;
1276
- }
1277
- };
1278
- }
1279
- //#endregion
1280
- //#region src/pretty.ts
1281
- const unicodeSymbols = {
1282
- bar: "│",
1283
- marks: {
1284
- trace: "·",
1285
- debug: "◆",
1286
- info: "●",
1287
- warning: "▲",
1288
- error: "■",
1289
- fatal: "✖",
1290
- step: "◇",
1291
- success: "✔",
1292
- usage: "◆",
1293
- version: "●",
1294
- errorEvent: "■",
1295
- prompt: "?",
1296
- exec: "$",
1297
- execSkip: "#"
1298
- }
1299
- };
1300
- const asciiSymbols = {
1301
- bar: "|",
1302
- marks: {
1303
- trace: ".",
1304
- debug: ":",
1305
- info: "i",
1306
- warning: "!",
1307
- error: "x",
1308
- fatal: "X",
1309
- step: ">",
1310
- success: "+",
1311
- usage: "?",
1312
- version: "i",
1313
- errorEvent: "x",
1314
- prompt: "?",
1315
- exec: "$",
1316
- execSkip: "#"
1317
- }
1318
- };
1319
- const markStyles = {
1320
- trace: "gray",
1321
- debug: "magenta",
1322
- info: "blue",
1323
- warning: "yellow",
1324
- error: "red",
1325
- fatal: [
1326
- "bgRed",
1327
- "white",
1328
- "bold"
1329
- ],
1330
- step: "cyan",
1331
- success: "green",
1332
- usage: "cyan",
1333
- version: "blue",
1334
- errorEvent: "red",
1335
- prompt: "cyan",
1336
- exec: "gray",
1337
- execSkip: "gray"
1338
- };
1339
- function safelyInspect(value) {
1340
- if (typeof value === "string") return value;
1341
573
  try {
1342
- return inspect(value, {
1343
- breakLength: 80,
1344
- colors: false,
1345
- compact: false,
1346
- depth: null,
1347
- sorted: true
1348
- });
574
+ const { version } = JSON.parse(text);
575
+ return typeof version === "string" ? version : void 0;
1349
576
  } catch {
1350
- return "[Unserializable]";
1351
- }
1352
- }
1353
- function propertyLines(properties) {
1354
- const lines = [];
1355
- for (const key of Object.keys(properties).sort()) {
1356
- const rendered = ` ${key}: ${safelyInspect(properties[key])}`.split("\n");
1357
- lines.push(rendered[0] ?? "");
1358
- for (const continuation of rendered.slice(1)) lines.push(` ${continuation}`);
1359
- }
1360
- return lines;
1361
- }
1362
- function logLines(record) {
1363
- const category = record.category.length > 0 ? `[${record.category.join(":")}] ` : "";
1364
- const message = record.message.split("\n");
1365
- return [
1366
- `${category}${message[0] ?? ""}`,
1367
- ...message.slice(1),
1368
- ...propertyLines(record.properties)
1369
- ];
1370
- }
1371
- function errorLines(error) {
1372
- const lines = error.message.split("\n");
1373
- const details = {};
1374
- if (error.cause !== void 0) details.cause = error.cause;
1375
- if (error.properties !== void 0) Object.assign(details, error.properties);
1376
- if (error.value !== void 0) details.value = error.value;
1377
- return [...lines, ...propertyLines(details)];
1378
- }
1379
- function eventMark(event) {
1380
- if (event.type === "usage") return "usage";
1381
- if (event.type === "version") return "version";
1382
- if (event.type === "error") return "errorEvent";
1383
- if (event.type === "prompt") return event.prompt.status === "submitted" ? "success" : "prompt";
1384
- if (event.type === "exec") return event.phase === "skip" ? "execSkip" : "exec";
1385
- return event.record.kind === "message" ? event.record.level : event.record.kind;
1386
- }
1387
- function eventLines(event) {
1388
- switch (event.type) {
1389
- case "usage": return event.text.split("\n");
1390
- case "version": return event.version.split("\n");
1391
- case "error": return errorLines(event.error);
1392
- case "log": return logLines(event.record);
1393
- case "prompt": {
1394
- const { prompt } = event;
1395
- if (prompt.status === "submitted") {
1396
- const result = prompt.kind === "select" ? prompt.choices.find(({ value }) => value === prompt.value)?.label ?? prompt.value : prompt.kind === "confirm" ? prompt.value ? "Yes" : "No" : prompt.value;
1397
- return [`${prompt.message}: ${result}`];
1398
- }
1399
- const lines = [prompt.message];
1400
- if (prompt.kind === "select") for (const choice of prompt.choices) lines.push(`${choice.selected ? ">" : " "} ${choice.label}${choice.hint ? ` — ${choice.hint}` : ""}`);
1401
- else if (prompt.kind === "confirm") lines.push(`${prompt.value ? "Yes" : "No"}${prompt.defaultValue === void 0 ? "" : ` (default: ${prompt.defaultValue ? "yes" : "no"})`}`);
1402
- else lines.push(prompt.value || (prompt.placeholder ? `(${prompt.placeholder})` : ""));
1403
- if (prompt.error) lines.push(prompt.error);
1404
- return lines;
1405
- }
1406
- case "exec": {
1407
- const lines = [formatExecCommand(event.command)];
1408
- if (event.command.cwd !== void 0) lines.push(...propertyLines({ cwd: event.command.cwd }));
1409
- return lines;
1410
- }
1411
- }
1412
- }
1413
- function colorize(enabled, style, text) {
1414
- return enabled ? styleText(style, text, { validateStream: false }) : text;
1415
- }
1416
- const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1417
- const combiningMark = /\p{Mark}/u;
1418
- const pictograph = /\p{Extended_Pictographic}/u;
1419
- function isWideCodePoint(codePoint) {
1420
- 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);
1421
- }
1422
- function displayWidth(text) {
1423
- let width = 0;
1424
- for (const { segment } of graphemeSegmenter.segment(stripVTControlCharacters(text))) {
1425
- if (segment === " ") {
1426
- width += 8 - width % 8;
1427
- continue;
1428
- }
1429
- if (pictograph.test(segment) || Array.from(segment).some((value) => isWideCodePoint(value.codePointAt(0)))) {
1430
- width += 2;
1431
- continue;
1432
- }
1433
- for (const value of segment) {
1434
- const codePoint = value.codePointAt(0);
1435
- if (codePoint < 32 || codePoint >= 127 && codePoint < 160) continue;
1436
- if (codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039) continue;
1437
- if (!combiningMark.test(value)) width += 1;
1438
- }
1439
- }
1440
- return width;
1441
- }
1442
- function renderedRows(rendered, columns) {
1443
- const width = Number.isInteger(columns) && columns !== void 0 && columns > 0 ? columns : void 0;
1444
- return rendered.split("\n").reduce((rows, line) => {
1445
- if (!width) return rows + 1;
1446
- return rows + Math.max(1, Math.ceil(displayWidth(line) / width));
1447
- }, 0);
1448
- }
1449
- /** Create the fixed human-readable renderer used for pretty output. */
1450
- function createPrettyPrinter(options) {
1451
- const stdout = options.stdout ?? process.stdout;
1452
- const stderr = options.stderr ?? process.stderr;
1453
- const symbols = options.unicode ? unicodeSymbols : asciiSymbols;
1454
- let activePrompt;
1455
- function render(event) {
1456
- const mark = eventMark(event);
1457
- const lines = eventLines(event);
1458
- const firstPrefix = colorize(options.color, markStyles[mark], symbols.marks[mark]);
1459
- const barPrefix = colorize(options.color, "gray", symbols.bar);
1460
- return lines.map((line, index) => `${index === 0 ? firstPrefix : barPrefix}${line ? ` ${line}` : ""}`).join("\n");
1461
- }
1462
- function eraseActivePrompt() {
1463
- return activePrompt ? "\x1B[1A\x1B[2K".repeat(activePrompt.lines) : "";
577
+ return;
1464
578
  }
1465
- return { print(event) {
1466
- const rendered = render(event);
1467
- if (event.type === "prompt") {
1468
- const erase = eraseActivePrompt();
1469
- if (event.prompt.status === "closed") {
1470
- activePrompt = void 0;
1471
- if (erase) stdout.write(erase);
1472
- return;
1473
- }
1474
- if (event.prompt.status === "submitted") activePrompt = void 0;
1475
- else activePrompt = {
1476
- rendered,
1477
- lines: renderedRows(rendered, stdout.columns)
1478
- };
1479
- stdout.write(`${erase}${rendered}\n`);
1480
- return;
1481
- }
1482
- const erase = eraseActivePrompt();
1483
- if (!activePrompt) {
1484
- (eventStream(event) === "stdout" ? stdout : stderr).write(`${rendered}\n`);
1485
- return;
1486
- }
1487
- stdout.write(erase);
1488
- if (event.type === "error") activePrompt = void 0;
1489
- (eventStream(event) === "stdout" ? stdout : stderr).write(`${rendered}\n`);
1490
- if (activePrompt) stdout.write(`${activePrompt.rendered}\n`);
1491
- } };
1492
579
  }
1493
580
  //#endregion
1494
581
  //#region src/prompts.ts
@@ -1774,103 +861,6 @@ var PromptController = class {
1774
861
  }
1775
862
  };
1776
863
  //#endregion
1777
- //#region src/logger.ts
1778
- function interpolate(message, properties) {
1779
- return message.replace(/\{([^{}]+)\}/g, (placeholder, key) => {
1780
- if (!Object.hasOwn(properties, key)) return placeholder;
1781
- try {
1782
- return String(properties[key]);
1783
- } catch {
1784
- return placeholder;
1785
- }
1786
- });
1787
- }
1788
- function createBoundLogger(printer, category, boundProperties, prompts) {
1789
- function emit(level, kind, input, properties = {}) {
1790
- const rawMessage = input instanceof Error ? input.message : input;
1791
- const mergedProperties = {
1792
- ...boundProperties,
1793
- ...properties
1794
- };
1795
- if (input instanceof Error) mergedProperties.error = input;
1796
- const summarizedProperties = summarizeErrors(mergedProperties);
1797
- printer.print({
1798
- type: "log",
1799
- record: {
1800
- level,
1801
- kind,
1802
- category,
1803
- message: interpolate(rawMessage, mergedProperties),
1804
- rawMessage,
1805
- properties: summarizedProperties,
1806
- timestamp: Date.now()
1807
- }
1808
- });
1809
- }
1810
- return {
1811
- category,
1812
- trace: (message, properties) => emit("trace", "message", message, properties),
1813
- debug: (message, properties) => emit("debug", "message", message, properties),
1814
- info: (message, properties) => emit("info", "message", message, properties),
1815
- warn: (message, properties) => emit("warning", "message", message, properties),
1816
- error: (message, properties) => emit("error", "message", message, properties),
1817
- fatal: (message, properties) => emit("fatal", "message", message, properties),
1818
- step: (message, properties) => emit("info", "step", message, properties),
1819
- success: (message, properties) => emit("info", "success", message, properties),
1820
- select: (options) => prompts.select(options),
1821
- confirm: (options) => prompts.confirm(options),
1822
- text: (options) => prompts.text(options),
1823
- with: (properties) => createBoundLogger(printer, category, {
1824
- ...boundProperties,
1825
- ...properties
1826
- }, prompts),
1827
- getChild: (child) => createBoundLogger(printer, [...category, child], boundProperties, prompts)
1828
- };
1829
- }
1830
- /** Create the logger for one command invocation. */
1831
- function createLogger(printer, category, options) {
1832
- const prompts = new PromptController({
1833
- interactive: options.interactive,
1834
- input: options.input,
1835
- print: (event) => options.printPrompt(event),
1836
- terminal: options.terminal,
1837
- signal: options.signal
1838
- });
1839
- return createBoundLogger(printer, [...category], {}, prompts);
1840
- }
1841
- //#endregion
1842
- //#region src/package-version.ts
1843
- async function findPackageVersion(entry) {
1844
- if (!entry) return void 0;
1845
- let directory;
1846
- try {
1847
- directory = dirname(await realpath(entry));
1848
- } catch {
1849
- return;
1850
- }
1851
- for (;;) {
1852
- const version = await readPackageVersion(join(directory, "package.json"));
1853
- if (version !== void 0) return version;
1854
- const parent = dirname(directory);
1855
- if (parent === directory) return void 0;
1856
- directory = parent;
1857
- }
1858
- }
1859
- async function readPackageVersion(path) {
1860
- let text;
1861
- try {
1862
- text = await readFile(path, "utf8");
1863
- } catch {
1864
- return;
1865
- }
1866
- try {
1867
- const { version } = JSON.parse(text);
1868
- return typeof version === "string" ? version : void 0;
1869
- } catch {
1870
- return;
1871
- }
1872
- }
1873
- //#endregion
1874
864
  //#region src/output.ts
1875
865
  function consoleWriter(method, stream) {
1876
866
  return {
@@ -1965,12 +955,11 @@ function summarizeUsage(command) {
1965
955
  });
1966
956
  }
1967
957
  const envNames = optionEnvNames(command, commandEnvPath(command));
1968
- 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]) => {
1969
959
  const info = readArgInfo(schema);
1970
- const argOptions = command.argOptions?.[key];
1971
- const aliases = argOptions?.alias ? aliasList(argOptions.alias).map((item) => `-${item}`) : [];
960
+ const aliases = info.alias === void 0 ? [] : aliasList(info.alias).map((item) => `-${item}`);
1972
961
  const name = kebabCase(key);
1973
- const valueName = argOptions?.valueName ?? name;
962
+ const valueName = info.valueName ?? name;
1974
963
  const envName = envNames.get(key);
1975
964
  return {
1976
965
  name,
@@ -2029,15 +1018,14 @@ function formatUsage(summary) {
2029
1018
  return lines.join("\n");
2030
1019
  }
2031
1020
  /** Generate usage text without writing to stdout or stderr. */
2032
- async function renderUsage(command) {
1021
+ function renderUsage(command) {
2033
1022
  return formatUsage(summarizeUsage(command));
2034
1023
  }
2035
- /** Generate and write usage text, awaiting asynchronous writers. */
2036
- async function showUsage(command, options = {}) {
1024
+ /** Generate a usage event and await its printer. */
1025
+ async function showUsage(command, options) {
2037
1026
  const usage = summarizeUsage(command);
2038
1027
  const text = formatUsage(usage);
2039
- if (options.write) return options.write(text);
2040
- return (options.printer ?? selectOutputPrinter(options)).print({
1028
+ return options.printer.print({
2041
1029
  type: "usage",
2042
1030
  usage,
2043
1031
  text,
@@ -2092,8 +1080,7 @@ function resolveCommand(root, rawArgs) {
2092
1080
  remaining = remaining.slice(index + 1);
2093
1081
  }
2094
1082
  }
2095
- async function executeCommand(root, argv, displayUsage, output, env, signal, fallbackVersion = async () => void 0) {
2096
- 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) {
2097
1084
  const { printer } = output;
2098
1085
  const argEnv = env ?? process.env;
2099
1086
  const resolved = resolveCommand(root, argv);
@@ -2106,7 +1093,7 @@ async function executeCommand(root, argv, displayUsage, output, env, signal, fal
2106
1093
  return;
2107
1094
  }
2108
1095
  if (resolved.segments.some(({ tokenized }) => tokenized.help)) {
2109
- await printUsage(displayUsage, resolved.command, printer, "stdout");
1096
+ await showUsage(resolved.command, { printer });
2110
1097
  return;
2111
1098
  }
2112
1099
  for (const parent of resolved.parents) await parseCommandArgs(parent.command, parent.rawArgs, parent.tokenized, argEnv, commandEnvPath(parent.command));
@@ -2115,13 +1102,18 @@ async function executeCommand(root, argv, displayUsage, output, env, signal, fal
2115
1102
  signal.throwIfAborted();
2116
1103
  const category = resolved.segments.flatMap(({ command }) => command.name === void 0 ? [] : [command.name]);
2117
1104
  return resolved.command.run({
2118
- ...parsed,
2119
- cmd: resolved.command,
2120
- rawArgs: resolved.rawArgs,
2121
- 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({
2122
1114
  interactive: output.interactive,
2123
1115
  input: output.input,
2124
- printPrompt: (event) => output.printChecked(event),
1116
+ print: (event) => output.printChecked(event),
2125
1117
  terminal: output.terminal,
2126
1118
  signal
2127
1119
  }),
@@ -2138,14 +1130,6 @@ async function executeCommand(root, argv, displayUsage, output, env, signal, fal
2138
1130
  signal
2139
1131
  });
2140
1132
  }
2141
- function printUsage(displayUsage, command, printer, stream) {
2142
- return displayUsage(command, { write: (text) => printer.print({
2143
- type: "usage",
2144
- usage: summarizeUsage(command),
2145
- text,
2146
- stream
2147
- }) });
2148
- }
2149
1133
  function prepareOutput(options, env) {
2150
1134
  const input = options.stdin ?? process.stdin;
2151
1135
  const mode = resolveOutputMode({
@@ -2165,7 +1149,7 @@ function prepareOutput(options, env) {
2165
1149
  async function runCommand(root, argv, options = {}) {
2166
1150
  const output = prepareOutput(options, options.env ?? process.env);
2167
1151
  try {
2168
- 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);
2169
1153
  } finally {
2170
1154
  await output.drain();
2171
1155
  }
@@ -2220,16 +1204,18 @@ async function reportOutputSetupError(error, options) {
2220
1204
  });
2221
1205
  return drainAndExit(output);
2222
1206
  }
2223
- async function reportCommandError(error, output, displayUsage) {
1207
+ async function reportCommandError(error, output) {
2224
1208
  if (error instanceof HackshExitError && error.message === "") return;
2225
1209
  await output.printer.print({
2226
1210
  type: "error",
2227
1211
  error: summarizeError(error)
2228
1212
  });
2229
- 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
+ });
2230
1217
  }
2231
1218
  async function runMain(command, options = {}) {
2232
- const displayUsage = options.showUsage ?? showUsage;
2233
1219
  let output;
2234
1220
  try {
2235
1221
  output = prepareOutput(options, options.env ?? process.env);
@@ -2242,14 +1228,14 @@ async function runMain(command, options = {}) {
2242
1228
  return drainAndExit(output, signals.exitCode() ?? code);
2243
1229
  };
2244
1230
  try {
2245
- 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]));
2246
1232
  } catch (error) {
2247
1233
  if (options.onError) {
2248
1234
  await options.onError(error);
2249
1235
  return exit(commandErrorExitCode(error));
2250
1236
  }
2251
1237
  try {
2252
- await reportCommandError(error, output, displayUsage);
1238
+ await reportCommandError(error, output);
2253
1239
  } finally {
2254
1240
  await exit(commandErrorExitCode(error));
2255
1241
  }
@@ -2260,13 +1246,7 @@ async function runMain(command, options = {}) {
2260
1246
  if (signalExitCode !== void 0) return drainAndExit(output, signalExitCode);
2261
1247
  await output.drain();
2262
1248
  }
2263
- function createMain(command, options = {}) {
2264
- return (argv) => runMain(command, argv === void 0 ? options : {
2265
- ...options,
2266
- argv
2267
- });
2268
- }
2269
1249
  //#endregion
2270
- 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 };
2271
1251
 
2272
1252
  //# sourceMappingURL=index.js.map