import type { SignalConstants } from 'node:os' import { execa as exec, type ResultPromise } from 'execa' import type * as Instance from '../Instance.js' import { stripColors } from '../internal/utils.js' export type Process_internal = ResultPromise<{ cleanup: true; reject: false }> export type ExecaStartOptions = Instance.define.InstanceStartOptions_internal & { resolver(options: { process: Process_internal reject(data: string): Promise resolve(): void }): void } export type Process = { _internal: { process: Process_internal } name: string start( command: (x: typeof exec) => void, options: ExecaStartOptions, ): Promise stop(signal?: keyof SignalConstants | number): Promise } export function execa(parameters: execa.Parameters): execa.ReturnType { const { name } = parameters const errorMessages: string[] = [] let process: Process_internal async function stop(signal?: keyof SignalConstants | number) { const killed = process.kill(signal) if (!killed) return return new Promise((resolve) => process.on('close', resolve)) } return { _internal: { get process() { return process }, }, name, start(command, { emitter, resolver, status }) { const { promise, resolve, reject } = Promise.withResolvers() process = command( exec({ cleanup: true, reject: false, }) as any, ) as unknown as Process_internal resolver({ process, async reject(data) { await stop() reject( new Error(`Failed to start process "${name}": ${data.toString()}`), ) }, resolve() { emitter.emit('listening') return resolve() }, }) process.stdout.on('data', (data) => { const message = stripColors(data.toString()) emitter.emit('message', message) emitter.emit('stdout', message) }) process.stderr.on('data', async (data) => { const message = stripColors(data.toString()) errorMessages.push(message) if (errorMessages.length > 20) errorMessages.shift() emitter.emit('message', message) emitter.emit('stderr', message) }) process.on('close', () => process.removeAllListeners()) process.on('exit', (code, signal) => { emitter.emit('exit', code, signal) if (!code) { process.removeAllListeners() if (status === 'starting') reject( new Error( `Failed to start process "${name}": ${ errorMessages.length > 0 ? `\n\n${errorMessages.join('\n')}` : 'exited' }`, ), ) } }) return promise }, async stop() { process.removeAllListeners() await stop() }, } } export declare namespace execa { export type Parameters = { name: string } export type ReturnType = Process }