/ src / utils / execFileNoThrow.ts
execFileNoThrow.ts
 1  import { execFile } from 'child_process'
 2  import { getCwd } from './state.js'
 3  import { logError } from './log.js'
 4  
 5  const MS_IN_SECOND = 1000
 6  const SECONDS_IN_MINUTE = 60
 7  
 8  /**
 9   * execFile, but always resolves (never throws)
10   */
11  export function execFileNoThrow(
12    file: string,
13    args: string[],
14    abortSignal?: AbortSignal,
15    timeout = 10 * SECONDS_IN_MINUTE * MS_IN_SECOND,
16    preserveOutputOnError = true,
17  ): Promise<{ stdout: string; stderr: string; code: number }> {
18    return new Promise(resolve => {
19      try {
20        execFile(
21          file,
22          args,
23          {
24            maxBuffer: 1_000_000,
25            signal: abortSignal,
26            timeout,
27            cwd: getCwd(),
28          },
29          (error, stdout, stderr) => {
30            if (error) {
31              if (preserveOutputOnError) {
32                const errorCode = typeof error.code === 'number' ? error.code : 1
33                resolve({
34                  stdout: stdout || '',
35                  stderr: stderr || '',
36                  code: errorCode,
37                })
38              } else {
39                resolve({ stdout: '', stderr: '', code: 1 })
40              }
41            } else {
42              resolve({ stdout, stderr, code: 0 })
43            }
44          },
45        )
46      } catch (error) {
47        logError(error)
48        resolve({ stdout: '', stderr: '', code: 1 })
49      }
50    })
51  }