UNPKG

@theia/process

Version:
157 lines (135 loc) 6.44 kB
// ***************************************************************************** // Copyright (C) 2017 Ericsson and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { injectable, inject, named } from '@theia/core/shared/inversify'; import { ProcessManager } from './process-manager'; import { ILogger } from '@theia/core/lib/common'; import { Process, ProcessType, ProcessOptions, ForkOptions, ProcessErrorEvent } from './process'; import { ChildProcess, spawn, fork } from 'child_process'; import * as stream from 'stream'; // The class was here before, exporting to not break anything. export { DevNullStream } from './dev-null-stream'; import { DevNullStream } from './dev-null-stream'; export const RawProcessOptions = Symbol('RawProcessOptions'); /** * Options to spawn a new process (`spawn`). * * For more information please refer to the spawn function of Node's * child_process module: * * https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options */ export interface RawProcessOptions extends ProcessOptions { } /** * Options to fork a new process using the current Node interpreter (`fork`). * * For more information please refer to the fork function of Node's * `child_process` module: * * https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options */ export interface RawForkOptions extends ForkOptions { } export const RawProcessFactory = Symbol('RawProcessFactory'); export interface RawProcessFactory { (options: RawProcessOptions | RawForkOptions): RawProcess; } @injectable() export class RawProcess extends Process { /** * If the process fails to launch, it will be undefined. */ readonly process: ChildProcess | undefined; readonly outputStream: stream.Readable; readonly errorStream: stream.Readable; readonly inputStream: stream.Writable; constructor( // eslint-disable-next-line @typescript-eslint/indent @inject(RawProcessOptions) options: RawProcessOptions | RawForkOptions, @inject(ProcessManager) processManager: ProcessManager, @inject(ILogger) @named('process') logger: ILogger ) { super(processManager, logger, ProcessType.Raw, options); const executable = this.isForkOptions(options) ? options.modulePath : options.command; this.logger.debug(`Starting raw process: ${executable},` + ` with args: ${options.args ? options.args.join(' ') : ''}, ` + ` with options: ${JSON.stringify(options.options)}`); // About catching errors: spawn will sometimes throw directly // (EACCES on Linux), sometimes return a Process object with the pid // property undefined (ENOENT on Linux) and then emit an 'error' event. // For now, we try to normalize that into always emitting an 'error' // event. try { if (this.isForkOptions(options)) { this.process = fork( options.modulePath, options.args || [], options.options || {}); } else { this.process = spawn( options.command, options.args || [], options.options || {}); } this.process.on('error', (error: NodeJS.ErrnoException) => { error.code = error.code || 'Unknown error'; this.emitOnError(error as ProcessErrorEvent); }); // When no stdio option is passed, it is null by default. this.outputStream = this.process.stdout || new DevNullStream({ autoDestroy: true }); this.inputStream = this.process.stdin || new DevNullStream({ autoDestroy: true }); this.errorStream = this.process.stderr || new DevNullStream({ autoDestroy: true }); this.process.on('exit', (exitCode, signal) => { // node's child_process exit sets the unused parameter to null, // but we want it to be undefined instead. this.emitOnExit( typeof exitCode === 'number' ? exitCode : undefined, typeof signal === 'string' ? signal : undefined, ); this.processManager.unregister(this); }); this.process.on('close', (exitCode, signal) => { // node's child_process exit sets the unused parameter to null, // but we want it to be undefined instead. this.emitOnClose( typeof exitCode === 'number' ? exitCode : undefined, typeof signal === 'string' ? signal : undefined, ); }); if (this.process.pid !== undefined) { process.nextTick(this.emitOnStarted.bind(this)); } } catch (error) { /* When an error is thrown, set up some fake streams, so the client code doesn't break because these field are undefined. */ this.outputStream = new DevNullStream({ autoDestroy: true }); this.inputStream = new DevNullStream({ autoDestroy: true }); this.errorStream = new DevNullStream({ autoDestroy: true }); /* Call the client error handler, but first give them a chance to register it. */ this.emitOnErrorAsync(error); } } get pid(): number { if (!this.process || !this.process.pid) { throw new Error('process did not start correctly'); } return this.process.pid; } kill(signal?: string): void { if (this.process && this.killed === false) { this.process.kill(signal as NodeJS.Signals); } } }