machinepack-process
Advanced tools
Comparing version
@@ -14,3 +14,5 @@ module.exports = { | ||
'If you need a more advanced/flexible interface, check out `spawnChildProcess()`. It is much lower-level, and exposes raw access to the '+ | ||
'child process instance.', | ||
'child process instance. Also note that only errors which can be reliably determined cross-platform are included in this machine. '+ | ||
'On *nix platforms (including OS X), you may be able to examine the `code` property of the object passed through the `erro` exit '+ | ||
'to determine the specific cause of an error (e.g. 127 for "command not found" or 126 for "command not executable").', | ||
@@ -24,3 +26,2 @@ | ||
command: { | ||
friendlyName: 'Command', | ||
description: 'The command to run, including any whitespace-delimited CLI args/opts.', | ||
@@ -32,3 +33,3 @@ example: 'ls -la', | ||
dir: { | ||
friendlyName: 'Run from...', | ||
friendlyName: 'Working directory', | ||
description: 'The path to the directory where this command will be run.', | ||
@@ -47,3 +48,2 @@ extendedDescription: 'If not set, this defaults to the present working directory. If a relative path is provided, it will be resolved relative to the present working directory.', | ||
timeout: { | ||
friendlyName: 'Timeout', | ||
description: 'The maximum number of miliseconds to wait for this command to finish.', | ||
@@ -59,19 +59,28 @@ extendedDescription: 'By default, no time limit will be enforced. Note that if the time limit is reached, SIGERM will be sent to the child process.', | ||
success: { | ||
outputFriendlyName: 'Command output', | ||
outputDescription: 'The output returned from executing the command.', | ||
extendedDescription: 'Note that the output is split into that which came from "stdout" vs. that which came from "stderr".', | ||
outputExample: { | ||
stdout: '...', | ||
stderr: '...' | ||
}, | ||
}, | ||
notADir: { | ||
friendlyName: 'not a directory', | ||
description: 'The specified path points to a something which is not a directory (e.g. a file or shortcut).' | ||
friendlyName: 'Not a directory', | ||
description: 'The specified path for the working directory pointed to something which is not a directory (e.g. a file or shortcut).' | ||
}, | ||
forbidden: { | ||
friendlyName: 'forbidden', | ||
description: 'Insufficient permissions to spawn process from the specified path (i.e. you might need to use `chown`/`chmod`)' | ||
description: 'The user running the machine had insufficient permissions to spawn process from the specified path.', | ||
extendedDescription: 'You might need to use `chown`/`chmod` to alter the permissions for the executable!' | ||
}, | ||
noSuchDir: { | ||
friendlyName: 'no such directory', | ||
description: 'Cannot run process from the specified path because no such directory exists.' | ||
friendlyName: 'No such directory', | ||
description: 'No directory could be found at the specified path for the working directory.' | ||
}, | ||
timedOut: { | ||
friendlyName: 'Timed out', | ||
description: 'The specified command was automatically killed because it had not finished before the configured time limit (`timeout`).', | ||
@@ -81,12 +90,2 @@ extendedDescription: 'Note that the command _may have already caused side effects_ before it was stopped.' | ||
success: { | ||
variableName: 'bufferedOutput', | ||
outputDescription: 'The output returned from executing the command.', | ||
extendedDescription: 'Note that the output is split into that which came from "stdout" vs. that which came from "stderr". ', | ||
example: { | ||
stdout: '...', | ||
stderr: '...' | ||
}, | ||
}, | ||
}, | ||
@@ -96,4 +95,8 @@ | ||
fn: function (inputs,exits) { | ||
// Import `util` and `child_process.exec` | ||
var path = require('path'); | ||
var executeCmdInChildProc = require('child_process').exec; | ||
// Import `isObject` and `isUndefined` Lodash functions. | ||
var isObject = require('lodash.isobject'); | ||
@@ -111,15 +114,20 @@ var isUndefined = require('lodash.isundefined'); | ||
else { | ||
// (or if a `dir` was specified, resolve it to make sure | ||
// it's an absolute path.) | ||
// If a `dir` was specified, resolve it to make sure | ||
// it's an absolute path. | ||
childProcOpts.cwd = path.resolve(inputs.dir); | ||
} | ||
// If `timeout` was provided, pass it in to `child_process.exec()`. | ||
// Note that we also track a timestamp (epoch ms) for use in negotiating errors below. | ||
// Declare a var for holding the current timestamp, if a timeout was provided. | ||
var timestampBeforeExecutingCmd; | ||
// If `timeout` was provided... | ||
if (!isUndefined(inputs.timeout)) { | ||
// If the timeout < 1 millisecond, return a validation error through our `error` exit. | ||
if (inputs.timeout < 1) { | ||
return exits.error('Invalid timeout (`'+inputs.timeout+'`). Must be greater than zero. Remember: `timeout` should be used to indicate the maximum number of miliseconds to wait for this command to finish before giving up.'); | ||
} | ||
// Otherwise add the specified timeout as an option to the `exec` command. | ||
childProcOpts.timeout = inputs.timeout; | ||
// Record the current time so we can have an idea later if the executable timed out. | ||
timestampBeforeExecutingCmd = (new Date()).getTime(); | ||
@@ -135,8 +143,6 @@ } | ||
// Now spawn the child process. | ||
// Now spawn the child process using the options that were passed in. | ||
var liveChildProc = executeCmdInChildProc(inputs.command, childProcOpts, function onClose(err, bufferedStdout, bufferedStderr) { | ||
// If an error occurred... | ||
if (err) { | ||
if (!isObject(err)) { | ||
return exits.error(err); | ||
} | ||
// console.log('err=>',err); | ||
@@ -150,10 +156,25 @@ // console.log('keys=>',Object.keys(err)); | ||
// If it's not an object, we don't know how to handle it, so pass it straight through | ||
// the `error` exit. | ||
// `err.syscall.match(/spawn/i)` should be true as well, but not testing since | ||
// Node v0.12 changed this a bit and we want to future-proof ourselves if possible. | ||
if (!isObject(err)) { | ||
return exits.error(err); | ||
} | ||
// If we get an `ENOTDIR` error, it means the specified working directory | ||
// was invalid, so leave through the `notADir` exit. | ||
if (err.code==='ENOTDIR') { | ||
return exits.notADir(); | ||
} | ||
// If we get an `ENOENT` error, it means the specified working directory | ||
// did not exist, so leave through the `noSuchDir` exit. | ||
if (err.code==='ENOENT') { | ||
return exits.noSuchDir(); | ||
} | ||
// If we get an `ENOENT` error, it means that the user running the machine | ||
// had insufficient permissions to run the command, so leave through | ||
// the `forbidden` exit. | ||
if (err.code==='EACCES') { | ||
@@ -165,2 +186,3 @@ return exits.forbidden(); | ||
if (err.signal==='SIGTERM' && inputs.timeout) { | ||
// If so, leave through the `timedOut` exit. | ||
var msElapsed = (new Date()).getTime() - timestampBeforeExecutingCmd; | ||
@@ -171,6 +193,9 @@ if (msElapsed >= inputs.timeout) { | ||
} | ||
// Forward all other errors through the `error` exit. | ||
return exits.error(err); | ||
} | ||
// console.log('Child process exited with exit code ' + code); | ||
// Process exited successfully (exit code 0), so leave through the `success` exit, | ||
// using the buffered output. | ||
return exits.success({ | ||
@@ -177,0 +202,0 @@ stdout: bufferedStdout, |
@@ -16,6 +16,8 @@ module.exports = { | ||
sideEffects: 'idempotent', | ||
inputs: { | ||
childProcess: { | ||
friendlyName: 'Child process', | ||
description: 'The child process to kill.', | ||
@@ -29,3 +31,3 @@ example: '===', | ||
description: 'If set, then force the child process to exit if it cannot be killed gracefully (e.g. because it is already dead).', | ||
extendedDescription: 'If set, this method will first attempt to shut down the child process gracefully (SIGTERM); but if that doesn\'t work after a few miliseconds (`maxMsToWait`), it will use the nuclear option (SIGKILL) to kill the child process with no exceptions.', | ||
extendedDescription: 'If set, this method will first attempt to shut down the child process gracefully (SIGTERM); but if that doesn\'t work after a few milliseconds (`maxMsToWait`), it will use the nuclear option (SIGKILL) to kill the child process with no exceptions.', | ||
example: false, | ||
@@ -36,2 +38,3 @@ defaultsTo: false | ||
maxMsToWait: { | ||
friendlyName: 'Timeout', | ||
description: 'The maximum number of miliseconds to wait for the child process to shut down gracefully.', | ||
@@ -47,5 +50,11 @@ example: 500, | ||
success: { | ||
description: 'The child process was killed successfully.', | ||
outputDescription: 'Whether or not the child process had to be force-killed with "SIGKILL".', | ||
outputFriendlyName: 'Was force killed?', | ||
outputExample: false | ||
}, | ||
invalidChildProcess: { | ||
friendlyName: 'Invalid child process', | ||
description: 'The specified value is not a valid child process instance.', | ||
description: 'The specified value was not a valid child process instance.', | ||
extendedDescription: 'You can obtain a child process instance by calling `spawnChildProcess()`.' | ||
@@ -61,9 +70,2 @@ }, | ||
success: { | ||
description: 'The child process has been killed successfully.', | ||
outputDescription: 'Whether or not the child process had to be force-killed with "SIGKILL".', | ||
outputVariableName: 'wasForceKilled', | ||
example: false | ||
} | ||
}, | ||
@@ -78,2 +80,3 @@ | ||
if (!isObject(inputs.childProcess) || !isFunction(inputs.childProcess.kill) || !isFunction(inputs.childProcess.on) || !isFunction(inputs.childProcess.removeListener)) { | ||
// If not, leave through the `invalidChildProcess` exit. | ||
return exits.invalidChildProcess(); | ||
@@ -110,4 +113,9 @@ } | ||
try { | ||
// Remove the `close` listener from the child process, so that it's not called later | ||
// if that signal comes in belatedly. | ||
inputs.childProcess.removeListener('close', handleClosingChildProc); | ||
// If `forceIfNecessary` is on, attempt to send a SIGKILL to the process, | ||
// and return through the `success` exit with a `true` value indicating that | ||
// the process was force-killed. | ||
if (inputs.forceIfNecessary) { | ||
@@ -117,2 +125,3 @@ inputs.childProcess.kill('SIGKILL'); | ||
} | ||
// Otherwise exit through `couldNotKill`. | ||
else { | ||
@@ -122,2 +131,4 @@ return exits.couldNotKill(); | ||
} | ||
// If any errors occurred attempting to clean up the process, forward them through | ||
// the `error` exit. | ||
catch (e) { | ||
@@ -131,10 +142,12 @@ return exits.error(e); | ||
handleClosingChildProc = function (code, signal) { | ||
// Ignore SIGKILL. | ||
// (if we're seeing it here, it came from somewhere else, and we don't want to get confused). | ||
if (signal === 'SIGKILL') { | ||
// Ignore SIGKILL | ||
// (if we're seeing it here, it came from somewhere else, and we don't want to get confused) | ||
return; | ||
} | ||
// Clear the timeout timer. | ||
clearTimeout(timer); | ||
// Graceful kill successful! | ||
// Graceful kill successful! Return `false` through the `success` exit to indicate a graceful shutdown. | ||
return exits.success(false); | ||
@@ -165,3 +178,3 @@ }; | ||
// then we need to either force kill it with SIGKILL, or fail via the | ||
// `couldNotKill` exit.) | ||
// `couldNotKill` exit). | ||
timer = setTimeout(forceKillOrGiveUp, inputs.maxMsToWait); | ||
@@ -168,0 +181,0 @@ |
@@ -24,4 +24,10 @@ module.exports = { | ||
fn: function (inputs, exits){ | ||
// Import `open` and `openBrowserAndNavigateToUrl`. | ||
var openBrowserAndNavigateToUrl = require('open'); | ||
// Attempt to open the given URL in a browser window. | ||
openBrowserAndNavigateToUrl(inputs.url); | ||
// Return through the `success` exit. | ||
return exits.success(); | ||
@@ -28,0 +34,0 @@ } |
@@ -27,3 +27,2 @@ module.exports = { | ||
command: { | ||
friendlyName: 'Command', | ||
description: 'The command to run in the child process, without any CLI arguments or options.', | ||
@@ -62,7 +61,7 @@ extendedDescription: 'Node core is tolerant of CLI args mixed in with the main "command" in `child_process.exec()`, but it is not so forgiving when using `child_process.spawn()`.', | ||
success: { | ||
outputVariableName: 'childProcess', | ||
outputFriendlyName: 'Child process', | ||
outputDescription: 'A Node child process instance.', | ||
moreInfoUrl: 'https://nodejs.org/api/child_process.html#child_process_class_childprocess', | ||
extendedDescription: 'By the time it is returned, a no-op `error` listener has already been bound to prevent accidental crashing in the event of an unexpected error.', | ||
example: '===', | ||
outputExample: '===', | ||
}, | ||
@@ -74,4 +73,10 @@ | ||
fn: function (inputs,exits) { | ||
// Import `path`. | ||
var path = require('path'); | ||
// Import `child_process.spawn`. | ||
var spawn = require('child_process').spawn; | ||
// Import the `isUndefined` Lodash function. | ||
var isUndefined = require('lodash.isundefined'); | ||
@@ -82,3 +87,3 @@ | ||
// Determine the appropriate `cwd` for `child_process.spawn()`. | ||
// Determine the appropriate `cwd` for `child_process.exec()`. | ||
if (isUndefined(inputs.dir)) { | ||
@@ -89,4 +94,4 @@ // Default directory to current working directory | ||
else { | ||
// (or if a `dir` was specified, resolve it to make sure | ||
// it's an absolute path.) | ||
// If a `dir` was specified, resolve it to make sure | ||
// it's an absolute path. | ||
childProcOpts.cwd = path.resolve(inputs.dir); | ||
@@ -104,3 +109,3 @@ } | ||
// Return live child process. | ||
// Return live child process through the `success` exit. | ||
return exits.success(liveChildProc); | ||
@@ -107,0 +112,0 @@ }, |
{ | ||
"name": "machinepack-process", | ||
"version": "2.0.2", | ||
"version": "3.0.0-0", | ||
"description": "Work with child procs and the running process.", | ||
@@ -20,3 +20,3 @@ "scripts": { | ||
"lodash.isundefined": "3.0.1", | ||
"machine": "~12.1.0", | ||
"machine": "^13.0.0-7", | ||
"machinepack-json": "~2.0.0", | ||
@@ -26,6 +26,6 @@ "open": "0.0.5" | ||
"devDependencies": { | ||
"async": "1.5.2", | ||
"lodash": "3.9.2", | ||
"mocha": "2.4.5", | ||
"test-machinepack-mocha": "^2.1.1" | ||
"async": "2.0.1", | ||
"lodash": "3.10.1", | ||
"mocha": "3.0.2", | ||
"test-machinepack-mocha": "^2.1.7" | ||
}, | ||
@@ -37,6 +37,6 @@ "machinepack": { | ||
"open-browser", | ||
"escape-cli-opt", | ||
"kill-child-process", | ||
"spawn-child-process", | ||
"execute-command" | ||
"execute-command", | ||
"escape-as-command-line-opt" | ||
], | ||
@@ -43,0 +43,0 @@ "testsUrl": "https://travis-ci.org/treelinehq/machinepack-process" |
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
33382
20.42%606
6.88%2
100%+ Added
+ Added
+ Added
+ Added
+ Added
Updated