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/README.md +90 -1
- package/dist/index.d.ts +133 -55
- package/dist/index.js +450 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|