* Run a `coder` CLI command with timeout + optional cancellation. * * We use spawn (not execAsync) so ensureReady() can't hang forever on a stuck * Coder CLI invocation.
(
args: string[],
options: { timeoutMs: number; signal?: AbortSignal }
)
| 1035 | * Coder CLI invocation. |
| 1036 | */ |
| 1037 | private runCoderCommand( |
| 1038 | args: string[], |
| 1039 | options: { timeoutMs: number; signal?: AbortSignal } |
| 1040 | ): Promise<CoderCommandResult> { |
| 1041 | return new Promise((resolve) => { |
| 1042 | if (options.timeoutMs <= 0) { |
| 1043 | resolve({ exitCode: null, stdout: "", stderr: "", error: "timeout" }); |
| 1044 | return; |
| 1045 | } |
| 1046 | |
| 1047 | if (options.signal?.aborted) { |
| 1048 | resolve({ exitCode: null, stdout: "", stderr: "", error: "aborted" }); |
| 1049 | return; |
| 1050 | } |
| 1051 | |
| 1052 | const child = spawn("coder", args, { |
| 1053 | stdio: ["ignore", "pipe", "pipe"], |
| 1054 | }); |
| 1055 | |
| 1056 | let stdout = ""; |
| 1057 | let stderr = ""; |
| 1058 | let resolved = false; |
| 1059 | |
| 1060 | let timeoutTimer: ReturnType<typeof setTimeout> | null = null; |
| 1061 | |
| 1062 | const terminator = createGracefulTerminator(child); |
| 1063 | |
| 1064 | const resolveOnce = (result: CoderCommandResult) => { |
| 1065 | if (resolved) return; |
| 1066 | resolved = true; |
| 1067 | resolve(result); |
| 1068 | }; |
| 1069 | |
| 1070 | const cleanup = (cleanupOptions?: { keepSigkillTimer?: boolean }) => { |
| 1071 | if (timeoutTimer) { |
| 1072 | clearTimeout(timeoutTimer); |
| 1073 | timeoutTimer = null; |
| 1074 | } |
| 1075 | if (!cleanupOptions?.keepSigkillTimer) { |
| 1076 | terminator.cleanup(); |
| 1077 | } |
| 1078 | child.removeListener("close", onClose); |
| 1079 | child.removeListener("error", onError); |
| 1080 | options.signal?.removeEventListener("abort", onAbort); |
| 1081 | }; |
| 1082 | |
| 1083 | function onAbort() { |
| 1084 | terminator.terminate(); |
| 1085 | // Keep SIGKILL escalation alive if SIGTERM doesn't work. |
| 1086 | cleanup({ keepSigkillTimer: true }); |
| 1087 | resolveOnce({ exitCode: null, stdout, stderr, error: "aborted" }); |
| 1088 | } |
| 1089 | |
| 1090 | function onError() { |
| 1091 | cleanup(); |
| 1092 | resolveOnce({ exitCode: null, stdout, stderr }); |
| 1093 | } |
| 1094 |
no test coverage detected