|
1 | | -import { spawn, type SpawnOptions, type SpawnOptionsWithoutStdio } from 'node:child_process'; |
| 1 | +import { Result } from '@sapphire/result'; |
| 2 | +import { execa, type ExecaError, type Options } from 'execa'; |
2 | 3 |
|
3 | 4 | import { normalizeExecutablePath } from './hooks/runtimes/utils.js'; |
4 | | -import { run } from './outputs.js'; |
| 5 | +import { error, run } from './outputs.js'; |
5 | 6 | import { cliDebugPrint } from './utils/cliDebugPrint.js'; |
6 | 7 |
|
7 | | -const windowsOptions: SpawnOptions = { |
8 | | - shell: true, |
9 | | - windowsHide: true, |
10 | | -}; |
11 | | - |
12 | | -/** |
13 | | - * Run child process and returns stdout and stderr to user stout |
14 | | - */ |
15 | | -const spawnPromised = async (cmd: string, args: string[], opts: SpawnOptionsWithoutStdio) => { |
| 8 | +const spawnPromised = async (cmd: string, args: string[], opts: Options) => { |
16 | 9 | const escapedCommand = normalizeExecutablePath(cmd); |
17 | 10 |
|
18 | | - cliDebugPrint('SpawnPromised', { escapedCommand, args, opts }); |
19 | | - |
20 | | - // NOTE: Pipes stderr, stdout to main process |
21 | | - const childProcess = spawn(escapedCommand, args, { |
22 | | - ...opts, |
23 | | - stdio: process.env.APIFY_NO_LOGS_IN_TESTS ? 'ignore' : 'inherit', |
24 | | - ...(process.platform === 'win32' ? windowsOptions : {}), |
25 | | - }); |
26 | | - |
27 | | - // Catch ctrl-c (SIGINT) and kills child process |
28 | | - // NOTE: This fix kills also puppeteer child node process |
29 | | - process.on('SIGINT', () => { |
30 | | - try { |
31 | | - childProcess.kill('SIGINT'); |
32 | | - } catch { |
33 | | - // SIGINT can come after the child process is finished, ignore it |
34 | | - } |
| 11 | + cliDebugPrint('spawnPromised', { escapedCommand, args, opts }); |
| 12 | + |
| 13 | + const childProcess = execa(escapedCommand, args, { |
| 14 | + shell: true, |
| 15 | + windowsHide: true, |
| 16 | + env: opts.env, |
| 17 | + cwd: opts.cwd, |
| 18 | + // Pipe means it gets collected by the parent process, inherit means it gets collected by the parent process and printed out to the console |
| 19 | + stdout: process.env.APIFY_NO_LOGS_IN_TESTS ? ['pipe'] : ['pipe', 'inherit'], |
| 20 | + stderr: process.env.APIFY_NO_LOGS_IN_TESTS ? ['pipe'] : ['pipe', 'inherit'], |
| 21 | + verbose: process.env.APIFY_CLI_DEBUG ? 'full' : undefined, |
35 | 22 | }); |
36 | 23 |
|
37 | | - return new Promise<void>((resolve, reject) => { |
38 | | - childProcess.on('error', reject); |
39 | | - childProcess.on('close', (code) => { |
40 | | - if (code !== 0) reject(new Error(`${cmd} exited with code ${code}`)); |
41 | | - resolve(); |
42 | | - }); |
43 | | - }); |
| 24 | + return Result.fromAsync( |
| 25 | + childProcess.catch((execaError: ExecaError) => { |
| 26 | + throw new Error(`${cmd} exited with code ${execaError.exitCode}`, { cause: execaError }); |
| 27 | + }), |
| 28 | + ) as Promise<Result<Awaited<typeof childProcess>, Error & { cause: ExecaError }>>; |
44 | 29 | }; |
45 | 30 |
|
46 | 31 | export interface ExecWithLogOptions { |
47 | 32 | cmd: string; |
48 | 33 | args?: string[]; |
49 | | - opts?: SpawnOptionsWithoutStdio; |
| 34 | + opts?: Options; |
50 | 35 | overrideCommand?: string; |
51 | 36 | } |
52 | 37 |
|
53 | 38 | export async function execWithLog({ cmd, args = [], opts = {}, overrideCommand }: ExecWithLogOptions) { |
54 | 39 | run({ message: `${overrideCommand || cmd} ${args.join(' ')}` }); |
55 | | - await spawnPromised(cmd, args, opts); |
| 40 | + const result = await spawnPromised(cmd, args, opts); |
| 41 | + |
| 42 | + if (result.isErr()) { |
| 43 | + const err = result.unwrapErr(); |
| 44 | + error({ message: err.message }); |
| 45 | + |
| 46 | + if (err.cause) { |
| 47 | + throw err.cause; |
| 48 | + } |
| 49 | + } |
56 | 50 | } |
0 commit comments