UNPKG

4.09 kBJavaScriptView Raw
1'use strict';
2
3var childProcess = require('child_process');
4var pathlib = require('path');
5var stream = require('stream');
6var util = require('util');
7
8var _ = require('lodash');
9var Vinyl = require('vinyl');
10var gutil = require('gulp-util');
11
12
13// new Command(commandTemplate, [opts])
14// --------------------------------------------------
15// Creates a command that can be executed later.
16
17module.exports = function Command(commandTemplate, opts) {
18
19 // We're on Windows if `process.platform` starts with "win", i.e. "win32" or "win64".
20 var isWindows = (process.platform.lastIndexOf('win') === 0);
21
22 // The cwd and environment of the command are the same as the main node process by default.
23 opts = _.defaults(opts||{}, {
24 cwd: process.cwd(),
25 env: process.env,
26 silent: false,
27 verbosity: (opts && opts.silent) ? 1 : 2
28 });
29
30 // Include node_modules/.bin on the path when we execute the command.
31 var oldPath = opts.env.PATH;
32 opts.env.PATH = pathlib.join(__dirname, '..', '..', '.bin');
33 opts.env.PATH += pathlib.delimiter;
34 opts.env.PATH += oldPath;
35
36
37 // Command#exec([stdin], [callback])
38 // --------------------------------------------------
39 // Execute the command, invoking the callback when the command exits.
40 // Returns a Vinyl file wrapping the command's stdout.
41
42 this.exec = function exec(stdin, callback) {
43
44 // Parse the arguments, both are optional
45 if (typeof arguments[0] === 'function') {
46 callback = arguments[0];
47 stdin = undefined;
48 } else if (typeof callback !== 'function') {
49 callback = function(){};
50 }
51 if (!(stdin instanceof Vinyl)) {
52 var defaultName = commandTemplate.split(' ')[0];
53 if (typeof stdin === 'string') {
54 stdin = new Vinyl({
55 path: defaultName,
56 contents: new Buffer(stdin)
57 });
58 } else if (stdin instanceof Buffer || stdin instanceof stream.Readable) {
59 stdin = new Vinyl({
60 path: defaultName,
61 contents: stdin
62 });
63 } else {
64 stdin = new Vinyl(stdin);
65 if (stdin.path === null) stdin.path = defaultName;
66 }
67 }
68
69 // We spawn the command in a subshell, so things like I/O redirection just work.
70 // i.e. `echo hello world >> ./hello.txt` works as expected.
71 var command = _.template(commandTemplate, {file:stdin});
72 var subshell;
73 if (isWindows) {
74 subshell = childProcess.spawn('cmd.exe', ['/c', command], {env:opts.env, cwd:opts.cwd});
75 } else {
76 subshell = childProcess.spawn('sh', ['-c', command], {env:opts.env, cwd:opts.cwd});
77 }
78
79 // Setup the logs. The stdout of the command is only logged if the verbosity is greater
80 // than 2. The stderr is always logged.
81 var logStream = new stream.PassThrough();
82 if (opts.verbosity >= 2) {
83 subshell.stdout.pipe(logStream);
84 }
85 subshell.stderr.pipe(logStream);
86
87 // Invoke the callback once the command has finished.
88 subshell.once('close', function (code) {
89
90 // Print the logs (i.e. the stdout and stderr). We wait until the subshell has closed
91 // to write all of the logs at once, avoiding race conditions with concurrent commands.
92 if (opts.verbosity >= 1) {
93 var logTitle = util.format(
94 '$ %s %s',
95 gutil.colors.blue(command),
96 (opts.verbosity < 2) ? gutil.colors.grey('# Silenced') : ''
97 );
98 gutil.log(logTitle);
99 var logContents = logStream.read();
100 if (logContents !== null) process.stdout.write(logContents);
101 }
102
103 // We consider it an error if the command exited with a non-zero exit code.
104 if (code !== 0) {
105 var errmsg = util.format('Command `%s` exited with code %s', command, code);
106 var err = new Error(errmsg);
107 err.status = code;
108 return callback(err);
109 }
110
111 callback(null);
112 });
113
114 // Get the party started by writing to the subshell's stdin
115 // and return a Vinyl file wrapping subshell's stdout.
116 var stdout = new Vinyl(stdin);
117 stdout.contents = subshell.stdout.pipe(new stream.PassThrough());
118 stdin.pipe(subshell.stdin);
119 return stdout;
120 };
121
122
123 // Command#toString()
124 // --------------------------------------------------
125 // Returns the command template
126
127 this.toString = function toString() {
128 return commandTemplate;
129 };
130
131};