市场夺宝奇兵
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

436 lines
11 KiB

  1. <picture>
  2. <source media="(prefers-color-scheme: dark)" srcset="media/logo_dark.svg">
  3. <img alt="execa logo" src="media/logo.svg" width="400">
  4. </picture>
  5. <br>
  6. [![Coverage Status](https://codecov.io/gh/sindresorhus/execa/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/execa)
  7. > Process execution for humans
  8. <br>
  9. ---
  10. <div align="center">
  11. <p>
  12. <p>
  13. <sup>
  14. <a href="https://github.com/sponsors/sindresorhus">Sindre's open source work is supported by the community</a>
  15. </sup>
  16. </p>
  17. <sup>Special thanks to:</sup>
  18. <br>
  19. <br>
  20. <a href="https://coderabbit.ai?utm_source=sindre&utm_medium=execa">
  21. <img width="300" src="https://sindresorhus.com/assets/thanks/coderabbit-logo.png" alt="CodeRabbit logo">
  22. </a>
  23. <br>
  24. <br>
  25. </p>
  26. </div>
  27. ---
  28. <br>
  29. Execa runs commands in your script, application or library. Unlike shells, it is [optimized](docs/bash.md) for programmatic usage. Built on top of the [`child_process`](https://nodejs.org/api/child_process.html) core module.
  30. ## Features
  31. - [Simple syntax](#simple-syntax): promises and [template strings](docs/execution.md#template-string-syntax), like [`zx`](docs/bash.md).
  32. - [Script](#script) interface.
  33. - [No escaping](docs/escaping.md) nor quoting needed. No risk of shell injection.
  34. - Execute [locally installed binaries](#local-binaries) without `npx`.
  35. - Improved [Windows support](docs/windows.md): [shebangs](docs/windows.md#shebang), [`PATHEXT`](https://ss64.com/nt/path.html#pathext), [graceful termination](#graceful-termination), [and more](https://github.com/moxystudio/node-cross-spawn?tab=readme-ov-file#why).
  36. - [Detailed errors](#detailed-error), [verbose mode](#verbose-mode) and [custom logging](#custom-logging), for [debugging](docs/debugging.md).
  37. - [Pipe multiple subprocesses](#pipe-multiple-subprocesses) better than in shells: retrieve [intermediate results](docs/pipe.md#result), use multiple [sources](docs/pipe.md#multiple-sources-1-destination)/[destinations](docs/pipe.md#1-source-multiple-destinations), [unpipe](docs/pipe.md#unpipe).
  38. - [Split](#split-into-text-lines) the output into text lines, or [iterate](#iterate-over-text-lines) progressively over them.
  39. - Strip [unnecessary newlines](docs/lines.md#newlines).
  40. - Pass any [input](docs/input.md) to the subprocess: [files](#file-input), [strings](#simple-input), [`Uint8Array`s](docs/binary.md#binary-input), [iterables](docs/streams.md#iterables-as-input), [objects](docs/transform.md#object-mode) and almost any [other type](#any-input-type).
  41. - Return [almost any type](#any-output-type) from the subprocess, or redirect it to [files](#file-output).
  42. - Get [interleaved output](#interleaved-output) from `stdout` and `stderr` similar to what is printed on the terminal.
  43. - Retrieve the output [programmatically and print it](#programmatic--terminal-output) on the console at the same time.
  44. - [Transform or filter](#transformfilter-output) the input and output with [simple functions](docs/transform.md).
  45. - Pass [Node.js streams](docs/streams.md#nodejs-streams) or [web streams](#web-streams) to subprocesses, or [convert](#convert-to-duplex-stream) subprocesses to [a stream](docs/streams.md#converting-a-subprocess-to-a-stream).
  46. - [Exchange messages](#exchange-messages) with the subprocess.
  47. - Ensure subprocesses exit even when they [intercept termination signals](docs/termination.md#forceful-termination), or when the current process [ends abruptly](docs/termination.md#current-process-exit).
  48. ## Install
  49. ```sh
  50. npm install execa
  51. ```
  52. ## Documentation
  53. Execution:
  54. - ▶️ [Basic execution](docs/execution.md)
  55. - 💬 [Escaping/quoting](docs/escaping.md)
  56. - 💻 [Shell](docs/shell.md)
  57. - 📜 [Scripts](docs/scripts.md)
  58. - 🐢 [Node.js files](docs/node.md)
  59. - 🌐 [Environment](docs/environment.md)
  60. - ❌ [Errors](docs/errors.md)
  61. - 🏁 [Termination](docs/termination.md)
  62. Input/output:
  63. - 🎹 [Input](docs/input.md)
  64. - 📢 [Output](docs/output.md)
  65. - 📃 [Text lines](docs/lines.md)
  66. - 🤖 [Binary data](docs/binary.md)
  67. - 🧙 [Transforms](docs/transform.md)
  68. Advanced usage:
  69. - 🔀 [Piping multiple subprocesses](docs/pipe.md)
  70. - ⏳️ [Streams](docs/streams.md)
  71. - 📞 [Inter-process communication](docs/ipc.md)
  72. - 🐛 [Debugging](docs/debugging.md)
  73. - 📎 [Windows](docs/windows.md)
  74. - 🔍 [Difference with Bash and zx](docs/bash.md)
  75. - 🐭 [Small packages](docs/small.md)
  76. - 🤓 [TypeScript](docs/typescript.md)
  77. - 📔 [API reference](docs/api.md)
  78. ## Examples
  79. ### Execution
  80. #### Simple syntax
  81. ```js
  82. import {execa} from 'execa';
  83. const {stdout} = await execa`npm run build`;
  84. // Print command's output
  85. console.log(stdout);
  86. ```
  87. #### Script
  88. ```js
  89. import {$} from 'execa';
  90. const {stdout: name} = await $`cat package.json`.pipe`grep name`;
  91. console.log(name);
  92. const branch = await $`git branch --show-current`;
  93. await $`dep deploy --branch=${branch}`;
  94. await Promise.all([
  95. $`sleep 1`,
  96. $`sleep 2`,
  97. $`sleep 3`,
  98. ]);
  99. const directoryName = 'foo bar';
  100. await $`mkdir /tmp/${directoryName}`;
  101. ```
  102. #### Local binaries
  103. ```sh
  104. $ npm install -D eslint
  105. ```
  106. ```js
  107. await execa({preferLocal: true})`eslint`;
  108. ```
  109. #### Pipe multiple subprocesses
  110. ```js
  111. const {stdout, pipedFrom} = await execa`npm run build`
  112. .pipe`sort`
  113. .pipe`head -n 2`;
  114. // Output of `npm run build | sort | head -n 2`
  115. console.log(stdout);
  116. // Output of `npm run build | sort`
  117. console.log(pipedFrom[0].stdout);
  118. // Output of `npm run build`
  119. console.log(pipedFrom[0].pipedFrom[0].stdout);
  120. ```
  121. ### Input/output
  122. #### Interleaved output
  123. ```js
  124. const {all} = await execa({all: true})`npm run build`;
  125. // stdout + stderr, interleaved
  126. console.log(all);
  127. ```
  128. #### Programmatic + terminal output
  129. ```js
  130. const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
  131. // stdout is also printed to the terminal
  132. console.log(stdout);
  133. ```
  134. #### Simple input
  135. ```js
  136. const getInputString = () => { /* ... */ };
  137. const {stdout} = await execa({input: getInputString()})`sort`;
  138. console.log(stdout);
  139. ```
  140. #### File input
  141. ```js
  142. // Similar to: npm run build < input.txt
  143. await execa({stdin: {file: 'input.txt'}})`npm run build`;
  144. ```
  145. #### File output
  146. ```js
  147. // Similar to: npm run build > output.txt
  148. await execa({stdout: {file: 'output.txt'}})`npm run build`;
  149. ```
  150. #### Split into text lines
  151. ```js
  152. const {stdout} = await execa({lines: true})`npm run build`;
  153. // Print first 10 lines
  154. console.log(stdout.slice(0, 10).join('\n'));
  155. ```
  156. ### Streaming
  157. #### Iterate over text lines
  158. ```js
  159. for await (const line of execa`npm run build`) {
  160. if (line.includes('WARN')) {
  161. console.warn(line);
  162. }
  163. }
  164. ```
  165. #### Transform/filter output
  166. ```js
  167. let count = 0;
  168. // Filter out secret lines, then prepend the line number
  169. const transform = function * (line) {
  170. if (!line.includes('secret')) {
  171. yield `[${count++}] ${line}`;
  172. }
  173. };
  174. await execa({stdout: transform})`npm run build`;
  175. ```
  176. #### Web streams
  177. ```js
  178. const response = await fetch('https://example.com');
  179. await execa({stdin: response.body})`sort`;
  180. ```
  181. #### Convert to Duplex stream
  182. ```js
  183. import {execa} from 'execa';
  184. import {pipeline} from 'node:stream/promises';
  185. import {createReadStream, createWriteStream} from 'node:fs';
  186. await pipeline(
  187. createReadStream('./input.txt'),
  188. execa`node ./transform.js`.duplex(),
  189. createWriteStream('./output.txt'),
  190. );
  191. ```
  192. ### IPC
  193. #### Exchange messages
  194. ```js
  195. // parent.js
  196. import {execaNode} from 'execa';
  197. const subprocess = execaNode`child.js`;
  198. await subprocess.sendMessage('Hello from parent');
  199. const message = await subprocess.getOneMessage();
  200. console.log(message); // 'Hello from child'
  201. ```
  202. ```js
  203. // child.js
  204. import {getOneMessage, sendMessage} from 'execa';
  205. const message = await getOneMessage(); // 'Hello from parent'
  206. const newMessage = message.replace('parent', 'child'); // 'Hello from child'
  207. await sendMessage(newMessage);
  208. ```
  209. #### Any input type
  210. ```js
  211. // main.js
  212. import {execaNode} from 'execa';
  213. const ipcInput = [
  214. {task: 'lint', ignore: /test\.js/},
  215. {task: 'copy', files: new Set(['main.js', 'index.js']),
  216. }];
  217. await execaNode({ipcInput})`build.js`;
  218. ```
  219. ```js
  220. // build.js
  221. import {getOneMessage} from 'execa';
  222. const ipcInput = await getOneMessage();
  223. ```
  224. #### Any output type
  225. ```js
  226. // main.js
  227. import {execaNode} from 'execa';
  228. const {ipcOutput} = await execaNode`build.js`;
  229. console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
  230. console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
  231. ```
  232. ```js
  233. // build.js
  234. import {sendMessage} from 'execa';
  235. const runBuild = () => { /* ... */ };
  236. await sendMessage({kind: 'start', timestamp: new Date()});
  237. await runBuild();
  238. await sendMessage({kind: 'stop', timestamp: new Date()});
  239. ```
  240. #### Graceful termination
  241. ```js
  242. // main.js
  243. import {execaNode} from 'execa';
  244. const controller = new AbortController();
  245. setTimeout(() => {
  246. controller.abort();
  247. }, 5000);
  248. await execaNode({
  249. cancelSignal: controller.signal,
  250. gracefulCancel: true,
  251. })`build.js`;
  252. ```
  253. ```js
  254. // build.js
  255. import {getCancelSignal} from 'execa';
  256. const cancelSignal = await getCancelSignal();
  257. const url = 'https://example.com/build/info';
  258. const response = await fetch(url, {signal: cancelSignal});
  259. ```
  260. ### Debugging
  261. #### Detailed error
  262. ```js
  263. import {execa, ExecaError} from 'execa';
  264. try {
  265. await execa`unknown command`;
  266. } catch (error) {
  267. if (error instanceof ExecaError) {
  268. console.log(error);
  269. }
  270. /*
  271. ExecaError: Command failed with ENOENT: unknown command
  272. spawn unknown ENOENT
  273. at ...
  274. at ... {
  275. shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT',
  276. originalMessage: 'spawn unknown ENOENT',
  277. command: 'unknown command',
  278. escapedCommand: 'unknown command',
  279. cwd: '/path/to/cwd',
  280. durationMs: 28.217566,
  281. failed: true,
  282. timedOut: false,
  283. isCanceled: false,
  284. isTerminated: false,
  285. isMaxBuffer: false,
  286. code: 'ENOENT',
  287. stdout: '',
  288. stderr: '',
  289. stdio: [undefined, '', ''],
  290. pipedFrom: []
  291. [cause]: Error: spawn unknown ENOENT
  292. at ...
  293. at ... {
  294. errno: -2,
  295. code: 'ENOENT',
  296. syscall: 'spawn unknown',
  297. path: 'unknown',
  298. spawnargs: [ 'command' ]
  299. }
  300. }
  301. */
  302. }
  303. ```
  304. #### Verbose mode
  305. ```js
  306. await execa`npm run build`;
  307. await execa`npm run test`;
  308. ```
  309. <img alt="execa verbose output" src="media/verbose.png" width="603">
  310. #### Custom logging
  311. ```js
  312. import {execa as execa_} from 'execa';
  313. import {createLogger, transports} from 'winston';
  314. // Log to a file using Winston
  315. const transport = new transports.File({filename: 'logs.txt'});
  316. const logger = createLogger({transports: [transport]});
  317. const LOG_LEVELS = {
  318. command: 'info',
  319. output: 'verbose',
  320. ipc: 'verbose',
  321. error: 'error',
  322. duration: 'info',
  323. };
  324. const execa = execa_({
  325. verbose(verboseLine, {message, ...verboseObject}) {
  326. const level = LOG_LEVELS[verboseObject.type];
  327. logger[level](message, verboseObject);
  328. },
  329. });
  330. await execa`npm run build`;
  331. await execa`npm run test`;
  332. ```
  333. ## Related
  334. - [nano-spawn](https://github.com/sindresorhus/nano-spawn) - Like Execa but [smaller](docs/small.md)
  335. - [gulp-execa](https://github.com/ehmicky/gulp-execa) - Gulp plugin for Execa
  336. - [nvexeca](https://github.com/ehmicky/nvexeca) - Run Execa using any Node.js version
  337. ## Maintainers
  338. - [Sindre Sorhus](https://github.com/sindresorhus)
  339. - [@ehmicky](https://github.com/ehmicky)