UNPKG

8.94 kBTypeScriptView Raw
1import type {Options} from '../arguments/options.js';
2import type {ResultPromise} from '../subprocess/subprocess.js';
3import type {TemplateString} from './template.js';
4
5/**
6Executes a command using `file ...arguments`.
7
8When `command` is a template string, it includes both the `file` and its `arguments`.
9
10`execa(options)` can be used to return a new instance of this method but with different default `options`. Consecutive calls are merged to previous ones.
11
12@param file - The program/script to execute, as a string or file URL
13@param arguments - Arguments to pass to `file` on execution.
14@returns A `ResultPromise` that is both:
15- the subprocess.
16- a `Promise` either resolving with its successful `result`, or rejecting with its `error`.
17@throws `ExecaError`
18
19@example <caption>Simple syntax</caption>
20
21```
22import {execa} from 'execa';
23
24const {stdout} = await execa`npm run build`;
25// Print command's output
26console.log(stdout);
27```
28
29@example <caption>Script</caption>
30
31```
32import {$} from 'execa';
33
34const {stdout: name} = await $`cat package.json`.pipe`grep name`;
35console.log(name);
36
37const branch = await $`git branch --show-current`;
38await $`dep deploy --branch=${branch}`;
39
40await Promise.all([
41 $`sleep 1`,
42 $`sleep 2`,
43 $`sleep 3`,
44]);
45
46const directoryName = 'foo bar';
47await $`mkdir /tmp/${directoryName}`;
48```
49
50@example <caption>Local binaries</caption>
51
52```
53$ npm install -D eslint
54```
55
56```
57await execa({preferLocal: true})`eslint`;
58```
59
60@example <caption>Pipe multiple subprocesses</caption>
61
62```
63const {stdout, pipedFrom} = await execa`npm run build`
64 .pipe`sort`
65 .pipe`head -n 2`;
66
67// Output of `npm run build | sort | head -n 2`
68console.log(stdout);
69// Output of `npm run build | sort`
70console.log(pipedFrom[0].stdout);
71// Output of `npm run build`
72console.log(pipedFrom[0].pipedFrom[0].stdout);
73```
74
75@example <caption>Interleaved output</caption>
76
77```
78const {all} = await execa({all: true})`npm run build`;
79// stdout + stderr, interleaved
80console.log(all);
81```
82
83@example <caption>Programmatic + terminal output</caption>
84
85```
86const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
87// stdout is also printed to the terminal
88console.log(stdout);
89```
90
91@example <caption>Simple input</caption>
92
93```
94const getInputString = () => { /* ... *\/ };
95const {stdout} = await execa({input: getInputString()})`sort`;
96console.log(stdout);
97```
98
99@example <caption>File input</caption>
100
101```
102// Similar to: npm run build < input.txt
103await execa({stdin: {file: 'input.txt'}})`npm run build`;
104```
105
106@example <caption>File output</caption>
107
108```
109// Similar to: npm run build > output.txt
110await execa({stdout: {file: 'output.txt'}})`npm run build`;
111```
112
113@example <caption>Split into text lines</caption>
114
115```
116const {stdout} = await execa({lines: true})`npm run build`;
117// Print first 10 lines
118console.log(stdout.slice(0, 10).join('\n'));
119```
120
121@example <caption>Iterate over text lines</caption>
122
123```
124for await (const line of execa`npm run build`) {
125 if (line.includes('WARN')) {
126 console.warn(line);
127 }
128}
129```
130
131@example <caption>Transform/filter output</caption>
132
133```
134let count = 0;
135
136// Filter out secret lines, then prepend the line number
137const transform = function * (line) {
138 if (!line.includes('secret')) {
139 yield `[${count++}] ${line}`;
140 }
141};
142
143await execa({stdout: transform})`npm run build`;
144```
145
146@example <caption>Web streams</caption>
147
148```
149const response = await fetch('https://example.com');
150await execa({stdin: response.body})`sort`;
151```
152
153@example <caption>Convert to Duplex stream</caption>
154
155```
156import {execa} from 'execa';
157import {pipeline} from 'node:stream/promises';
158import {createReadStream, createWriteStream} from 'node:fs';
159
160await pipeline(
161 createReadStream('./input.txt'),
162 execa`node ./transform.js`.duplex(),
163 createWriteStream('./output.txt'),
164);
165```
166
167@example <caption>Exchange messages</caption>
168
169```
170// parent.js
171import {execaNode} from 'execa';
172
173const subprocess = execaNode`child.js`;
174await subprocess.sendMessage('Hello from parent');
175const message = await subprocess.getOneMessage();
176console.log(message); // 'Hello from child'
177```
178
179```
180// child.js
181import {getOneMessage, sendMessage} from 'execa';
182
183const message = await getOneMessage(); // 'Hello from parent'
184const newMessage = message.replace('parent', 'child'); // 'Hello from child'
185await sendMessage(newMessage);
186```
187
188@example <caption>Any input type</caption>
189
190```
191// main.js
192import {execaNode} from 'execa';
193
194const ipcInput = [
195 {task: 'lint', ignore: /test\.js/},
196 {task: 'copy', files: new Set(['main.js', 'index.js']),
197}];
198await execaNode({ipcInput})`build.js`;
199```
200
201```
202// build.js
203import {getOneMessage} from 'execa';
204
205const ipcInput = await getOneMessage();
206```
207
208@example <caption>Any output type</caption>
209
210```
211// main.js
212import {execaNode} from 'execa';
213
214const {ipcOutput} = await execaNode`build.js`;
215console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
216console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
217```
218
219```
220// build.js
221import {sendMessage} from 'execa';
222
223const runBuild = () => { /* ... *\/ };
224
225await sendMessage({kind: 'start', timestamp: new Date()});
226await runBuild();
227await sendMessage({kind: 'stop', timestamp: new Date()});
228```
229
230@example <caption>Graceful termination</caption>
231
232```
233// main.js
234import {execaNode} from 'execa';
235
236const controller = new AbortController();
237setTimeout(() => {
238 controller.abort();
239}, 5000);
240
241await execaNode({
242 cancelSignal: controller.signal,
243 gracefulCancel: true,
244})`build.js`;
245```
246
247```
248// build.js
249import {getCancelSignal} from 'execa';
250
251const cancelSignal = await getCancelSignal();
252const url = 'https://example.com/build/info';
253const response = await fetch(url, {signal: cancelSignal});
254```
255
256@example <caption>Detailed error</caption>
257
258```
259import {execa, ExecaError} from 'execa';
260
261try {
262 await execa`unknown command`;
263} catch (error) {
264 if (error instanceof ExecaError) {
265 console.log(error);
266 }
267 /*
268 ExecaError: Command failed with ENOENT: unknown command
269 spawn unknown ENOENT
270 at ...
271 at ... {
272 shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT',
273 originalMessage: 'spawn unknown ENOENT',
274 command: 'unknown command',
275 escapedCommand: 'unknown command',
276 cwd: '/path/to/cwd',
277 durationMs: 28.217566,
278 failed: true,
279 timedOut: false,
280 isCanceled: false,
281 isTerminated: false,
282 isMaxBuffer: false,
283 code: 'ENOENT',
284 stdout: '',
285 stderr: '',
286 stdio: [undefined, '', ''],
287 pipedFrom: []
288 [cause]: Error: spawn unknown ENOENT
289 at ...
290 at ... {
291 errno: -2,
292 code: 'ENOENT',
293 syscall: 'spawn unknown',
294 path: 'unknown',
295 spawnargs: [ 'command' ]
296 }
297 }
298 *\/
299}
300```
301
302@example <caption>Verbose mode</caption>
303
304```
305await execa`npm run build`;
306await execa`npm run test`;
307```
308
309```
310$ NODE_DEBUG=execa node build.js
311[00:57:44.581] [0] $ npm run build
312[00:57:44.653] [0] Building application...
313[00:57:44.653] [0] Done building.
314[00:57:44.658] [0] ✔ (done in 78ms)
315[00:57:44.658] [1] $ npm run test
316[00:57:44.740] [1] Running tests...
317[00:57:44.740] [1] Error: the entrypoint is invalid.
318[00:57:44.747] [1] ✘ Command failed with exit code 1: npm run test
319[00:57:44.747] [1] ✘ (done in 89ms)
320```
321
322@example <caption>Custom logging</caption>
323
324```
325import {execa as execa_} from 'execa';
326import {createLogger, transports} from 'winston';
327
328// Log to a file using Winston
329const transport = new transports.File({filename: 'logs.txt'});
330const logger = createLogger({transports: [transport]});
331const LOG_LEVELS = {
332 command: 'info',
333 output: 'verbose',
334 ipc: 'verbose',
335 error: 'error',
336 duration: 'info',
337};
338
339const execa = execa_({
340 verbose(verboseLine, {message, ...verboseObject}) {
341 const level = LOG_LEVELS[verboseObject.type];
342 logger[level](message, verboseObject);
343 },
344});
345
346await execa`npm run build`;
347await execa`npm run test`;
348```
349*/
350export declare const execa: ExecaMethod<{}>;
351
352/**
353`execa()` method either exported by Execa, or bound using `execa(options)`.
354*/
355export type ExecaMethod<OptionsType extends Options = Options> =
356 & ExecaBind<OptionsType>
357 & ExecaTemplate<OptionsType>
358 & ExecaArrayLong<OptionsType>
359 & ExecaArrayShort<OptionsType>;
360
361// `execa(options)` binding
362type ExecaBind<OptionsType extends Options> =
363 <NewOptionsType extends Options = {}>(options: NewOptionsType)
364 => ExecaMethod<OptionsType & NewOptionsType>;
365
366// `execa`command`` template syntax
367type ExecaTemplate<OptionsType extends Options> =
368 (...templateString: TemplateString)
369 => ResultPromise<OptionsType>;
370
371// `execa('file', ['argument'], {})` array syntax
372type ExecaArrayLong<OptionsType extends Options> =
373 <NewOptionsType extends Options = {}>(file: string | URL, arguments?: readonly string[], options?: NewOptionsType)
374 => ResultPromise<OptionsType & NewOptionsType>;
375
376// `execa('file', {})` array syntax
377type ExecaArrayShort<OptionsType extends Options> =
378 <NewOptionsType extends Options = {}>(file: string | URL, options?: NewOptionsType)
379 => ResultPromise<OptionsType & NewOptionsType>;