UNPKG

8.62 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.prependNodeModulesBinToPath = exports.Shell = void 0;
4const cli_framework_output_1 = require("@ionic/cli-framework-output");
5const utils_process_1 = require("@ionic/utils-process");
6const utils_subprocess_1 = require("@ionic/utils-subprocess");
7const utils_terminal_1 = require("@ionic/utils-terminal");
8const chalk = require("chalk");
9const Debug = require("debug");
10const path = require("path");
11const split2 = require("split2");
12const combineStreams = require("stream-combiner2");
13const guards_1 = require("../guards");
14const color_1 = require("./color");
15const errors_1 = require("./errors");
16const debug = Debug('ionic:lib:shell');
17class Shell {
18 constructor(e, options) {
19 this.e = e;
20 this.alterPath = options && options.alterPath ? options.alterPath : (p) => p;
21 }
22 async run(command, args, { stream, killOnExit = true, showCommand = true, showError = true, fatalOnNotFound = true, fatalOnError = true, truncateErrorOutput, ...crossSpawnOptions }) {
23 this.prepareSpawnOptions(crossSpawnOptions);
24 const proc = await this.createSubprocess(command, args, crossSpawnOptions);
25 const fullCmd = proc.bashify();
26 const truncatedCmd = fullCmd.length > 80 ? fullCmd.substring(0, 80) + '...' : fullCmd;
27 if (showCommand && this.e.log.level >= cli_framework_output_1.LOGGER_LEVELS.INFO) {
28 this.e.log.rawmsg(`> ${color_1.input(fullCmd)}`);
29 }
30 const ws = stream ? stream : this.e.log.createWriteStream(cli_framework_output_1.LOGGER_LEVELS.INFO, false);
31 try {
32 const promise = proc.run();
33 if (promise.p.stdout) {
34 const s = combineStreams(split2(), ws);
35 // TODO: https://github.com/angular/angular-cli/issues/10922
36 s.on('error', (err) => {
37 debug('Error in subprocess stdout pipe: %o', err);
38 });
39 promise.p.stdout.pipe(s);
40 }
41 if (promise.p.stderr) {
42 const s = combineStreams(split2(), ws);
43 // TODO: https://github.com/angular/angular-cli/issues/10922
44 s.on('error', (err) => {
45 debug('Error in subprocess stderr pipe: %o', err);
46 });
47 promise.p.stderr.pipe(s);
48 }
49 if (killOnExit) {
50 utils_process_1.onBeforeExit(async () => {
51 if (promise.p.pid) {
52 await utils_process_1.killProcessTree(promise.p.pid);
53 }
54 });
55 }
56 await promise;
57 }
58 catch (e) {
59 if (e instanceof utils_subprocess_1.SubprocessError && e.code === utils_subprocess_1.ERROR_COMMAND_NOT_FOUND) {
60 if (fatalOnNotFound) {
61 throw new errors_1.FatalException(`Command not found: ${color_1.input(command)}`, 127);
62 }
63 else {
64 throw e;
65 }
66 }
67 if (!guards_1.isExitCodeException(e)) {
68 throw e;
69 }
70 let err = e.message || '';
71 if (truncateErrorOutput && err.length > truncateErrorOutput) {
72 err = `${color_1.strong('(truncated)')} ... ` + err.substring(err.length - truncateErrorOutput);
73 }
74 const publicErrorMsg = (`An error occurred while running subprocess ${color_1.input(command)}.\n` +
75 `${color_1.input(truncatedCmd)} exited with exit code ${e.exitCode}.\n\n` +
76 `Re-running this command with the ${color_1.input('--verbose')} flag may provide more information.`);
77 const privateErrorMsg = `Subprocess (${color_1.input(command)}) encountered an error (exit code ${e.exitCode}).`;
78 if (fatalOnError) {
79 if (showError) {
80 throw new errors_1.FatalException(publicErrorMsg, e.exitCode);
81 }
82 else {
83 throw new errors_1.FatalException(privateErrorMsg, e.exitCode);
84 }
85 }
86 else {
87 if (showError) {
88 this.e.log.error(publicErrorMsg);
89 }
90 }
91 throw e;
92 }
93 }
94 async output(command, args, { fatalOnNotFound = true, fatalOnError = true, showError = true, showCommand = false, ...crossSpawnOptions }) {
95 const proc = await this.createSubprocess(command, args, crossSpawnOptions);
96 const fullCmd = proc.bashify();
97 const truncatedCmd = fullCmd.length > 80 ? fullCmd.substring(0, 80) + '...' : fullCmd;
98 if (showCommand && this.e.log.level >= cli_framework_output_1.LOGGER_LEVELS.INFO) {
99 this.e.log.rawmsg(`> ${color_1.input(fullCmd)}`);
100 }
101 try {
102 return await proc.output();
103 }
104 catch (e) {
105 if (e instanceof utils_subprocess_1.SubprocessError && e.code === utils_subprocess_1.ERROR_COMMAND_NOT_FOUND) {
106 if (fatalOnNotFound) {
107 throw new errors_1.FatalException(`Command not found: ${color_1.input(command)}`, 127);
108 }
109 else {
110 throw e;
111 }
112 }
113 if (!guards_1.isExitCodeException(e)) {
114 throw e;
115 }
116 const errorMsg = `An error occurred while running ${color_1.input(truncatedCmd)} (exit code ${e.exitCode})\n`;
117 if (fatalOnError) {
118 throw new errors_1.FatalException(errorMsg, e.exitCode);
119 }
120 else {
121 if (showError) {
122 this.e.log.error(errorMsg);
123 }
124 }
125 return '';
126 }
127 }
128 /**
129 * When `child_process.spawn` isn't provided a full path to the command
130 * binary, it behaves differently on Windows than other platforms. For
131 * Windows, discover the full path to the binary, otherwise fallback to the
132 * command provided.
133 *
134 * @see https://github.com/ionic-team/ionic-cli/issues/3563#issuecomment-425232005
135 */
136 async resolveCommandPath(command, options) {
137 if (utils_terminal_1.TERMINAL_INFO.windows) {
138 try {
139 return await this.which(command, { PATH: options.env && options.env.PATH ? options.env.PATH : process.env.PATH });
140 }
141 catch (e) {
142 // ignore
143 }
144 }
145 return command;
146 }
147 async which(command, { PATH = process.env.PATH } = {}) {
148 return utils_subprocess_1.which(command, { PATH: this.alterPath(PATH || '') });
149 }
150 async spawn(command, args, { showCommand = true, ...crossSpawnOptions }) {
151 this.prepareSpawnOptions(crossSpawnOptions);
152 const proc = await this.createSubprocess(command, args, crossSpawnOptions);
153 const p = proc.spawn();
154 if (showCommand && this.e.log.level >= cli_framework_output_1.LOGGER_LEVELS.INFO) {
155 this.e.log.rawmsg(`> ${color_1.input(proc.bashify())}`);
156 }
157 return p;
158 }
159 async cmdinfo(command, args = []) {
160 const opts = {};
161 this.prepareSpawnOptions(opts);
162 const proc = await this.createSubprocess(command, args, opts);
163 try {
164 const out = await proc.output();
165 return out.split('\n').join(' ').trim();
166 }
167 catch (e) {
168 // no command info at this point
169 }
170 }
171 async createSubprocess(command, args = [], options = {}) {
172 const cmdpath = await this.resolveCommandPath(command, options);
173 const proc = new utils_subprocess_1.Subprocess(cmdpath, args, options);
174 return proc;
175 }
176 prepareSpawnOptions(options) {
177 var _a;
178 // Create a `process.env`-type object from all key/values of `process.env`,
179 // then `options.env`, then add several key/values. PATH is supplemented
180 // with the `node_modules\.bin` folder in the project directory so that we
181 // can run binaries inside a project.
182 options.env = utils_process_1.createProcessEnv(process.env, (_a = options.env) !== null && _a !== void 0 ? _a : {}, {
183 PATH: this.alterPath(process.env.PATH || ''),
184 FORCE_COLOR: chalk.level > 0 ? '1' : '0',
185 });
186 }
187}
188exports.Shell = Shell;
189function prependNodeModulesBinToPath(projectDir, p) {
190 return path.resolve(projectDir, 'node_modules', '.bin') + path.delimiter + p;
191}
192exports.prependNodeModulesBinToPath = prependNodeModulesBinToPath;