hacksh 0.9.0 → 0.11.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/README.md +44 -5
- package/dist/index.d.ts +9 -2
- package/dist/index.js +99 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -304,10 +304,12 @@ multiselect prompt.
|
|
|
304
304
|
Prompts are allowed only when the resolved mode is interactive: pretty output outside CI with TTY
|
|
305
305
|
stdin and stdout. JSON, CI, and non-TTY invocations throw `HackshPromptError` immediately even if a
|
|
306
306
|
default is available. Its `reason` and `prompt` fields identify why input failed and the prompt's
|
|
307
|
-
`kind`, `message`, and select `choices`. Reasons distinguish `non-interactive`, `cancelled`,
|
|
308
|
-
`overlapping`, `invalid-options`, `input-error`, and `printer-error`. Ctrl-C and
|
|
309
|
-
`runMain` reports cancellation and exits with status 130.
|
|
310
|
-
invocation
|
|
307
|
+
`kind`, `message`, and select `choices`. Reasons distinguish `non-interactive`, `cancelled`,
|
|
308
|
+
`aborted`, `eof`, `overlapping`, `invalid-options`, `input-error`, and `printer-error`. Ctrl-C and
|
|
309
|
+
Escape cancel; `runMain` reports cancellation and exits with status 130. A prompt that starts or is
|
|
310
|
+
waiting after the invocation's `signal` aborts fails with `aborted` and the signal's reason as its
|
|
311
|
+
`cause`. Only one prompt may be active in an invocation. Input listeners and raw terminal mode are
|
|
312
|
+
restored after every outcome.
|
|
311
313
|
Failed prompts emit a non-visible `closed` prompt state through the same printer before rejecting,
|
|
312
314
|
so callers that catch cancellation, EOF, input, or printer errors do not leave stale terminal UI.
|
|
313
315
|
|
|
@@ -352,7 +354,8 @@ narrowed before reading `exitCode`.
|
|
|
352
354
|
|
|
353
355
|
Options are `cwd`, `env`, `input`, `reject`, `timeout` (milliseconds), `signal` (an
|
|
354
356
|
`AbortSignal`), `maxBuffer` (bytes per stream, default 100,000,000), and `forceKillAfterDelay`
|
|
355
|
-
(milliseconds, default 5,000).
|
|
357
|
+
(milliseconds, default 5,000). Every call also stops with `aborted` when the invocation's `signal`
|
|
358
|
+
aborts. `input` is written to stdin and closed; otherwise stdin is empty.
|
|
356
359
|
Output beyond `maxBuffer` is truncated to that many bytes. On timeout, abort, or output limit,
|
|
357
360
|
hacksh sends `SIGTERM` to the child it started and `SIGKILL` after `forceKillAfterDelay`; it does
|
|
358
361
|
not stop grandchildren, and it does not wait for them to close inherited pipes. Invalid arguments
|
|
@@ -571,6 +574,42 @@ Usage failures are `HackshUsageError` instances. Argument failures are
|
|
|
571
574
|
`HackshValidationError` instances whose `issues` identify CLI names such as `--repo` or `<PR>`.
|
|
572
575
|
Prompt failures are `HackshPromptError` instances with structured `reason` and `prompt` fields.
|
|
573
576
|
|
|
577
|
+
## Cancellation and exit status
|
|
578
|
+
|
|
579
|
+
Every `run` context has a `signal`, an `AbortSignal` for that invocation. `exec` calls and prompts
|
|
580
|
+
already follow it; pass it to `fetch` and other APIs that accept one. Cancellation is cooperative:
|
|
581
|
+
hacksh does not stop code that ignores the signal.
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
import { defineCommand, HackshExitError, runCommand } from "hacksh";
|
|
585
|
+
|
|
586
|
+
const check = defineCommand({
|
|
587
|
+
name: "check",
|
|
588
|
+
async run({ signal }) {
|
|
589
|
+
const response = await fetch("https://example.com/health", { signal });
|
|
590
|
+
if (!response.ok) throw new HackshExitError(2, `Health check returned ${response.status}`);
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const controller = new AbortController();
|
|
595
|
+
await runCommand(check, [], { signal: controller.signal });
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
`runCommand` accepts a `signal` option and never listens for process signals or exits. If that
|
|
599
|
+
signal is already aborted, `runCommand` rejects with its reason without calling `run`; aborting it
|
|
600
|
+
later aborts the context's `signal`.
|
|
601
|
+
|
|
602
|
+
`runMain` listens for `SIGINT` and `SIGTERM` while it runs and removes those listeners before it
|
|
603
|
+
returns or exits. The first signal aborts the invocation with an `AbortError` named after the
|
|
604
|
+
signal; once `run` settles, output is drained and the process exits with 130 for `SIGINT` or 143 for
|
|
605
|
+
`SIGTERM`, even if `run` threw. A second signal while `run` is still pending exits immediately with
|
|
606
|
+
that signal's status.
|
|
607
|
+
|
|
608
|
+
Throw `HackshExitError(exitCode, message?)` to choose the exit status, an integer from 0 to 255.
|
|
609
|
+
`runMain` prints a non-empty message as an ordinary error and prints nothing when the message is
|
|
610
|
+
omitted; `onError` still receives the error. `runCommand` rethrows it, so library callers read
|
|
611
|
+
`exitCode` from the caught error. Other failures exit with 1, or 130 for a cancelled prompt.
|
|
612
|
+
|
|
574
613
|
## Development
|
|
575
614
|
|
|
576
615
|
| Command | Description |
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ declare class HackshValidationError extends HackshUsageError {
|
|
|
14
14
|
constructor(command: AnyCommandDef, issues: ReadonlyArray<HackshIssue>);
|
|
15
15
|
}
|
|
16
16
|
type PromptKind = "select" | "confirm" | "text";
|
|
17
|
-
type HackshPromptErrorReason = "non-interactive" | "cancelled" | "eof" | "overlapping" | "invalid-options" | "input-error" | "printer-error";
|
|
17
|
+
type HackshPromptErrorReason = "non-interactive" | "cancelled" | "aborted" | "eof" | "overlapping" | "invalid-options" | "input-error" | "printer-error";
|
|
18
18
|
interface PromptErrorDetails {
|
|
19
19
|
kind: PromptKind;
|
|
20
20
|
message: string;
|
|
@@ -32,6 +32,11 @@ declare class HackshExecError extends Error {
|
|
|
32
32
|
constructor(message: string, result: ExecFailure, options?: ErrorOptions);
|
|
33
33
|
get result(): ExecFailure;
|
|
34
34
|
}
|
|
35
|
+
/** A requested process exit status; `runMain` exits with it and reports only a non-empty message. */
|
|
36
|
+
declare class HackshExitError extends Error {
|
|
37
|
+
readonly exitCode: number;
|
|
38
|
+
constructor(exitCode: number, message?: string, options?: ErrorOptions);
|
|
39
|
+
}
|
|
35
40
|
type HackshErrorKind = "error" | "prompt" | "usage" | "validation" | "thrown";
|
|
36
41
|
interface ValidationIssueSummary {
|
|
37
42
|
arg: string;
|
|
@@ -367,6 +372,7 @@ interface CommandContext<S extends ArgsSchema> {
|
|
|
367
372
|
extraArgs: string[];
|
|
368
373
|
logger: Logger;
|
|
369
374
|
exec: Exec;
|
|
375
|
+
signal: AbortSignal;
|
|
370
376
|
}
|
|
371
377
|
interface CommandDef<S extends ArgsSchema = EmptyArgs> {
|
|
372
378
|
name?: string;
|
|
@@ -403,6 +409,7 @@ declare function createPrettyPrinter(options: PrettyPrinterOptions): Printer;
|
|
|
403
409
|
//#region src/run.d.ts
|
|
404
410
|
interface RunCommandOptions extends OutputOptions {
|
|
405
411
|
printer?: Printer;
|
|
412
|
+
signal?: AbortSignal;
|
|
406
413
|
showUsage?: ShowUsageCallback;
|
|
407
414
|
}
|
|
408
415
|
declare function runCommand<S extends ArgsSchema>(root: CommandDef<S>, argv: string[], options?: RunCommandOptions): Promise<unknown>;
|
|
@@ -415,5 +422,5 @@ interface RunMainOptions extends OutputOptions {
|
|
|
415
422
|
declare function runMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): Promise<void>;
|
|
416
423
|
declare function createMain<S extends ArgsSchema>(command: CommandDef<S>, options?: RunMainOptions): (argv?: string[]) => Promise<void>;
|
|
417
424
|
//#endregion
|
|
418
|
-
export { type ArgOptions, type ArgOptionsMap, type CommandContext, type CommandDef, type ConfirmOptions, type ConfirmPromptState, type ErrorPrintEvent, type Exec, type ExecCommand, type ExecFailure, type ExecFailureReason, type ExecOptions, type ExecPrintEvent, type ExecResult, type ExecReturn, type ExecSuccess, type HackshErrorKind, type HackshErrorSummary, HackshExecError, type HackshIssue, HackshPromptError, type HackshPromptErrorReason, HackshUsageError, HackshValidationError, type JsonPrinterOptions, type LogKind, type LogLevel, type LogPrintEvent, type LogProperties, type LogRecord, type Logger, type OutputFormat, type OutputMode, type OutputOptions, type OutputPreference, type OutputWriter, type PrettyPrinterOptions, type PrintEvent, type PrintStream, type Printer, type PromptChoiceState, type PromptErrorDetails, type PromptKind, type PromptPrintEvent, type PromptState, type PromptStatus, type Prompter, type ResolveOutputModeOptions, type RunCommandOptions, type RunMainOptions, type SelectOption, type SelectOptions, type SelectPromptState, type ShowUsageCallback, type ShowUsageOptions, type StreamPrinterOptions, type TerminalInput, type TerminalOutput, type TextOptions, type TextPromptState, type UsageArgumentSummary, type UsageCommandSummary, type UsageOptionSummary, type UsageOutput, type UsagePrintEvent, type UsageSummary, type UsageWriter, type ValidationIssueSummary, type VersionPrintEvent, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
|
|
425
|
+
export { type ArgOptions, type ArgOptionsMap, type CommandContext, type CommandDef, type ConfirmOptions, type ConfirmPromptState, type ErrorPrintEvent, type Exec, type ExecCommand, type ExecFailure, type ExecFailureReason, type ExecOptions, type ExecPrintEvent, type ExecResult, type ExecReturn, type ExecSuccess, type HackshErrorKind, type HackshErrorSummary, HackshExecError, HackshExitError, type HackshIssue, HackshPromptError, type HackshPromptErrorReason, HackshUsageError, HackshValidationError, type JsonPrinterOptions, type LogKind, type LogLevel, type LogPrintEvent, type LogProperties, type LogRecord, type Logger, type OutputFormat, type OutputMode, type OutputOptions, type OutputPreference, type OutputWriter, type PrettyPrinterOptions, type PrintEvent, type PrintStream, type Printer, type PromptChoiceState, type PromptErrorDetails, type PromptKind, type PromptPrintEvent, type PromptState, type PromptStatus, type Prompter, type ResolveOutputModeOptions, type RunCommandOptions, type RunMainOptions, type SelectOption, type SelectOptions, type SelectPromptState, type ShowUsageCallback, type ShowUsageOptions, type StreamPrinterOptions, type TerminalInput, type TerminalOutput, type TextOptions, type TextPromptState, type UsageArgumentSummary, type UsageCommandSummary, type UsageOptionSummary, type UsageOutput, type UsagePrintEvent, type UsageSummary, type UsageWriter, type ValidationIssueSummary, type VersionPrintEvent, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
|
|
419
426
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -185,6 +185,16 @@ var HackshExecError = class extends Error {
|
|
|
185
185
|
return this.#result;
|
|
186
186
|
}
|
|
187
187
|
};
|
|
188
|
+
/** A requested process exit status; `runMain` exits with it and reports only a non-empty message. */
|
|
189
|
+
var HackshExitError = class extends Error {
|
|
190
|
+
exitCode;
|
|
191
|
+
constructor(exitCode, message, options) {
|
|
192
|
+
if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) throw new TypeError("Exit code must be an integer from 0 to 255");
|
|
193
|
+
super(message, options);
|
|
194
|
+
this.name = "HackshExitError";
|
|
195
|
+
this.exitCode = exitCode;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
188
198
|
function safeString$1(value, fallback) {
|
|
189
199
|
try {
|
|
190
200
|
return String(value);
|
|
@@ -757,7 +767,7 @@ function redactError(error, secrets) {
|
|
|
757
767
|
function observedError(plan, error) {
|
|
758
768
|
return plan.redact.length === 0 ? error : redactError(error, plan.redact);
|
|
759
769
|
}
|
|
760
|
-
function parsePlan(file, args, options,
|
|
770
|
+
function parsePlan(file, args, options, context) {
|
|
761
771
|
if (typeof file !== "string" || file === "") throw new TypeError("Exec file must be a non-empty string");
|
|
762
772
|
assertNoNul(file, "Exec file");
|
|
763
773
|
const argv = args === void 0 ? [] : args;
|
|
@@ -787,11 +797,11 @@ function parsePlan(file, args, options, baseEnv) {
|
|
|
787
797
|
return {
|
|
788
798
|
command,
|
|
789
799
|
reported: redactCommand(command, redact),
|
|
790
|
-
env: mergeEnvironment(
|
|
800
|
+
env: mergeEnvironment(context.env, raw.env),
|
|
791
801
|
input,
|
|
792
802
|
reject: reject ?? true,
|
|
793
803
|
timeout: timeout === void 0 ? void 0 : readInteger(timeout, "Exec option `timeout`", 1, MAX_DELAY),
|
|
794
|
-
signal,
|
|
804
|
+
signal: signal === void 0 ? context.signal : AbortSignal.any([context.signal, signal]),
|
|
795
805
|
maxBuffer: maxBuffer === void 0 ? 1e8 : readInteger(maxBuffer, "Exec option `maxBuffer`", 1, Number.MAX_SAFE_INTEGER),
|
|
796
806
|
forceKillAfterDelay: forceKillAfterDelay === void 0 ? 5e3 : readInteger(forceKillAfterDelay, "Exec option `forceKillAfterDelay`", 0, MAX_DELAY),
|
|
797
807
|
stdin,
|
|
@@ -902,7 +912,7 @@ function runPlan(plan) {
|
|
|
902
912
|
spawnError: void 0,
|
|
903
913
|
exit: void 0
|
|
904
914
|
};
|
|
905
|
-
if (plan.signal
|
|
915
|
+
if (plan.signal.aborted) {
|
|
906
916
|
outcome.cause = "aborted";
|
|
907
917
|
return Promise.resolve(toResult(plan.reported, outcome, "", ""));
|
|
908
918
|
}
|
|
@@ -921,7 +931,7 @@ function runPlan(plan) {
|
|
|
921
931
|
settled = true;
|
|
922
932
|
clearTimeout(timeoutTimer);
|
|
923
933
|
clearTimeout(killTimer);
|
|
924
|
-
plan.signal
|
|
934
|
+
plan.signal.removeEventListener("abort", onAbort);
|
|
925
935
|
resolve(toResult(plan.reported, outcome, readStdout(), readStderr()));
|
|
926
936
|
};
|
|
927
937
|
const release = () => {
|
|
@@ -943,7 +953,7 @@ function runPlan(plan) {
|
|
|
943
953
|
const readStdout = stdout ? collect(stdout, plan.maxBuffer, () => terminate("output-limit")) : () => "";
|
|
944
954
|
const readStderr = stderr ? collect(stderr, plan.maxBuffer, () => terminate("output-limit")) : () => "";
|
|
945
955
|
const timeoutTimer = plan.timeout === void 0 ? void 0 : setTimeout(() => terminate("timeout"), plan.timeout);
|
|
946
|
-
plan.signal
|
|
956
|
+
plan.signal.addEventListener("abort", onAbort, { once: true });
|
|
947
957
|
child.on("error", (error) => {
|
|
948
958
|
if (child.pid !== void 0) return;
|
|
949
959
|
outcome.spawnError = observedError(plan, error);
|
|
@@ -1003,8 +1013,8 @@ function blockedReason(plan, context, stdio) {
|
|
|
1003
1013
|
if (plan.interactive && !context.mode.interactive) return "non-interactive";
|
|
1004
1014
|
}
|
|
1005
1015
|
async function execute(file, args, options, context) {
|
|
1006
|
-
const plan = parsePlan(file, args, options, context
|
|
1007
|
-
if (plan.signal
|
|
1016
|
+
const plan = parsePlan(file, args, options, context);
|
|
1017
|
+
if (plan.signal.aborted) return complete(plan, emptyFailure(plan, "aborted"));
|
|
1008
1018
|
const stdio = commandStdio(plan);
|
|
1009
1019
|
const blocked = blockedReason(plan, context, stdio);
|
|
1010
1020
|
if (blocked) return complete(plan, emptyFailure(plan, blocked));
|
|
@@ -1493,11 +1503,13 @@ var PromptController = class {
|
|
|
1493
1503
|
#printEvent;
|
|
1494
1504
|
#bufferedKeys = [];
|
|
1495
1505
|
#terminal;
|
|
1506
|
+
#signal;
|
|
1496
1507
|
constructor(options) {
|
|
1497
1508
|
this.#interactive = options.interactive;
|
|
1498
1509
|
this.#inputValue = options.input;
|
|
1499
1510
|
this.#printEvent = (event) => options.print(event);
|
|
1500
1511
|
this.#terminal = options.terminal;
|
|
1512
|
+
this.#signal = options.signal;
|
|
1501
1513
|
}
|
|
1502
1514
|
async #print(state, prompt) {
|
|
1503
1515
|
try {
|
|
@@ -1511,6 +1523,7 @@ var PromptController = class {
|
|
|
1511
1523
|
}
|
|
1512
1524
|
async #run(prompt, initial, accept, update, submitted, closed) {
|
|
1513
1525
|
if (!this.#interactive) throw promptError("non-interactive", prompt, `Cannot request ${prompt.kind} input in non-interactive mode: ${prompt.message}`);
|
|
1526
|
+
if (this.#signal.aborted) throw promptError("aborted", prompt, "Prompt aborted", this.#signal.reason);
|
|
1514
1527
|
const input = asInput(this.#inputValue, prompt);
|
|
1515
1528
|
if (!this.#terminal.acquire("prompt")) throw promptError("overlapping", prompt, this.#terminal.owner === "exec" ? "A command is using the terminal" : "Another prompt is already active");
|
|
1516
1529
|
let originallyFlowing = false;
|
|
@@ -1597,6 +1610,7 @@ var PromptController = class {
|
|
|
1597
1610
|
};
|
|
1598
1611
|
const onEnd = () => fail("eof", "Prompt input ended before a value was submitted");
|
|
1599
1612
|
const onError = (cause) => fail("input-error", "Prompt input failed", cause);
|
|
1613
|
+
const onAbort = () => fail("aborted", "Prompt aborted", this.#signal.reason);
|
|
1600
1614
|
const onData = (chunk) => {
|
|
1601
1615
|
if (typeof chunk === "string" || chunk instanceof Uint8Array) keypressInput.write(chunk);
|
|
1602
1616
|
else fail("input-error", "Prompt input produced a non-byte value");
|
|
@@ -1608,12 +1622,15 @@ var PromptController = class {
|
|
|
1608
1622
|
input.removeListener("end", onEnd);
|
|
1609
1623
|
input.removeListener("close", onEnd);
|
|
1610
1624
|
input.removeListener("error", onError);
|
|
1625
|
+
this.#signal.removeEventListener("abort", onAbort);
|
|
1611
1626
|
};
|
|
1612
1627
|
keypressInput.on("keypress", onKeypress);
|
|
1613
1628
|
input.on("data", onData);
|
|
1614
1629
|
input.on("end", onEnd);
|
|
1615
1630
|
input.on("close", onEnd);
|
|
1616
1631
|
input.on("error", onError);
|
|
1632
|
+
this.#signal.addEventListener("abort", onAbort, { once: true });
|
|
1633
|
+
if (this.#signal.aborted) onAbort();
|
|
1617
1634
|
input.resume();
|
|
1618
1635
|
drainKeys().catch((cause) => fail("input-error", "Prompt input could not be processed", cause));
|
|
1619
1636
|
})
|
|
@@ -1798,7 +1815,8 @@ function createLogger(printer, category, options) {
|
|
|
1798
1815
|
interactive: options.interactive,
|
|
1799
1816
|
input: options.input,
|
|
1800
1817
|
print: (event) => options.printPrompt(event),
|
|
1801
|
-
terminal: options.terminal
|
|
1818
|
+
terminal: options.terminal,
|
|
1819
|
+
signal: options.signal
|
|
1802
1820
|
});
|
|
1803
1821
|
return createBoundLogger(printer, [...category], {}, prompts);
|
|
1804
1822
|
}
|
|
@@ -2055,9 +2073,10 @@ function resolveCommand(root, rawArgs) {
|
|
|
2055
2073
|
remaining = remaining.slice(index + 1);
|
|
2056
2074
|
}
|
|
2057
2075
|
}
|
|
2058
|
-
async function executeCommand(root, argv, displayUsage,
|
|
2076
|
+
async function executeCommand(root, argv, displayUsage, output, env, signal, fallbackVersion = async () => void 0) {
|
|
2059
2077
|
if (root.args && "version" in schemaShape(root.args)) throw new TypeError("Root option `version` conflicts with the builtin --version");
|
|
2060
|
-
const
|
|
2078
|
+
const { printer } = output;
|
|
2079
|
+
const argEnv = env ?? process.env;
|
|
2061
2080
|
const resolved = resolveCommand(root, argv);
|
|
2062
2081
|
if (resolved.segments[0]?.tokenized.version) {
|
|
2063
2082
|
const version = root.version ?? await fallbackVersion() ?? "";
|
|
@@ -2071,16 +2090,33 @@ async function executeCommand(root, argv, displayUsage, printer, loggerOptions,
|
|
|
2071
2090
|
await printUsage(displayUsage, resolved.command, printer, "stdout");
|
|
2072
2091
|
return;
|
|
2073
2092
|
}
|
|
2074
|
-
for (const parent of resolved.parents) await parseCommandArgs(parent.command, parent.rawArgs, parent.tokenized,
|
|
2075
|
-
const parsed = await parseCommandArgs(resolved.command, resolved.rawArgs, resolved.tokenized,
|
|
2093
|
+
for (const parent of resolved.parents) await parseCommandArgs(parent.command, parent.rawArgs, parent.tokenized, argEnv, commandEnvPath(parent.command));
|
|
2094
|
+
const parsed = await parseCommandArgs(resolved.command, resolved.rawArgs, resolved.tokenized, argEnv, commandEnvPath(resolved.command));
|
|
2076
2095
|
if (!resolved.command.run) throw new HackshUsageError("No command specified", resolved.command);
|
|
2096
|
+
signal.throwIfAborted();
|
|
2077
2097
|
const category = resolved.segments.flatMap(({ command }) => command.name === void 0 ? [] : [command.name]);
|
|
2078
2098
|
return resolved.command.run({
|
|
2079
2099
|
...parsed,
|
|
2080
2100
|
cmd: resolved.command,
|
|
2081
2101
|
rawArgs: resolved.rawArgs,
|
|
2082
|
-
logger: createLogger(printer, category,
|
|
2083
|
-
|
|
2102
|
+
logger: createLogger(printer, category, {
|
|
2103
|
+
interactive: output.interactive,
|
|
2104
|
+
input: output.input,
|
|
2105
|
+
printPrompt: (event) => output.printChecked(event),
|
|
2106
|
+
terminal: output.terminal,
|
|
2107
|
+
signal
|
|
2108
|
+
}),
|
|
2109
|
+
exec: createExec({
|
|
2110
|
+
env,
|
|
2111
|
+
mode: {
|
|
2112
|
+
format: output.format,
|
|
2113
|
+
interactive: output.interactive
|
|
2114
|
+
},
|
|
2115
|
+
print: (event) => printer.print(event),
|
|
2116
|
+
terminal: output.terminal,
|
|
2117
|
+
signal
|
|
2118
|
+
}),
|
|
2119
|
+
signal
|
|
2084
2120
|
});
|
|
2085
2121
|
}
|
|
2086
2122
|
function printUsage(displayUsage, command, printer, stream) {
|
|
@@ -2107,26 +2143,10 @@ function prepareOutput(options, env) {
|
|
|
2107
2143
|
terminal: new TerminalLock()
|
|
2108
2144
|
};
|
|
2109
2145
|
}
|
|
2110
|
-
function invocationExec(output, env) {
|
|
2111
|
-
return {
|
|
2112
|
-
env,
|
|
2113
|
-
mode: {
|
|
2114
|
-
format: output.format,
|
|
2115
|
-
interactive: output.interactive
|
|
2116
|
-
},
|
|
2117
|
-
print: (event) => output.printer.print(event),
|
|
2118
|
-
terminal: output.terminal
|
|
2119
|
-
};
|
|
2120
|
-
}
|
|
2121
2146
|
async function runCommand(root, argv, options = {}) {
|
|
2122
2147
|
const output = prepareOutput(options, options.env ?? process.env);
|
|
2123
2148
|
try {
|
|
2124
|
-
return await executeCommand(root, argv, options.showUsage ?? showUsage, output.
|
|
2125
|
-
interactive: output.interactive,
|
|
2126
|
-
input: output.input,
|
|
2127
|
-
printPrompt: (event) => output.printChecked(event),
|
|
2128
|
-
terminal: output.terminal
|
|
2129
|
-
}, invocationExec(output, options.env));
|
|
2149
|
+
return await executeCommand(root, argv, options.showUsage ?? showUsage, output, options.env, options.signal ?? new AbortController().signal);
|
|
2130
2150
|
} finally {
|
|
2131
2151
|
await output.drain();
|
|
2132
2152
|
}
|
|
@@ -2139,8 +2159,33 @@ async function drainAndExit(output, code = 1) {
|
|
|
2139
2159
|
}
|
|
2140
2160
|
}
|
|
2141
2161
|
function commandErrorExitCode(error) {
|
|
2162
|
+
if (error instanceof HackshExitError) return error.exitCode;
|
|
2142
2163
|
return error instanceof HackshPromptError && error.reason === "cancelled" ? 130 : 1;
|
|
2143
2164
|
}
|
|
2165
|
+
const SIGNAL_EXIT_CODES = {
|
|
2166
|
+
SIGINT: 130,
|
|
2167
|
+
SIGTERM: 143
|
|
2168
|
+
};
|
|
2169
|
+
function listenForExitSignals() {
|
|
2170
|
+
const controller = new AbortController();
|
|
2171
|
+
let exitCode;
|
|
2172
|
+
const listeners = Object.entries(SIGNAL_EXIT_CODES).map(([signal, code]) => {
|
|
2173
|
+
const listener = () => {
|
|
2174
|
+
if (exitCode !== void 0) process.exit(code);
|
|
2175
|
+
exitCode = code;
|
|
2176
|
+
controller.abort(new DOMException(`Received ${signal}`, "AbortError"));
|
|
2177
|
+
};
|
|
2178
|
+
process.on(signal, listener);
|
|
2179
|
+
return [signal, listener];
|
|
2180
|
+
});
|
|
2181
|
+
return {
|
|
2182
|
+
signal: controller.signal,
|
|
2183
|
+
exitCode: () => exitCode,
|
|
2184
|
+
release() {
|
|
2185
|
+
for (const [signal, listener] of listeners) process.removeListener(signal, listener);
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2144
2189
|
async function reportOutputSetupError(error, options) {
|
|
2145
2190
|
if (options.onError) {
|
|
2146
2191
|
await options.onError(error);
|
|
@@ -2156,6 +2201,14 @@ async function reportOutputSetupError(error, options) {
|
|
|
2156
2201
|
});
|
|
2157
2202
|
return drainAndExit(output);
|
|
2158
2203
|
}
|
|
2204
|
+
async function reportCommandError(error, output, displayUsage) {
|
|
2205
|
+
if (error instanceof HackshExitError && error.message === "") return;
|
|
2206
|
+
await output.printer.print({
|
|
2207
|
+
type: "error",
|
|
2208
|
+
error: summarizeError(error)
|
|
2209
|
+
});
|
|
2210
|
+
if (error instanceof HackshUsageError) await printUsage(displayUsage, error.command, output.printer, "stderr");
|
|
2211
|
+
}
|
|
2159
2212
|
async function runMain(command, options = {}) {
|
|
2160
2213
|
const displayUsage = options.showUsage ?? showUsage;
|
|
2161
2214
|
let output;
|
|
@@ -2164,33 +2217,28 @@ async function runMain(command, options = {}) {
|
|
|
2164
2217
|
} catch (error) {
|
|
2165
2218
|
return reportOutputSetupError(error, options);
|
|
2166
2219
|
}
|
|
2220
|
+
const signals = listenForExitSignals();
|
|
2221
|
+
const exit = (code) => {
|
|
2222
|
+
signals.release();
|
|
2223
|
+
return drainAndExit(output, signals.exitCode() ?? code);
|
|
2224
|
+
};
|
|
2167
2225
|
try {
|
|
2168
|
-
await executeCommand(command, options.argv ?? process.argv.slice(2), displayUsage, output.
|
|
2169
|
-
interactive: output.interactive,
|
|
2170
|
-
input: output.input,
|
|
2171
|
-
printPrompt: (event) => output.printChecked(event),
|
|
2172
|
-
terminal: output.terminal
|
|
2173
|
-
}, invocationExec(output, options.env), () => findPackageVersion(process.argv[1]));
|
|
2226
|
+
await executeCommand(command, options.argv ?? process.argv.slice(2), displayUsage, output, options.env, signals.signal, () => findPackageVersion(process.argv[1]));
|
|
2174
2227
|
} catch (error) {
|
|
2175
2228
|
if (options.onError) {
|
|
2176
2229
|
await options.onError(error);
|
|
2177
|
-
return
|
|
2230
|
+
return exit(commandErrorExitCode(error));
|
|
2178
2231
|
}
|
|
2179
2232
|
try {
|
|
2180
|
-
|
|
2181
|
-
await output.printer.print({
|
|
2182
|
-
type: "error",
|
|
2183
|
-
error: summarizeError(error)
|
|
2184
|
-
});
|
|
2185
|
-
await printUsage(displayUsage, error.command, output.printer, "stderr");
|
|
2186
|
-
} else await output.printer.print({
|
|
2187
|
-
type: "error",
|
|
2188
|
-
error: summarizeError(error)
|
|
2189
|
-
});
|
|
2233
|
+
await reportCommandError(error, output, displayUsage);
|
|
2190
2234
|
} finally {
|
|
2191
|
-
await
|
|
2235
|
+
await exit(commandErrorExitCode(error));
|
|
2192
2236
|
}
|
|
2237
|
+
} finally {
|
|
2238
|
+
signals.release();
|
|
2193
2239
|
}
|
|
2240
|
+
const signalExitCode = signals.exitCode();
|
|
2241
|
+
if (signalExitCode !== void 0) return drainAndExit(output, signalExitCode);
|
|
2194
2242
|
await output.drain();
|
|
2195
2243
|
}
|
|
2196
2244
|
function createMain(command, options = {}) {
|
|
@@ -2200,6 +2248,6 @@ function createMain(command, options = {}) {
|
|
|
2200
2248
|
});
|
|
2201
2249
|
}
|
|
2202
2250
|
//#endregion
|
|
2203
|
-
export { HackshExecError, HackshPromptError, HackshUsageError, HackshValidationError, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
|
|
2251
|
+
export { HackshExecError, HackshExitError, HackshPromptError, HackshUsageError, HackshValidationError, consolePrinter, createJsonPrinter, createMain, createPrettyPrinter, createStreamPrinter, defineCommand, parseArgs, renderUsage, resolveOutputMode, runCommand, runMain, showUsage, summarizeUsage };
|
|
2204
2252
|
|
|
2205
2253
|
//# sourceMappingURL=index.js.map
|