UNPKG

8.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.findExecutables = exports.which = exports.fork = exports.spawn = exports.Subprocess = exports.SubprocessError = exports.convertPATH = exports.expandTildePath = exports.TILDE_PATH_REGEX = exports.ERROR_SIGNAL_EXIT = exports.ERROR_NON_ZERO_EXIT = exports.ERROR_COMMAND_NOT_FOUND = void 0;
4const utils_array_1 = require("@ionic/utils-array");
5const utils_fs_1 = require("@ionic/utils-fs");
6const utils_process_1 = require("@ionic/utils-process");
7const utils_stream_1 = require("@ionic/utils-stream");
8const utils_terminal_1 = require("@ionic/utils-terminal");
9const child_process_1 = require("child_process");
10const crossSpawn = require("cross-spawn");
11const os = require("os");
12const pathlib = require("path");
13exports.ERROR_COMMAND_NOT_FOUND = 'ERR_SUBPROCESS_COMMAND_NOT_FOUND';
14exports.ERROR_NON_ZERO_EXIT = 'ERR_SUBPROCESS_NON_ZERO_EXIT';
15exports.ERROR_SIGNAL_EXIT = 'ERR_SUBPROCESS_SIGNAL_EXIT';
16exports.TILDE_PATH_REGEX = /^~($|\/|\\)/;
17function expandTildePath(p) {
18 const h = os.homedir();
19 return p.replace(exports.TILDE_PATH_REGEX, `${h}$1`);
20}
21exports.expandTildePath = expandTildePath;
22/**
23 * Prepare the PATH environment variable for use with subprocesses.
24 *
25 * If a raw tilde is found in PATH, e.g. `~/.bin`, it is expanded. The raw
26 * tilde works in Bash, but not in Node's `child_process` outside of a shell.
27 *
28 * This is a utility method. You do not need to use it with `Subprocess`.
29 *
30 * @param path Defaults to `process.env.PATH`
31 */
32function convertPATH(path = process.env.PATH || '') {
33 return path.split(pathlib.delimiter).map(expandTildePath).join(pathlib.delimiter);
34}
35exports.convertPATH = convertPATH;
36class SubprocessError extends Error {
37 constructor(message) {
38 super(message);
39 this.name = 'SubprocessError';
40 this.message = message;
41 this.stack = (new Error()).stack || '';
42 }
43}
44exports.SubprocessError = SubprocessError;
45class Subprocess {
46 constructor(name, args, options = {}) {
47 this.name = name;
48 this.args = args;
49 const masked = this.maskArg(name);
50 if (masked !== name) {
51 this.name = masked;
52 this.path = name;
53 }
54 this._options = options;
55 }
56 get options() {
57 const opts = this._options;
58 if (!opts.env) {
59 opts.env = process.env;
60 }
61 const env = utils_process_1.createProcessEnv(opts.env || {}, {
62 PATH: convertPATH(typeof opts.env.PATH === 'string' ? opts.env.PATH : process.env.PATH),
63 });
64 return { ...opts, env };
65 }
66 async output() {
67 this._options.stdio = 'pipe';
68 const promise = this.run();
69 const stdoutBuf = new utils_stream_1.WritableStreamBuffer();
70 const stderrBuf = new utils_stream_1.WritableStreamBuffer();
71 const combinedBuf = new utils_stream_1.WritableStreamBuffer();
72 promise.p.stdout.pipe(stdoutBuf);
73 promise.p.stdout.pipe(combinedBuf);
74 promise.p.stderr.pipe(stderrBuf);
75 promise.p.stderr.pipe(combinedBuf);
76 try {
77 await promise;
78 }
79 catch (e) {
80 stdoutBuf.end();
81 stderrBuf.end();
82 e.output = combinedBuf.consume().toString();
83 throw e;
84 }
85 stderrBuf.end();
86 combinedBuf.end();
87 return stdoutBuf.consume().toString();
88 }
89 async combinedOutput() {
90 this._options.stdio = 'pipe';
91 const promise = this.run();
92 const buf = new utils_stream_1.WritableStreamBuffer();
93 promise.p.stdout.pipe(buf);
94 promise.p.stderr.pipe(buf);
95 try {
96 await promise;
97 }
98 catch (e) {
99 e.output = buf.consume().toString();
100 throw e;
101 }
102 return buf.consume().toString();
103 }
104 run() {
105 const p = this.spawn();
106 const promise = new Promise((resolve, reject) => {
107 p.on('error', (error) => {
108 let err;
109 if (error.code === 'ENOENT') {
110 err = new SubprocessError('Command not found.');
111 err.code = exports.ERROR_COMMAND_NOT_FOUND;
112 }
113 else {
114 err = new SubprocessError('Command error.');
115 }
116 err.error = error;
117 reject(err);
118 });
119 p.on('close', (code, signal) => {
120 let err;
121 if (code === 0) {
122 return resolve();
123 }
124 if (signal) {
125 err = new SubprocessError('Signal exit from subprocess.');
126 err.code = exports.ERROR_SIGNAL_EXIT;
127 err.signal = signal;
128 }
129 else {
130 err = new SubprocessError('Non-zero exit from subprocess.');
131 err.code = exports.ERROR_NON_ZERO_EXIT;
132 err.exitCode = code;
133 }
134 reject(err);
135 });
136 });
137 Object.defineProperties(promise, {
138 p: { value: p },
139 });
140 return promise;
141 }
142 spawn() {
143 return spawn(this.path ? this.path : this.name, this.args, this.options);
144 }
145 bashify({ maskArgv0 = true, maskArgv1 = false, shiftArgv0 = false } = {}) {
146 const args = [this.path ? this.path : this.name, ...this.args];
147 if (shiftArgv0) {
148 args.shift();
149 }
150 if (args[0] && maskArgv0) {
151 args[0] = this.maskArg(args[0]);
152 }
153 if (args[1] && maskArgv1) {
154 args[1] = this.maskArg(args[1]);
155 }
156 return args.length > 0
157 ? args.map(arg => this.bashifyArg(arg)).join(' ')
158 : '';
159 }
160 bashifyArg(arg) {
161 return arg.includes(' ') ? `"${arg.replace(/\"/g, '\\"')}"` : arg;
162 }
163 maskArg(arg) {
164 const i = arg.lastIndexOf(pathlib.sep);
165 return i >= 0 ? arg.substring(i + 1) : arg;
166 }
167}
168exports.Subprocess = Subprocess;
169function spawn(command, args = [], options) {
170 return crossSpawn(command, [...args], options);
171}
172exports.spawn = spawn;
173function fork(modulePath, args = [], options = {}) {
174 return child_process_1.fork(modulePath, [...args], options);
175}
176exports.fork = fork;
177const DEFAULT_PATHEXT = utils_terminal_1.TERMINAL_INFO.windows ? '.COM;.EXE;.BAT;.CMD' : undefined;
178/**
179 * Find the first instance of a program in PATH.
180 *
181 * If `program` contains a path separator, this function will merely return it.
182 *
183 * @param program A command name, such as `ionic`
184 */
185async function which(program, { PATH = process.env.PATH, PATHEXT = process.env.PATHEXT || DEFAULT_PATHEXT } = {}) {
186 if (program.includes(pathlib.sep)) {
187 return program;
188 }
189 const results = await _findExecutables(program, { PATH });
190 if (!results.length) {
191 const err = new Error(`${program} cannot be found within PATH`);
192 err.code = 'ENOENT';
193 throw err;
194 }
195 return results[0];
196}
197exports.which = which;
198/**
199 * Find all instances of a program in PATH.
200 *
201 * If `program` contains a path separator, this function will merely return it
202 * inside an array.
203 *
204 * @param program A command name, such as `ionic`
205 */
206async function findExecutables(program, { PATH = process.env.PATH, PATHEXT = process.env.PATHEXT || DEFAULT_PATHEXT } = {}) {
207 if (program.includes(pathlib.sep)) {
208 return [program];
209 }
210 return _findExecutables(program, { PATH });
211}
212exports.findExecutables = findExecutables;
213async function _findExecutables(program, { PATH = process.env.PATH, PATHEXT = process.env.PATHEXT || DEFAULT_PATHEXT } = {}) {
214 const pathParts = utils_process_1.getPathParts(PATH);
215 let programNames;
216 // if windows, cycle through all possible executable extensions
217 // ex: node.exe, npm.cmd, etc.
218 if (utils_terminal_1.TERMINAL_INFO.windows) {
219 const exts = utils_process_1.getPathParts(PATHEXT).map(ext => ext.toLowerCase());
220 // don't append extensions if one has already been provided
221 programNames = exts.includes(pathlib.extname(program).toLowerCase()) ? [program] : exts.map(ext => program + ext);
222 }
223 else {
224 programNames = [program];
225 }
226 return [].concat(...await utils_array_1.map(programNames, async (programName) => utils_array_1.concurrentFilter(pathParts.map(p => pathlib.join(p, programName)), async (p) => utils_fs_1.isExecutableFile(p))));
227}