From a76928426990561d84942005fd962353e249482c Mon Sep 17 00:00:00 2001 From: Patrick Stadler Date: Mon, 9 Jun 2014 02:47:35 +0200 Subject: [PATCH] implement tasks. fixes #18 --- README.md | 40 ++++++++++++++-- bin/fly.js | 13 ++++-- gulpfile.js | 2 + lib/flightplan.js | 102 ++++++++++++++++++++++++++++++++++++----- lib/transport/index.js | 3 +- package.json | 2 +- 6 files changed, 142 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 6299cfe..d6871f5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ $ npm install -g flightplan $ npm install flightplan --save-dev # run a flightplan (`fly --help` for more information) -$ fly [--plan flightplan.(js|coffee)] +$ fly [task:] [--plan flightplan.(js|coffee)] ``` By default, the `fly` command will look for `flightplan.js` or `flightplan.coffee`. @@ -160,6 +160,34 @@ plan.local(function(transport) {}); // ... ``` +### Tasks +Flightplan supports optional tasks to run a subset of flights. + +```javascript +// fly deploy: +plan.local('deploy', function(transport) {}); + +// fly build: +plan.local('build', function(transport) {}); + +// fly deploy: or... +// fly build: +plan.local(['deploy', 'build'], function(transport) {}); +plan.remote(['deploy', 'build'], function(transport) {}); +``` + +If no task is specified it's implicitly set to "default". Therefore, +`fly ` is the same as `fly default:`. + +```javascript +// fly +plan.local(function(transport) {}); +// is the same as... +plan.local('default', function(transport) {}); +// "default" + other tasks: +plan.remote(['default', 'deploy', 'build'], function(transport) {}); +``` + ### flightplan.briefing(config) → this Configure the flightplan's destinations with `briefing()`. Without a @@ -205,7 +233,7 @@ the `-u|--username` option: fly production --username=admin ``` -### flightplan.local(fn) → this +### flightplan.local([task|tasks, ]fn) → this Calling this method registers a local flight. Local flights are executed on your localhost. When `fn` gets called a `Transport` object @@ -217,7 +245,10 @@ plan.local(function(local) { }); ``` -### flightplan.remote(fn) → this +An optional first parameter of type Array or String can be passed for +defining the flight's task(s). + +### flightplan.remote([task|tasks, ]fn) → this Calling this method registers a remote flight. Remote flights are executed on the current destination's remote hosts defined @@ -230,6 +261,9 @@ plan.remote(function(remote) { }); ``` +An optional first parameter of type Array or String can be passed for +defining the flight's task(s). + ### flightplan.success(fn) → this `fn()` is called after the flightplan (and therefore all flights) diff --git a/bin/fly.js b/bin/fly.js index 071cabd..c7b554f 100755 --- a/bin/fly.js +++ b/bin/fly.js @@ -6,7 +6,7 @@ var path = require('path') , version = require('../package.json').version; program - .usage(' [options]') + .usage('[task:]destination [options]') .version(version) .option('-p, --plan ', 'path to flightplan (default: flightplan.js)', 'flightplan.js') .option('-u, --username ', 'user for connecting to remote hosts') @@ -47,7 +47,14 @@ var flightplan = require(flightFile) debug: program.debug || null }; -var destination = program.args[0]; +var destination = program.args[0] + , task = 'default'; + +if(~(destination || '').indexOf(':')) { + var arr = destination.split(':'); + task = arr[0]; + destination = arr[1]; +} if(!flightplan.requiresDestination) { logger.error(logger.format('%s is not a valid flightplan', program.plan.white)); @@ -60,4 +67,4 @@ if(!destination && flightplan.requiresDestination()) { process.exit(1); } -flightplan.start(destination, options); \ No newline at end of file +flightplan.start(task, destination, options); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index f15b1b2..f53175c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -44,6 +44,8 @@ gulp.task('docs', function(taskFinished) { , readmeStr = fs.readFileSync(readme, 'utf8'); docsStr = docsStr + .replace(/</g, "<") + .replace(/>/g, ">") .replace(/'/g, "'") .replace(/"/g, '"') .replace(/&/g, '&'); diff --git a/lib/flightplan.js b/lib/flightplan.js index 0fb6189..a943231 100644 --- a/lib/flightplan.js +++ b/lib/flightplan.js @@ -57,13 +57,42 @@ var Fiber = require('fibers') * // ... * ``` * + * ### Tasks + * Flightplan supports optional tasks to run a subset of flights. + * + * ```javascript + * // fly deploy: + * plan.local('deploy', function(transport) {}); + * + * // fly build: + * plan.local('build', function(transport) {}); + * + * // fly deploy: or... + * // fly build: + * plan.local(['deploy', 'build'], function(transport) {}); + * plan.remote(['deploy', 'build'], function(transport) {}); + * ``` + * + * If no task is specified it's implicitly set to "default". Therefore, + * `fly ` is the same as `fly default:`. + * + * ```javascript + * // fly + * plan.local(function(transport) {}); + * // is the same as... + * plan.local('default', function(transport) {}); + * // "default" + other tasks: + * plan.remote(['default', 'deploy', 'build'], function(transport) {}); + * ``` + * * @class Flightplan * @return flightplan */ function Flightplan() { this._briefing = null; - this.flights = []; + this.flights = {}; this.target = { + task: 'default', destination: null, hosts: [] }; @@ -164,11 +193,33 @@ Flightplan.prototype = { * }); * ``` * - * @method local(fn) + * An optional first parameter of type Array or String can be passed for + * defining the flight's task(s). + * + * @method local([tasks, ]fn) * @return this */ - local: function(fn) { - this.flights.push(new LocalFlight(this, fn)); + local: function() { + var args = Array.prototype.slice.call(arguments, 0); + var fn, tasks = []; + + if(typeof args[0] === 'string') { + tasks.push(args[0]); + fn = args[1]; + } else if(args[0] instanceof Array) { + tasks = args[0]; + fn = args[1]; + } else { + tasks.push('default'); + } + + var flight = new LocalFlight(this, fn || args[0]); + + tasks.forEach(function(task) { + this.flights[task] = this.flights[task] || []; + this.flights[task].push(flight); + }.bind(this)); + return this; }, @@ -184,11 +235,33 @@ Flightplan.prototype = { * }); * ``` * - * @method remote(fn) + * An optional first parameter of type Array or String can be passed for + * defining the flight's task(s). + * + * @method remote([tasks, ]fn) * @return this */ - remote: function(fn) { - this.flights.push(new RemoteFlight(this, fn)); + remote: function() { + var args = Array.prototype.slice.call(arguments, 0); + var fn, tasks = []; + + if(typeof args[0] === 'string') { + tasks.push(args[0]); + fn = args[1]; + } else if(args[0] instanceof Array) { + tasks = args[0]; + fn = args[1]; + } else { + tasks.push('default'); + } + + var flight = new RemoteFlight(this, fn || args[0]); + + tasks.forEach(function(task) { + this.flights[task] = this.flights[task] || []; + this.flights[task].push(flight); + }.bind(this)); + this.hasRemoteFlights = true; return this; }, @@ -244,7 +317,8 @@ Flightplan.prototype = { return this.hasRemoteFlights; }, - start: function(destination, options) { + start: function(task, destination, options) { + this.target.task = task; this.target.destination = destination; if(this.briefing()) { @@ -257,22 +331,26 @@ Flightplan.prototype = { this.logger.error((destination || '').warn, 'is not a valid destination'); process.exit(1); } + + var flightsForTask = this.flights[this.target.task] || []; this.target.hosts = this.briefing().getHostsForDestination(this.target.destination); if(this.isAborted()) { this.logger.error('Flightplan aborted'.error); process.exit(1); } - this.logger.info('Executing flightplan with'.info, String(this.flights.length).magenta - , 'planned flight(s) to'.info, (this.target.destination || 'localhost').warn); + this.logger.info('Flying to'.info + , (this.target.task === 'default' ? '' : (this.target.task.warn + ':')) + + (this.target.destination || 'localhost').warn + , 'with '.info + String(flightsForTask.length).magenta + ' flight(s)'.info); this.logger.space(); new Fiber(function() { var t = process.hrtime(); - for(var i=0, len=this.flights.length; i < len; i++) { - var flight = this.flights[i]; + for(var i=0, len=flightsForTask.length; i < len; i++) { + var flight = flightsForTask[i]; this.logger.info('Flight'.info, this.logger.format('%s/%s', i+1, len).magenta , 'launched...'.info); diff --git a/lib/transport/index.js b/lib/transport/index.js index 2a2a63d..22a53b4 100644 --- a/lib/transport/index.js +++ b/lib/transport/index.js @@ -314,6 +314,7 @@ Transport.prototype = { * @method with(cmd|options[, options], fn) */ with: function() { + var previousExecWith = this._execWith; var previousOptions = util._extend({}, this.options); // clone var args = Array.prototype.slice.call(arguments, 0); @@ -326,7 +327,7 @@ Transport.prototype = { args[i](); } } - this._execWith = ''; + this._execWith = previousExecWith; this.options = previousOptions; }, diff --git a/package.json b/package.json index 7442aad..b043ff3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "flightplan", "description": "Library for streamlining application deployment or systems administration tasks", - "version": "0.3.4", + "version": "0.4.0", "author": "Patrick Stadler ", "keywords": [ "deploy",