From bee67cc37fa81f6b03ecbecae92d190f3d474656 Mon Sep 17 00:00:00 2001 From: Dario Vladovic Date: Tue, 21 May 2019 00:58:10 +0200 Subject: [PATCH 1/2] Control logging through `options.logging` param - implementing `options.logging` that defaults to `console.log`; updating tests to use `options.logging = false;` - updating docs - swapping map + join with single pass reduce --- README.md | 2 + deploy.js | 61 +++++++++++++++++---------- package.json | 2 +- test/deploy.mocha.js | 98 ++++++++++++++++++++++---------------------- 4 files changed, 92 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 82937fd..f468661 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ Deploy to a single environment - `deployConfig` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** object containing deploy configs for all environments - `env` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the environment to deploy to - `args` **[array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** custom deploy command-line arguments +- `options` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** deploy options (optional, default `{}`) + - `options.logging` **([boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) \| [function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function))** logging function or `false` to disable logging (optional, default `console.log`) - `cb` **[DeployCallback](#deploycallback)** done callback Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** return value is always `false` diff --git a/deploy.js b/deploy.js index 3f2db67..e9bf05f 100644 --- a/deploy.js +++ b/deploy.js @@ -7,6 +7,8 @@ var path = require('path'); var series = require('run-series'); var tv4 = require('tv4'); +var noop = Function.prototype; + var schema = { type: 'object', properties: { @@ -32,10 +34,10 @@ function spawn(config, args, cb) { args = args || []; if (args.length > 0) { - var cmdArgs = args.map(function (arg) { - return format('"%s"', arg); - }).join(' '); - cmd = [cmd, cmdArgs].join(' '); + cmd = args.reduce(function (acc, arg) { + acc += format(' "%s"', arg); + return acc; + }, cmd).trim(); } var proc = child_process.spawn('sh', ['-c', cmd], { stdio: 'inherit' }); @@ -53,6 +55,10 @@ function spawn(config, args, cb) { }); } +function isFunction(arg) { + return typeof arg === 'function'; +} + function clone(obj) { return JSON.parse(JSON.stringify(obj)); } @@ -66,29 +72,39 @@ function castArray(arg) { * @param {object} deployConfig object containing deploy configs for all environments * @param {string} env the name of the environment to deploy to * @param {array} args custom deploy command-line arguments + * @param {object} [options={}] deploy options + * @param {boolean|function} [options.logging=console.log] logging function or `false` to disable logging * @param {DeployCallback} cb done callback * @returns {boolean} return value is always `false` */ -function deployForEnv(deployConfig, env, args, cb) { - if (!deployConfig[env]) { - return cb(new Error(format('%s not defined in deploy section', env))); +function deployForEnv(deployConfig, env, args, options, cb) { + if (isFunction(options)) { + cb = options; + options = {}; } - var envConfig = clone(deployConfig[env]); + options = options || {}; + options.logging = options.hasOwnProperty('logging') ? options.logging : console.log; + if (options.logging === true) options.logging = console.log; - if (envConfig.ssh_options) { - envConfig.ssh_options = castArray(envConfig.ssh_options).map(function (option) { - return format('-o %s', option); - }).join(' '); + var log = isFunction(options.logging) ? options.logging : noop; + + if (!deployConfig[env]) { + return cb(new Error(format('%s not defined in deploy section', env))); } + var envConfig = clone(deployConfig[env]); var result = tv4.validateResult(envConfig, schema); if (!result.valid) { return cb(result.error); } - if (process.env.NODE_ENV !== 'test') { - console.log('--> Deploying to %s environment', env); + if (envConfig.ssh_options) { + envConfig.ssh_options = castArray(envConfig.ssh_options) + .reduce(function (acc, option) { + acc += format(' -o %s', option); + return acc; + }, '').trim(); } if (process.platform !== 'win32') { @@ -98,19 +114,18 @@ function deployForEnv(deployConfig, env, args, cb) { var hosts = castArray(envConfig.host); var jobs = hosts.map(function (host) { return function job(done) { - if (process.env.NODE_ENV !== 'test') { - console.log('--> on host %s', host.host ? host.host : host); - } - var config = clone(envConfig); config.host = host; config['post-deploy'] = prependEnv(config['post-deploy'], config.env); + log(format('--> on host %s', host)); spawn(config, args, done); }; }); + + log(format('--> Deploying to %s environment', env)); series(jobs, function (err, result) { - result = Array.isArray(envConfig.host) ? result : result[0]; + if (!Array.isArray(envConfig.host)) result = result && result[0]; cb(err, result); }); @@ -119,9 +134,11 @@ function deployForEnv(deployConfig, env, args, cb) { function envToString(env) { env = env || {}; - return Object.keys(env).map(function (name) { - return format('%s=%s', name.toUpperCase(), env[name]); - }).join(' '); + return Object.keys(env) + .reduce(function (acc, name) { + acc += format(' %s=%s', name.toUpperCase(), env[name]); + return acc; + }, '').trim(); } /** diff --git a/package.json b/package.json index a995215..67b4705 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "url": "https://github.com/Unitech/pm2-deploy.git" }, "scripts": { - "lint": "eslint \"**/*.js\"", + "lint": "eslint .", "test": "mocha", "docs": "documentation readme ./deploy.js --section=API" }, diff --git a/test/deploy.mocha.js b/test/deploy.mocha.js index b29a215..1c8d325 100644 --- a/test/deploy.mocha.js +++ b/test/deploy.mocha.js @@ -29,9 +29,10 @@ describe('deploy', function () { return proc; }; - var conf; + var config; + var options; beforeEach(function () { - conf = { + config = { staging: { user: 'user', host: 'host', @@ -41,21 +42,22 @@ describe('deploy', function () { 'post-deploy': 'post-deploy', }, }; + options = { logging: false }; }); it('is a function', function () { deploy.deployForEnv.should.be.a.Function(); }); - it('returns false()', function () { - var ret = deploy.deployForEnv(conf, 'staging', [], function () {}); + it('returns `false`', function () { + var ret = deploy.deployForEnv(config, 'staging', [], options, function () {}); ret.should.be.false(); }); describe('deploy_conf validation', function () { it('validate user', function (done) { - conf.staging.user = ''; - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + config.staging.user = ''; + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.equal(200); err.message.should.match('String is too short (0 chars), minimum 1'); @@ -64,8 +66,8 @@ describe('deploy', function () { }); it('requires host', function (done) { - delete conf.staging.host; - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + delete config.staging.host; + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.equal(302); err.message.should.match('Missing required property: host'); @@ -74,8 +76,8 @@ describe('deploy', function () { }); it('requires repo', function (done) { - delete conf.staging.repo; - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + delete config.staging.repo; + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.equal(302); err.message.should.match('Missing required property: repo'); @@ -84,8 +86,8 @@ describe('deploy', function () { }); it('requires path', function (done) { - delete conf.staging.path; - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + delete config.staging.path; + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.equal(302); err.message.should.match('Missing required property: path'); @@ -94,8 +96,8 @@ describe('deploy', function () { }); it('requires ref', function (done) { - delete conf.staging.ref; - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + delete config.staging.ref; + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.equal(302); err.message.should.match('Missing required property: ref'); @@ -111,17 +113,17 @@ describe('deploy', function () { spawnNotifier.on('spawned', function (proc) { proc.emit('close', 0); }); - deploy.deployForEnv(conf, 'staging', argsIn, function (err, argsOut) { + deploy.deployForEnv(config, 'staging', argsIn, options, function (err, argsOut) { argsOut.should.eql(argsIn); done(err); }); }); - it('invokes sh -c', function (done) { + it('invokes `sh -c`', function (done) { spawnNotifier.on('spawned', function (proc) { proc.emit('close', 0); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { spawnCalls.length.should.equal(1); spawnCalls[0][0].should.equal('sh'); spawnCalls[0][1].should.be.an.Array(); @@ -134,7 +136,7 @@ describe('deploy', function () { spawnNotifier.on('spawned', function (proc) { proc.emit('close', 0); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { spawnCalls.length.should.equal(1); spawnCalls[0][1][1].should.be.a.String(); @@ -146,24 +148,24 @@ describe('deploy', function () { var echoData = JSON.parse(echoJSON); echoData.should.be.an.Object(); - echoData.ref.should.eql(conf.staging.ref); - echoData.user.should.eql(conf.staging.user); - echoData.repo.should.eql(conf.staging.repo); - echoData.path.should.eql(path.resolve(conf.staging.path)); - echoData.host.should.eql(conf.staging.host); - echoData['post-deploy'].should.eql(conf.staging['post-deploy']); - - conf.staging.env = { a: 1, b: 2 }; - deploy.deployForEnv(conf, 'staging', [], function () { + echoData.ref.should.eql(config.staging.ref); + echoData.user.should.eql(config.staging.user); + echoData.repo.should.eql(config.staging.repo); + echoData.path.should.eql(path.resolve(config.staging.path)); + echoData.host.should.eql(config.staging.host); + echoData['post-deploy'].should.eql(config.staging['post-deploy']); + + config.staging.env = { a: 1, b: 2 }; + deploy.deployForEnv(config, 'staging', [], options, function () { spawnCalls.length.should.equal(2); spawnCalls[1][1][1].should.be.a.String(); echoData = JSON.parse(spawnCalls[1][1][1].match(/^echo '(.+?)'/)[1]); echoData['post-deploy'].should.eql( - format('export A=1 B=2 && %s', conf.staging['post-deploy']) + format('export A=1 B=2 && %s', config.staging['post-deploy']) ); - conf.staging['post-deploy'] = ''; - deploy.deployForEnv(conf, 'staging', [], function () { + config.staging['post-deploy'] = ''; + deploy.deployForEnv(config, 'staging', [], options, function () { spawnCalls.length.should.equal(3); spawnCalls[2][1][1].should.be.a.String(); echoData = JSON.parse(spawnCalls[2][1][1].match(/^echo '(.+?)'/)[1]); @@ -178,7 +180,7 @@ describe('deploy', function () { spawnNotifier.on('spawned', function (proc) { proc.emit('close', 0); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { spawnCalls.length.should.equal(1); spawnCalls[0][1][1].should.be.a.String(); var pipeTo = spawnCalls[0][1][1].split(/\s*\|\s*/)[1]; @@ -196,7 +198,7 @@ describe('deploy', function () { proc.emit('error', error); proc.emit('close', 1); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.stack.should.eql(error.stack); done(); @@ -210,7 +212,7 @@ describe('deploy', function () { proc.emit('error', error); proc.emit('close', 1); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.eql(error.code); done(); @@ -222,7 +224,7 @@ describe('deploy', function () { var hosts = ['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4']; beforeEach(function () { - conf.staging.host = hosts; + config.staging.host = hosts; }); it('runs each host in series', function (done) { @@ -235,7 +237,7 @@ describe('deploy', function () { spawnCount -= 1; }); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { done(err); }); }); @@ -253,11 +255,11 @@ describe('deploy', function () { var echoData = JSON.parse(echoJSON); echoData.should.be.an.Object(); - echoData.ref.should.eql(conf.staging.ref); - echoData.repo.should.eql(conf.staging.repo); - echoData.path.should.eql(path.resolve(conf.staging.path)); + echoData.ref.should.eql(config.staging.ref); + echoData.repo.should.eql(config.staging.repo); + echoData.path.should.eql(path.resolve(config.staging.path)); echoData.host.should.eql(hosts[spawnCount]); - echoData['post-deploy'].should.eql(conf.staging['post-deploy']); + echoData['post-deploy'].should.eql(config.staging['post-deploy']); spawnCount += 1; @@ -266,7 +268,7 @@ describe('deploy', function () { }); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { spawnCount.should.eql(4); done(err); }); @@ -285,12 +287,12 @@ describe('deploy', function () { var echoData = JSON.parse(echoJSON); echoData.should.be.an.Object(); - echoData.ref.should.eql(conf.staging.ref); - echoData.repo.should.eql(conf.staging.repo); - echoData.path.should.eql(path.resolve(conf.staging.path)); + echoData.ref.should.eql(config.staging.ref); + echoData.repo.should.eql(config.staging.repo); + echoData.path.should.eql(path.resolve(config.staging.path)); echoData.host.should.eql(hosts[spawnCount]); echoData['post-deploy'].should.eql( - format('export A=1 B=2 && %s', conf.staging['post-deploy']) + format('export A=1 B=2 && %s', config.staging['post-deploy']) ); spawnCount += 1; @@ -300,8 +302,8 @@ describe('deploy', function () { }); }); - Object.assign(conf.staging, { env: { a: 1, b: 2 } }); - deploy.deployForEnv(conf, 'staging', [], function () { + Object.assign(config.staging, { env: { a: 1, b: 2 } }); + deploy.deployForEnv(config, 'staging', [], options, function () { spawnCount.should.eql(4); done(); }); @@ -313,7 +315,7 @@ describe('deploy', function () { proc.emit('close', 0); }); - deploy.deployForEnv(conf, 'staging', argsIn, function (err, argsOut) { + deploy.deployForEnv(config, 'staging', argsIn, options, function (err, argsOut) { argsOut.should.be.an.Array(); argsOut.length.should.eql(4); argsOut[0].should.eql(argsIn); @@ -332,7 +334,7 @@ describe('deploy', function () { proc.emit('error', error); proc.emit('close', 1); }); - deploy.deployForEnv(conf, 'staging', [], function (err, args) { + deploy.deployForEnv(config, 'staging', [], options, function (err) { err.should.be.an.Error(); err.code.should.eql(error.code); spawnCalls.length.should.eql(1); From 657ce74fae6abf54b304e0c055e706e349b4dfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Vladovi=C4=87?= Date: Tue, 21 May 2019 05:49:50 +0200 Subject: [PATCH 2/2] Enable semantic logging :wrench: --- deploy.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/deploy.js b/deploy.js index e9bf05f..6797b8b 100644 --- a/deploy.js +++ b/deploy.js @@ -7,8 +7,6 @@ var path = require('path'); var series = require('run-series'); var tv4 = require('tv4'); -var noop = Function.prototype; - var schema = { type: 'object', properties: { @@ -87,7 +85,12 @@ function deployForEnv(deployConfig, env, args, options, cb) { options.logging = options.hasOwnProperty('logging') ? options.logging : console.log; if (options.logging === true) options.logging = console.log; - var log = isFunction(options.logging) ? options.logging : noop; + function log() { + if (!isFunction(options.logging)) return; + var args = arguments.slice(0); + if (options.logging === console.log) args.shift(); + return options.logging.apply(options, args); + } if (!deployConfig[env]) { return cb(new Error(format('%s not defined in deploy section', env))); @@ -118,12 +121,12 @@ function deployForEnv(deployConfig, env, args, options, cb) { config.host = host; config['post-deploy'] = prependEnv(config['post-deploy'], config.env); - log(format('--> on host %s', host)); + log({ host: host }, format('--> on host %s', host)); spawn(config, args, done); }; }); - log(format('--> Deploying to %s environment', env)); + log({ env: env }, format('--> Deploying to %s environment', env)); series(jobs, function (err, result) { if (!Array.isArray(envConfig.host)) result = result && result[0]; cb(err, result);