@orkestrel/process 0.0.15 → 0.0.16
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/src/core/index.cjs +46 -21
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +14 -14
- package/dist/src/core/index.d.ts +14 -14
- package/dist/src/core/index.js +47 -22
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +345 -314
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +23 -9
- package/dist/src/server/index.d.ts +23 -9
- package/dist/src/server/index.js +345 -315
- package/dist/src/server/index.js.map +1 -1
- package/package.json +16 -16
|
@@ -9,7 +9,7 @@ let _src_core = require("../core/index.cjs");
|
|
|
9
9
|
let node_readline = require("node:readline");
|
|
10
10
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
11
11
|
let node_string_decoder = require("node:string_decoder");
|
|
12
|
-
|
|
12
|
+
// src/server/cloners.ts
|
|
13
13
|
/**
|
|
14
14
|
* Takes one owned frozen snapshot of a caller's command.
|
|
15
15
|
*
|
|
@@ -44,8 +44,7 @@ function snapshotCommand(command) {
|
|
|
44
44
|
...isolated === void 0 ? {} : { isolated }
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
//#region src/server/helpers.ts
|
|
47
|
+
// src/server/helpers.ts
|
|
49
48
|
/**
|
|
50
49
|
* Trims a buffer to at most `limit` trailing bytes without splitting a UTF-8 sequence.
|
|
51
50
|
*
|
|
@@ -66,13 +65,13 @@ function snapshotCommand(command) {
|
|
|
66
65
|
function trimTail(bytes, limit) {
|
|
67
66
|
const buffer = node_buffer.Buffer.isBuffer(bytes) ? bytes : node_buffer.Buffer.from(bytes);
|
|
68
67
|
if (buffer.byteLength <= limit) return buffer;
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const offset = buffer.byteLength - limit;
|
|
69
|
+
if (!(offset < buffer.byteLength)) return node_buffer.Buffer.from(buffer.subarray(offset));
|
|
70
|
+
for (const start of Array.from({ length: Math.ceil(buffer.byteLength - offset) }, (_, index) => offset + index)) {
|
|
71
71
|
const byte = buffer[start];
|
|
72
|
-
if (byte === void 0 || (byte & 192) !== 128)
|
|
73
|
-
start += 1;
|
|
72
|
+
if (byte === void 0 || (byte & 192) !== 128) return node_buffer.Buffer.from(buffer.subarray(start));
|
|
74
73
|
}
|
|
75
|
-
return node_buffer.Buffer.from(buffer.subarray(
|
|
74
|
+
return node_buffer.Buffer.from(buffer.subarray(buffer.byteLength));
|
|
76
75
|
}
|
|
77
76
|
/**
|
|
78
77
|
* Trims a buffer to at most `limit` leading bytes without splitting a UTF-8 sequence.
|
|
@@ -100,18 +99,12 @@ function trimHead(bytes, limit) {
|
|
|
100
99
|
const excluded = buffer[limit];
|
|
101
100
|
if (excluded === void 0 || (excluded & 192) !== 128) return node_buffer.Buffer.from(buffer.subarray(0, limit));
|
|
102
101
|
const floor = Math.max(0, limit - 3);
|
|
103
|
-
|
|
104
|
-
while (start >= floor) {
|
|
102
|
+
for (const start of Array.from({ length: Math.max(0, Math.floor(limit - floor)) }, (_, index) => limit - 1 - index)) {
|
|
105
103
|
const lead = buffer[start];
|
|
106
104
|
if (lead !== void 0 && (lead & 192) !== 128) {
|
|
107
|
-
|
|
108
|
-
if ((lead & 224) === 192) span = 2;
|
|
109
|
-
else if ((lead & 240) === 224) span = 3;
|
|
110
|
-
else if ((lead & 248) === 240) span = 4;
|
|
111
|
-
if (start + span > limit) return node_buffer.Buffer.from(buffer.subarray(0, start));
|
|
105
|
+
if (start + ((lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1) > limit) return node_buffer.Buffer.from(buffer.subarray(0, start));
|
|
112
106
|
break;
|
|
113
107
|
}
|
|
114
|
-
start -= 1;
|
|
115
108
|
}
|
|
116
109
|
return node_buffer.Buffer.from(buffer.subarray(0, limit));
|
|
117
110
|
}
|
|
@@ -553,6 +546,27 @@ function isExited(child) {
|
|
|
553
546
|
return child.exitCode !== null || child.signalCode !== null;
|
|
554
547
|
}
|
|
555
548
|
/**
|
|
549
|
+
* Reports whether a process or POSIX process group still exists.
|
|
550
|
+
*
|
|
551
|
+
* @param pid - A positive process id, or a negative POSIX process-group id
|
|
552
|
+
* @returns True when the host confirms existence or refuses permission; false for ESRCH; undefined for another host failure
|
|
553
|
+
* @throws A {@link ProcessError} coded `invalid` for an unsafe integer, zero, minus one, or a negative id on Windows
|
|
554
|
+
* @remarks Negative ids probe POSIX process groups. The `process.platform` branch refuses negative ids on Windows, where the host supports positive process ids only.
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* inspectProcess(process.pid) // true
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
function inspectProcess(pid) {
|
|
561
|
+
if (!Number.isSafeInteger(pid) || pid === 0 || pid === -1 || process.platform === "win32" && pid < 0) throw (0, _src_core.createInvalidError)("process id", pid);
|
|
562
|
+
try {
|
|
563
|
+
return process.kill(pid, 0);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
const code = (0, _orkestrel_contract.isError)(error) && "code" in error ? error.code : void 0;
|
|
566
|
+
return code === "ESRCH" ? false : code === "EPERM" ? true : void 0;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
556
570
|
* Signals one owned child process, or its detached process group on a POSIX host.
|
|
557
571
|
*
|
|
558
572
|
* @remarks
|
|
@@ -745,262 +759,6 @@ function buildExecuteResult(input) {
|
|
|
745
759
|
});
|
|
746
760
|
}
|
|
747
761
|
/**
|
|
748
|
-
* Runs one command to completion, buffering its output, and settles with the outcome.
|
|
749
|
-
*
|
|
750
|
-
* @remarks
|
|
751
|
-
* The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
|
|
752
|
-
* child is detached, which is what lets its whole process group be terminated. Standard output and
|
|
753
|
-
* error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
|
|
754
|
-
* whether either stream exceeded it. Each capture keeps one byte beyond `limit`, so the single
|
|
755
|
-
* code-point-boundary trim in {@link buildExecuteResult} reads the first excluded byte and never
|
|
756
|
-
* delivers a split multibyte sequence. A positive `timeout` and an aborting `signal` both terminate the
|
|
757
|
-
* child through the same bounded stop, and only the earliest is recorded: a timeout reports
|
|
758
|
-
* `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
|
|
759
|
-
* a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
|
|
760
|
-
* covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
|
|
761
|
-
* rather than on process exit, so a descendant that inherited the child's stdio holds the run open
|
|
762
|
-
* after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
|
|
763
|
-
* the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
|
|
764
|
-
* overrides `command.input`. A host fault while writing that input terminates the run by design and
|
|
765
|
-
* marks its result failed; a strict rejection carries the host fault as its cause. This differs
|
|
766
|
-
* from `Process` constructor input, whose package-initiated input phase stays quiet. Unless `strict`
|
|
767
|
-
* is `false`, a failed run rejects with a {@link ProcessError} carrying the {@link ExecuteResult};
|
|
768
|
-
* {@link createExecuteError} constructs every rejection outside the input-fault door. An invalid
|
|
769
|
-
* option or command string rejects before the child is spawned, because an async function cannot
|
|
770
|
-
* throw synchronously.
|
|
771
|
-
*
|
|
772
|
-
* @param command - The executable, arguments, and optional environment and input
|
|
773
|
-
* @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
|
|
774
|
-
* @returns The settled run outcome
|
|
775
|
-
* @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
|
|
776
|
-
*
|
|
777
|
-
* @example
|
|
778
|
-
* ```ts
|
|
779
|
-
* const result = await execute({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
|
|
780
|
-
* ```
|
|
781
|
-
*/
|
|
782
|
-
async function execute(command, options) {
|
|
783
|
-
const snapshot = snapshotCommand(command);
|
|
784
|
-
const optionEnvironment = options?.environment;
|
|
785
|
-
const optionWorkspace = options?.workspace;
|
|
786
|
-
const optionInput = options?.input;
|
|
787
|
-
const optionTimeout = options?.timeout;
|
|
788
|
-
const optionGrace = options?.grace;
|
|
789
|
-
const optionSignal = options?.signal;
|
|
790
|
-
const optionStrict = options?.strict;
|
|
791
|
-
const optionLimit = options?.limit;
|
|
792
|
-
validateCommand(snapshot);
|
|
793
|
-
validateEnvironment(optionEnvironment);
|
|
794
|
-
validateWorkspace(optionWorkspace);
|
|
795
|
-
validateTimer(optionTimeout, "option 'timeout'");
|
|
796
|
-
validateTimer(optionGrace, "option 'grace'");
|
|
797
|
-
validateBytes(optionLimit, "option 'limit'", 0);
|
|
798
|
-
const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
|
|
799
|
-
const grace = optionGrace ?? _src_core.PROCESS_GRACE;
|
|
800
|
-
const timeout = optionTimeout ?? 0;
|
|
801
|
-
const strict = optionStrict ?? true;
|
|
802
|
-
const text = optionInput ?? snapshot.input;
|
|
803
|
-
const line = formatCommand(snapshot);
|
|
804
|
-
const workspace = optionWorkspace ?? process.cwd();
|
|
805
|
-
const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
|
|
806
|
-
const plan = buildSpawn(snapshot, {
|
|
807
|
-
workspace,
|
|
808
|
-
environment
|
|
809
|
-
});
|
|
810
|
-
const settled = Promise.withResolvers();
|
|
811
|
-
const terminate = new AbortController();
|
|
812
|
-
const cleanup = new AbortController();
|
|
813
|
-
const finish = new AbortController();
|
|
814
|
-
const inputFailure = new AbortController();
|
|
815
|
-
const outChunks = [];
|
|
816
|
-
const errChunks = [];
|
|
817
|
-
let spawned = false;
|
|
818
|
-
let expired = false;
|
|
819
|
-
let aborted = false;
|
|
820
|
-
let outRetained = 0;
|
|
821
|
-
let errRetained = 0;
|
|
822
|
-
let cause;
|
|
823
|
-
let timeoutTimer;
|
|
824
|
-
let confirmTimer;
|
|
825
|
-
const child = (0, node_child_process.spawn)(plan.file, [...plan.arguments], {
|
|
826
|
-
cwd: workspace,
|
|
827
|
-
detached: process.platform !== "win32",
|
|
828
|
-
env: environment,
|
|
829
|
-
stdio: [
|
|
830
|
-
"pipe",
|
|
831
|
-
"pipe",
|
|
832
|
-
"pipe"
|
|
833
|
-
],
|
|
834
|
-
windowsHide: true,
|
|
835
|
-
windowsVerbatimArguments: plan.verbatim
|
|
836
|
-
});
|
|
837
|
-
finish.signal.addEventListener("abort", () => {
|
|
838
|
-
clearTimeout(timeoutTimer);
|
|
839
|
-
clearTimeout(confirmTimer);
|
|
840
|
-
cleanup.abort();
|
|
841
|
-
settled.resolve(buildExecuteResult({
|
|
842
|
-
command: line,
|
|
843
|
-
stdout: node_buffer.Buffer.concat(outChunks),
|
|
844
|
-
stderr: node_buffer.Buffer.concat(errChunks),
|
|
845
|
-
code: child.exitCode,
|
|
846
|
-
signal: child.signalCode,
|
|
847
|
-
expired,
|
|
848
|
-
aborted,
|
|
849
|
-
truncated: outRetained > limit || errRetained > limit,
|
|
850
|
-
limit,
|
|
851
|
-
...cause === void 0 ? {} : { cause }
|
|
852
|
-
}));
|
|
853
|
-
}, { once: true });
|
|
854
|
-
terminate.signal.addEventListener("abort", () => {
|
|
855
|
-
if (finish.signal.aborted) return;
|
|
856
|
-
clearTimeout(timeoutTimer);
|
|
857
|
-
cleanup.abort();
|
|
858
|
-
stopChild(child, grace, _src_core.PROCESS_CONFIRMATION).then(() => {
|
|
859
|
-
if (finish.signal.aborted) return;
|
|
860
|
-
confirmTimer = setTimeout(() => finish.abort(), _src_core.PROCESS_CONFIRMATION);
|
|
861
|
-
});
|
|
862
|
-
}, { once: true });
|
|
863
|
-
inputFailure.signal.addEventListener("abort", () => {
|
|
864
|
-
if (finish.signal.aborted) return;
|
|
865
|
-
cause = inputFailure.signal.reason;
|
|
866
|
-
terminate.abort();
|
|
867
|
-
}, { once: true });
|
|
868
|
-
child.stdin.on("error", (error) => inputFailure.abort(error));
|
|
869
|
-
child.stdout.on("data", (chunk) => {
|
|
870
|
-
const captured = captureChunk(chunk, limit + 1 - outRetained);
|
|
871
|
-
if (captured === void 0) return;
|
|
872
|
-
outRetained += captured.byteLength;
|
|
873
|
-
outChunks.push(captured);
|
|
874
|
-
});
|
|
875
|
-
child.stderr.on("data", (chunk) => {
|
|
876
|
-
const captured = captureChunk(chunk, limit + 1 - errRetained);
|
|
877
|
-
if (captured === void 0) return;
|
|
878
|
-
errRetained += captured.byteLength;
|
|
879
|
-
errChunks.push(captured);
|
|
880
|
-
});
|
|
881
|
-
child.once("spawn", () => {
|
|
882
|
-
spawned = true;
|
|
883
|
-
});
|
|
884
|
-
child.once("error", (error) => {
|
|
885
|
-
if (spawned) return;
|
|
886
|
-
cause = error;
|
|
887
|
-
finish.abort();
|
|
888
|
-
});
|
|
889
|
-
child.once("close", () => finish.abort());
|
|
890
|
-
if (text !== void 0) child.stdin.write(text, (error) => {
|
|
891
|
-
if (error !== void 0 && error !== null) inputFailure.abort(error);
|
|
892
|
-
});
|
|
893
|
-
child.stdin.end();
|
|
894
|
-
if (timeout > 0) timeoutTimer = setTimeout(() => {
|
|
895
|
-
expired = true;
|
|
896
|
-
terminate.abort();
|
|
897
|
-
}, timeout);
|
|
898
|
-
if (optionSignal !== void 0) {
|
|
899
|
-
optionSignal.addEventListener("abort", () => {
|
|
900
|
-
aborted = true;
|
|
901
|
-
terminate.abort();
|
|
902
|
-
}, {
|
|
903
|
-
once: true,
|
|
904
|
-
signal: cleanup.signal
|
|
905
|
-
});
|
|
906
|
-
if (optionSignal.aborted) {
|
|
907
|
-
aborted = true;
|
|
908
|
-
terminate.abort();
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
const result = await settled.promise;
|
|
912
|
-
if (result.failed && strict) {
|
|
913
|
-
if (cause !== void 0 && cause === inputFailure.signal.reason) throw new _src_core.ProcessError(`Command '${result.command}' failed while writing standard input`, {
|
|
914
|
-
code: "input",
|
|
915
|
-
context: {
|
|
916
|
-
command: result.command,
|
|
917
|
-
code: result.code,
|
|
918
|
-
signal: result.signal
|
|
919
|
-
},
|
|
920
|
-
cause: inputFailure.signal.reason,
|
|
921
|
-
result
|
|
922
|
-
});
|
|
923
|
-
throw (0, _src_core.createExecuteError)(result, cause);
|
|
924
|
-
}
|
|
925
|
-
return result;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Runs one command to completion synchronously, buffering its output, and returns the outcome.
|
|
929
|
-
*
|
|
930
|
-
* @remarks
|
|
931
|
-
* The synchronous counterpart of {@link execute}, spawned through the same resolver and never through a
|
|
932
|
-
* shell. The host offers no cooperative termination window and no in-flight cancellation, so this
|
|
933
|
-
* contract carries neither. A positive `timeout` ends only the root process and can leave
|
|
934
|
-
* descendants running; use {@link execute} or {@link Process} when timeout must terminate the tree. A
|
|
935
|
-
* timeout and an output overflow both end the root with `SIGKILL`: an overflow reports `truncated`
|
|
936
|
-
* and `failed` together and trims the partial output to `limit`, where {@link execute} keeps reading and
|
|
937
|
-
* reports `truncated` without failing. The environment and input follow the same merge as
|
|
938
|
-
* {@link execute}. Unless `strict` is `false`, a failed run throws a {@link createExecuteError}
|
|
939
|
-
* carrying the {@link ExecuteResult}.
|
|
940
|
-
*
|
|
941
|
-
* @param command - The executable, arguments, and optional environment and input
|
|
942
|
-
* @param options - Working directory, timeout, capture limit, and failure delivery
|
|
943
|
-
* @returns The run outcome
|
|
944
|
-
* @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
|
|
945
|
-
*
|
|
946
|
-
* @example
|
|
947
|
-
* ```ts
|
|
948
|
-
* const result = executeSync({ file: 'git', arguments: ['--version'] }, { strict: false })
|
|
949
|
-
* ```
|
|
950
|
-
*/
|
|
951
|
-
function executeSync(command, options) {
|
|
952
|
-
const snapshot = snapshotCommand(command);
|
|
953
|
-
const optionEnvironment = options?.environment;
|
|
954
|
-
const optionWorkspace = options?.workspace;
|
|
955
|
-
const optionInput = options?.input;
|
|
956
|
-
const optionTimeout = options?.timeout;
|
|
957
|
-
const optionStrict = options?.strict;
|
|
958
|
-
const optionLimit = options?.limit;
|
|
959
|
-
validateCommand(snapshot);
|
|
960
|
-
validateEnvironment(optionEnvironment);
|
|
961
|
-
validateWorkspace(optionWorkspace);
|
|
962
|
-
validateTimer(optionTimeout, "option 'timeout'");
|
|
963
|
-
validateBytes(optionLimit, "option 'limit'", 0);
|
|
964
|
-
const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
|
|
965
|
-
const timeout = optionTimeout ?? 0;
|
|
966
|
-
const strict = optionStrict ?? true;
|
|
967
|
-
const text = optionInput ?? snapshot.input;
|
|
968
|
-
const line = formatCommand(snapshot);
|
|
969
|
-
const workspace = optionWorkspace ?? process.cwd();
|
|
970
|
-
const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
|
|
971
|
-
const plan = buildSpawn(snapshot, {
|
|
972
|
-
workspace,
|
|
973
|
-
environment
|
|
974
|
-
});
|
|
975
|
-
const outcome = (0, node_child_process.spawnSync)(plan.file, [...plan.arguments], {
|
|
976
|
-
cwd: workspace,
|
|
977
|
-
env: environment,
|
|
978
|
-
encoding: "buffer",
|
|
979
|
-
maxBuffer: limit,
|
|
980
|
-
killSignal: "SIGKILL",
|
|
981
|
-
windowsHide: true,
|
|
982
|
-
windowsVerbatimArguments: plan.verbatim,
|
|
983
|
-
...text !== void 0 ? { input: node_buffer.Buffer.from(text) } : {},
|
|
984
|
-
...timeout > 0 ? { timeout } : {}
|
|
985
|
-
});
|
|
986
|
-
const error = outcome.error;
|
|
987
|
-
const fault = error !== void 0 && "code" in error && (0, _orkestrel_contract.isString)(error.code) ? error.code : "";
|
|
988
|
-
const result = buildExecuteResult({
|
|
989
|
-
command: line,
|
|
990
|
-
stdout: node_buffer.Buffer.isBuffer(outcome.stdout) ? outcome.stdout : node_buffer.Buffer.alloc(0),
|
|
991
|
-
stderr: node_buffer.Buffer.isBuffer(outcome.stderr) ? outcome.stderr : node_buffer.Buffer.alloc(0),
|
|
992
|
-
code: outcome.status,
|
|
993
|
-
signal: outcome.signal,
|
|
994
|
-
expired: fault === "ETIMEDOUT",
|
|
995
|
-
aborted: false,
|
|
996
|
-
truncated: fault === "ENOBUFS",
|
|
997
|
-
limit,
|
|
998
|
-
...error === void 0 ? {} : { cause: error }
|
|
999
|
-
});
|
|
1000
|
-
if (result.failed && strict) throw (0, _src_core.createExecuteError)(result, error);
|
|
1001
|
-
return result;
|
|
1002
|
-
}
|
|
1003
|
-
/**
|
|
1004
762
|
* Spawns one command as a detached process and returns without waiting for it.
|
|
1005
763
|
*
|
|
1006
764
|
* @remarks
|
|
@@ -1041,8 +799,7 @@ function detach(command, options) {
|
|
|
1041
799
|
child.once("error", () => void 0);
|
|
1042
800
|
child.unref();
|
|
1043
801
|
}
|
|
1044
|
-
|
|
1045
|
-
//#region src/server/processes/Supervisor.ts
|
|
802
|
+
// src/server/processes/Supervisor.ts
|
|
1046
803
|
/**
|
|
1047
804
|
* Supervises one child process and reports each lifecycle moment to the face composing it.
|
|
1048
805
|
*
|
|
@@ -1055,7 +812,7 @@ function detach(command, options) {
|
|
|
1055
812
|
* emitter and event map its consumers subscribe to.
|
|
1056
813
|
*
|
|
1057
814
|
* The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read the
|
|
1058
|
-
* host child directly, so they expose native exit as soon as the host records it, and `
|
|
815
|
+
* host child directly, so they expose native exit as soon as the host records it, and `ended`
|
|
1059
816
|
* settles at that same moment. `settled`, `exit`, and `evidence` reach one terminal moment after the
|
|
1060
817
|
* read channels close or the bounded `drain` window cuts them off. That window is armed by the
|
|
1061
818
|
* child's native exit and by a termination this package initiated, so every way a child can end
|
|
@@ -1105,12 +862,12 @@ var Supervisor = class {
|
|
|
1105
862
|
#delivery;
|
|
1106
863
|
#decoder = new node_string_decoder.StringDecoder("utf8");
|
|
1107
864
|
#exit = Promise.withResolvers();
|
|
1108
|
-
#
|
|
865
|
+
#ended = Promise.withResolvers();
|
|
1109
866
|
#signal;
|
|
1110
867
|
#abort;
|
|
1111
868
|
#writes = /* @__PURE__ */ new Map();
|
|
1112
869
|
#tail = node_buffer.Buffer.alloc(0);
|
|
1113
|
-
#
|
|
870
|
+
#terminated = false;
|
|
1114
871
|
#settled = false;
|
|
1115
872
|
#input = 0;
|
|
1116
873
|
#failure;
|
|
@@ -1221,8 +978,8 @@ var Supervisor = class {
|
|
|
1221
978
|
return this.#settled;
|
|
1222
979
|
}
|
|
1223
980
|
/** Reports whether termination began, including after the terminal moment. */
|
|
1224
|
-
get
|
|
1225
|
-
return this.#
|
|
981
|
+
get terminated() {
|
|
982
|
+
return this.#terminated;
|
|
1226
983
|
}
|
|
1227
984
|
/**
|
|
1228
985
|
* Settles at the child's own ending, awaited without the terminal moment's drain window.
|
|
@@ -1232,8 +989,8 @@ var Supervisor = class {
|
|
|
1232
989
|
* the host records them. It settles at the native exit, and at the terminal moment for a child
|
|
1233
990
|
* whose spawn produced no native exit at all, so it can never outlive the supervision.
|
|
1234
991
|
*/
|
|
1235
|
-
get
|
|
1236
|
-
return this.#
|
|
992
|
+
get ended() {
|
|
993
|
+
return this.#ended.promise;
|
|
1237
994
|
}
|
|
1238
995
|
/** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
|
|
1239
996
|
get exit() {
|
|
@@ -1279,7 +1036,7 @@ var Supervisor = class {
|
|
|
1279
1036
|
*/
|
|
1280
1037
|
deliver(bytes) {
|
|
1281
1038
|
const stdin = this.#child.stdin;
|
|
1282
|
-
if (this.#settled || this.#
|
|
1039
|
+
if (this.#settled || this.#terminated || this.#failure !== void 0 || !stdin.writable) return Promise.resolve(false);
|
|
1283
1040
|
const settled = Promise.withResolvers();
|
|
1284
1041
|
this.#writes.set(settled, void 0);
|
|
1285
1042
|
try {
|
|
@@ -1299,7 +1056,7 @@ var Supervisor = class {
|
|
|
1299
1056
|
* Closes the standard-input channel while leaving the child running.
|
|
1300
1057
|
*
|
|
1301
1058
|
* @remarks
|
|
1302
|
-
* Never rejects, and terminates nothing: no signal is sent, `
|
|
1059
|
+
* Never rejects, and terminates nothing: no signal is sent, `terminated` stays false, no drain
|
|
1303
1060
|
* window is armed, and the terminal moment does not arrive. The child observes end of input and
|
|
1304
1061
|
* decides for itself what to do about it, which is what separates this from `stop`.
|
|
1305
1062
|
*
|
|
@@ -1357,7 +1114,7 @@ var Supervisor = class {
|
|
|
1357
1114
|
this.#settle(true);
|
|
1358
1115
|
}
|
|
1359
1116
|
#expire() {
|
|
1360
|
-
this.#
|
|
1117
|
+
this.#ended.resolve();
|
|
1361
1118
|
if (this.#settled) return;
|
|
1362
1119
|
this.#wait();
|
|
1363
1120
|
}
|
|
@@ -1379,7 +1136,7 @@ var Supervisor = class {
|
|
|
1379
1136
|
drained
|
|
1380
1137
|
});
|
|
1381
1138
|
this.#exit.resolve(exit);
|
|
1382
|
-
this.#
|
|
1139
|
+
this.#ended.resolve();
|
|
1383
1140
|
this.#terminal(exit);
|
|
1384
1141
|
this.#child.stdout.destroy();
|
|
1385
1142
|
this.#child.stderr.destroy();
|
|
@@ -1403,7 +1160,7 @@ var Supervisor = class {
|
|
|
1403
1160
|
}
|
|
1404
1161
|
#endInput() {
|
|
1405
1162
|
const stdin = this.#child.stdin;
|
|
1406
|
-
if (this.#
|
|
1163
|
+
if (this.#terminated || this.#settled || stdin.writableEnded || stdin.destroyed) return Promise.resolve();
|
|
1407
1164
|
const ended = Promise.withResolvers();
|
|
1408
1165
|
stdin.end(() => ended.resolve());
|
|
1409
1166
|
return ended.promise;
|
|
@@ -1430,7 +1187,7 @@ var Supervisor = class {
|
|
|
1430
1187
|
this.#settleWrites();
|
|
1431
1188
|
return;
|
|
1432
1189
|
}
|
|
1433
|
-
if (this.#failure !== void 0 || this.#
|
|
1190
|
+
if (this.#failure !== void 0 || this.#terminated) return;
|
|
1434
1191
|
this.#failure = cause;
|
|
1435
1192
|
this.#settleWrites();
|
|
1436
1193
|
this.#fault(new _src_core.ProcessError("The standard-input channel failed", {
|
|
@@ -1439,7 +1196,7 @@ var Supervisor = class {
|
|
|
1439
1196
|
}));
|
|
1440
1197
|
}
|
|
1441
1198
|
async #kill() {
|
|
1442
|
-
this.#
|
|
1199
|
+
this.#terminated = true;
|
|
1443
1200
|
this.#relieve?.();
|
|
1444
1201
|
const confirmed = await stopChild(this.#child, this.#grace, _src_core.PROCESS_CONFIRMATION);
|
|
1445
1202
|
this.#settleWrites();
|
|
@@ -1453,8 +1210,7 @@ var Supervisor = class {
|
|
|
1453
1210
|
this.#teardown();
|
|
1454
1211
|
}
|
|
1455
1212
|
};
|
|
1456
|
-
|
|
1457
|
-
//#region src/server/processes/Process.ts
|
|
1213
|
+
// src/server/processes/Process.ts
|
|
1458
1214
|
/**
|
|
1459
1215
|
* Supervises one child, frames its standard output into lines under a bounded backlog, and keeps
|
|
1460
1216
|
* every observation channel aligned at termination.
|
|
@@ -1590,8 +1346,8 @@ var Process = class {
|
|
|
1590
1346
|
return this.#engine.settled;
|
|
1591
1347
|
}
|
|
1592
1348
|
/** Reports whether termination began, including after the terminal moment. */
|
|
1593
|
-
get
|
|
1594
|
-
return this.#engine.
|
|
1349
|
+
get terminated() {
|
|
1350
|
+
return this.#engine.terminated;
|
|
1595
1351
|
}
|
|
1596
1352
|
/** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
|
|
1597
1353
|
get exit() {
|
|
@@ -1685,9 +1441,9 @@ var Process = class {
|
|
|
1685
1441
|
return;
|
|
1686
1442
|
}
|
|
1687
1443
|
const bytes = node_buffer.Buffer.byteLength(line) + 1;
|
|
1688
|
-
const
|
|
1689
|
-
const limit =
|
|
1690
|
-
if ((!this.#requested ||
|
|
1444
|
+
const terminated = this.#engine.terminated;
|
|
1445
|
+
const limit = terminated ? this.#backlog * 2 : this.#backlog;
|
|
1446
|
+
if ((!this.#requested || terminated) && this.#pending + bytes > limit) {
|
|
1691
1447
|
this.#truncated = true;
|
|
1692
1448
|
return;
|
|
1693
1449
|
}
|
|
@@ -1696,14 +1452,14 @@ var Process = class {
|
|
|
1696
1452
|
this.#restrain();
|
|
1697
1453
|
}
|
|
1698
1454
|
#restrain() {
|
|
1699
|
-
if (this.#paused || this.#engine.
|
|
1455
|
+
if (this.#paused || this.#engine.terminated || !this.#requested) return;
|
|
1700
1456
|
if (this.#pending < this.#backlog) return;
|
|
1701
1457
|
this.#paused = true;
|
|
1702
1458
|
this.#reader.pause();
|
|
1703
1459
|
}
|
|
1704
1460
|
#relieve() {
|
|
1705
1461
|
if (!this.#paused || this.#ended) return;
|
|
1706
|
-
if (!this.#engine.
|
|
1462
|
+
if (!this.#engine.terminated && this.#pending > this.#backlog / 2) return;
|
|
1707
1463
|
this.#paused = false;
|
|
1708
1464
|
this.#reader.resume();
|
|
1709
1465
|
}
|
|
@@ -1743,8 +1499,7 @@ var Process = class {
|
|
|
1743
1499
|
}
|
|
1744
1500
|
}
|
|
1745
1501
|
};
|
|
1746
|
-
|
|
1747
|
-
//#region src/server/processes/ProcessManager.ts
|
|
1502
|
+
// src/server/processes/ProcessManager.ts
|
|
1748
1503
|
/**
|
|
1749
1504
|
* Launches supervised children under caller-chosen ids, evicts each one as it settles, and destroys
|
|
1750
1505
|
* every live child on teardown.
|
|
@@ -1781,7 +1536,7 @@ var ProcessManager = class {
|
|
|
1781
1536
|
#ids = /* @__PURE__ */ new Set();
|
|
1782
1537
|
#orphans = /* @__PURE__ */ new Set();
|
|
1783
1538
|
#destroying = false;
|
|
1784
|
-
#
|
|
1539
|
+
#ended;
|
|
1785
1540
|
/**
|
|
1786
1541
|
* Constructs a process registry.
|
|
1787
1542
|
*
|
|
@@ -1869,10 +1624,10 @@ var ProcessManager = class {
|
|
|
1869
1624
|
* @returns The stable barrier shared by every call
|
|
1870
1625
|
*/
|
|
1871
1626
|
destroy() {
|
|
1872
|
-
if (this.#
|
|
1627
|
+
if (this.#ended !== void 0) return this.#ended;
|
|
1873
1628
|
this.#destroying = true;
|
|
1874
|
-
this.#
|
|
1875
|
-
return this.#
|
|
1629
|
+
this.#ended = this.#teardown();
|
|
1630
|
+
return this.#ended;
|
|
1876
1631
|
}
|
|
1877
1632
|
#construct(id, options) {
|
|
1878
1633
|
try {
|
|
@@ -1911,8 +1666,7 @@ var ProcessManager = class {
|
|
|
1911
1666
|
this.#emitter.destroy();
|
|
1912
1667
|
}
|
|
1913
1668
|
};
|
|
1914
|
-
|
|
1915
|
-
//#region src/server/processes/Session.ts
|
|
1669
|
+
// src/server/processes/Session.ts
|
|
1916
1670
|
/**
|
|
1917
1671
|
* Supervises one child and publishes its standard output as raw bytes.
|
|
1918
1672
|
*
|
|
@@ -1930,7 +1684,7 @@ var ProcessManager = class {
|
|
|
1930
1684
|
*
|
|
1931
1685
|
* The standard-input channel is open from the spawn, and `end` closes it without terminating
|
|
1932
1686
|
* anything. That is the member a cooperative protocol reaches for: the child observes end of input
|
|
1933
|
-
* and exits on its own, which `
|
|
1687
|
+
* and exits on its own, which `ended` reports. `stop` and `destroy` remain the terminations, and
|
|
1934
1688
|
* `exit` remains the terminal moment — the native exit plus at most `drain` while a descendant holds
|
|
1935
1689
|
* the inherited read ends.
|
|
1936
1690
|
*
|
|
@@ -2012,12 +1766,12 @@ var Session = class {
|
|
|
2012
1766
|
return this.#engine.settled;
|
|
2013
1767
|
}
|
|
2014
1768
|
/** Reports whether termination began, including after the terminal moment. `end` never turns it true. */
|
|
2015
|
-
get
|
|
2016
|
-
return this.#engine.
|
|
1769
|
+
get terminated() {
|
|
1770
|
+
return this.#engine.terminated;
|
|
2017
1771
|
}
|
|
2018
1772
|
/** Settles at the child's own ending, awaited without the terminal moment's drain window. */
|
|
2019
|
-
get
|
|
2020
|
-
return this.#engine.
|
|
1773
|
+
get ended() {
|
|
1774
|
+
return this.#engine.ended;
|
|
2021
1775
|
}
|
|
2022
1776
|
/** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
|
|
2023
1777
|
get exit() {
|
|
@@ -2042,7 +1796,7 @@ var Session = class {
|
|
|
2042
1796
|
* Closes the standard-input channel and leaves the child running.
|
|
2043
1797
|
*
|
|
2044
1798
|
* @remarks
|
|
2045
|
-
* Never rejects and terminates nothing: `
|
|
1799
|
+
* Never rejects and terminates nothing: `terminated` stays false and the terminal moment does not
|
|
2046
1800
|
* arrive. Every call shares one barrier, which resolves after the host flushes the writes it had
|
|
2047
1801
|
* already accepted. A later `write` resolves `false`.
|
|
2048
1802
|
*
|
|
@@ -2090,8 +1844,7 @@ var Session = class {
|
|
|
2090
1844
|
this.#engine.stdout.off("data", this.#output);
|
|
2091
1845
|
}
|
|
2092
1846
|
};
|
|
2093
|
-
|
|
2094
|
-
//#region src/server/factories.ts
|
|
1847
|
+
// src/server/factories.ts
|
|
2095
1848
|
/**
|
|
2096
1849
|
* Creates one supervised child process and returns it as a {@link ProcessInterface}, so a caller
|
|
2097
1850
|
* holds the published contract rather than the `Process` class.
|
|
@@ -2160,7 +1913,284 @@ function createSession(options) {
|
|
|
2160
1913
|
function createProcessManager(options) {
|
|
2161
1914
|
return new ProcessManager(options);
|
|
2162
1915
|
}
|
|
2163
|
-
|
|
1916
|
+
// src/server/processes/Execution.ts
|
|
1917
|
+
/** Owns the child, capture, cancellation, and terminal state of one buffered execution. */
|
|
1918
|
+
var Execution = class {
|
|
1919
|
+
#child;
|
|
1920
|
+
#settled;
|
|
1921
|
+
#terminate;
|
|
1922
|
+
#cleanup;
|
|
1923
|
+
#finish;
|
|
1924
|
+
#inputFailure;
|
|
1925
|
+
#outChunks = [];
|
|
1926
|
+
#errChunks = [];
|
|
1927
|
+
#strict;
|
|
1928
|
+
#spawned = false;
|
|
1929
|
+
#expired = false;
|
|
1930
|
+
#aborted = false;
|
|
1931
|
+
#outRetained = 0;
|
|
1932
|
+
#errRetained = 0;
|
|
1933
|
+
#cause;
|
|
1934
|
+
#timeoutTimer;
|
|
1935
|
+
#confirmTimer;
|
|
1936
|
+
constructor(command, options) {
|
|
1937
|
+
const snapshot = snapshotCommand(command);
|
|
1938
|
+
const optionEnvironment = options?.environment;
|
|
1939
|
+
const optionWorkspace = options?.workspace;
|
|
1940
|
+
const optionInput = options?.input;
|
|
1941
|
+
const optionTimeout = options?.timeout;
|
|
1942
|
+
const optionGrace = options?.grace;
|
|
1943
|
+
const optionSignal = options?.signal;
|
|
1944
|
+
const optionStrict = options?.strict;
|
|
1945
|
+
const optionLimit = options?.limit;
|
|
1946
|
+
validateCommand(snapshot);
|
|
1947
|
+
validateEnvironment(optionEnvironment);
|
|
1948
|
+
validateWorkspace(optionWorkspace);
|
|
1949
|
+
validateTimer(optionTimeout, "option 'timeout'");
|
|
1950
|
+
validateTimer(optionGrace, "option 'grace'");
|
|
1951
|
+
validateBytes(optionLimit, "option 'limit'", 0);
|
|
1952
|
+
const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
|
|
1953
|
+
const grace = optionGrace ?? _src_core.PROCESS_GRACE;
|
|
1954
|
+
const timeout = optionTimeout ?? 0;
|
|
1955
|
+
this.#strict = optionStrict ?? true;
|
|
1956
|
+
const text = optionInput ?? snapshot.input;
|
|
1957
|
+
const line = formatCommand(snapshot);
|
|
1958
|
+
const workspace = optionWorkspace ?? process.cwd();
|
|
1959
|
+
const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
|
|
1960
|
+
const plan = buildSpawn(snapshot, {
|
|
1961
|
+
workspace,
|
|
1962
|
+
environment
|
|
1963
|
+
});
|
|
1964
|
+
this.#settled = Promise.withResolvers();
|
|
1965
|
+
this.#terminate = new AbortController();
|
|
1966
|
+
this.#cleanup = new AbortController();
|
|
1967
|
+
this.#finish = new AbortController();
|
|
1968
|
+
this.#inputFailure = new AbortController();
|
|
1969
|
+
this.#child = (0, node_child_process.spawn)(plan.file, [...plan.arguments], {
|
|
1970
|
+
cwd: workspace,
|
|
1971
|
+
detached: process.platform !== "win32",
|
|
1972
|
+
env: environment,
|
|
1973
|
+
stdio: [
|
|
1974
|
+
"pipe",
|
|
1975
|
+
"pipe",
|
|
1976
|
+
"pipe"
|
|
1977
|
+
],
|
|
1978
|
+
windowsHide: true,
|
|
1979
|
+
windowsVerbatimArguments: plan.verbatim
|
|
1980
|
+
});
|
|
1981
|
+
this.#finish.signal.addEventListener("abort", () => {
|
|
1982
|
+
clearTimeout(this.#timeoutTimer);
|
|
1983
|
+
clearTimeout(this.#confirmTimer);
|
|
1984
|
+
this.#cleanup.abort();
|
|
1985
|
+
this.#settled.resolve(buildExecuteResult({
|
|
1986
|
+
command: line,
|
|
1987
|
+
stdout: node_buffer.Buffer.concat(this.#outChunks),
|
|
1988
|
+
stderr: node_buffer.Buffer.concat(this.#errChunks),
|
|
1989
|
+
code: this.#child.exitCode,
|
|
1990
|
+
signal: this.#child.signalCode,
|
|
1991
|
+
expired: this.#expired,
|
|
1992
|
+
aborted: this.#aborted,
|
|
1993
|
+
truncated: this.#outRetained > limit || this.#errRetained > limit,
|
|
1994
|
+
limit,
|
|
1995
|
+
...this.#cause === void 0 ? {} : { cause: this.#cause }
|
|
1996
|
+
}));
|
|
1997
|
+
}, { once: true });
|
|
1998
|
+
this.#terminate.signal.addEventListener("abort", () => {
|
|
1999
|
+
if (this.#finish.signal.aborted) return;
|
|
2000
|
+
clearTimeout(this.#timeoutTimer);
|
|
2001
|
+
this.#cleanup.abort();
|
|
2002
|
+
stopChild(this.#child, grace, _src_core.PROCESS_CONFIRMATION).then(() => {
|
|
2003
|
+
if (this.#finish.signal.aborted) return;
|
|
2004
|
+
this.#confirmTimer = setTimeout(() => this.#finish.abort(), _src_core.PROCESS_CONFIRMATION);
|
|
2005
|
+
});
|
|
2006
|
+
}, { once: true });
|
|
2007
|
+
this.#inputFailure.signal.addEventListener("abort", () => {
|
|
2008
|
+
if (this.#finish.signal.aborted) return;
|
|
2009
|
+
this.#cause = this.#inputFailure.signal.reason;
|
|
2010
|
+
this.#terminate.abort();
|
|
2011
|
+
}, { once: true });
|
|
2012
|
+
this.#child.stdin.on("error", (error) => this.#inputFailure.abort(error));
|
|
2013
|
+
this.#child.stdout.on("data", (chunk) => {
|
|
2014
|
+
const captured = captureChunk(chunk, limit + 1 - this.#outRetained);
|
|
2015
|
+
if (captured === void 0) return;
|
|
2016
|
+
this.#outRetained += captured.byteLength;
|
|
2017
|
+
this.#outChunks.push(captured);
|
|
2018
|
+
});
|
|
2019
|
+
this.#child.stderr.on("data", (chunk) => {
|
|
2020
|
+
const captured = captureChunk(chunk, limit + 1 - this.#errRetained);
|
|
2021
|
+
if (captured === void 0) return;
|
|
2022
|
+
this.#errRetained += captured.byteLength;
|
|
2023
|
+
this.#errChunks.push(captured);
|
|
2024
|
+
});
|
|
2025
|
+
this.#child.once("spawn", () => {
|
|
2026
|
+
this.#spawned = true;
|
|
2027
|
+
});
|
|
2028
|
+
this.#child.once("error", (error) => {
|
|
2029
|
+
if (this.#spawned) return;
|
|
2030
|
+
this.#cause = error;
|
|
2031
|
+
this.#finish.abort();
|
|
2032
|
+
});
|
|
2033
|
+
this.#child.once("close", () => this.#finish.abort());
|
|
2034
|
+
if (text !== void 0) this.#child.stdin.write(text, (error) => {
|
|
2035
|
+
if (error !== void 0 && error !== null) this.#inputFailure.abort(error);
|
|
2036
|
+
});
|
|
2037
|
+
this.#child.stdin.end();
|
|
2038
|
+
if (timeout > 0) this.#timeoutTimer = setTimeout(() => {
|
|
2039
|
+
this.#expired = true;
|
|
2040
|
+
this.#terminate.abort();
|
|
2041
|
+
}, timeout);
|
|
2042
|
+
if (optionSignal !== void 0) {
|
|
2043
|
+
optionSignal.addEventListener("abort", () => {
|
|
2044
|
+
this.#aborted = true;
|
|
2045
|
+
this.#terminate.abort();
|
|
2046
|
+
}, {
|
|
2047
|
+
once: true,
|
|
2048
|
+
signal: this.#cleanup.signal
|
|
2049
|
+
});
|
|
2050
|
+
if (optionSignal.aborted) {
|
|
2051
|
+
this.#aborted = true;
|
|
2052
|
+
this.#terminate.abort();
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
get settled() {
|
|
2057
|
+
return this.#settled.promise;
|
|
2058
|
+
}
|
|
2059
|
+
read(result) {
|
|
2060
|
+
if (result.failed && this.#strict) {
|
|
2061
|
+
if (this.#cause !== void 0 && this.#cause === this.#inputFailure.signal.reason) throw new _src_core.ProcessError(`Command '${result.command}' failed while writing standard input`, {
|
|
2062
|
+
code: "input",
|
|
2063
|
+
context: {
|
|
2064
|
+
command: result.command,
|
|
2065
|
+
code: result.code,
|
|
2066
|
+
signal: result.signal
|
|
2067
|
+
},
|
|
2068
|
+
cause: this.#inputFailure.signal.reason,
|
|
2069
|
+
result
|
|
2070
|
+
});
|
|
2071
|
+
throw (0, _src_core.createExecuteError)(result, this.#cause);
|
|
2072
|
+
}
|
|
2073
|
+
return result;
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
// src/server/execution/execute.ts
|
|
2077
|
+
/**
|
|
2078
|
+
* Runs one command to completion, buffering its output, and settles with the outcome.
|
|
2079
|
+
*
|
|
2080
|
+
* @remarks
|
|
2081
|
+
* The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
|
|
2082
|
+
* child is detached, which is what lets its whole process group be terminated. Standard output and
|
|
2083
|
+
* error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
|
|
2084
|
+
* whether either stream exceeded it. Each capture keeps one byte beyond `limit`, so the single
|
|
2085
|
+
* code-point-boundary trim in {@link buildExecuteResult} reads the first excluded byte and never
|
|
2086
|
+
* delivers a split multibyte sequence. A positive `timeout` and an aborting `signal` both terminate the
|
|
2087
|
+
* child through the same bounded stop, and only the earliest is recorded: a timeout reports
|
|
2088
|
+
* `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
|
|
2089
|
+
* a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
|
|
2090
|
+
* covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
|
|
2091
|
+
* rather than on process exit, so a descendant that inherited the child's stdio holds the run open
|
|
2092
|
+
* after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
|
|
2093
|
+
* the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
|
|
2094
|
+
* overrides `command.input`. A host fault while writing that input terminates the run by design and
|
|
2095
|
+
* marks its result failed; a strict rejection carries the host fault as its cause. This differs
|
|
2096
|
+
* from `Process` constructor input, whose package-initiated input phase stays quiet. Unless `strict`
|
|
2097
|
+
* is `false`, a failed run rejects with a {@link ProcessError} carrying the {@link ExecuteResult};
|
|
2098
|
+
* {@link createExecuteError} constructs every rejection outside the input-fault door. An invalid
|
|
2099
|
+
* option or command string rejects before the child is spawned, because an async function cannot
|
|
2100
|
+
* throw synchronously.
|
|
2101
|
+
*
|
|
2102
|
+
* @param command - The executable, arguments, and optional environment and input
|
|
2103
|
+
* @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
|
|
2104
|
+
* @returns The settled run outcome
|
|
2105
|
+
* @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
|
|
2106
|
+
*
|
|
2107
|
+
* @example
|
|
2108
|
+
* ```ts
|
|
2109
|
+
* const result = await execute({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
|
|
2110
|
+
* ```
|
|
2111
|
+
*/
|
|
2112
|
+
async function execute(command, options) {
|
|
2113
|
+
const execution = new Execution(command, options);
|
|
2114
|
+
const result = await execution.settled;
|
|
2115
|
+
return execution.read(result);
|
|
2116
|
+
}
|
|
2117
|
+
// src/server/execution/executeSync.ts
|
|
2118
|
+
/**
|
|
2119
|
+
* Runs one command to completion synchronously, buffering its output, and returns the outcome.
|
|
2120
|
+
*
|
|
2121
|
+
* @remarks
|
|
2122
|
+
* The synchronous counterpart of {@link execute}, spawned through the same resolver and never through a
|
|
2123
|
+
* shell. The host offers no cooperative termination window and no in-flight cancellation, so this
|
|
2124
|
+
* contract carries neither. A positive `timeout` ends only the root process and can leave
|
|
2125
|
+
* descendants running; use {@link execute} or {@link Process} when timeout must terminate the tree. A
|
|
2126
|
+
* timeout and an output overflow both end the root with `SIGKILL`: an overflow reports `truncated`
|
|
2127
|
+
* and `failed` together and trims the partial output to `limit`, where {@link execute} keeps reading and
|
|
2128
|
+
* reports `truncated` without failing. The environment and input follow the same merge as
|
|
2129
|
+
* {@link execute}. Unless `strict` is `false`, a failed run throws a {@link createExecuteError}
|
|
2130
|
+
* carrying the {@link ExecuteResult}.
|
|
2131
|
+
*
|
|
2132
|
+
* @param command - The executable, arguments, and optional environment and input
|
|
2133
|
+
* @param options - Working directory, timeout, capture limit, and failure delivery
|
|
2134
|
+
* @returns The run outcome
|
|
2135
|
+
* @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
|
|
2136
|
+
*
|
|
2137
|
+
* @example
|
|
2138
|
+
* ```ts
|
|
2139
|
+
* const result = executeSync({ file: 'git', arguments: ['--version'] }, { strict: false })
|
|
2140
|
+
* ```
|
|
2141
|
+
*/
|
|
2142
|
+
function executeSync(command, options) {
|
|
2143
|
+
const snapshot = snapshotCommand(command);
|
|
2144
|
+
const optionEnvironment = options?.environment;
|
|
2145
|
+
const optionWorkspace = options?.workspace;
|
|
2146
|
+
const optionInput = options?.input;
|
|
2147
|
+
const optionTimeout = options?.timeout;
|
|
2148
|
+
const optionStrict = options?.strict;
|
|
2149
|
+
const optionLimit = options?.limit;
|
|
2150
|
+
validateCommand(snapshot);
|
|
2151
|
+
validateEnvironment(optionEnvironment);
|
|
2152
|
+
validateWorkspace(optionWorkspace);
|
|
2153
|
+
validateTimer(optionTimeout, "option 'timeout'");
|
|
2154
|
+
validateBytes(optionLimit, "option 'limit'", 0);
|
|
2155
|
+
const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
|
|
2156
|
+
const timeout = optionTimeout ?? 0;
|
|
2157
|
+
const strict = optionStrict ?? true;
|
|
2158
|
+
const text = optionInput ?? snapshot.input;
|
|
2159
|
+
const line = formatCommand(snapshot);
|
|
2160
|
+
const workspace = optionWorkspace ?? process.cwd();
|
|
2161
|
+
const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
|
|
2162
|
+
const plan = buildSpawn(snapshot, {
|
|
2163
|
+
workspace,
|
|
2164
|
+
environment
|
|
2165
|
+
});
|
|
2166
|
+
const outcome = (0, node_child_process.spawnSync)(plan.file, [...plan.arguments], {
|
|
2167
|
+
cwd: workspace,
|
|
2168
|
+
env: environment,
|
|
2169
|
+
encoding: "buffer",
|
|
2170
|
+
maxBuffer: limit,
|
|
2171
|
+
killSignal: "SIGKILL",
|
|
2172
|
+
windowsHide: true,
|
|
2173
|
+
windowsVerbatimArguments: plan.verbatim,
|
|
2174
|
+
...text !== void 0 ? { input: node_buffer.Buffer.from(text) } : {},
|
|
2175
|
+
...timeout > 0 ? { timeout } : {}
|
|
2176
|
+
});
|
|
2177
|
+
const error = outcome.error;
|
|
2178
|
+
const fault = error !== void 0 && "code" in error && (0, _orkestrel_contract.isString)(error.code) ? error.code : "";
|
|
2179
|
+
const result = buildExecuteResult({
|
|
2180
|
+
command: line,
|
|
2181
|
+
stdout: node_buffer.Buffer.isBuffer(outcome.stdout) ? outcome.stdout : node_buffer.Buffer.alloc(0),
|
|
2182
|
+
stderr: node_buffer.Buffer.isBuffer(outcome.stderr) ? outcome.stderr : node_buffer.Buffer.alloc(0),
|
|
2183
|
+
code: outcome.status,
|
|
2184
|
+
signal: outcome.signal,
|
|
2185
|
+
expired: fault === "ETIMEDOUT",
|
|
2186
|
+
aborted: false,
|
|
2187
|
+
truncated: fault === "ENOBUFS",
|
|
2188
|
+
limit,
|
|
2189
|
+
...error === void 0 ? {} : { cause: error }
|
|
2190
|
+
});
|
|
2191
|
+
if (result.failed && strict) throw (0, _src_core.createExecuteError)(result, error);
|
|
2192
|
+
return result;
|
|
2193
|
+
}
|
|
2164
2194
|
exports.Process = Process;
|
|
2165
2195
|
exports.ProcessManager = ProcessManager;
|
|
2166
2196
|
exports.Session = Session;
|
|
@@ -2177,6 +2207,7 @@ exports.detach = detach;
|
|
|
2177
2207
|
exports.execute = execute;
|
|
2178
2208
|
exports.executeSync = executeSync;
|
|
2179
2209
|
exports.formatCommand = formatCommand;
|
|
2210
|
+
exports.inspectProcess = inspectProcess;
|
|
2180
2211
|
exports.isExited = isExited;
|
|
2181
2212
|
exports.isFile = isFile;
|
|
2182
2213
|
exports.killProcess = killProcess;
|