1 | import type {Options} from '../arguments/options.js';
|
2 | import type {ResultPromise} from '../subprocess/subprocess.js';
|
3 | import type {TemplateString} from './template.js';
|
4 |
|
5 | /**
|
6 | Executes a command using `file ...arguments`.
|
7 |
|
8 | When `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 | ```
|
22 | import {execa} from 'execa';
|
23 |
|
24 | const {stdout} = await execa`npm run build`;
|
25 | // Print command's output
|
26 | console.log(stdout);
|
27 | ```
|
28 |
|
29 | @example <caption>Script</caption>
|
30 |
|
31 | ```
|
32 | import {$} from 'execa';
|
33 |
|
34 | const {stdout: name} = await $`cat package.json`.pipe`grep name`;
|
35 | console.log(name);
|
36 |
|
37 | const branch = await $`git branch --show-current`;
|
38 | await $`dep deploy --branch=${branch}`;
|
39 |
|
40 | await Promise.all([
|
41 | $`sleep 1`,
|
42 | $`sleep 2`,
|
43 | $`sleep 3`,
|
44 | ]);
|
45 |
|
46 | const directoryName = 'foo bar';
|
47 | await $`mkdir /tmp/${directoryName}`;
|
48 | ```
|
49 |
|
50 | @example <caption>Local binaries</caption>
|
51 |
|
52 | ```
|
53 | $ npm install -D eslint
|
54 | ```
|
55 |
|
56 | ```
|
57 | await execa({preferLocal: true})`eslint`;
|
58 | ```
|
59 |
|
60 | @example <caption>Pipe multiple subprocesses</caption>
|
61 |
|
62 | ```
|
63 | const {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`
|
68 | console.log(stdout);
|
69 | // Output of `npm run build | sort`
|
70 | console.log(pipedFrom[0].stdout);
|
71 | // Output of `npm run build`
|
72 | console.log(pipedFrom[0].pipedFrom[0].stdout);
|
73 | ```
|
74 |
|
75 | @example <caption>Interleaved output</caption>
|
76 |
|
77 | ```
|
78 | const {all} = await execa({all: true})`npm run build`;
|
79 | // stdout + stderr, interleaved
|
80 | console.log(all);
|
81 | ```
|
82 |
|
83 | @example <caption>Programmatic + terminal output</caption>
|
84 |
|
85 | ```
|
86 | const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
|
87 | // stdout is also printed to the terminal
|
88 | console.log(stdout);
|
89 | ```
|
90 |
|
91 | @example <caption>Simple input</caption>
|
92 |
|
93 | ```
|
94 | const getInputString = () => { /* ... *\/ };
|
95 | const {stdout} = await execa({input: getInputString()})`sort`;
|
96 | console.log(stdout);
|
97 | ```
|
98 |
|
99 | @example <caption>File input</caption>
|
100 |
|
101 | ```
|
102 | // Similar to: npm run build < input.txt
|
103 | await 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
|
110 | await execa({stdout: {file: 'output.txt'}})`npm run build`;
|
111 | ```
|
112 |
|
113 | @example <caption>Split into text lines</caption>
|
114 |
|
115 | ```
|
116 | const {stdout} = await execa({lines: true})`npm run build`;
|
117 | // Print first 10 lines
|
118 | console.log(stdout.slice(0, 10).join('\n'));
|
119 | ```
|
120 |
|
121 | @example <caption>Iterate over text lines</caption>
|
122 |
|
123 | ```
|
124 | for 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 | ```
|
134 | let count = 0;
|
135 |
|
136 | // Filter out secret lines, then prepend the line number
|
137 | const transform = function * (line) {
|
138 | if (!line.includes('secret')) {
|
139 | yield `[${count++}] ${line}`;
|
140 | }
|
141 | };
|
142 |
|
143 | await execa({stdout: transform})`npm run build`;
|
144 | ```
|
145 |
|
146 | @example <caption>Web streams</caption>
|
147 |
|
148 | ```
|
149 | const response = await fetch('https://example.com');
|
150 | await execa({stdin: response.body})`sort`;
|
151 | ```
|
152 |
|
153 | @example <caption>Convert to Duplex stream</caption>
|
154 |
|
155 | ```
|
156 | import {execa} from 'execa';
|
157 | import {pipeline} from 'node:stream/promises';
|
158 | import {createReadStream, createWriteStream} from 'node:fs';
|
159 |
|
160 | await 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
|
171 | import {execaNode} from 'execa';
|
172 |
|
173 | const subprocess = execaNode`child.js`;
|
174 | await subprocess.sendMessage('Hello from parent');
|
175 | const message = await subprocess.getOneMessage();
|
176 | console.log(message); // 'Hello from child'
|
177 | ```
|
178 |
|
179 | ```
|
180 | // child.js
|
181 | import {getOneMessage, sendMessage} from 'execa';
|
182 |
|
183 | const message = await getOneMessage(); // 'Hello from parent'
|
184 | const newMessage = message.replace('parent', 'child'); // 'Hello from child'
|
185 | await sendMessage(newMessage);
|
186 | ```
|
187 |
|
188 | @example <caption>Any input type</caption>
|
189 |
|
190 | ```
|
191 | // main.js
|
192 | import {execaNode} from 'execa';
|
193 |
|
194 | const ipcInput = [
|
195 | {task: 'lint', ignore: /test\.js/},
|
196 | {task: 'copy', files: new Set(['main.js', 'index.js']),
|
197 | }];
|
198 | await execaNode({ipcInput})`build.js`;
|
199 | ```
|
200 |
|
201 | ```
|
202 | // build.js
|
203 | import {getOneMessage} from 'execa';
|
204 |
|
205 | const ipcInput = await getOneMessage();
|
206 | ```
|
207 |
|
208 | @example <caption>Any output type</caption>
|
209 |
|
210 | ```
|
211 | // main.js
|
212 | import {execaNode} from 'execa';
|
213 |
|
214 | const {ipcOutput} = await execaNode`build.js`;
|
215 | console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
|
216 | console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
|
217 | ```
|
218 |
|
219 | ```
|
220 | // build.js
|
221 | import {sendMessage} from 'execa';
|
222 |
|
223 | const runBuild = () => { /* ... *\/ };
|
224 |
|
225 | await sendMessage({kind: 'start', timestamp: new Date()});
|
226 | await runBuild();
|
227 | await sendMessage({kind: 'stop', timestamp: new Date()});
|
228 | ```
|
229 |
|
230 | @example <caption>Graceful termination</caption>
|
231 |
|
232 | ```
|
233 | // main.js
|
234 | import {execaNode} from 'execa';
|
235 |
|
236 | const controller = new AbortController();
|
237 | setTimeout(() => {
|
238 | controller.abort();
|
239 | }, 5000);
|
240 |
|
241 | await execaNode({
|
242 | cancelSignal: controller.signal,
|
243 | gracefulCancel: true,
|
244 | })`build.js`;
|
245 | ```
|
246 |
|
247 | ```
|
248 | // build.js
|
249 | import {getCancelSignal} from 'execa';
|
250 |
|
251 | const cancelSignal = await getCancelSignal();
|
252 | const url = 'https://example.com/build/info';
|
253 | const response = await fetch(url, {signal: cancelSignal});
|
254 | ```
|
255 |
|
256 | @example <caption>Detailed error</caption>
|
257 |
|
258 | ```
|
259 | import {execa, ExecaError} from 'execa';
|
260 |
|
261 | try {
|
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 | ```
|
305 | await execa`npm run build`;
|
306 | await 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 | ```
|
325 | import {execa as execa_} from 'execa';
|
326 | import {createLogger, transports} from 'winston';
|
327 |
|
328 | // Log to a file using Winston
|
329 | const transport = new transports.File({filename: 'logs.txt'});
|
330 | const logger = createLogger({transports: [transport]});
|
331 | const LOG_LEVELS = {
|
332 | command: 'info',
|
333 | output: 'verbose',
|
334 | ipc: 'verbose',
|
335 | error: 'error',
|
336 | duration: 'info',
|
337 | };
|
338 |
|
339 | const execa = execa_({
|
340 | verbose(verboseLine, {message, ...verboseObject}) {
|
341 | const level = LOG_LEVELS[verboseObject.type];
|
342 | logger[level](message, verboseObject);
|
343 | },
|
344 | });
|
345 |
|
346 | await execa`npm run build`;
|
347 | await execa`npm run test`;
|
348 | ```
|
349 | */
|
350 | export declare const execa: ExecaMethod<{}>;
|
351 |
|
352 | /**
|
353 | `execa()` method either exported by Execa, or bound using `execa(options)`.
|
354 | */
|
355 | export type ExecaMethod<OptionsType extends Options = Options> =
|
356 | & ExecaBind<OptionsType>
|
357 | & ExecaTemplate<OptionsType>
|
358 | & ExecaArrayLong<OptionsType>
|
359 | & ExecaArrayShort<OptionsType>;
|
360 |
|
361 | // `execa(options)` binding
|
362 | type ExecaBind<OptionsType extends Options> =
|
363 | <NewOptionsType extends Options = {}>(options: NewOptionsType)
|
364 | => ExecaMethod<OptionsType & NewOptionsType>;
|
365 |
|
366 | // `execa`command`` template syntax
|
367 | type ExecaTemplate<OptionsType extends Options> =
|
368 | (...templateString: TemplateString)
|
369 | => ResultPromise<OptionsType>;
|
370 |
|
371 | // `execa('file', ['argument'], {})` array syntax
|
372 | type 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
|
377 | type ExecaArrayShort<OptionsType extends Options> =
|
378 | <NewOptionsType extends Options = {}>(file: string | URL, options?: NewOptionsType)
|
379 | => ResultPromise<OptionsType & NewOptionsType>;
|