spawn-rx 2.0.12 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,341 +1,405 @@
1
- import * as path from 'path';
2
- import * as net from 'net';
3
- import * as sfs from 'fs';
4
- import * as assign from 'lodash.assign';
5
-
6
- import 'rxjs/add/observable/of';
7
- import 'rxjs/add/observable/merge';
8
- import 'rxjs/add/operator/pluck';
9
- import 'rxjs/add/operator/reduce';
10
- import { Observable } from 'rxjs/Observable';
11
- import { Observer } from 'rxjs/Observer';
12
- import { Subscription } from 'rxjs/Subscription';
13
- import { AsyncSubject } from 'rxjs/AsyncSubject';
14
- import { Subject } from 'rxjs/Subject';
15
- import * as childProcess from 'child_process';
16
-
17
- const spawnOg: typeof childProcess.spawn = require('child_process').spawn; //tslint:disable-line:no-var-requires
18
- const isWindows = process.platform === 'win32';
19
-
20
- const d = require('debug')('spawn-rx'); //tslint:disable-line:no-var-requires
21
-
22
- /**
23
- * stat a file but don't throw if it doesn't exist
24
- *
25
- * @param {string} file The path to a file
26
- * @return {Stats} The stats structure
27
- *
28
- * @private
29
- */
30
- function statSyncNoException(file: string): sfs.Stats | null {
31
- try {
32
- return sfs.statSync(file);
33
- } catch (e) {
34
- return null;
35
- }
36
- }
37
-
38
- /**
39
- * Search PATH to see if a file exists in any of the path folders.
40
- *
41
- * @param {string} exe The file to search for
42
- * @return {string} A fully qualified path, or the original path if nothing
43
- * is found
44
- *
45
- * @private
46
- */
47
- function runDownPath(exe: string): string {
48
- // NB: Windows won't search PATH looking for executables in spawn like
49
- // Posix does
50
-
51
- // Files with any directory path don't get this applied
52
- if (exe.match(/[\\\/]/)) {
53
- d('Path has slash in directory, bailing');
54
- return exe;
55
- }
56
-
57
- let target = path.join('.', exe);
58
- if (statSyncNoException(target)) {
59
- d(`Found executable in currect directory: ${target}`);
60
- return target;
61
- }
62
-
63
- let haystack = process.env.PATH!.split(isWindows ? ';' : ':');
64
- for (let p of haystack) {
65
- let needle = path.join(p, exe);
66
- if (statSyncNoException(needle)) {
67
- return needle;
68
- };
69
- }
70
-
71
- d('Failed to find executable anywhere in path');
72
- return exe;
73
- }
74
-
75
- /**
76
- * Finds the actual executable and parameters to run on Windows. This method
77
- * mimics the POSIX behavior of being able to run scripts as executables by
78
- * replacing the passed-in executable with the script runner, for PowerShell,
79
- * CMD, and node scripts.
80
- *
81
- * This method also does the work of running down PATH, which spawn on Windows
82
- * also doesn't do, unlike on POSIX.
83
- *
84
- * @param {string} exe The executable to run
85
- * @param {Array<string>} args The arguments to run
86
- *
87
- * @return {Object} The cmd and args to run
88
- * @property {string} cmd The command to pass to spawn
89
- * @property {Array<string>} args The arguments to pass to spawn
90
- */
91
- export function findActualExecutable(exe: string, args: Array<string>): {
92
- cmd: string;
93
- args: Array<string>
94
- } {
95
- // POSIX can just execute scripts directly, no need for silly goosery
96
- if (process.platform !== 'win32') {
97
- return { cmd: runDownPath(exe), args: args };
98
- }
99
-
100
- if (!sfs.existsSync(exe)) {
101
- // NB: When you write something like `surf-client ... -- surf-build` on Windows,
102
- // a shell would normally convert that to surf-build.cmd, but since it's passed
103
- // in as an argument, it doesn't happen
104
- const possibleExts = ['.exe', '.bat', '.cmd', '.ps1'];
105
- for (let ext of possibleExts) {
106
- let possibleFullPath = runDownPath(`${exe}${ext}`);
107
-
108
- if (sfs.existsSync(possibleFullPath)) {
109
- return findActualExecutable(possibleFullPath, args);
110
- }
111
- }
112
- }
113
-
114
- if (exe.match(/\.ps1$/i)) {
115
- let cmd = path.join(process.env.SYSTEMROOT!, 'System32', 'WindowsPowerShell', 'v1.0', 'PowerShell.exe');
116
- let psargs = ['-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', exe];
117
-
118
- return { cmd: cmd, args: psargs.concat(args) };
119
- }
120
-
121
- if (exe.match(/\.(bat|cmd)$/i)) {
122
- let cmd = path.join(process.env.SYSTEMROOT!, 'System32', 'cmd.exe');
123
- let cmdArgs = ['/C', exe, ...args];
124
-
125
- return { cmd: cmd, args: cmdArgs };
126
- }
127
-
128
- if (exe.match(/\.(js)$/i)) {
129
- let cmd = process.execPath;
130
- let nodeArgs = [exe];
131
-
132
- return { cmd: cmd, args: nodeArgs.concat(args) };
133
- }
134
-
135
- // Dunno lol
136
- return { cmd: exe, args: args };
137
- }
138
-
139
- /**
140
- * Spawns a process but detached from the current process. The process is put
141
- * into its own Process Group that can be killed by unsubscribing from the
142
- * return Observable.
143
- *
144
- * @param {string} exe The executable to run
145
- * @param {Array<string>} params The parameters to pass to the child
146
- * @param {Object} opts Options to pass to spawn.
147
- *
148
- * @return {Observable<string>} Returns an Observable that when subscribed
149
- * to, will create a detached process. The
150
- * process output will be streamed to this
151
- * Observable, and if unsubscribed from, the
152
- * process will be terminated early. If the
153
- * process terminates with a non-zero value,
154
- * the Observable will terminate with onError.
155
- */
156
- export function spawnDetached(exe: string, params: Array<string>, opts: any = null): Observable<string> {
157
- const { cmd, args } = findActualExecutable(exe, params);
158
-
159
- if (!isWindows) {
160
- return spawn(cmd, args, assign({}, opts || {}, { detached: true }));
161
- };
162
-
163
- const newParams = [cmd].concat(args);
164
-
165
- let target = path.join(__dirname, '..', '..', 'vendor', 'jobber', 'Jobber.exe');
166
- let options = assign({}, opts || {}, { detached: true, jobber: true });
167
-
168
- d(`spawnDetached: ${target}, ${newParams}`);
169
- return spawn(target, newParams, options);
170
- }
171
-
172
- /**
173
- * Spawns a process attached as a child of the current process.
174
- *
175
- * @param {string} exe The executable to run
176
- * @param {Array<string>} params The parameters to pass to the child
177
- * @param {Object} opts Options to pass to spawn.
178
- *
179
- * @return {Observable<string>} Returns an Observable that when subscribed
180
- * to, will create a child process. The
181
- * process output will be streamed to this
182
- * Observable, and if unsubscribed from, the
183
- * process will be terminated early. If the
184
- * process terminates with a non-zero value,
185
- * the Observable will terminate with onError.
186
- */
187
-
188
- export function spawn<T = string>(exe: string, params: Array<string> = [], opts: any = null): Observable<T> {
189
- opts = opts || {};
190
- let spawnObs = Observable.create((subj: Observer<{
191
- source: any,
192
- text: any
193
- }>) => {
194
- let { stdin, ...optsWithoutStdIn } = opts;
195
- let { cmd, args } = findActualExecutable(exe, params);
196
- d(`spawning process: ${cmd} ${args.join()}, ${JSON.stringify(optsWithoutStdIn)}`);
197
- let origOpts = assign({}, optsWithoutStdIn);
198
- if ('jobber' in origOpts) {
199
- delete origOpts.jobber;
200
- }
201
- if ('split' in origOpts) {
202
- delete origOpts.split;
203
- };
204
-
205
- const proc = spawnOg(cmd, args, origOpts);
206
-
207
- let bufHandler = (source: string) => (b: string | Buffer) => {
208
- if (b.length < 1) {
209
- return;
210
- };
211
- let chunk = '<< String sent back was too long >>';
212
- try {
213
- if (typeof b === 'string') {
214
- chunk = b.toString();
215
- } else {
216
- chunk = b.toString(origOpts.encoding || 'utf8');
217
- }
218
- } catch (e) {
219
- chunk = `<< Lost chunk of process output for ${exe} - length was ${b.length}>>`;
220
- }
221
-
222
- subj.next({ source: source, text: chunk });
223
- };
224
-
225
- let ret = new Subscription();
226
-
227
- if (opts.stdin) {
228
- if (proc.stdin) {
229
- ret.add(opts.stdin.subscribe(
230
- (x: any) => proc.stdin.write(x),
231
- subj.error.bind(subj),
232
- () => proc.stdin.end()
233
- ));
234
- } else {
235
- subj.error(new Error(`opts.stdio conflicts with provided spawn opts.stdin observable, 'pipe' is required`));
236
- }
237
- }
238
-
239
- let stderrCompleted: Subject<boolean> | Observable<boolean> | null = null;
240
- let stdoutCompleted: Subject<boolean> | Observable<boolean> | null = null;
241
- let noClose = false;
242
-
243
- if (proc.stdout) {
244
- stdoutCompleted = new AsyncSubject<boolean>();
245
- proc.stdout.on('data', bufHandler('stdout'));
246
- proc.stdout.on('close', () => { (stdoutCompleted! as Subject<boolean>).next(true); (stdoutCompleted! as Subject<boolean>).complete(); });
247
- } else {
248
- stdoutCompleted = Observable.of(true);
249
- }
250
-
251
- if (proc.stderr) {
252
- stderrCompleted = new AsyncSubject<boolean>();
253
- proc.stderr.on('data', bufHandler('stderr'));
254
- proc.stderr.on('close', () => { (stderrCompleted! as Subject<boolean>).next(true); (stderrCompleted! as Subject<boolean>).complete(); });
255
- } else {
256
- stderrCompleted = Observable.of(true);
257
- }
258
-
259
- proc.on('error', (e: Error) => {
260
- noClose = true;
261
- subj.error(e);
262
- });
263
-
264
- proc.on('close', (code: number) => {
265
- noClose = true;
266
- let pipesClosed = Observable.merge(stdoutCompleted!, stderrCompleted!)
267
- .reduce((acc) => acc, true);
268
-
269
- if (code === 0) {
270
- pipesClosed.subscribe(() => subj.complete());
271
- } else {
272
- pipesClosed.subscribe(() => subj.error(new Error(`Failed with exit code: ${code}`)));
273
- }
274
- });
275
-
276
- ret.add(new Subscription(() => {
277
- if (noClose) {
278
- return;
279
- };
280
-
281
- d(`Killing process: ${cmd} ${args.join()}`);
282
- if (opts.jobber) {
283
- // NB: Connecting to Jobber's named pipe will kill it
284
- net.connect(`\\\\.\\pipe\\jobber-${proc.pid}`);
285
- setTimeout(() => proc.kill(), 5 * 1000);
286
- } else {
287
- proc.kill();
288
- }
289
- }));
290
-
291
- return ret;
292
- });
293
-
294
- return opts.split ? spawnObs : spawnObs.pluck('text');
295
- }
296
-
297
- function wrapObservableInPromise<T>(obs: Observable<T>) {
298
- return new Promise<string>((res, rej) => {
299
- let out = '';
300
-
301
- obs.subscribe(
302
- (x) => out += x,
303
- (e) => rej(new Error(`${out}\n${e.message}`)),
304
- () => res(out));
305
- });
306
- }
307
-
308
- /**
309
- * Spawns a process but detached from the current process. The process is put
310
- * into its own Process Group.
311
- *
312
- * @param {string} exe The executable to run
313
- * @param {Array<string>} params The parameters to pass to the child
314
- * @param {Object} opts Options to pass to spawn.
315
- *
316
- * @return {Promise<string>} Returns an Promise that represents a detached
317
- * process. The value returned is the process
318
- * output. If the process terminates with a
319
- * non-zero value, the Promise will resolve with
320
- * an Error.
321
- */
322
- export function spawnDetachedPromise(exe: string, params: Array<string>, opts: any = null): Promise<string> {
323
- return wrapObservableInPromise<string>(spawnDetached(exe, params, opts));
324
- }
325
-
326
- /**
327
- * Spawns a process as a child process.
328
- *
329
- * @param {string} exe The executable to run
330
- * @param {Array<string>} params The parameters to pass to the child
331
- * @param {Object} opts Options to pass to spawn.
332
- *
333
- * @return {Promise<string>} Returns an Promise that represents a child
334
- * process. The value returned is the process
335
- * output. If the process terminates with a
336
- * non-zero value, the Promise will resolve with
337
- * an Error.
338
- */
339
- export function spawnPromise(exe: string, params: Array<string>, opts: any = null): Promise<string> {
340
- return wrapObservableInPromise<string>(spawn(exe, params, opts));
341
- }
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import * as path from "path";
3
+ import * as net from "net";
4
+ import * as sfs from "fs";
5
+ import * as assign from "lodash.assign";
6
+
7
+ import type { Observer, Subject } from "rxjs";
8
+ import { Observable, Subscription, AsyncSubject, of, merge } from "rxjs";
9
+ import { map, reduce } from "rxjs/operators";
10
+ import { spawn as spawnOg } from "child_process";
11
+ import Debug from "debug";
12
+
13
+ const isWindows = process.platform === "win32";
14
+
15
+ const d = Debug("spawn-rx"); // tslint:disable-line:no-var-requires
16
+
17
+ /**
18
+ * stat a file but don't throw if it doesn't exist
19
+ *
20
+ * @param {string} file The path to a file
21
+ * @return {Stats} The stats structure
22
+ *
23
+ * @private
24
+ */
25
+ function statSyncNoException(file: string): sfs.Stats | null {
26
+ try {
27
+ return sfs.statSync(file);
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Search PATH to see if a file exists in any of the path folders.
35
+ *
36
+ * @param {string} exe The file to search for
37
+ * @return {string} A fully qualified path, or the original path if nothing
38
+ * is found
39
+ *
40
+ * @private
41
+ */
42
+ function runDownPath(exe: string): string {
43
+ // NB: Windows won't search PATH looking for executables in spawn like
44
+ // Posix does
45
+
46
+ // Files with any directory path don't get this applied
47
+ if (exe.match(/[\\/]/)) {
48
+ d("Path has slash in directory, bailing");
49
+ return exe;
50
+ }
51
+
52
+ const target = path.join(".", exe);
53
+ if (statSyncNoException(target)) {
54
+ d(`Found executable in currect directory: ${target}`);
55
+ return target;
56
+ }
57
+
58
+ const haystack = process.env.PATH!.split(isWindows ? ";" : ":");
59
+ for (const p of haystack) {
60
+ const needle = path.join(p, exe);
61
+ if (statSyncNoException(needle)) {
62
+ return needle;
63
+ }
64
+ }
65
+
66
+ d("Failed to find executable anywhere in path");
67
+ return exe;
68
+ }
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
+ */
86
+ export function findActualExecutable(
87
+ exe: string,
88
+ args: Array<string>,
89
+ ): {
90
+ cmd: string;
91
+ args: Array<string>;
92
+ } {
93
+ // POSIX can just execute scripts directly, no need for silly goosery
94
+ if (process.platform !== "win32") {
95
+ return { cmd: runDownPath(exe), args: args };
96
+ }
97
+
98
+ if (!sfs.existsSync(exe)) {
99
+ // NB: When you write something like `surf-client ... -- surf-build` on Windows,
100
+ // a shell would normally convert that to surf-build.cmd, but since it's passed
101
+ // in as an argument, it doesn't happen
102
+ const possibleExts = [".exe", ".bat", ".cmd", ".ps1"];
103
+ for (const ext of possibleExts) {
104
+ const possibleFullPath = runDownPath(`${exe}${ext}`);
105
+
106
+ if (sfs.existsSync(possibleFullPath)) {
107
+ return findActualExecutable(possibleFullPath, args);
108
+ }
109
+ }
110
+ }
111
+
112
+ if (exe.match(/\.ps1$/i)) {
113
+ const cmd = path.join(
114
+ process.env.SYSTEMROOT!,
115
+ "System32",
116
+ "WindowsPowerShell",
117
+ "v1.0",
118
+ "PowerShell.exe",
119
+ );
120
+ const psargs = [
121
+ "-ExecutionPolicy",
122
+ "Unrestricted",
123
+ "-NoLogo",
124
+ "-NonInteractive",
125
+ "-File",
126
+ exe,
127
+ ];
128
+
129
+ return { cmd: cmd, args: psargs.concat(args) };
130
+ }
131
+
132
+ if (exe.match(/\.(bat|cmd)$/i)) {
133
+ const cmd = path.join(process.env.SYSTEMROOT!, "System32", "cmd.exe");
134
+ const cmdArgs = ["/C", exe, ...args];
135
+
136
+ return { cmd: cmd, args: cmdArgs };
137
+ }
138
+
139
+ if (exe.match(/\.(js)$/i)) {
140
+ const cmd = process.execPath;
141
+ const nodeArgs = [exe];
142
+
143
+ return { cmd: cmd, args: nodeArgs.concat(args) };
144
+ }
145
+
146
+ // Dunno lol
147
+ return { cmd: exe, args: args };
148
+ }
149
+
150
+ /**
151
+ * Spawns a process but detached from the current process. The process is put
152
+ * into its own Process Group that can be killed by unsubscribing from the
153
+ * return Observable.
154
+ *
155
+ * @param {string} exe The executable to run
156
+ * @param {Array<string>} params The parameters to pass to the child
157
+ * @param {Object} opts Options to pass to spawn.
158
+ *
159
+ * @return {Observable<string>} Returns an Observable that when subscribed
160
+ * to, will create a detached process. The
161
+ * process output will be streamed to this
162
+ * Observable, and if unsubscribed from, the
163
+ * process will be terminated early. If the
164
+ * process terminates with a non-zero value,
165
+ * the Observable will terminate with onError.
166
+ */
167
+ export function spawnDetached(
168
+ exe: string,
169
+ params: Array<string>,
170
+ opts: any = null,
171
+ ): Observable<string> {
172
+ const { cmd, args } = findActualExecutable(exe, params);
173
+
174
+ if (!isWindows) {
175
+ return spawn(cmd, args, assign({}, opts || {}, { detached: true }));
176
+ }
177
+
178
+ const newParams = [cmd].concat(args);
179
+
180
+ const target = path.join(
181
+ __dirname,
182
+ "..",
183
+ "..",
184
+ "vendor",
185
+ "jobber",
186
+ "Jobber.exe",
187
+ );
188
+ const options = assign({}, opts || {}, { detached: true, jobber: true });
189
+
190
+ d(`spawnDetached: ${target}, ${newParams}`);
191
+ return spawn(target, newParams, options);
192
+ }
193
+
194
+ /**
195
+ * Spawns a process attached as a child of the current process.
196
+ *
197
+ * @param {string} exe The executable to run
198
+ * @param {Array<string>} params The parameters to pass to the child
199
+ * @param {Object} opts Options to pass to spawn.
200
+ *
201
+ * @return {Observable<string>} Returns an Observable that when subscribed
202
+ * to, will create a child process. The
203
+ * process output will be streamed to this
204
+ * Observable, and if unsubscribed from, the
205
+ * process will be terminated early. If the
206
+ * process terminates with a non-zero value,
207
+ * the Observable will terminate with onError.
208
+ */
209
+
210
+ export function spawn<T = string>(
211
+ exe: string,
212
+ params: Array<string> = [],
213
+ opts: any = null,
214
+ ): Observable<T> {
215
+ opts = opts || {};
216
+ const spawnObs = Observable.create(
217
+ (
218
+ subj: Observer<{
219
+ source: any;
220
+ text: any;
221
+ }>,
222
+ ) => {
223
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
224
+ const { _, ...optsWithoutStdIn } = opts;
225
+ const { cmd, args } = findActualExecutable(exe, params);
226
+ d(
227
+ `spawning process: ${cmd} ${args.join()}, ${JSON.stringify(
228
+ optsWithoutStdIn,
229
+ )}`,
230
+ );
231
+ const origOpts = assign({}, optsWithoutStdIn);
232
+ if ("jobber" in origOpts) {
233
+ delete origOpts.jobber;
234
+ }
235
+ if ("split" in origOpts) {
236
+ delete origOpts.split;
237
+ }
238
+
239
+ const proc = spawnOg(cmd, args, origOpts);
240
+
241
+ const bufHandler = (source: string) => (b: string | Buffer) => {
242
+ if (b.length < 1) {
243
+ return;
244
+ }
245
+ let chunk = "<< String sent back was too long >>";
246
+ try {
247
+ if (typeof b === "string") {
248
+ chunk = b.toString();
249
+ } else {
250
+ chunk = b.toString(origOpts.encoding || "utf8");
251
+ }
252
+ } catch {
253
+ chunk = `<< Lost chunk of process output for ${exe} - length was ${b.length}>>`;
254
+ }
255
+
256
+ subj.next({ source: source, text: chunk });
257
+ };
258
+
259
+ const ret = new Subscription();
260
+
261
+ if (opts.stdin) {
262
+ if (proc.stdin) {
263
+ ret.add(
264
+ opts.stdin.subscribe(
265
+ (x: any) => proc.stdin.write(x),
266
+ subj.error.bind(subj),
267
+ () => proc.stdin.end(),
268
+ ),
269
+ );
270
+ } else {
271
+ subj.error(
272
+ new Error(
273
+ `opts.stdio conflicts with provided spawn opts.stdin observable, 'pipe' is required`,
274
+ ),
275
+ );
276
+ }
277
+ }
278
+
279
+ let stderrCompleted: Subject<boolean> | Observable<boolean> | null = null;
280
+ let stdoutCompleted: Subject<boolean> | Observable<boolean> | null = null;
281
+ let noClose = false;
282
+
283
+ if (proc.stdout) {
284
+ stdoutCompleted = new AsyncSubject<boolean>();
285
+ proc.stdout.on("data", bufHandler("stdout"));
286
+ proc.stdout.on("close", () => {
287
+ (stdoutCompleted! as Subject<boolean>).next(true);
288
+ (stdoutCompleted! as Subject<boolean>).complete();
289
+ });
290
+ } else {
291
+ stdoutCompleted = of(true);
292
+ }
293
+
294
+ if (proc.stderr) {
295
+ stderrCompleted = new AsyncSubject<boolean>();
296
+ proc.stderr.on("data", bufHandler("stderr"));
297
+ proc.stderr.on("close", () => {
298
+ (stderrCompleted! as Subject<boolean>).next(true);
299
+ (stderrCompleted! as Subject<boolean>).complete();
300
+ });
301
+ } else {
302
+ stderrCompleted = of(true);
303
+ }
304
+
305
+ proc.on("error", (e: Error) => {
306
+ noClose = true;
307
+ subj.error(e);
308
+ });
309
+
310
+ proc.on("close", (code: number) => {
311
+ noClose = true;
312
+ const pipesClosed = merge(stdoutCompleted!, stderrCompleted!).pipe(
313
+ reduce((acc) => acc, true),
314
+ );
315
+
316
+ if (code === 0) {
317
+ pipesClosed.subscribe(() => subj.complete());
318
+ } else {
319
+ pipesClosed.subscribe(() => {
320
+ const e: any = new Error(`Failed with exit code: ${code}`);
321
+ e.exitCode = code;
322
+
323
+ subj.error(e);
324
+ });
325
+ }
326
+ });
327
+
328
+ ret.add(
329
+ new Subscription(() => {
330
+ if (noClose) {
331
+ return;
332
+ }
333
+
334
+ d(`Killing process: ${cmd} ${args.join()}`);
335
+ if (opts.jobber) {
336
+ // NB: Connecting to Jobber's named pipe will kill it
337
+ net.connect(`\\\\.\\pipe\\jobber-${proc.pid}`);
338
+ setTimeout(() => proc.kill(), 5 * 1000);
339
+ } else {
340
+ proc.kill();
341
+ }
342
+ }),
343
+ );
344
+
345
+ return ret;
346
+ },
347
+ );
348
+
349
+ return opts.split ? spawnObs : spawnObs.pipe(map((x: any) => x?.text));
350
+ }
351
+
352
+ function wrapObservableInPromise<T>(obs: Observable<T>) {
353
+ return new Promise<string>((res, rej) => {
354
+ let out = "";
355
+
356
+ obs.subscribe(
357
+ (x) => (out += x),
358
+ (e) => rej(new Error(`${out}\n${e.message}`)),
359
+ () => res(out),
360
+ );
361
+ });
362
+ }
363
+
364
+ /**
365
+ * Spawns a process but detached from the current process. The process is put
366
+ * into its own Process Group.
367
+ *
368
+ * @param {string} exe The executable to run
369
+ * @param {Array<string>} params The parameters to pass to the child
370
+ * @param {Object} opts Options to pass to spawn.
371
+ *
372
+ * @return {Promise<string>} Returns an Promise that represents a detached
373
+ * process. The value returned is the process
374
+ * output. If the process terminates with a
375
+ * non-zero value, the Promise will resolve with
376
+ * an Error.
377
+ */
378
+ export function spawnDetachedPromise(
379
+ exe: string,
380
+ params: Array<string>,
381
+ opts: any = null,
382
+ ): Promise<string> {
383
+ return wrapObservableInPromise<string>(spawnDetached(exe, params, opts));
384
+ }
385
+
386
+ /**
387
+ * Spawns a process as a child process.
388
+ *
389
+ * @param {string} exe The executable to run
390
+ * @param {Array<string>} params The parameters to pass to the child
391
+ * @param {Object} opts Options to pass to spawn.
392
+ *
393
+ * @return {Promise<string>} Returns an Promise that represents a child
394
+ * process. The value returned is the process
395
+ * output. If the process terminates with a
396
+ * non-zero value, the Promise will resolve with
397
+ * an Error.
398
+ */
399
+ export function spawnPromise(
400
+ exe: string,
401
+ params: Array<string>,
402
+ opts: any = null,
403
+ ): Promise<string> {
404
+ return wrapObservableInPromise<string>(spawn(exe, params, opts));
405
+ }