UNPKG

12.9 kBJavaScriptView Raw
1"use strict";
2var __rest = (this && this.__rest) || function (s, e) {
3 var t = {};
4 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5 t[p] = s[p];
6 if (s != null && typeof Object.getOwnPropertySymbols === "function")
7 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
8 t[p[i]] = s[p[i]];
9 return t;
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12var path = require("path");
13var net = require("net");
14var sfs = require("fs");
15var assign = require("lodash.assign");
16var rxjs_1 = require("rxjs");
17var operators_1 = require("rxjs/operators");
18var spawnOg = require('child_process').spawn; //tslint:disable-line:no-var-requires
19var isWindows = process.platform === 'win32';
20var d = require('debug')('spawn-rx'); //tslint:disable-line:no-var-requires
21/**
22 * stat a file but don't throw if it doesn't exist
23 *
24 * @param {string} file The path to a file
25 * @return {Stats} The stats structure
26 *
27 * @private
28 */
29function statSyncNoException(file) {
30 try {
31 return sfs.statSync(file);
32 }
33 catch (e) {
34 return null;
35 }
36}
37/**
38 * Search PATH to see if a file exists in any of the path folders.
39 *
40 * @param {string} exe The file to search for
41 * @return {string} A fully qualified path, or the original path if nothing
42 * is found
43 *
44 * @private
45 */
46function runDownPath(exe) {
47 // NB: Windows won't search PATH looking for executables in spawn like
48 // Posix does
49 // Files with any directory path don't get this applied
50 if (exe.match(/[\\\/]/)) {
51 d('Path has slash in directory, bailing');
52 return exe;
53 }
54 var target = path.join('.', exe);
55 if (statSyncNoException(target)) {
56 d("Found executable in currect directory: " + target);
57 return target;
58 }
59 var haystack = process.env.PATH.split(isWindows ? ';' : ':');
60 for (var _i = 0, haystack_1 = haystack; _i < haystack_1.length; _i++) {
61 var p = haystack_1[_i];
62 var needle = path.join(p, exe);
63 if (statSyncNoException(needle)) {
64 return needle;
65 }
66 }
67 d('Failed to find executable anywhere in path');
68 return exe;
69}
70/**
71 * Finds the actual executable and parameters to run on Windows. This method
72 * mimics the POSIX behavior of being able to run scripts as executables by
73 * replacing the passed-in executable with the script runner, for PowerShell,
74 * CMD, and node scripts.
75 *
76 * This method also does the work of running down PATH, which spawn on Windows
77 * also doesn't do, unlike on POSIX.
78 *
79 * @param {string} exe The executable to run
80 * @param {Array<string>} args The arguments to run
81 *
82 * @return {Object} The cmd and args to run
83 * @property {string} cmd The command to pass to spawn
84 * @property {Array<string>} args The arguments to pass to spawn
85 */
86function findActualExecutable(exe, args) {
87 // POSIX can just execute scripts directly, no need for silly goosery
88 if (process.platform !== 'win32') {
89 return { cmd: runDownPath(exe), args: args };
90 }
91 if (!sfs.existsSync(exe)) {
92 // NB: When you write something like `surf-client ... -- surf-build` on Windows,
93 // a shell would normally convert that to surf-build.cmd, but since it's passed
94 // in as an argument, it doesn't happen
95 var possibleExts = ['.exe', '.bat', '.cmd', '.ps1'];
96 for (var _i = 0, possibleExts_1 = possibleExts; _i < possibleExts_1.length; _i++) {
97 var ext = possibleExts_1[_i];
98 var possibleFullPath = runDownPath("" + exe + ext);
99 if (sfs.existsSync(possibleFullPath)) {
100 return findActualExecutable(possibleFullPath, args);
101 }
102 }
103 }
104 if (exe.match(/\.ps1$/i)) {
105 var cmd = path.join(process.env.SYSTEMROOT, 'System32', 'WindowsPowerShell', 'v1.0', 'PowerShell.exe');
106 var psargs = ['-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', exe];
107 return { cmd: cmd, args: psargs.concat(args) };
108 }
109 if (exe.match(/\.(bat|cmd)$/i)) {
110 var cmd = path.join(process.env.SYSTEMROOT, 'System32', 'cmd.exe');
111 var cmdArgs = ['/C', exe].concat(args);
112 return { cmd: cmd, args: cmdArgs };
113 }
114 if (exe.match(/\.(js)$/i)) {
115 var cmd = process.execPath;
116 var nodeArgs = [exe];
117 return { cmd: cmd, args: nodeArgs.concat(args) };
118 }
119 // Dunno lol
120 return { cmd: exe, args: args };
121}
122exports.findActualExecutable = findActualExecutable;
123/**
124 * Spawns a process but detached from the current process. The process is put
125 * into its own Process Group that can be killed by unsubscribing from the
126 * return Observable.
127 *
128 * @param {string} exe The executable to run
129 * @param {Array<string>} params The parameters to pass to the child
130 * @param {Object} opts Options to pass to spawn.
131 *
132 * @return {Observable<string>} Returns an Observable that when subscribed
133 * to, will create a detached process. The
134 * process output will be streamed to this
135 * Observable, and if unsubscribed from, the
136 * process will be terminated early. If the
137 * process terminates with a non-zero value,
138 * the Observable will terminate with onError.
139 */
140function spawnDetached(exe, params, opts) {
141 if (opts === void 0) { opts = null; }
142 var _a = findActualExecutable(exe, params), cmd = _a.cmd, args = _a.args;
143 if (!isWindows) {
144 return spawn(cmd, args, assign({}, opts || {}, { detached: true }));
145 }
146 var newParams = [cmd].concat(args);
147 var target = path.join(__dirname, '..', '..', 'vendor', 'jobber', 'Jobber.exe');
148 var options = assign({}, opts || {}, { detached: true, jobber: true });
149 d("spawnDetached: " + target + ", " + newParams);
150 return spawn(target, newParams, options);
151}
152exports.spawnDetached = spawnDetached;
153/**
154 * Spawns a process attached as a child of the current process.
155 *
156 * @param {string} exe The executable to run
157 * @param {Array<string>} params The parameters to pass to the child
158 * @param {Object} opts Options to pass to spawn.
159 *
160 * @return {Observable<string>} Returns an Observable that when subscribed
161 * to, will create a child process. The
162 * process output will be streamed to this
163 * Observable, and if unsubscribed from, the
164 * process will be terminated early. If the
165 * process terminates with a non-zero value,
166 * the Observable will terminate with onError.
167 */
168function spawn(exe, params, opts) {
169 if (params === void 0) { params = []; }
170 if (opts === void 0) { opts = null; }
171 opts = opts || {};
172 var spawnObs = rxjs_1.Observable.create(function (subj) {
173 var stdin = opts.stdin, optsWithoutStdIn = __rest(opts, ["stdin"]);
174 var _a = findActualExecutable(exe, params), cmd = _a.cmd, args = _a.args;
175 d("spawning process: " + cmd + " " + args.join() + ", " + JSON.stringify(optsWithoutStdIn));
176 var origOpts = assign({}, optsWithoutStdIn);
177 if ('jobber' in origOpts) {
178 delete origOpts.jobber;
179 }
180 if ('split' in origOpts) {
181 delete origOpts.split;
182 }
183 var proc = spawnOg(cmd, args, origOpts);
184 var bufHandler = function (source) { return function (b) {
185 if (b.length < 1) {
186 return;
187 }
188 var chunk = '<< String sent back was too long >>';
189 try {
190 if (typeof b === 'string') {
191 chunk = b.toString();
192 }
193 else {
194 chunk = b.toString(origOpts.encoding || 'utf8');
195 }
196 }
197 catch (e) {
198 chunk = "<< Lost chunk of process output for " + exe + " - length was " + b.length + ">>";
199 }
200 subj.next({ source: source, text: chunk });
201 }; };
202 var ret = new rxjs_1.Subscription();
203 if (opts.stdin) {
204 if (proc.stdin) {
205 ret.add(opts.stdin.subscribe(function (x) { return proc.stdin.write(x); }, subj.error.bind(subj), function () { return proc.stdin.end(); }));
206 }
207 else {
208 subj.error(new Error("opts.stdio conflicts with provided spawn opts.stdin observable, 'pipe' is required"));
209 }
210 }
211 var stderrCompleted = null;
212 var stdoutCompleted = null;
213 var noClose = false;
214 if (proc.stdout) {
215 stdoutCompleted = new rxjs_1.AsyncSubject();
216 proc.stdout.on('data', bufHandler('stdout'));
217 proc.stdout.on('close', function () { stdoutCompleted.next(true); stdoutCompleted.complete(); });
218 }
219 else {
220 stdoutCompleted = rxjs_1.of(true);
221 }
222 if (proc.stderr) {
223 stderrCompleted = new rxjs_1.AsyncSubject();
224 proc.stderr.on('data', bufHandler('stderr'));
225 proc.stderr.on('close', function () { stderrCompleted.next(true); stderrCompleted.complete(); });
226 }
227 else {
228 stderrCompleted = rxjs_1.of(true);
229 }
230 proc.on('error', function (e) {
231 noClose = true;
232 subj.error(e);
233 });
234 proc.on('close', function (code) {
235 noClose = true;
236 var pipesClosed = rxjs_1.merge(stdoutCompleted, stderrCompleted)
237 .pipe(operators_1.reduce(function (acc) { return acc; }, true));
238 if (code === 0) {
239 pipesClosed.subscribe(function () { return subj.complete(); });
240 }
241 else {
242 pipesClosed.subscribe(function () { return subj.error(new Error("Failed with exit code: " + code)); });
243 }
244 });
245 ret.add(new rxjs_1.Subscription(function () {
246 if (noClose) {
247 return;
248 }
249 d("Killing process: " + cmd + " " + args.join());
250 if (opts.jobber) {
251 // NB: Connecting to Jobber's named pipe will kill it
252 net.connect("\\\\.\\pipe\\jobber-" + proc.pid);
253 setTimeout(function () { return proc.kill(); }, 5 * 1000);
254 }
255 else {
256 proc.kill();
257 }
258 }));
259 return ret;
260 });
261 return opts.split ? spawnObs : spawnObs.pipe(operators_1.pluck('text'));
262}
263exports.spawn = spawn;
264function wrapObservableInPromise(obs) {
265 return new Promise(function (res, rej) {
266 var out = '';
267 obs.subscribe(function (x) { return out += x; }, function (e) { return rej(new Error(out + "\n" + e.message)); }, function () { return res(out); });
268 });
269}
270/**
271 * Spawns a process but detached from the current process. The process is put
272 * into its own Process Group.
273 *
274 * @param {string} exe The executable to run
275 * @param {Array<string>} params The parameters to pass to the child
276 * @param {Object} opts Options to pass to spawn.
277 *
278 * @return {Promise<string>} Returns an Promise that represents a detached
279 * process. The value returned is the process
280 * output. If the process terminates with a
281 * non-zero value, the Promise will resolve with
282 * an Error.
283 */
284function spawnDetachedPromise(exe, params, opts) {
285 if (opts === void 0) { opts = null; }
286 return wrapObservableInPromise(spawnDetached(exe, params, opts));
287}
288exports.spawnDetachedPromise = spawnDetachedPromise;
289/**
290 * Spawns a process as a child process.
291 *
292 * @param {string} exe The executable to run
293 * @param {Array<string>} params The parameters to pass to the child
294 * @param {Object} opts Options to pass to spawn.
295 *
296 * @return {Promise<string>} Returns an Promise that represents a child
297 * process. The value returned is the process
298 * output. If the process terminates with a
299 * non-zero value, the Promise will resolve with
300 * an Error.
301 */
302function spawnPromise(exe, params, opts) {
303 if (opts === void 0) { opts = null; }
304 return wrapObservableInPromise(spawn(exe, params, opts));
305}
306exports.spawnPromise = spawnPromise;
307//# sourceMappingURL=index.js.map
\No newline at end of file