版博士V2.0程序
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 

310 rader
7.7 KiB

  1. import {Buffer} from 'node:buffer';
  2. import path from 'node:path';
  3. import childProcess from 'node:child_process';
  4. import process from 'node:process';
  5. import crossSpawn from 'cross-spawn';
  6. import stripFinalNewline from 'strip-final-newline';
  7. import {npmRunPathEnv} from 'npm-run-path';
  8. import onetime from 'onetime';
  9. import {makeError} from './lib/error.js';
  10. import {normalizeStdio, normalizeStdioNode} from './lib/stdio.js';
  11. import {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} from './lib/kill.js';
  12. import {addPipeMethods} from './lib/pipe.js';
  13. import {handleInput, getSpawnedResult, makeAllStream, handleInputSync} from './lib/stream.js';
  14. import {mergePromise, getSpawnedPromise} from './lib/promise.js';
  15. import {joinCommand, parseCommand, parseTemplates, getEscapedCommand} from './lib/command.js';
  16. import {logCommand, verboseDefault} from './lib/verbose.js';
  17. const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
  18. const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => {
  19. const env = extendEnv ? {...process.env, ...envOption} : envOption;
  20. if (preferLocal) {
  21. return npmRunPathEnv({env, cwd: localDir, execPath});
  22. }
  23. return env;
  24. };
  25. const handleArguments = (file, args, options = {}) => {
  26. const parsed = crossSpawn._parse(file, args, options);
  27. file = parsed.command;
  28. args = parsed.args;
  29. options = parsed.options;
  30. options = {
  31. maxBuffer: DEFAULT_MAX_BUFFER,
  32. buffer: true,
  33. stripFinalNewline: true,
  34. extendEnv: true,
  35. preferLocal: false,
  36. localDir: options.cwd || process.cwd(),
  37. execPath: process.execPath,
  38. encoding: 'utf8',
  39. reject: true,
  40. cleanup: true,
  41. all: false,
  42. windowsHide: true,
  43. verbose: verboseDefault,
  44. ...options,
  45. };
  46. options.env = getEnv(options);
  47. options.stdio = normalizeStdio(options);
  48. if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') {
  49. // #116
  50. args.unshift('/q');
  51. }
  52. return {file, args, options, parsed};
  53. };
  54. const handleOutput = (options, value, error) => {
  55. if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
  56. // When `execaSync()` errors, we normalize it to '' to mimic `execa()`
  57. return error === undefined ? undefined : '';
  58. }
  59. if (options.stripFinalNewline) {
  60. return stripFinalNewline(value);
  61. }
  62. return value;
  63. };
  64. export function execa(file, args, options) {
  65. const parsed = handleArguments(file, args, options);
  66. const command = joinCommand(file, args);
  67. const escapedCommand = getEscapedCommand(file, args);
  68. logCommand(escapedCommand, parsed.options);
  69. validateTimeout(parsed.options);
  70. let spawned;
  71. try {
  72. spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
  73. } catch (error) {
  74. // Ensure the returned error is always both a promise and a child process
  75. const dummySpawned = new childProcess.ChildProcess();
  76. const errorPromise = Promise.reject(makeError({
  77. error,
  78. stdout: '',
  79. stderr: '',
  80. all: '',
  81. command,
  82. escapedCommand,
  83. parsed,
  84. timedOut: false,
  85. isCanceled: false,
  86. killed: false,
  87. }));
  88. mergePromise(dummySpawned, errorPromise);
  89. return dummySpawned;
  90. }
  91. const spawnedPromise = getSpawnedPromise(spawned);
  92. const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise);
  93. const processDone = setExitHandler(spawned, parsed.options, timedPromise);
  94. const context = {isCanceled: false};
  95. spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned));
  96. spawned.cancel = spawnedCancel.bind(null, spawned, context);
  97. const handlePromise = async () => {
  98. const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone);
  99. const stdout = handleOutput(parsed.options, stdoutResult);
  100. const stderr = handleOutput(parsed.options, stderrResult);
  101. const all = handleOutput(parsed.options, allResult);
  102. if (error || exitCode !== 0 || signal !== null) {
  103. const returnedError = makeError({
  104. error,
  105. exitCode,
  106. signal,
  107. stdout,
  108. stderr,
  109. all,
  110. command,
  111. escapedCommand,
  112. parsed,
  113. timedOut,
  114. isCanceled: context.isCanceled || (parsed.options.signal ? parsed.options.signal.aborted : false),
  115. killed: spawned.killed,
  116. });
  117. if (!parsed.options.reject) {
  118. return returnedError;
  119. }
  120. throw returnedError;
  121. }
  122. return {
  123. command,
  124. escapedCommand,
  125. exitCode: 0,
  126. stdout,
  127. stderr,
  128. all,
  129. failed: false,
  130. timedOut: false,
  131. isCanceled: false,
  132. killed: false,
  133. };
  134. };
  135. const handlePromiseOnce = onetime(handlePromise);
  136. handleInput(spawned, parsed.options);
  137. spawned.all = makeAllStream(spawned, parsed.options);
  138. addPipeMethods(spawned);
  139. mergePromise(spawned, handlePromiseOnce);
  140. return spawned;
  141. }
  142. export function execaSync(file, args, options) {
  143. const parsed = handleArguments(file, args, options);
  144. const command = joinCommand(file, args);
  145. const escapedCommand = getEscapedCommand(file, args);
  146. logCommand(escapedCommand, parsed.options);
  147. const input = handleInputSync(parsed.options);
  148. let result;
  149. try {
  150. result = childProcess.spawnSync(parsed.file, parsed.args, {...parsed.options, input});
  151. } catch (error) {
  152. throw makeError({
  153. error,
  154. stdout: '',
  155. stderr: '',
  156. all: '',
  157. command,
  158. escapedCommand,
  159. parsed,
  160. timedOut: false,
  161. isCanceled: false,
  162. killed: false,
  163. });
  164. }
  165. const stdout = handleOutput(parsed.options, result.stdout, result.error);
  166. const stderr = handleOutput(parsed.options, result.stderr, result.error);
  167. if (result.error || result.status !== 0 || result.signal !== null) {
  168. const error = makeError({
  169. stdout,
  170. stderr,
  171. error: result.error,
  172. signal: result.signal,
  173. exitCode: result.status,
  174. command,
  175. escapedCommand,
  176. parsed,
  177. timedOut: result.error && result.error.code === 'ETIMEDOUT',
  178. isCanceled: false,
  179. killed: result.signal !== null,
  180. });
  181. if (!parsed.options.reject) {
  182. return error;
  183. }
  184. throw error;
  185. }
  186. return {
  187. command,
  188. escapedCommand,
  189. exitCode: 0,
  190. stdout,
  191. stderr,
  192. failed: false,
  193. timedOut: false,
  194. isCanceled: false,
  195. killed: false,
  196. };
  197. }
  198. const normalizeScriptStdin = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined
  199. ? {stdin: 'inherit'}
  200. : {};
  201. const normalizeScriptOptions = (options = {}) => ({
  202. preferLocal: true,
  203. ...normalizeScriptStdin(options),
  204. ...options,
  205. });
  206. function create$(options) {
  207. function $(templatesOrOptions, ...expressions) {
  208. if (!Array.isArray(templatesOrOptions)) {
  209. return create$({...options, ...templatesOrOptions});
  210. }
  211. const [file, ...args] = parseTemplates(templatesOrOptions, expressions);
  212. return execa(file, args, normalizeScriptOptions(options));
  213. }
  214. $.sync = (templates, ...expressions) => {
  215. if (!Array.isArray(templates)) {
  216. throw new TypeError('Please use $(options).sync`command` instead of $.sync(options)`command`.');
  217. }
  218. const [file, ...args] = parseTemplates(templates, expressions);
  219. return execaSync(file, args, normalizeScriptOptions(options));
  220. };
  221. return $;
  222. }
  223. export const $ = create$();
  224. export function execaCommand(command, options) {
  225. const [file, ...args] = parseCommand(command);
  226. return execa(file, args, options);
  227. }
  228. export function execaCommandSync(command, options) {
  229. const [file, ...args] = parseCommand(command);
  230. return execaSync(file, args, options);
  231. }
  232. export function execaNode(scriptPath, args, options = {}) {
  233. if (args && !Array.isArray(args) && typeof args === 'object') {
  234. options = args;
  235. args = [];
  236. }
  237. const stdio = normalizeStdioNode(options);
  238. const defaultExecArgv = process.execArgv.filter(arg => !arg.startsWith('--inspect'));
  239. const {
  240. nodePath = process.execPath,
  241. nodeOptions = defaultExecArgv,
  242. } = options;
  243. return execa(
  244. nodePath,
  245. [
  246. ...nodeOptions,
  247. scriptPath,
  248. ...(Array.isArray(args) ? args : []),
  249. ],
  250. {
  251. ...options,
  252. stdin: undefined,
  253. stdout: undefined,
  254. stderr: undefined,
  255. stdio,
  256. shell: false,
  257. },
  258. );
  259. }