machine-as-script
Advanced tools
Comparing version 3.0.2 to 4.0.0-0
@@ -17,2 +17,3 @@ #!/usr/bin/env node | ||
var _ = require('lodash'); | ||
var chalk = require('chalk'); | ||
var asScript = require('../'); | ||
@@ -22,10 +23,88 @@ | ||
if (_.isUndefined(yargs.argv._[0])) { | ||
console.error('Usage:'); | ||
console.error('machine-as-script path/to/the/machine/def/you/want/to/use.js'); | ||
console.error(chalk.bold('MACHINE AS SCRIPT')+' -- Run any machine as a script.'); | ||
console.error(); | ||
console.error(); | ||
console.error(chalk.bold('USAGE')); | ||
console.error(' machine-as-script path/to/the/machine/def/you/want/to/use.js'); | ||
console.error(); | ||
console.error(); | ||
console.error(chalk.bold('NOTES')); | ||
console.error(' You can specify the path to a "dry" node machine def (e.g. `machines/foo.js`)'); | ||
console.error(' or to a module which exports a "wet" machine (i.e. already instantiated)'); | ||
console.error(); | ||
return; | ||
} | ||
var pathToMachineDef = path.resolve(yargs.argv._[0]); | ||
asScript({ | ||
machine: require(pathToMachineDef) | ||
}).exec(); | ||
// Resolve the path. | ||
var absPath = path.resolve(yargs.argv._[0]); | ||
// Load the machine. | ||
var wetOrDryMachine; | ||
try { | ||
wetOrDryMachine = require(absPath); | ||
} | ||
catch (e) { | ||
if (e.code === 'MODULE_NOT_FOUND') { | ||
console.error(chalk.bold(chalk.yellow('Could not locate a module at the specified path (`'+absPath+'`).'))); | ||
console.error('Details:'); | ||
throw e; | ||
} | ||
else { | ||
console.error(chalk.bold(chalk.yellow('Encountered an error when trying to load the module at the specified path (`'+absPath+'`).'))); | ||
console.error(); | ||
console.error('Error details:'); | ||
throw e; | ||
} | ||
} | ||
if (_.isEqual(wetOrDryMachine, {})) { | ||
console.error(chalk.bold(chalk.yellow('The module at the specified path (`'+absPath+'`) does not see to export anything.'))); | ||
console.error(chalk.bold(chalk.yellow('Specifically, requiring it yielded an empty dictionary (`{}`).'))); | ||
console.error('--------------------------------------------------------------------------'); | ||
console.error('TROUBLESHOOTING:'); | ||
console.error(); | ||
console.error('This could be for any of the following reasons:'); | ||
console.error(' => It does not export anything.'); | ||
console.error(); | ||
console.error(' => It requires a cyclical dependency.'); | ||
console.error(' (For more info on that, see: https://nodejs.org/api/modules.html#modules_cycles)'); | ||
console.error(); | ||
console.error(' => Or (unlikely) it _actually_ exports an empty dictionary (`{}`)'); | ||
console.error(); | ||
console.error('Check and make sure that the code at the specified path does either:'); | ||
console.error('```'); | ||
console.error('module.exports = {...<<machine def here>>...};'); | ||
console.error('```'); | ||
console.error(); | ||
console.error('Or:'); | ||
console.error('```'); | ||
console.error('module.exports = require(\'machine\').build({...<<machine def here>>...});'); | ||
console.error('```'); | ||
console.error(); | ||
console.error('--------------------------------------------------------------------------'); | ||
return; | ||
} | ||
// If the module at the specified path exports a wet machine that is already wrapped | ||
// in a call machine-as-script, then fail with an error message. This shouldn't ever | ||
// really happen, since scripts don't normally export anything, but it's possible, so | ||
// we aim to handle this case in a way that helps diagnose the problem as quickly as | ||
// possible. | ||
if (wetOrDryMachine._telltale === 'machine-as-script') { | ||
console.error(chalk.bold(chalk.yellow('The script at the specified path ran. But...'))); | ||
console.error('It looks like the module at `'+absPath+'` is using machine-as-script internally.'); | ||
console.error('-----------------------------------------------------------------------------------------------------------------------'); | ||
console.error('First of all, the `machine-as-script` command-line tool is designed to be used with "dry" node machine definitions,'); | ||
console.error('or with modules which export "wet" node machine instances. It should not be called on scripts.'); | ||
console.error('Secondly, it shouldn\'t even be possible for me to know this!'); | ||
console.error('Modules which use `require(\'machine-as-script\')` should never export anything-- they\'re designed to be run in-place.'); | ||
console.error('-----------------------------------------------------------------------------------------------------------------------'); | ||
return; | ||
} | ||
// Otherwise, wrap it with machine-as-script, then exec() it. | ||
else { | ||
asScript({ | ||
machine: wetOrDryMachine | ||
}).exec(); | ||
} |
214
index.js
@@ -16,14 +16,19 @@ /** | ||
/** | ||
* asScript() | ||
* | ||
* @param {Dictionary|Machine} opts | ||
* @property {Dictionary|Machine} opts.machine | ||
* @property {Array} opts.args | ||
* @property {Array} opts.envVarNamespace | ||
* (see readme for more information) | ||
* (See README.md for more information.) | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* @param {Dictionary|Machine} optsOrMachineDef | ||
* @property {Dictionary?} machine | ||
* @property {Array?} args | ||
* @property {Array?} envVarNamespace | ||
* @property {SailsApp?} sails | ||
* | ||
* @return {Machine} [a machine instance] | ||
* @return {Machine} | ||
* A live machine instance, but warped to accept CLI args/opts & env vars. | ||
* Also granted other special abilities. | ||
*/ | ||
module.exports = function runMachineAsScript(opts){ | ||
module.exports = function runMachineAsScript(optsOrMachineDef){ | ||
opts = opts||{}; | ||
optsOrMachineDef = optsOrMachineDef||{}; | ||
@@ -35,12 +40,16 @@ // Use either `opts` or `opts.machine` as the machine definition | ||
var machineDef; | ||
if (!opts.machine) { | ||
machineDef = opts; | ||
var opts; | ||
var MISC_OPTIONS = ['args', 'envVarNamespace', 'sails']; | ||
if (!optsOrMachineDef.machine) { | ||
machineDef = optsOrMachineDef; | ||
opts = _.pick(optsOrMachineDef, MISC_OPTIONS); | ||
} | ||
else { | ||
machineDef = opts.machine; | ||
delete opts.machine; | ||
machineDef = optsOrMachineDef.machine; | ||
opts = _.pick(optsOrMachineDef, MISC_OPTIONS); | ||
} | ||
// Tolerate if no machine was provided (this is just for backwards compatibility-- should be deprecated.) | ||
machineDef = machineDef || {}; | ||
if (!_.isObject(machineDef)) { | ||
throw new Error('Consistency violation: Machine definition must be provided as a dictionary.'); | ||
} | ||
@@ -80,3 +89,3 @@ // Set up namespace for environment variables. | ||
fn: function (inputs, exits){ | ||
exits.error(new Error('Not implemented yet!')); | ||
exits.error(new Error('Not implemented yet! (This is a default `fn` injected by `machine-as-script`.)')); | ||
} | ||
@@ -87,2 +96,33 @@ },machineDef)); | ||
// Finally, before moving on, we check the `habitat` and potentially provide | ||
// access to `env.sails`. | ||
var sailsApp; | ||
if (wetMachine.habitat === 'request') { | ||
throw new Error('The target machine defintion declares a dependency on the `request` habitat, which cannot be provided via the command-line interface. This machine cannot be run using machine-as-script.'); | ||
} | ||
else if (wetMachine.habitat === 'sails') { | ||
// If the machine depends on the Sails habitat, then we'll attempt to use the provided version of `sails`. | ||
if (opts.sails) { | ||
if (!_.isObject(opts.sails) || opts.sails.constructor.name !== 'Sails') { | ||
throw new Error('The supposed Sails app instance provided as `sails` seems a little sketchy. Make sure you are doing `sails: require(\'sails\')`.'); | ||
} | ||
// Down below, we'll attempt to load (but not lift) the Sails app in the current working directory. | ||
// If it works, then we'll run the script, providing it with `env.sails`. After that, regardless of | ||
// how the script exits, we'll call `sails.lower()` to clean up. | ||
sailsApp = opts.sails; | ||
} | ||
// If no `sails` was provided to machine-as-script, then we'll throw an error. | ||
else { | ||
throw new Error('The target machine defintion declares a dependency on the `sails` habitat, but no `sails` app instance was provided as a top-level option to machine-as-script. Make sure this script module is doing: `sails: require(\'sails\')`'); | ||
} | ||
} | ||
// ====================================================================== | ||
// Now we'll put together the configuration for our wet machine instance. | ||
// (using CLI opts, serial CLI args, and/or env vars) | ||
// ====================================================================== | ||
// Configure CLI usage helptext and set up commander | ||
@@ -191,34 +231,130 @@ program.usage('[options]'); | ||
// Set some default exit handlers | ||
liveMachine.setExits({ | ||
error: function(err) { | ||
// console.error(chalk.red('Unexpected error occurred:\n'), err); | ||
console.log(chalk.red('Something went wrong:')); | ||
console.error(err.stack ? chalk.gray(err.stack) : err); | ||
}, | ||
success: function(output) { | ||
// If output is expected, then log it. | ||
if (!_.isUndefined(output)) { | ||
try { | ||
if ( | ||
!_.isUndefined(liveMachine.exits.success.example) || | ||
_.isFunction(liveMachine.exits.success.getExample) || | ||
!_.isUndefined(liveMachine.exits.success.like) || | ||
!_.isUndefined(liveMachine.exits.success.itemOf) | ||
) { | ||
// TODO: support json-encoded output vs colors | ||
console.log(util.inspect(output, {depth: null, colors: true})); | ||
// Now build up a default handler callback for each exit. | ||
// (Note that these can be overridden though!) | ||
var callbacks = {}; | ||
// We use a local variable (`alreadyExited`) as a spinlock. | ||
var alreadyExited; | ||
_.each(_.keys(wetMachine.exits), function builtExitCallback(exitCodeName){ | ||
// Build a callback for this exit that sends the appropriate response. | ||
callbacks[exitCodeName] = function respondApropos(output){ | ||
// This spinlock protects against the machine calling more than one | ||
// exit, or the same exit twice. | ||
if (alreadyExited) { return; } | ||
alreadyExited = true; | ||
if (exitCodeName === 'error') { | ||
console.error(chalk.red('Unexpected error occurred:\n'), output); | ||
console.error(output.stack ? chalk.gray(output.stack) : output); | ||
return; | ||
} | ||
else if (exitCodeName === 'success') { | ||
if (_.isUndefined(output)) { | ||
try { | ||
if ( | ||
!_.isUndefined(liveMachine.exits.success.example) || | ||
_.isFunction(liveMachine.exits.success.getExample) || | ||
!_.isUndefined(liveMachine.exits.success.like) || | ||
!_.isUndefined(liveMachine.exits.success.itemOf) | ||
) { | ||
// TODO: support json-encoded output vs colors | ||
console.log(util.inspect(output, {depth: null, colors: true})); | ||
} | ||
} | ||
catch (e) { /* fail silently if anything goes awry */ } | ||
} | ||
catch (e) { /* fail silently if anything goes awry */ } | ||
return; | ||
// Otherwise, output is expected. So log it. | ||
else { | ||
console.log(chalk.green('OK.')); | ||
} | ||
} | ||
// Miscellaneous exit. | ||
else { | ||
console.log(chalk.cyan('Something went wrong:')); | ||
console.error(output.stack ? chalk.gray(output.stack) : output); | ||
} | ||
};//</callback definition> | ||
});//</each exit> | ||
// Otherwise, log a generic message. | ||
console.log(chalk.green('OK.')); | ||
// Now intercept `.exec()` to take care of sails.lower(), if relevant. | ||
// (we have to do this because any of the callbacks above _could_ be overridden!) | ||
var _originalExecBeforeItWasChangedForUseByMachineAsScript = liveMachine.exec; | ||
liveMachine.exec = function () { | ||
var args = Array.prototype.slice.call(arguments); | ||
// If we're not managing a Sails app instance for this script, then just do the normal thing. | ||
if (_.isUndefined(sailsApp)) { | ||
if (_.isObject(args[0])) { | ||
var combinedCbs = _.extend({}, callbacks, args[0]); | ||
_originalExecBeforeItWasChangedForUseByMachineAsScript.apply(liveMachine, [combinedCbs]); | ||
} | ||
else if (_.isFunction(args[0])) { | ||
_originalExecBeforeItWasChangedForUseByMachineAsScript.apply(liveMachine, [args[0]]); | ||
} | ||
else { | ||
_originalExecBeforeItWasChangedForUseByMachineAsScript.apply(liveMachine, [callbacks]); | ||
} | ||
return; | ||
} | ||
}); | ||
// --• Otherwise, we need to load Sails first, then lower it afterwards. | ||
// Load the Sails app. | ||
sailsApp.load(function (err){ | ||
if (err) { | ||
throw new Error('This script relies on access to Sails, but when attempting to load this Sails app automatically, an error occurred. Details: '+err.stack); | ||
} | ||
// Run underlying .exec(), but intercept it to tear down the Sails app. | ||
_originalExecBeforeItWasChangedForUseByMachineAsScript.apply(liveMachine, [function (sbErr, successResult){ | ||
sailsApp.lower(function (sailsLowerErr) { | ||
if (sailsLowerErr) { | ||
console.warn('This script relies on access to Sails, but when attempting to lower this Sails app automatically after running the script, an error occurred. Details:',sailsLowerErr.stack); | ||
console.warn('Continuing to run the appropriate exit callback anyway...'); | ||
} | ||
// Success | ||
if (!sbErr) { | ||
if (_.isObject(args[0])) { | ||
if (args[0].success) { args[0].success(successResult); } | ||
else { callbacks.success(successResult); } | ||
} | ||
else if (_.isFunction(args[0])) { | ||
args[0](undefined, successResult); | ||
} | ||
else { callbacks.success(successResult); } | ||
} | ||
// Some other exit (or catchall error) | ||
else { | ||
if (_.isObject(args[0]) && _.contains(_.keys(args[0]), sbErr.exit)) { | ||
args[0][sbErr.exit](sbErr.output); | ||
} | ||
else if (_.isFunction(args[0])) { | ||
args[0](sbErr); | ||
} | ||
else if (_.contains(_.keys(callbacks), sbErr.exit)) { | ||
callbacks[sbErr.exit](sbErr.output); | ||
} | ||
else { callbacks.error(sbErr); } | ||
} | ||
});//</after sails.lower()> | ||
}]);//</after calling underlying .exec()> | ||
});//</after sails.load()> | ||
};//</definition of our .exec() override> | ||
// If we're managing a Sails app instance for this script, then pass through `env.sails`. | ||
if (!_.isUndefined(sailsApp)) { | ||
liveMachine.setEnv({ sails: sailsApp }); | ||
} | ||
// Set a telltale property to allow `bin/machine-as-script` to be more | ||
// intelligent about catching wet machine instances which are already wrapped | ||
// in a call to machine-as-script. Realistically, this rarely matters since | ||
// script modules don't normally export anything, but it's here just in case. | ||
liveMachine._telltale = 'machine-as-script'; | ||
// Return the ready-to-exec machine. | ||
@@ -225,0 +361,0 @@ return liveMachine; |
{ | ||
"name": "machine-as-script", | ||
"version": "3.0.2", | ||
"version": "4.0.0-0", | ||
"description": "Run a machine as a command-line script.", | ||
@@ -22,3 +22,3 @@ "scripts": { | ||
"lodash": "3.10.1", | ||
"machine": "^12.3.0", | ||
"machine": "^13.0.0-0", | ||
"rttc": "^9.7.0", | ||
@@ -25,0 +25,0 @@ "yargs": "3.4.5" |
@@ -5,4 +5,5 @@ # machine-as-script | ||
Useful for running jobs (cron, Heroku scheduler), automating repetitive tasks (Grunt, gulp), writing one-off scripts (NPM, Chef), and building production-ready tools with command-line interfaces (e.g. `treeline`, `machinepack`). Suppots _serial command-line arguments_, command-line opts (`--`), and environment variables. | ||
Useful for running jobs (cron, Heroku scheduler), automating repetitive tasks (Grunt, gulp), writing one-off scripts (NPM, Chef), and building production-ready tools with command-line interfaces (e.g. `treeline`, `machinepack`). Supports _serial command-line arguments_, command-line opts (`--`), and environment variables. | ||
```sh | ||
@@ -41,2 +42,3 @@ $ npm install machine-as-script --save | ||
##### Assorted examples | ||
@@ -53,3 +55,15 @@ | ||
## Available Options | ||
Aside from the [normal properties that go into a Node Machine definition](http://node-machine.org/spec), the following additional options are supported: | ||
| Option | Type | Description | | ||
|:------------------|-----------------|:-------------------------------------------------------| | ||
| `machine` | ((dictionary?)) | If specified, `machine-as-script` will use this as the machine definition. Otherwise by default, it expects the machine definition to be passed in at the top-level. In that case, the non-standard (machine-as-script-specific) options are omitted when the machine is built). | ||
| `args` | ((array?)) | The names of inputs, in order, to use for handling serial CLI arguments (more on that [below](#using-serial-cli-arguments)). | ||
| `envVarNamespace` | ((string?)) | The namespace to use when mapping environment variables to runtime arguments for particular inputs (more on that [below](#using-environment-variables)). | ||
| `sails` | ((SailsApp?)) | Only relevant if the machine def declares `habitat: 'sails'`. This is the Sails app instance that will be provided to this machine as `env.sails`. In most cases, if you are using this, you'll want to set it to `require('sails'). The Sails app instance will be automatically loaded before running the machine, and automatically lowered as soon as the machine exits. | ||
## Using serial CLI arguments | ||
@@ -83,2 +97,3 @@ | ||
> Note: In a future release, this will likely change to rely on either something like `env.args` or to use a new option to assign an input for this purpose (similar to machine-as-action's `urlWildcardSuffix`) | ||
@@ -250,2 +265,2 @@ | ||
MIT © 2015 Mike McNeil | ||
MIT © 2015-2016 Mike McNeil, The Treeline Co. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
30241
403
262
2
+ Added@sailshq/lodash@3.10.6(transitive)
+ Addeddebug@3.1.0(transitive)
+ Addedinclude-all@1.0.8(transitive)
+ Addedmachine@13.0.0-24(transitive)
+ Addedms@2.0.0(transitive)
+ Addedswitchback@2.0.5(transitive)
- Removeddebug@2.2.0(transitive)
- Removedlodash@2.4.2(transitive)
- Removedmachine@12.4.0(transitive)
- Removedms@0.7.1(transitive)
- Removedobject-hash@0.3.0(transitive)
- Removedswitchback@2.0.0(transitive)
Updatedmachine@^13.0.0-0