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