(
command: string,
opts?: ProcessOptions,
)
| 223 | } |
| 224 | |
| 225 | private async spawnProcess( |
| 226 | command: string, |
| 227 | opts?: ProcessOptions, |
| 228 | ): Promise<SpawnHandle> { |
| 229 | const controller = new AbortController() |
| 230 | if (opts?.signal) { |
| 231 | opts.signal.addEventListener('abort', () => controller.abort(), { |
| 232 | once: true, |
| 233 | }) |
| 234 | } |
| 235 | |
| 236 | const cmd: Command = await this.sandbox.runCommand({ |
| 237 | cmd: 'sh', |
| 238 | args: ['-c', command], |
| 239 | cwd: opts?.cwd ? this.abs(opts.cwd) : this.workdir, |
| 240 | env: this.mergedEnv(opts?.env), |
| 241 | detached: true, |
| 242 | signal: controller.signal, |
| 243 | }) |
| 244 | |
| 245 | const stdoutQ = new AsyncChunkQueue() |
| 246 | const stderrQ = new AsyncChunkQueue() |
| 247 | |
| 248 | // Fan the single interleaved log stream out into stdout/stderr iterables. |
| 249 | const pump = (async (): Promise<void> => { |
| 250 | try { |
| 251 | for await (const log of cmd.logs()) { |
| 252 | if (log.stream === 'stderr') stderrQ.push(log.data) |
| 253 | else stdoutQ.push(log.data) |
| 254 | } |
| 255 | } catch { |
| 256 | // Stream torn down (kill/abort) — fall through and close the queues. |
| 257 | } finally { |
| 258 | stdoutQ.end() |
| 259 | stderrQ.end() |
| 260 | } |
| 261 | })() |
| 262 | |
| 263 | return { |
| 264 | pid: -1, // Vercel commands do not surface a host-visible pid. |
| 265 | stdout: stdoutQ, |
| 266 | stderr: stderrQ, |
| 267 | stdin: { |
| 268 | write: () => |
| 269 | Promise.reject( |
| 270 | new Error( |
| 271 | 'vercel: background process stdin is not writable (see capabilities.writableStdin)', |
| 272 | ), |
| 273 | ), |
| 274 | end: () => Promise.resolve(), |
| 275 | }, |
| 276 | wait: async () => { |
| 277 | const finished = await cmd.wait() |
| 278 | await pump |
| 279 | return finished.exitCode |
| 280 | }, |
| 281 | kill: () => { |
| 282 | controller.abort() |
no test coverage detected