(options: LaunchProcessOptions)
| 129 | } |
| 130 | |
| 131 | export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> { |
| 132 | const stdio: ('ignore' | 'pipe')[] = options.stdio === 'pipe' ? ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe']; |
| 133 | options.log(`<launching> ${options.command} ${options.args ? options.args.join(' ') : ''}`); |
| 134 | const spawnOptions: childProcess.SpawnOptions = { |
| 135 | // On non-windows platforms, `detached: true` makes child process a leader of a new |
| 136 | // process group, making it possible to kill child process tree with `.kill(-pid)` command. |
| 137 | // @see https://nodejs.org/api/child_process.html#child_process_options_detached |
| 138 | detached: process.platform !== 'win32', |
| 139 | env: options.env, |
| 140 | cwd: options.cwd, |
| 141 | shell: options.shell, |
| 142 | stdio, |
| 143 | }; |
| 144 | const spawnedProcess = childProcess.spawn(options.command, options.args || [], spawnOptions); |
| 145 | |
| 146 | const cleanup = async () => { |
| 147 | options.log(`[pid=${spawnedProcess.pid || 'N/A'}] starting temporary directories cleanup`); |
| 148 | const errors = await removeFolders(options.tempDirectories); |
| 149 | for (let i = 0; i < options.tempDirectories.length; ++i) { |
| 150 | if (errors[i]) |
| 151 | options.log(`[pid=${spawnedProcess.pid || 'N/A'}] exception while removing ${options.tempDirectories[i]}: ${errors[i]}`); |
| 152 | } |
| 153 | options.log(`[pid=${spawnedProcess.pid || 'N/A'}] finished temporary directories cleanup`); |
| 154 | }; |
| 155 | |
| 156 | // Prevent Unhandled 'error' event. |
| 157 | spawnedProcess.on('error', () => {}); |
| 158 | |
| 159 | if (!spawnedProcess.pid) { |
| 160 | let failed: (e: Error) => void; |
| 161 | const failedPromise = new Promise<Error>((f, r) => failed = f); |
| 162 | spawnedProcess.once('error', error => { |
| 163 | failed(new Error('Failed to launch: ' + error)); |
| 164 | }); |
| 165 | return failedPromise.then(async error => { |
| 166 | await cleanup(); |
| 167 | throw error; |
| 168 | }); |
| 169 | } |
| 170 | options.log(`<launched> pid=${spawnedProcess.pid}`); |
| 171 | |
| 172 | const stdout = readline.createInterface({ input: spawnedProcess.stdout! }); |
| 173 | stdout.on('line', (data: string) => { |
| 174 | options.log(`[pid=${spawnedProcess.pid}][out] ` + data); |
| 175 | }); |
| 176 | |
| 177 | const stderr = readline.createInterface({ input: spawnedProcess.stderr! }); |
| 178 | stderr.on('line', (data: string) => { |
| 179 | options.log(`[pid=${spawnedProcess.pid}][err] ` + data); |
| 180 | }); |
| 181 | |
| 182 | let processClosed = false; |
| 183 | let fulfillCleanup = () => {}; |
| 184 | const waitForCleanup = new Promise<void>(f => fulfillCleanup = f); |
| 185 | spawnedProcess.once('close', (exitCode, signal) => { |
| 186 | options.log(`[pid=${spawnedProcess.pid}] <process did exit: exitCode=${exitCode}, signal=${signal}>`); |
| 187 | processClosed = true; |
| 188 | gracefullyCloseSet.delete(gracefullyClose); |
no test coverage detected
searching dependent graphs…