spawn-rx 2.0.12 → 4.0.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }