hacksh 0.5.4 → 0.7.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,5 +1,6 @@
1
1
  import { globalRegistry, safeParseAsync } from "zod/v4/core";
2
2
  import { inspect, parseArgs as parseArgs$1, stripVTControlCharacters, styleText } from "node:util";
3
+ import { spawn } from "node:child_process";
3
4
  import { emitKeypressEvents } from "node:readline";
4
5
  import { PassThrough } from "node:stream";
5
6
  //#region src/options.ts
@@ -167,6 +168,18 @@ var HackshPromptError = class extends Error {
167
168
  this.prompt = prompt;
168
169
  }
169
170
  };
171
+ /** An external command that did not succeed; `result` is not enumerable so error summaries omit child output. */
172
+ var HackshExecError = class extends Error {
173
+ #result;
174
+ constructor(message, result, options) {
175
+ super(message, options);
176
+ this.name = "HackshExecError";
177
+ this.#result = result;
178
+ }
179
+ get result() {
180
+ return this.#result;
181
+ }
182
+ };
170
183
  function safeString$1(value, fallback) {
171
184
  try {
172
185
  return String(value);
@@ -630,6 +643,395 @@ async function parseArgs(command, argv, options) {
630
643
  return (await parseCommandArgs(command, argv, tokenizeCommandArgs(command, argv), options?.env, envPath)).args;
631
644
  }
632
645
  //#endregion
646
+ //#region src/terminal.ts
647
+ var TerminalLock = class {
648
+ #owner;
649
+ get owner() {
650
+ return this.#owner;
651
+ }
652
+ acquire(owner) {
653
+ if (this.#owner !== void 0) return false;
654
+ this.#owner = owner;
655
+ return true;
656
+ }
657
+ release() {
658
+ this.#owner = void 0;
659
+ }
660
+ };
661
+ //#endregion
662
+ //#region src/exec.ts
663
+ const OPTION_KEYS = /* @__PURE__ */ new Set([
664
+ "cwd",
665
+ "env",
666
+ "input",
667
+ "reject",
668
+ "timeout",
669
+ "signal",
670
+ "maxBuffer",
671
+ "forceKillAfterDelay",
672
+ "stdin",
673
+ "stdout",
674
+ "stderr",
675
+ "interactive",
676
+ "display",
677
+ "redact"
678
+ ]);
679
+ const MAX_DELAY = 2147483647;
680
+ const REFUSAL_MESSAGE = {
681
+ "json-output": "Command cannot use the terminal in JSON output mode",
682
+ "non-interactive": "Command requires an interactive terminal",
683
+ overlapping: "Command cannot use the terminal while it is in use"
684
+ };
685
+ function isRecord(value) {
686
+ return typeof value === "object" && value !== null && !Array.isArray(value);
687
+ }
688
+ function assertNoNul(value, label) {
689
+ if (value.includes("\0")) throw new TypeError(`${label} must not contain NUL characters`);
690
+ }
691
+ function readInteger(value, label, minimum, maximum) {
692
+ if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) throw new TypeError(`${label} must be an integer from ${minimum} to ${maximum}`);
693
+ return value;
694
+ }
695
+ function readEnum(value, label, allowed) {
696
+ if (typeof value === "string" && allowed.includes(value)) return value;
697
+ throw new TypeError(`Exec option \`${label}\` must be ${allowed.map((item) => JSON.stringify(item)).join(" or ")}`);
698
+ }
699
+ function readBoolean(value, label) {
700
+ if (typeof value !== "boolean") throw new TypeError(`Exec option \`${label}\` must be a boolean`);
701
+ return value;
702
+ }
703
+ function readRedact(value) {
704
+ if (!Array.isArray(value)) throw new TypeError("Exec option `redact` must be an array");
705
+ for (const item of value) {
706
+ if (typeof item !== "string" || item === "") throw new TypeError("Exec option `redact` must contain only non-empty strings");
707
+ assertNoNul(item, "Exec option `redact`");
708
+ }
709
+ return value;
710
+ }
711
+ function mergeEnvironment(base, overrides) {
712
+ if (overrides === void 0 && base === void 0) return void 0;
713
+ if (overrides !== void 0 && !isRecord(overrides)) throw new TypeError("Exec option `env` must be an object");
714
+ const merged = {};
715
+ for (const [key, value] of Object.entries(base ?? process.env)) if (value !== void 0) merged[key] = value;
716
+ for (const [key, value] of Object.entries(overrides ?? {})) {
717
+ if (key === "" || key.includes("=") || key.includes("\0")) throw new TypeError(`Invalid environment variable name ${JSON.stringify(key)}`);
718
+ if (value === void 0) {
719
+ delete merged[key];
720
+ continue;
721
+ }
722
+ if (typeof value !== "string") throw new TypeError(`Environment variable ${key} must be a string or undefined`);
723
+ assertNoNul(value, `Environment variable ${key}`);
724
+ merged[key] = value;
725
+ }
726
+ return merged;
727
+ }
728
+ function redactValue(value, secrets) {
729
+ let redacted = value;
730
+ for (const secret of [...secrets].sort((left, right) => right.length - left.length)) redacted = redacted.split(secret).join("***");
731
+ return redacted;
732
+ }
733
+ function redactCommand(command, secrets) {
734
+ if (secrets.length === 0) return command;
735
+ return {
736
+ file: redactValue(command.file, secrets),
737
+ args: command.args.map((arg) => redactValue(arg, secrets)),
738
+ cwd: command.cwd === void 0 ? void 0 : redactValue(command.cwd, secrets)
739
+ };
740
+ }
741
+ function redactError(error, secrets) {
742
+ const source = error;
743
+ const redacted = new Error(redactValue(error.message, secrets));
744
+ if (typeof error.stack === "string") redacted.stack = redactValue(error.stack, secrets);
745
+ if (source.code !== void 0) redacted.code = source.code;
746
+ if (source.errno !== void 0) redacted.errno = source.errno;
747
+ if (typeof source.syscall === "string") redacted.syscall = redactValue(source.syscall, secrets);
748
+ if (typeof source.path === "string") redacted.path = redactValue(source.path, secrets);
749
+ if (Array.isArray(source.spawnargs)) redacted.spawnargs = source.spawnargs.map((arg) => typeof arg === "string" ? redactValue(arg, secrets) : arg);
750
+ return redacted;
751
+ }
752
+ function observedError(plan, error) {
753
+ return plan.redact.length === 0 ? error : redactError(error, plan.redact);
754
+ }
755
+ function parsePlan(file, args, options, baseEnv) {
756
+ if (typeof file !== "string" || file === "") throw new TypeError("Exec file must be a non-empty string");
757
+ assertNoNul(file, "Exec file");
758
+ const argv = args === void 0 ? [] : args;
759
+ if (!Array.isArray(argv) || !argv.every((arg) => typeof arg === "string")) throw new TypeError("Exec args must be an array of strings");
760
+ for (const arg of argv) assertNoNul(arg, "Exec args");
761
+ const raw = options === void 0 ? {} : options;
762
+ if (!isRecord(raw)) throw new TypeError("Exec options must be an object");
763
+ for (const key of Object.keys(raw)) if (!OPTION_KEYS.has(key)) throw new TypeError(`Unknown exec option \`${key}\``);
764
+ const { cwd, input, reject, timeout, signal, maxBuffer, forceKillAfterDelay } = raw;
765
+ if (cwd !== void 0) {
766
+ if (typeof cwd !== "string" || cwd === "") throw new TypeError("Exec option `cwd` must be a non-empty string");
767
+ assertNoNul(cwd, "Exec option `cwd`");
768
+ }
769
+ if (input !== void 0 && typeof input !== "string") throw new TypeError("Exec option `input` must be a string");
770
+ if (reject !== void 0 && typeof reject !== "boolean") throw new TypeError("Exec option `reject` must be a boolean");
771
+ if (signal !== void 0 && !(signal instanceof AbortSignal)) throw new TypeError("Exec option `signal` must be an AbortSignal");
772
+ const stdin = raw.stdin === void 0 ? "pipe" : readEnum(raw.stdin, "stdin", ["pipe", "inherit"]);
773
+ const stdout = raw.stdout === void 0 ? "pipe" : readEnum(raw.stdout, "stdout", ["pipe", "inherit"]);
774
+ const stderr = raw.stderr === void 0 ? "pipe" : readEnum(raw.stderr, "stderr", ["pipe", "inherit"]);
775
+ if (input !== void 0 && stdin === "inherit") throw new TypeError("Exec option `input` cannot be combined with stdin inherit");
776
+ const command = {
777
+ file,
778
+ args: [...argv],
779
+ cwd
780
+ };
781
+ const redact = raw.redact === void 0 ? [] : readRedact(raw.redact);
782
+ return {
783
+ command,
784
+ reported: redactCommand(command, redact),
785
+ env: mergeEnvironment(baseEnv, raw.env),
786
+ input,
787
+ reject: reject ?? true,
788
+ timeout: timeout === void 0 ? void 0 : readInteger(timeout, "Exec option `timeout`", 1, MAX_DELAY),
789
+ signal,
790
+ maxBuffer: maxBuffer === void 0 ? 1e8 : readInteger(maxBuffer, "Exec option `maxBuffer`", 1, Number.MAX_SAFE_INTEGER),
791
+ forceKillAfterDelay: forceKillAfterDelay === void 0 ? 5e3 : readInteger(forceKillAfterDelay, "Exec option `forceKillAfterDelay`", 0, MAX_DELAY),
792
+ stdin,
793
+ stdout,
794
+ stderr,
795
+ interactive: raw.interactive === void 0 ? false : readBoolean(raw.interactive, "interactive"),
796
+ display: raw.display === void 0 ? true : readBoolean(raw.display, "display"),
797
+ redact
798
+ };
799
+ }
800
+ function stripFinalNewline(text) {
801
+ if (text.endsWith("\r\n")) return text.slice(0, -2);
802
+ if (text.endsWith("\n")) return text.slice(0, -1);
803
+ return text;
804
+ }
805
+ function collect(stream, limit, onLimit) {
806
+ const chunks = [];
807
+ let length = 0;
808
+ let exceeded = false;
809
+ stream.on("data", (chunk) => {
810
+ if (exceeded) return;
811
+ const remaining = limit - length;
812
+ if (chunk.length <= remaining) {
813
+ chunks.push(chunk);
814
+ length += chunk.length;
815
+ return;
816
+ }
817
+ chunks.push(chunk.subarray(0, remaining));
818
+ length = limit;
819
+ exceeded = true;
820
+ onLimit();
821
+ });
822
+ return () => stripFinalNewline(Buffer.concat(chunks).toString("utf8"));
823
+ }
824
+ function toResult(command, outcome, stdout, stderr) {
825
+ const failed = {
826
+ status: "failed",
827
+ command,
828
+ stdout,
829
+ stderr
830
+ };
831
+ if (outcome.cause) return {
832
+ ...failed,
833
+ reason: outcome.cause
834
+ };
835
+ if (outcome.spawnError) return {
836
+ ...failed,
837
+ reason: "spawn",
838
+ error: outcome.spawnError
839
+ };
840
+ const { code, signal } = outcome.exit ?? {
841
+ code: null,
842
+ signal: null
843
+ };
844
+ if (signal !== null) return {
845
+ ...failed,
846
+ reason: "signal",
847
+ signal
848
+ };
849
+ if (code !== 0) return {
850
+ ...failed,
851
+ reason: "exit-code",
852
+ exitCode: code ?? 1
853
+ };
854
+ return {
855
+ status: "succeeded",
856
+ command,
857
+ stdout,
858
+ stderr,
859
+ exitCode: 0
860
+ };
861
+ }
862
+ function isSpawnErrno(error) {
863
+ return error instanceof Error && "syscall" in error;
864
+ }
865
+ function commandStdio(plan) {
866
+ return {
867
+ stdin: plan.stdin === "inherit" ? "inherit" : plan.input === void 0 ? "ignore" : "input",
868
+ stdout: plan.stdout,
869
+ stderr: plan.stderr
870
+ };
871
+ }
872
+ function usesTerminal(plan, stdio) {
873
+ return plan.interactive || stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
874
+ }
875
+ function startChild(plan) {
876
+ const stdio = commandStdio(plan);
877
+ try {
878
+ return spawn(plan.command.file, plan.command.args, {
879
+ cwd: plan.command.cwd,
880
+ env: plan.env,
881
+ shell: false,
882
+ windowsHide: true,
883
+ stdio: [
884
+ stdio.stdin === "inherit" ? "inherit" : stdio.stdin === "input" ? "pipe" : "ignore",
885
+ stdio.stdout,
886
+ stdio.stderr
887
+ ]
888
+ });
889
+ } catch (error) {
890
+ if (isSpawnErrno(error)) return error;
891
+ throw error;
892
+ }
893
+ }
894
+ function runPlan(plan) {
895
+ const outcome = {
896
+ cause: void 0,
897
+ spawnError: void 0,
898
+ exit: void 0
899
+ };
900
+ if (plan.signal?.aborted) {
901
+ outcome.cause = "aborted";
902
+ return Promise.resolve(toResult(plan.reported, outcome, "", ""));
903
+ }
904
+ const child = startChild(plan);
905
+ if (child instanceof Error) {
906
+ outcome.spawnError = observedError(plan, child);
907
+ return Promise.resolve(toResult(plan.reported, outcome, "", ""));
908
+ }
909
+ return new Promise((resolve) => {
910
+ const stdout = child.stdout;
911
+ const stderr = child.stderr;
912
+ let settled = false;
913
+ let killTimer;
914
+ const settle = () => {
915
+ if (settled) return;
916
+ settled = true;
917
+ clearTimeout(timeoutTimer);
918
+ clearTimeout(killTimer);
919
+ plan.signal?.removeEventListener("abort", onAbort);
920
+ resolve(toResult(plan.reported, outcome, readStdout(), readStderr()));
921
+ };
922
+ const release = () => {
923
+ stdout?.destroy();
924
+ stderr?.destroy();
925
+ settle();
926
+ };
927
+ const terminate = (cause) => {
928
+ if (settled || outcome.cause) return;
929
+ outcome.cause = cause;
930
+ if (outcome.exit) {
931
+ release();
932
+ return;
933
+ }
934
+ child.kill("SIGTERM");
935
+ killTimer = setTimeout(() => child.kill("SIGKILL"), plan.forceKillAfterDelay);
936
+ };
937
+ const onAbort = () => terminate("aborted");
938
+ const readStdout = stdout ? collect(stdout, plan.maxBuffer, () => terminate("output-limit")) : () => "";
939
+ const readStderr = stderr ? collect(stderr, plan.maxBuffer, () => terminate("output-limit")) : () => "";
940
+ const timeoutTimer = plan.timeout === void 0 ? void 0 : setTimeout(() => terminate("timeout"), plan.timeout);
941
+ plan.signal?.addEventListener("abort", onAbort, { once: true });
942
+ child.on("error", (error) => {
943
+ if (child.pid !== void 0) return;
944
+ outcome.spawnError = observedError(plan, error);
945
+ release();
946
+ });
947
+ child.on("exit", (code, signal) => {
948
+ outcome.exit = {
949
+ code,
950
+ signal
951
+ };
952
+ clearTimeout(killTimer);
953
+ if (outcome.cause) release();
954
+ });
955
+ child.on("close", settle);
956
+ if (child.stdin) {
957
+ child.stdin.on("error", () => void 0);
958
+ child.stdin.end(plan.input);
959
+ }
960
+ });
961
+ }
962
+ function formatArgument(value) {
963
+ return value === "" || /[\s"'\\$`;|&<>*?()[\]{}~#!]/.test(value) ? JSON.stringify(value) : value;
964
+ }
965
+ function formatExecCommand(command) {
966
+ return [command.file, ...command.args].map(formatArgument).join(" ");
967
+ }
968
+ function failureMessage(plan, result) {
969
+ const display = formatExecCommand(plan.reported);
970
+ switch (result.reason) {
971
+ case "exit-code": return `Command failed with exit code ${result.exitCode}: ${display}`;
972
+ case "signal": return `Command was terminated by ${result.signal}: ${display}`;
973
+ case "spawn": return `Command could not be started (${result.error.code ?? result.error.message}): ${display}`;
974
+ case "timeout": return `Command timed out after ${plan.timeout} ms: ${display}`;
975
+ case "aborted": return `Command was aborted: ${display}`;
976
+ case "output-limit": return `Command output exceeded ${plan.maxBuffer} bytes: ${display}`;
977
+ case "json-output":
978
+ case "non-interactive":
979
+ case "overlapping": return `${REFUSAL_MESSAGE[result.reason]}: ${display}`;
980
+ }
981
+ }
982
+ function emptyFailure(plan, reason) {
983
+ return {
984
+ status: "failed",
985
+ command: plan.reported,
986
+ stdout: "",
987
+ stderr: "",
988
+ reason
989
+ };
990
+ }
991
+ function complete(plan, result) {
992
+ if (result.status === "failed" && plan.reject) throw new HackshExecError(failureMessage(plan, result), result, result.reason === "spawn" ? { cause: result.error } : void 0);
993
+ return result;
994
+ }
995
+ function blockedReason(plan, context, stdio) {
996
+ const inherited = stdio.stdin === "inherit" || stdio.stdout === "inherit" || stdio.stderr === "inherit";
997
+ if (context.mode.format === "json" && inherited) return "json-output";
998
+ if (plan.interactive && !context.mode.interactive) return "non-interactive";
999
+ }
1000
+ async function execute(file, args, options, context) {
1001
+ const plan = parsePlan(file, args, options, context.env);
1002
+ if (plan.signal?.aborted) return complete(plan, emptyFailure(plan, "aborted"));
1003
+ const stdio = commandStdio(plan);
1004
+ const blocked = blockedReason(plan, context, stdio);
1005
+ if (blocked) return complete(plan, emptyFailure(plan, blocked));
1006
+ const holdTerminal = usesTerminal(plan, stdio);
1007
+ if (holdTerminal && !context.terminal.acquire("exec")) return complete(plan, emptyFailure(plan, "overlapping"));
1008
+ try {
1009
+ if (plan.display) {
1010
+ const { file, args, cwd } = plan.reported;
1011
+ await context.print({
1012
+ type: "exec",
1013
+ phase: "start",
1014
+ command: cwd === void 0 ? {
1015
+ file,
1016
+ args
1017
+ } : {
1018
+ file,
1019
+ args,
1020
+ cwd
1021
+ },
1022
+ stdio,
1023
+ interactive: plan.interactive
1024
+ });
1025
+ }
1026
+ return complete(plan, await runPlan(plan));
1027
+ } finally {
1028
+ if (holdTerminal) context.terminal.release();
1029
+ }
1030
+ }
1031
+ function createExec(context) {
1032
+ return ((file, args, options) => execute(file, args, options, context));
1033
+ }
1034
+ //#endregion
633
1035
  //#region src/serialization.ts
634
1036
  function safeString(value) {
635
1037
  try {
@@ -758,7 +1160,7 @@ function isErrorLevel(level) {
758
1160
  return level === "warning" || level === "error" || level === "fatal";
759
1161
  }
760
1162
  function eventStream(event) {
761
- if (event.type === "error") return "stderr";
1163
+ if (event.type === "error" || event.type === "exec") return "stderr";
762
1164
  if (event.type === "usage") return event.stream;
763
1165
  if (event.type === "log" && isErrorLevel(event.record.level)) return "stderr";
764
1166
  return "stdout";
@@ -770,6 +1172,7 @@ function formatTextEvent(event) {
770
1172
  case "error": return event.error.kind === "usage" || event.error.kind === "validation" ? event.error.message : `Error: ${event.error.message}`;
771
1173
  case "log": return event.record.message;
772
1174
  case "prompt": return event.prompt.message;
1175
+ case "exec": return `$ ${formatExecCommand(event.command)}`;
773
1176
  }
774
1177
  }
775
1178
  /** Undecorated console compatibility adapter; automatic output uses the selected format printer. */
@@ -859,7 +1262,8 @@ const unicodeSymbols = {
859
1262
  usage: "◆",
860
1263
  version: "●",
861
1264
  errorEvent: "■",
862
- prompt: "?"
1265
+ prompt: "?",
1266
+ exec: "$"
863
1267
  }
864
1268
  };
865
1269
  const asciiSymbols = {
@@ -876,7 +1280,8 @@ const asciiSymbols = {
876
1280
  usage: "?",
877
1281
  version: "i",
878
1282
  errorEvent: "x",
879
- prompt: "?"
1283
+ prompt: "?",
1284
+ exec: "$"
880
1285
  }
881
1286
  };
882
1287
  const markStyles = {
@@ -895,7 +1300,8 @@ const markStyles = {
895
1300
  usage: "cyan",
896
1301
  version: "blue",
897
1302
  errorEvent: "red",
898
- prompt: "cyan"
1303
+ prompt: "cyan",
1304
+ exec: "gray"
899
1305
  };
900
1306
  function safelyInspect(value) {
901
1307
  if (typeof value === "string") return value;
@@ -942,6 +1348,7 @@ function eventMark(event) {
942
1348
  if (event.type === "version") return "version";
943
1349
  if (event.type === "error") return "errorEvent";
944
1350
  if (event.type === "prompt") return event.prompt.status === "submitted" ? "success" : "prompt";
1351
+ if (event.type === "exec") return "exec";
945
1352
  return event.record.kind === "message" ? event.record.level : event.record.kind;
946
1353
  }
947
1354
  function eventLines(event) {
@@ -963,6 +1370,11 @@ function eventLines(event) {
963
1370
  if (prompt.error) lines.push(prompt.error);
964
1371
  return lines;
965
1372
  }
1373
+ case "exec": {
1374
+ const lines = [formatExecCommand(event.command)];
1375
+ if (event.command.cwd !== void 0) lines.push(...propertyLines({ cwd: event.command.cwd }));
1376
+ return lines;
1377
+ }
966
1378
  }
967
1379
  }
968
1380
  function colorize(enabled, style, text) {
@@ -1075,11 +1487,12 @@ var PromptController = class {
1075
1487
  #inputValue;
1076
1488
  #printEvent;
1077
1489
  #bufferedKeys = [];
1078
- #active = false;
1490
+ #terminal;
1079
1491
  constructor(options) {
1080
1492
  this.#interactive = options.interactive;
1081
1493
  this.#inputValue = options.input;
1082
1494
  this.#printEvent = (event) => options.print(event);
1495
+ this.#terminal = options.terminal;
1083
1496
  }
1084
1497
  async #print(state, prompt) {
1085
1498
  try {
@@ -1093,9 +1506,8 @@ var PromptController = class {
1093
1506
  }
1094
1507
  async #run(prompt, initial, accept, update, submitted, closed) {
1095
1508
  if (!this.#interactive) throw promptError("non-interactive", prompt, `Cannot request ${prompt.kind} input in non-interactive mode: ${prompt.message}`);
1096
- if (this.#active) throw promptError("overlapping", prompt, "Another prompt is already active");
1097
1509
  const input = asInput(this.#inputValue, prompt);
1098
- this.#active = true;
1510
+ if (!this.#terminal.acquire("prompt")) throw promptError("overlapping", prompt, this.#terminal.owner === "exec" ? "A command is using the terminal" : "Another prompt is already active");
1099
1511
  let originallyFlowing = false;
1100
1512
  let rawChanged = false;
1101
1513
  let outcome;
@@ -1218,7 +1630,7 @@ var PromptController = class {
1218
1630
  try {
1219
1631
  await this.#print(closed(), prompt);
1220
1632
  } catch {} finally {
1221
- this.#active = false;
1633
+ this.#terminal.release();
1222
1634
  }
1223
1635
  throw failure;
1224
1636
  };
@@ -1233,7 +1645,7 @@ var PromptController = class {
1233
1645
  } catch {}
1234
1646
  throw error;
1235
1647
  } finally {
1236
- this.#active = false;
1648
+ this.#terminal.release();
1237
1649
  }
1238
1650
  }
1239
1651
  async select(options) {
@@ -1380,7 +1792,8 @@ function createLogger(printer, category, options) {
1380
1792
  const prompts = new PromptController({
1381
1793
  interactive: options.interactive,
1382
1794
  input: options.input,
1383
- print: (event) => options.printPrompt(event)
1795
+ print: (event) => options.printPrompt(event),
1796
+ terminal: options.terminal
1384
1797
  });
1385
1798
  return createBoundLogger(printer, [...category], {}, prompts);
1386
1799
  }
@@ -1605,7 +2018,8 @@ function resolveCommand(root, rawArgs) {
1605
2018
  remaining = remaining.slice(index + 1);
1606
2019
  }
1607
2020
  }
1608
- async function executeCommand(root, argv, displayUsage, printer, loggerOptions, env) {
2021
+ async function executeCommand(root, argv, displayUsage, printer, loggerOptions, execContext) {
2022
+ const env = execContext.env ?? process.env;
1609
2023
  const resolved = resolveCommand(root, argv);
1610
2024
  if (resolved.segments[0]?.tokenized.version) {
1611
2025
  await printer.print({
@@ -1626,7 +2040,8 @@ async function executeCommand(root, argv, displayUsage, printer, loggerOptions,
1626
2040
  ...parsed,
1627
2041
  cmd: resolved.command,
1628
2042
  rawArgs: resolved.rawArgs,
1629
- logger: createLogger(printer, category, loggerOptions)
2043
+ logger: createLogger(printer, category, loggerOptions),
2044
+ exec: createExec(execContext)
1630
2045
  });
1631
2046
  }
1632
2047
  function printUsage(displayUsage, command, printer, stream) {
@@ -1647,19 +2062,32 @@ function prepareOutput(options, env) {
1647
2062
  });
1648
2063
  return {
1649
2064
  ...createPrintQueue(selectOutputPrinter(options, options.printer, mode)),
2065
+ format: mode.format,
1650
2066
  interactive: mode.interactive,
1651
- input
2067
+ input,
2068
+ terminal: new TerminalLock()
2069
+ };
2070
+ }
2071
+ function invocationExec(output, env) {
2072
+ return {
2073
+ env,
2074
+ mode: {
2075
+ format: output.format,
2076
+ interactive: output.interactive
2077
+ },
2078
+ print: (event) => output.printer.print(event),
2079
+ terminal: output.terminal
1652
2080
  };
1653
2081
  }
1654
2082
  async function runCommand(root, argv, options = {}) {
1655
- const env = options.env ?? process.env;
1656
- const output = prepareOutput(options, env);
2083
+ const output = prepareOutput(options, options.env ?? process.env);
1657
2084
  try {
1658
2085
  return await executeCommand(root, argv, options.showUsage ?? showUsage, output.printer, {
1659
2086
  interactive: output.interactive,
1660
2087
  input: output.input,
1661
- printPrompt: (event) => output.printChecked(event)
1662
- }, env);
2088
+ printPrompt: (event) => output.printChecked(event),
2089
+ terminal: output.terminal
2090
+ }, invocationExec(output, options.env));
1663
2091
  } finally {
1664
2092
  await output.drain();
1665
2093
  }
@@ -1691,10 +2119,9 @@ async function reportOutputSetupError(error, options) {
1691
2119
  }
1692
2120
  async function runMain(command, options = {}) {
1693
2121
  const displayUsage = options.showUsage ?? showUsage;
1694
- const env = options.env ?? process.env;
1695
2122
  let output;
1696
2123
  try {
1697
- output = prepareOutput(options, env);
2124
+ output = prepareOutput(options, options.env ?? process.env);
1698
2125
  } catch (error) {
1699
2126
  return reportOutputSetupError(error, options);
1700
2127
  }
@@ -1702,8 +2129,9 @@ async function runMain(command, options = {}) {
1702
2129
  await executeCommand(command, options.argv ?? process.argv.slice(2), displayUsage, output.printer, {
1703
2130
  interactive: output.interactive,
1704
2131
  input: output.input,
1705
- printPrompt: (event) => output.printChecked(event)
1706
- }, env);
2132
+ printPrompt: (event) => output.printChecked(event),
2133
+ terminal: output.terminal
2134
+ }, invocationExec(output, options.env));
1707
2135
  } catch (error) {
1708
2136
  if (options.onError) {
1709
2137
  await options.onError(error);
@@ -1733,6 +2161,6 @@ function createMain(command, options = {}) {
1733
2161
  });
1734
2162
  }
1735
2163
  //#endregion
1736
- export { HackshPromptError, HackshUsageError, HackshValidationError, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
2164
+ export { HackshExecError, HackshPromptError, HackshUsageError, HackshValidationError, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
1737
2165
 
1738
2166
  //# sourceMappingURL=index.js.map