From 1c2cfed673948e7bb172130bc10e669ba385cc51 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont <4503241+Benjozork@users.noreply.github.com> Date: Sun, 1 Oct 2023 23:45:10 -0400 Subject: [PATCH] fix: ExecTask never exiting when spawned process fails (#10) * fix: ExecTask never exiting when errors thrown in spawned process * refactor: cleanup * build: bump version --- package-lock.json | 4 ++-- package.json | 2 +- src/Library/Tasks/ExecTask.ts | 22 ++++++++++++++++++++-- src/Library/Tasks/ExecTaskError.ts | 5 +++++ src/Library/Tasks/GenericTask.ts | 11 +++++++++-- src/task-pool.d.ts | 2 +- 6 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 src/Library/Tasks/ExecTaskError.ts diff --git a/package-lock.json b/package-lock.json index a303178..81489a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@flybywiresim/igniter", - "version": "1.2.2", + "version": "1.2.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@flybywiresim/igniter", - "version": "1.2.2", + "version": "1.2.3", "bin": { "igniter": "dist/binary.mjs" }, diff --git a/package.json b/package.json index baee128..c7cdc72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@flybywiresim/igniter", - "version": "1.2.2", + "version": "1.2.3", "types": "dist/index.d.ts", "description": "An intelligent task runner written in Typescript.", "repository": { diff --git a/src/Library/Tasks/ExecTask.ts b/src/Library/Tasks/ExecTask.ts index 2c7c9a5..20a76e6 100644 --- a/src/Library/Tasks/ExecTask.ts +++ b/src/Library/Tasks/ExecTask.ts @@ -1,7 +1,7 @@ import { exec } from 'child_process'; -import { promisify } from 'util'; import GenericTask from './GenericTask'; import { TaskStatus } from '../Contracts/Task'; +import ExecTaskError from './ExecTaskError'; export default class ExecTask extends GenericTask { constructor( @@ -12,7 +12,25 @@ export default class ExecTask extends GenericTask { const commands = typeof command === 'string' ? [command] : command; super(key, async () => { for await (const cmd of commands) { - const poolExec = this.context.taskPool.promise.wrap(promisify(exec)); + const poolExec = this.context.taskPool.promise.wrap((execCmd) => new Promise((resolve, reject) => { + const p = exec(execCmd); + + let stderr = ''; + p.stderr.on('data', (data) => { + stderr += data; + }); + + p.on('exit', (code) => { + p.stdout.destroy(); + p.stderr.destroy(); + + if (code === 0) { + resolve(code); + } else { + reject(new ExecTaskError(stderr)); + } + }); + })); const task = poolExec(cmd); diff --git a/src/Library/Tasks/ExecTaskError.ts b/src/Library/Tasks/ExecTaskError.ts new file mode 100644 index 0000000..c8f54ab --- /dev/null +++ b/src/Library/Tasks/ExecTaskError.ts @@ -0,0 +1,5 @@ +export default class ExecTaskError extends Error { + constructor(public readonly stderr: string) { + super('Error in ExecTask spawned process'); + } +} diff --git a/src/Library/Tasks/GenericTask.ts b/src/Library/Tasks/GenericTask.ts index b505577..4155a2d 100644 --- a/src/Library/Tasks/GenericTask.ts +++ b/src/Library/Tasks/GenericTask.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { TaskRunner, Task, TaskStatus } from '../Contracts/Task'; import { generateHashFromPaths, storage } from '../../Helpers'; import { Context } from '../Contracts/Context'; +import ExecTaskError from './ExecTaskError'; export default class GenericTask implements Task { protected context: Context; @@ -50,9 +51,15 @@ export default class GenericTask implements Task { this.context.cache.set(taskKey, generateHash); } } catch (error) { - if (this.context.debug) throw error; + if (this.context.debug) { + throw error; + } + this.status = TaskStatus.Failed; - if ('stderr' in error) this.errorOutput = error.stderr; + + if (error instanceof ExecTaskError) { + this.errorOutput = error.stderr; + } } } diff --git a/src/task-pool.d.ts b/src/task-pool.d.ts index e7c781f..8fc9f46 100644 --- a/src/task-pool.d.ts +++ b/src/task-pool.d.ts @@ -38,7 +38,7 @@ declare module "task-pool" { queue: PoolTask[] - wrap(Function, options?: Partial): PoolTaskFactory + wrap(Function: (...args: any[]) => Promise, options?: Partial): PoolTaskFactory next()