UNPKG

6.42 kBJavaScriptView Raw
1var util = require('util');
2var childProcess = require('child_process');
3var sshPool = require('ssh-pool');
4var _ = require('lodash');
5var LineWrapper = require('stream-line-wrapper');
6var Orchestrator = require('orchestrator');
7var chalk = require('chalk');
8var prettyTime = require('pretty-hrtime');
9var Promise = require('bluebird');
10
11// Expose module.
12module.exports = Shipit;
13
14/**
15 * Initialize a new `Shipit`.
16 */
17
18function Shipit(options) {
19 this.options = _.defaults(options || {}, {
20 stdout: process.stdout,
21 stderr: process.stderr,
22 log: console.log.bind(console)
23 });
24 this.environment = this.options.environment;
25
26 this.initializeEvents();
27
28 if (this.options.stdout === process.stdout)
29 process.stdout.setMaxListeners(100);
30
31 if (this.options.stderr === process.stderr)
32 process.stderr.setMaxListeners(100);
33
34 // Inherits from EventEmitter
35 Orchestrator.call(this);
36}
37
38/**
39 * Inherits from Orchestrator.
40 */
41
42util.inherits(Shipit, Orchestrator);
43
44/**
45 * Initialize the `shipit`.
46 *
47 * @returns {shipit} for chaining
48 */
49
50Shipit.prototype.initialize = function () {
51 this.emit('init');
52 return this.initSshPool();
53};
54
55/**
56 * Initialize events.
57 */
58
59Shipit.prototype.initializeEvents = function () {
60 var shipit = this;
61 var log = shipit.log.bind(shipit);
62
63 shipit.on('task_start', function (e) {
64 // Specific log for noop functions.
65 if (shipit.tasks[e.task].fn.toString() === 'function () {}')
66 return;
67
68 log('\nRunning', '\'' + chalk.cyan(e.task) + '\' task...');
69 });
70
71 shipit.on('task_stop', function (e) {
72 var task = shipit.tasks[e.task];
73 // Specific log for noop functions.
74 if (task.fn.toString() === 'function () {}')
75 return log(
76 'Finished', '\'' + chalk.cyan(e.task) + '\'',
77 chalk.cyan('[ ' + task.dep.join(', ') + ' ]')
78 );
79
80 var time = prettyTime(e.hrDuration);
81 log(
82 'Finished', '\'' + chalk.cyan(e.task) + '\'',
83 'after', chalk.magenta(time)
84 );
85 });
86
87 shipit.on('task_err', function (e) {
88 var msg = formatError(e);
89 var time = prettyTime(e.hrDuration);
90 log(
91 '\'' + chalk.cyan(e.task) + '\'',
92 chalk.red('errored after'),
93 chalk.magenta(time)
94 );
95 log(msg);
96 });
97
98 shipit.on('task_not_found', function (err) {
99 log(
100 chalk.red('Task \'' + err.task + '\' is not in your shipitfile')
101 );
102 log('Please check the documentation for proper shipitfile formatting');
103 });
104};
105
106/**
107 * Format orchestrator error.
108 *
109 * @param {Error} e
110 * @returns {Error}
111 */
112
113function formatError(e) {
114 if (!e.err) {
115 return e.message;
116 }
117
118 // PluginError
119 if (typeof e.err.showStack === 'boolean') {
120 return e.err.toString();
121 }
122
123 // normal error
124 if (e.err.stack) {
125 return e.err.stack;
126 }
127
128 // unknown (string, number, etc.)
129 return new Error(String(e.err)).stack;
130}
131
132/**
133 * Initialize SSH connections.
134 *
135 * @returns {shipit} for chaining
136 */
137
138Shipit.prototype.initSshPool = function () {
139 if (!this.config.servers)
140 throw new Error('Servers not filled');
141
142 var servers = _.isArray(this.config.servers) ? this.config.servers : [this.config.servers];
143 this.pool = new sshPool.ConnectionPool(servers, _.extend({}, this.options, _.pick(this.config, 'key', 'strict')));
144 return this;
145};
146
147/**
148 * Initialize shipit configuration.
149 *
150 * @param {Object} config
151 * @returns {shipit} for chaining
152 */
153
154Shipit.prototype.initConfig = function (config) {
155 config = config || {};
156
157 if (!config[this.environment])
158 throw new Error('Environment "' + this.environment + '" not found in config');
159
160 this.config = _.assign({
161 branch: 'master',
162 keepReleases: 5,
163 shallowClone: false
164 }, config.default || {}, config[this.environment]);
165 return this;
166};
167
168/**
169 * Run a command locally.
170 *
171 * @param {String} command
172 * @param {Object} options
173 * @param {Function} cb
174 * @returns {ChildObject}
175 */
176
177Shipit.prototype.local = function (command, options, cb) {
178 var shipit = this;
179
180 // local(command, cb)
181 if (_.isFunction(options)) {
182 cb = options;
183 options = undefined;
184 }
185
186 return new Promise(function (resolve, reject) {
187 shipit.log('Running "%s" on local.', command);
188
189 options = _.defaults(options || {}, {
190 maxBuffer: 1000 * 1024
191 });
192
193 var stdoutWrapper = new LineWrapper({prefix: '@ '});
194 var stderrWrapper = new LineWrapper({prefix: '@ '});
195
196 var child = childProcess.exec(command, options, function (err, stdout, stderr) {
197 if (err) return reject(err);
198 resolve({
199 child: child,
200 stdout: stdout,
201 stderr: stderr
202 });
203 });
204
205 if (shipit.options.stdout)
206 child.stdout.pipe(stdoutWrapper).pipe(shipit.options.stdout);
207
208 if (shipit.options.stderr)
209 child.stderr.pipe(stderrWrapper).pipe(shipit.options.stderr);
210 }).nodeify(cb);
211};
212
213/**
214 * Run a command remotely.
215 *
216 * @param {String} command
217 * @param {Object} options
218 * @param {Function} cb
219 * @returns {ChildObject}
220 */
221
222Shipit.prototype.remote = function (command, options, cb) {
223 if (options && options.cwd) {
224 command = 'cd "' + options.cwd.replace(/"/g, '\\"') + '" && ' + command;
225 delete options.cwd;
226 }
227 return this.pool.run(command, options, cb);
228};
229
230/**
231 * Copy from local to remote or vice versa.
232 *
233 * @param {String} src
234 * @param {String} dest
235 * @param {Function} callback
236 * @returns {ChildObject}
237 */
238
239Shipit.prototype.remoteCopy = function (src, dest, options, callback) {
240
241 // remoteCopy(command, callback)
242 if (_.isFunction(options)) {
243 callback = options;
244 options = undefined;
245 }
246
247 options = _.defaults(options || {}, {
248 ignores: this.config && this.config.ignores ? this.config.ignores : [],
249 rsync: this.config && this.config.rsync ? this.config.rsync : []
250 });
251
252 return this.pool.copy(src, dest, options, callback);
253};
254
255/**
256 * Log.
257 *
258 * @see console.log
259 */
260
261Shipit.prototype.log = function () {
262 this.options.log.apply(null, arguments);
263};
264
265/**
266 * Create a new blocking task.
267 *
268 * @see shipit.task
269 */
270
271Shipit.prototype.blTask = function (name) {
272 this.task.apply(this, arguments);
273
274 var task = this.tasks[name];
275 task.blocking = true;
276 return task;
277};
278
279/**
280 * Test if we are ready to run a task.
281 * Implement blocking task.
282 */
283
284Shipit.prototype._readyToRunTask = function () {
285 if (_.find(this.tasks, {running: true, blocking: true}))
286 return false;
287
288 return Orchestrator.prototype._readyToRunTask.apply(this, arguments);
289};