UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

605 lines (507 loc) 22.7 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _get = function get(_x14, _x15, _x16) { var _again = true; _function: while (_again) { var object = _x14, property = _x15, receiver = _x16; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x14 = parent; _x15 = property; _x16 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; /** * Since OS X apps don't inherit PATH when not launched from the CLI, this function creates a new * environment object given the original environment by modifying the env.PATH using following * logic: * 1) If originalEnv.PATH doesn't equal to process.env.PATH, which means the PATH has been * modified, we shouldn't do anything. * 1) If we are running in OS X, use `/usr/libexec/path_helper -s` to get the correct PATH and * REPLACE the PATH. * 2) If step 1 failed or we are not running in OS X, APPEND commonBinaryPaths to current PATH. */ var createExecEnvironment = _asyncToGenerator(function* (originalEnv, commonBinaryPaths) { var execEnv = _extends({}, originalEnv); if (execEnv.PATH !== process.env.PATH) { return execEnv; } execEnv.PATH = execEnv.PATH || ''; var platformPath = null; try { platformPath = yield getPlatformPath(); } catch (error) { logError('Failed to getPlatformPath', error); } // If the platform returns a non-empty PATH, use it. Otherwise use the default set of common // binary paths. if (platformPath) { execEnv.PATH = platformPath; } else if (commonBinaryPaths.length) { (function () { var paths = (_nuclideUri2 || _nuclideUri()).default.splitPathList(execEnv.PATH); commonBinaryPaths.forEach(function (commonBinaryPath) { if (paths.indexOf(commonBinaryPath) === -1) { paths.push(commonBinaryPath); } }); execEnv.PATH = (_nuclideUri2 || _nuclideUri()).default.joinPathList(paths); })(); } return execEnv; }); exports.createExecEnvironment = createExecEnvironment; /** * Basically like spawn, except it handles and logs errors instead of crashing * the process. This is much lower-level than asyncExecute. Unless you have a * specific reason you should use asyncExecute instead. */ var safeSpawn = _asyncToGenerator(function* (command) { var args = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; options.env = yield createExecEnvironment(options.env || process.env, COMMON_BINARY_PATHS); var child = (_child_process2 || _child_process()).default.spawn(command, args, options); monitorStreamErrors(child, command, args, options); child.on('error', function (error) { logError('error with command:', command, args, options, 'error:', error); }); return child; }); exports.safeSpawn = safeSpawn; var forkWithExecEnvironment = _asyncToGenerator(function* (modulePath) { var args = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var forkOptions = _extends({}, options, { env: yield createExecEnvironment(options.env || process.env, COMMON_BINARY_PATHS) }); var child = (_child_process2 || _child_process()).default.fork(modulePath, args, forkOptions); child.on('error', function (error) { logError('error from module:', modulePath, args, options, 'error:', error); }); return child; } /** * Takes the command and args that you would normally pass to `spawn()` and returns `newArgs` such * that you should call it with `spawn('script', newArgs)` to run the original command/args pair * under `script`. */ ); exports.forkWithExecEnvironment = forkWithExecEnvironment; exports.createArgsForScriptCommand = createArgsForScriptCommand; exports.scriptSafeSpawn = scriptSafeSpawn; exports.scriptSafeSpawnAndObserveOutput = scriptSafeSpawnAndObserveOutput; exports.createProcessStream = createProcessStream; exports.observeProcessExit = observeProcessExit; exports.getOutputStream = getOutputStream; exports.observeProcess = observeProcess; /** * Returns a promise that resolves to the result of executing a process. * * @param command The command to execute. * @param args The arguments to pass to the command. * @param options Options for changing how to run the command. * Supports the options listed here: http://nodejs.org/api/child_process.html * in addition to the custom options listed in AsyncExecuteOptions. */ var asyncExecute = _asyncToGenerator(function* (command, args) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var env = yield createExecEnvironment(options.env || process.env, COMMON_BINARY_PATHS); var executor = function executor(resolve, reject) { var process = (_child_process2 || _child_process()).default.execFile(command, args, _extends({ maxBuffer: DEFAULT_MAX_BUFFER }, options, { env: env }), // Node embeds various properties like code/errno in the Error object. function (err, /* Error */stdoutBuf, stderrBuf) { var stdout = stdoutBuf.toString('utf8'); var stderr = stderrBuf.toString('utf8'); if (err != null) { if (Number.isInteger(err.code)) { resolve({ stdout: stdout, stderr: stderr, exitCode: err.code }); } else { resolve({ stdout: stdout, stderr: stderr, errorCode: err.errno || 'EUNKNOWN', errorMessage: err.message }); } } resolve({ stdout: stdout, stderr: stderr, exitCode: 0 }); }); if (typeof options.stdin === 'string' && process.stdin != null) { // Note that the Node docs have this scary warning about stdin.end() on // http://nodejs.org/api/child_process.html#child_process_child_stdin: // // "A Writable Stream that represents the child process's stdin. Closing // this stream via end() often causes the child process to terminate." // // In practice, this has not appeared to cause any issues thus far. process.stdin.write(options.stdin); process.stdin.end(); } }; var queueName = options.queueName; if (queueName === undefined) { return new Promise(executor); } else { if (!blockingQueues[queueName]) { blockingQueues[queueName] = new (_promiseExecutors2 || _promiseExecutors()).PromiseQueue(); } return blockingQueues[queueName].submit(executor); } } /** * Simple wrapper around asyncExecute that throws if the exitCode is non-zero. */ ); exports.asyncExecute = asyncExecute; var checkOutput = _asyncToGenerator(function* (command, args) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var result = yield asyncExecute(command, args, options); if (result.exitCode !== 0) { var reason = result.exitCode != null ? 'exitCode: ' + result.exitCode : 'error: ' + (0, (_string2 || _string()).maybeToString)(result.errorMessage); throw new Error('asyncExecute "' + command + '" failed with ' + reason + ', ' + ('stderr: ' + result.stderr + ', stdout: ' + result.stdout + '.')); } return result; } /** * Run a command, accumulate the output. Errors are surfaced as stream errors and unsubscribing will * kill the process. */ ); exports.checkOutput = checkOutput; exports.runCommand = runCommand; function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _child_process2; function _child_process() { return _child_process2 = _interopRequireDefault(require('child_process')); } var _nuclideUri2; function _nuclideUri() { return _nuclideUri2 = _interopRequireDefault(require('./nuclideUri')); } var _stream2; function _stream() { return _stream2 = require('./stream'); } var _string2; function _string() { return _string2 = require('./string'); } var _rxjsBundlesRxUmdMinJs2; function _rxjsBundlesRxUmdMinJs() { return _rxjsBundlesRxUmdMinJs2 = require('rxjs/bundles/Rx.umd.min.js'); } var _promiseExecutors2; function _promiseExecutors() { return _promiseExecutors2 = require('./promise-executors'); } var _shellQuote2; function _shellQuote() { return _shellQuote2 = require('shell-quote'); } // Node crashes if we allow buffers that are too large. var DEFAULT_MAX_BUFFER = 100 * 1024 * 1024; var ProcessSystemError = (function (_Error) { _inherits(ProcessSystemError, _Error); function ProcessSystemError(opts) { _classCallCheck(this, ProcessSystemError); _get(Object.getPrototypeOf(ProcessSystemError.prototype), 'constructor', this).call(this, '"' + opts.command + '" failed with code ' + opts.code); this.name = 'ProcessSystemError'; this.command = opts.command; this.args = opts.args; this.options = opts.options; this.code = opts.code; this.originalError = opts.originalError; } return ProcessSystemError; })(Error); exports.ProcessSystemError = ProcessSystemError; var ProcessExitError = (function (_Error2) { _inherits(ProcessExitError, _Error2); function ProcessExitError(opts) { _classCallCheck(this, ProcessExitError); _get(Object.getPrototypeOf(ProcessExitError.prototype), 'constructor', this).call(this, '"' + opts.command + '" failed with code ' + opts.code + '\n\n' + opts.stderr); this.name = 'ProcessExitError'; this.command = opts.command; this.args = opts.args; this.options = opts.options; this.code = opts.code; this.stdout = opts.stdout; this.stderr = opts.stderr; } return ProcessExitError; })(Error); exports.ProcessExitError = ProcessExitError; var platformPathPromise = undefined; var blockingQueues = {}; var COMMON_BINARY_PATHS = ['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']; /** * Captures the value of the PATH env variable returned by Darwin's (OS X) `path_helper` utility. * `path_helper -s`'s return value looks like this: * * PATH="/usr/bin"; export PATH; */ var DARWIN_PATH_HELPER_REGEXP = /PATH="([^"]+)"/; var STREAM_NAMES = ['stdin', 'stdout', 'stderr']; function getPlatformPath() { // Do not return the cached value if we are executing under the test runner. if (platformPathPromise && process.env.NODE_ENV !== 'test') { // Path is being fetched, await the Promise that's in flight. return platformPathPromise; } // We do not cache the result of this check because we have unit tests that temporarily redefine // the value of process.platform. if (process.platform === 'darwin') { // OS X apps don't inherit PATH when not launched from the CLI, so reconstruct it. This is a // bug, filed against Atom Linter here: https://github.com/AtomLinter/Linter/issues/150 // TODO(jjiaa): remove this hack when the Atom issue is closed platformPathPromise = new Promise(function (resolve, reject) { (_child_process2 || _child_process()).default.execFile('/usr/libexec/path_helper', ['-s'], function (error, stdout, stderr) { if (error) { reject(error); } else { // $FlowFixMe (stdout is a Buffer, which does not have match) var match = stdout.match(DARWIN_PATH_HELPER_REGEXP); resolve(match && match.length > 1 ? match[1] : ''); } }); }); } else { platformPathPromise = Promise.resolve(''); } return platformPathPromise; } function logError() { // Can't use nuclide-logging here to not cause cycle dependency. // eslint-disable-next-line no-console console.error.apply(console, arguments); } function monitorStreamErrors(process, command, args, options) { STREAM_NAMES.forEach(function (streamName) { // $FlowIssue var stream = process[streamName]; if (stream == null) { return; } stream.on('error', function (error) { // This can happen without the full execution of the command to fail, // but we want to learn about it. logError('stream error on stream ' + streamName + ' with command:', command, args, options, 'error:', error); }); }); } function createArgsForScriptCommand(command) { var args = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; if (process.platform === 'darwin') { // On OS X, script takes the program to run and its arguments as varargs at the end. return ['-q', '/dev/null', command].concat(args); } else { // On Linux, script takes the command to run as the -c parameter. var allArgs = [command].concat(args); return ['-q', '/dev/null', '-c', (0, (_shellQuote2 || _shellQuote()).quote)(allArgs)]; } } /** * Basically like safeSpawn, but runs the command with the `script` command. * `script` ensures terminal-like environment and commands we run give colored output. */ function scriptSafeSpawn(command) { var args = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var newArgs = createArgsForScriptCommand(command, args); return safeSpawn('script', newArgs, options); } /** * Wraps scriptSafeSpawn with an Observable that lets you listen to the stdout and * stderr of the spawned process. */ function scriptSafeSpawnAndObserveOutput(command) { var args = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.create(function (observer) { var childProcess = undefined; scriptSafeSpawn(command, args, options).then(function (proc) { childProcess = proc; childProcess.stdout.on('data', function (data) { observer.next({ stdout: data.toString() }); }); var stderr = ''; childProcess.stderr.on('data', function (data) { stderr += data; observer.next({ stderr: data.toString() }); }); childProcess.on('exit', function (exitCode) { if (exitCode !== 0) { observer.error(stderr); } else { observer.complete(); } childProcess = null; }); }); return function () { if (childProcess) { childProcess.kill(); } }; }); } /** * Creates an observable with the following properties: * * 1. It contains a process that's created using the provided factory upon subscription. * 2. It doesn't complete until the process exits (or errors). * 3. The process is killed when there are no more subscribers. * * IMPORTANT: The exit event does NOT mean that all stdout and stderr events have been received. */ function _createProcessStream(createProcess, throwOnError) { return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.create(function (observer) { var promise = Promise.resolve(createProcess()); var process = undefined; var disposed = false; var exited = false; var maybeKill = function maybeKill() { if (process != null && disposed && !exited) { process.kill(); process = null; } }; promise.then(function (p) { process = p; maybeKill(); }); // Create a stream that contains the process but never completes. We'll use this to build the // completion conditions. var processStream = (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromPromise(promise).merge((_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.never()); var errors = processStream.switchMap(function (p) { return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromEvent(p, 'error'); }); var exit = processStream.flatMap(function (p) { return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromEvent(p, 'exit', function (code, signal) { return signal; }); }) // An exit signal from SIGUSR1 doesn't actually exit the process, so skip that. .filter(function (signal) { return signal !== 'SIGUSR1'; }).do(function () { exited = true; }); var completion = throwOnError ? exit : exit.race(errors); return new (_stream2 || _stream()).CompositeSubscription(processStream.merge(throwOnError ? errors.flatMap((_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.throw) : (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.empty()).takeUntil(completion).subscribe(observer), function () { disposed = true;maybeKill(); }); }); // TODO: We should really `.share()` this observable, but there seem to be issues with that and // `.retry()` in Rx 3 and 4. Once we upgrade to Rx5, we should share this observable and verify // that our retry logic (e.g. in adb-logcat) works. } function createProcessStream(createProcess) { return _createProcessStream(createProcess, true); } /** * Observe the stdout, stderr and exit code of a process. * stdout and stderr are split by newlines. */ function observeProcessExit(createProcess) { return _createProcessStream(createProcess, false).flatMap(function (process) { return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromEvent(process, 'exit').take(1); }); } function getOutputStream(childProcess) { return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromPromise(Promise.resolve(childProcess)).flatMap(function (process) { // We need to start listening for the exit event immediately, but defer emitting it until the // output streams end. var exit = (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromEvent(process, 'exit').take(1).map(function (exitCode) { return { kind: 'exit', exitCode: exitCode }; }).publishReplay(); var exitSub = exit.connect(); var error = (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.fromEvent(process, 'error').map(function (errorObj) { return { kind: 'error', error: errorObj }; }); var stdout = (0, (_stream2 || _stream()).splitStream)((0, (_stream2 || _stream()).observeStream)(process.stdout)).map(function (data) { return { kind: 'stdout', data: data }; }); var stderr = (0, (_stream2 || _stream()).splitStream)((0, (_stream2 || _stream()).observeStream)(process.stderr)).map(function (data) { return { kind: 'stderr', data: data }; }); return (0, (_stream2 || _stream()).takeWhileInclusive)((_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.merge((_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.merge(stdout, stderr).concat(exit), error), function (event) { return event.kind !== 'error' && event.kind !== 'exit'; }).finally(function () { exitSub.unsubscribe(); }); }); } /** * Observe the stdout, stderr and exit code of a process. */ function observeProcess(createProcess) { return _createProcessStream(createProcess, false).flatMap(getOutputStream); } function runCommand(command) { var args = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; return observeProcess(function () { return safeSpawn(command, args, options); }).reduce(function (acc, event) { switch (event.kind) { case 'stdout': acc.stdout += event.data; break; case 'stderr': acc.stderr += event.data; break; case 'error': acc.error = event.error; break; case 'exit': acc.exitCode = event.exitCode; break; } return acc; }, { error: null, stdout: '', stderr: '', exitCode: null }).map(function (acc) { if (acc.error != null) { throw new ProcessSystemError({ command: command, args: args, options: options, code: acc.error.code, // Alias of errno originalError: acc.error }); } // Just in case. if (acc.exitCode != null && acc.exitCode !== 0) { throw new ProcessExitError({ command: command, args: args, options: options, code: acc.exitCode, stdout: acc.stdout, stderr: acc.stderr }); } return acc.stdout; }); } var __test__ = { DARWIN_PATH_HELPER_REGEXP: DARWIN_PATH_HELPER_REGEXP }; exports.__test__ = __test__; // If the process fails to even start up, exitCode will not be set // and errorCode / errorMessage will contain the actual error message. // Otherwise, exitCode will always be defined. // The queue on which to block dependent calls. // The contents to write to stdin.