shell.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.prependNodeModulesBinToPath = exports.Shell = void 0;
  4. const tslib_1 = require("tslib");
  5. const cli_framework_output_1 = require("@ionic/cli-framework-output");
  6. const utils_process_1 = require("@ionic/utils-process");
  7. const utils_subprocess_1 = require("@ionic/utils-subprocess");
  8. const utils_terminal_1 = require("@ionic/utils-terminal");
  9. const chalk_1 = tslib_1.__importDefault(require("chalk"));
  10. const debug_1 = require("debug");
  11. const path = tslib_1.__importStar(require("path"));
  12. const split2_1 = tslib_1.__importDefault(require("split2"));
  13. const stream_combiner2_1 = tslib_1.__importDefault(require("stream-combiner2"));
  14. const guards_1 = require("../guards");
  15. const color_1 = require("./color");
  16. const errors_1 = require("./errors");
  17. const debug = (0, debug_1.debug)('ionic:lib:shell');
  18. class Shell {
  19. constructor(e, options) {
  20. this.e = e;
  21. this.alterPath = options && options.alterPath ? options.alterPath : (p) => p;
  22. }
  23. async run(command, args, { stream, killOnExit = true, showCommand = true, showError = true, fatalOnNotFound = true, fatalOnError = true, truncateErrorOutput, ...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(`> ${(0, 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 = (0, stream_combiner2_1.default)((0, split2_1.default)(), 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 = (0, stream_combiner2_1.default)((0, split2_1.default)(), 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. (0, utils_process_1.onBeforeExit)(async () => {
  51. if (promise.p.pid) {
  52. await (0, 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: ${(0, color_1.input)(command)}`, 127);
  62. }
  63. else {
  64. throw e;
  65. }
  66. }
  67. if (!(0, guards_1.isExitCodeException)(e)) {
  68. throw e;
  69. }
  70. let err = e.message || '';
  71. if (truncateErrorOutput && err.length > truncateErrorOutput) {
  72. err = `${(0, color_1.strong)('(truncated)')} ... ` + err.substring(err.length - truncateErrorOutput);
  73. }
  74. const publicErrorMsg = (`An error occurred while running subprocess ${(0, color_1.input)(command)}.\n` +
  75. `${(0, color_1.input)(truncatedCmd)} exited with exit code ${e.exitCode}.\n\n` +
  76. `Re-running this command with the ${(0, color_1.input)('--verbose')} flag may provide more information.`);
  77. const privateErrorMsg = `Subprocess (${(0, 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(`> ${(0, 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: ${(0, color_1.input)(command)}`, 127);
  108. }
  109. else {
  110. throw e;
  111. }
  112. }
  113. if (!(0, guards_1.isExitCodeException)(e)) {
  114. throw e;
  115. }
  116. const errorMsg = `An error occurred while running ${(0, 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 (0, utils_subprocess_1.which)(command, { PATH: this.alterPath(PATH || '') });
  149. }
  150. async spawn(command, args, { showCommand = true, ...crossSpawnOptions }) {
  151. const proc = await this.createSubprocess(command, args, crossSpawnOptions);
  152. const p = proc.spawn();
  153. if (showCommand && this.e.log.level >= cli_framework_output_1.LOGGER_LEVELS.INFO) {
  154. this.e.log.rawmsg(`> ${(0, color_1.input)(proc.bashify())}`);
  155. }
  156. return p;
  157. }
  158. async cmdinfo(command, args = []) {
  159. const opts = {};
  160. const proc = await this.createSubprocess(command, args, opts);
  161. try {
  162. const out = await proc.output();
  163. return out.split('\n').join(' ').trim();
  164. }
  165. catch (e) {
  166. // no command info at this point
  167. }
  168. }
  169. async createSubprocess(command, args = [], options = {}) {
  170. this.prepareSpawnOptions(options);
  171. const cmdpath = await this.resolveCommandPath(command, options);
  172. const proc = new utils_subprocess_1.Subprocess(cmdpath, args, options);
  173. return proc;
  174. }
  175. prepareSpawnOptions(options) {
  176. // Create a `process.env`-type object from all key/values of `process.env`,
  177. // then `options.env`, then add several key/values. PATH is supplemented
  178. // with the `node_modules\.bin` folder in the project directory so that we
  179. // can run binaries inside a project.
  180. options.env = (0, utils_process_1.createProcessEnv)(process.env, options.env ?? {}, {
  181. PATH: this.alterPath(process.env.PATH || ''),
  182. FORCE_COLOR: chalk_1.default.level > 0 ? '1' : '0',
  183. });
  184. }
  185. }
  186. exports.Shell = Shell;
  187. function prependNodeModulesBinToPath(projectDir, p) {
  188. return path.resolve(projectDir, 'node_modules', '.bin') + path.delimiter + p;
  189. }
  190. exports.prependNodeModulesBinToPath = prependNodeModulesBinToPath;