Giter Club home page Giter Club logo

execa's Introduction

execa logo

Coverage Status

Process execution for humans





Execa runs commands in your script, application or library. Unlike shells, it is optimized for programmatic usage. Built on top of the child_process core module.

Features

Install

npm install execa

Documentation

Execution:

Input/output:

Advanced usage:

Examples

Execution

Simple syntax

import {execa} from 'execa';

const {stdout} = await execa`npm run build`;
// Print command's output
console.log(stdout);

Script

import {$} from 'execa';

const {stdout: name} = await $`cat package.json`.pipe`grep name`;
console.log(name);

const branch = await $`git branch --show-current`;
await $`dep deploy --branch=${branch}`;

await Promise.all([
	$`sleep 1`,
	$`sleep 2`,
	$`sleep 3`,
]);

const directoryName = 'foo bar';
await $`mkdir /tmp/${directoryName}`;

Local binaries

$ npm install -D eslint
await execa({preferLocal: true})`eslint`;

Pipe multiple subprocesses

const {stdout, pipedFrom} = await execa`npm run build`
	.pipe`sort`
	.pipe`head -n 2`;

// Output of `npm run build | sort | head -n 2`
console.log(stdout);
// Output of `npm run build | sort`
console.log(pipedFrom[0].stdout);
// Output of `npm run build`
console.log(pipedFrom[0].pipedFrom[0].stdout);

Input/output

Interleaved output

const {all} = await execa({all: true})`npm run build`;
// stdout + stderr, interleaved
console.log(all);

Programmatic + terminal output

const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
// stdout is also printed to the terminal
console.log(stdout);

Files

// Similar to: npm run build > output.txt
await execa({stdout: {file: './output.txt'}})`npm run build`;

Simple input

const {stdout} = await execa({input: getInputString()})`sort`;
console.log(stdout);

Split into text lines

const {stdout} = await execa({lines: true})`npm run build`;
// Print first 10 lines
console.log(stdout.slice(0, 10).join('\n'));

Streaming

Iterate over text lines

for await (const line of execa`npm run build`) {
	if (line.includes('WARN')) {
		console.warn(line);
	}
}

Transform/filter output

let count = 0;

// Filter out secret lines, then prepend the line number
const transform = function * (line) {
	if (!line.includes('secret')) {
		yield `[${count++}] ${line}`;
	}
};

await execa({stdout: transform})`npm run build`;

Web streams

const response = await fetch('https://example.com');
await execa({stdin: response.body})`sort`;

Convert to Duplex stream

import {execa} from 'execa';
import {pipeline} from 'node:stream/promises';
import {createReadStream, createWriteStream} from 'node:fs';

await pipeline(
	createReadStream('./input.txt'),
	execa`node ./transform.js`.duplex(),
	createWriteStream('./output.txt'),
);

Debugging

Detailed error

import {execa, ExecaError} from 'execa';

try {
	await execa`unknown command`;
} catch (error) {
	if (error instanceof ExecaError) {
		console.log(error);
	}
	/*
	ExecaError: Command failed with ENOENT: unknown command
	spawn unknown ENOENT
			at ...
			at ... {
		shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT',
		originalMessage: 'spawn unknown ENOENT',
		command: 'unknown command',
		escapedCommand: 'unknown command',
		cwd: '/path/to/cwd',
		durationMs: 28.217566,
		failed: true,
		timedOut: false,
		isCanceled: false,
		isTerminated: false,
		isMaxBuffer: false,
		code: 'ENOENT',
		stdout: '',
		stderr: '',
		stdio: [undefined, '', ''],
		pipedFrom: []
		[cause]: Error: spawn unknown ENOENT
				at ...
				at ... {
			errno: -2,
			code: 'ENOENT',
			syscall: 'spawn unknown',
			path: 'unknown',
			spawnargs: [ 'command' ]
		}
	}
	*/
}

Verbose mode

await execa`npm run build`;
await execa`npm run test`;

execa verbose output

Related

Maintainers

execa's People

Contributors

aaronccasanova avatar acburdine avatar addaleax avatar ammarbinfaisal avatar bendingbender avatar bradfordlemley avatar brandon93s avatar coreyfarrell avatar dignifiedquire avatar ehmicky avatar fisker avatar forivall avatar fregante avatar gmartigny avatar honzajavorek avatar jamestalmage avatar jopemachine avatar justsml avatar kevva avatar kikobeats avatar leafac avatar linusu avatar mifi avatar otakustay avatar papb avatar samverschueren avatar satazor avatar sindresorhus avatar sonicdoe avatar strikeentco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

execa's Issues

How to maintain console colors

I have created a simple script:

	execa('bash',['./scripts/lint-styles.sh'])
		.then(result => {
			console.log(result.stdout);
		});

This script calls

#!/usr/bin/env bash

./node_modules/.bin/sass-lint "./resources/assets/sass/*.s+(a|c)ss" -q -v

When executed, the output is correct, but it does not have the color output which displays when executing from cli

Add a promise-getter to spawned child process objects

promoted from #6 (comment)

Sometimes you want to use execa.spawn() because you need an object representing the child process while it's running, so you can send signals or listen to events. But then you lose the convenience of a promise-based API (i.e. having a thing you can easily await, which will collect up the stdout/stderr for you).

Suggest adding a method to spawned child objects. This method would return a promise that resolves when the child process exits.

const child = execa.spawn(...);

// do any necessary manual stuff with events/signals etc
child.on(...);

// await the final outcome
const { stdio, stderr, code } = await child.exited();

(I'm not sure about the name exited though.)


Possible extra feature: the method could take an optional parameter specifying the expected exit code, e.g. .exited(0). If the child process exits with any other code, the promise rejects. If no exit code is specified, the promise will always resolve when the child process exits, no matter how.

Allow userland streams to be passed to `stdio` option

UPDATE: I think this is the right way to do it. Everything in between here and there is interesting background though.


I think you should be able to do this:

const PassThrough = require('stream').PassThrough;
const execa = require('execa');

const stdin = new PassThrough();
const stdout = new PassThrough();
const stderr = new PassThrough();

execa('foo', [], {stdio: [stdin, stdout, stderr]});

But you aren't actually allowed to pass any old stream to child_process. It has to be a very specific type of native stream (here is the internal Node code that checks that).

Instead, if you've got some userland stream you want to use, you've got to go about it a bit more convoluted way:

const PassThrough = require('stream').PassThrough;
const execa = require('execa');

const stdin = new PassThrough();
const stdout = new PassThrough();
const stderr = new PassThrough();

const cp = execa('foo', [], ['pipe', 'pipe', 'pipe']);

stdin.pipe(cp.stdin);
cp.stdout.pipe(stdout);
cp.stderr.pipe(stderr);

I think it's a weird choice to allow streams, but only certain types of streams. I've written native-stream-type which would help us figure out if we can pass the stream directly to child_process.spawn or not. For userland streams, we just convert to using pipe.

Release version w/ cross-spawn

#42

Could this one possibly be released? 0.4.1?
So that we don't get the annoying warning in all packages using execa ๐Ÿ‘ผ

Thanks!

Inherited stdio results in null stdout, stderr in Error

Ran across a funky error case today while converting gulp-mocha to use execa. If {stdio: 'inherit'} is used in execa's options, and the command fails, the resulting Error message contains nullnull

Line in question: https://github.com/sindresorhus/execa/blob/master/index.js#L201

Example output:

{ Error: Command failed: mocha /.../github/gulp-mocha/test/fixtures/fixture-fail.js
nullnull
    at Promise.all.then.arr (/.../github/gulp-mocha/node_modules/execa/index.js:201:11)
    at process._tickCallback (internal/process/next_tick.js:103:7)

FWIW, for commands that succeed, result.stdout and result.stderr are also both null in the .then(result => handler when {stdio: 'inherit'{ is used. That isn't mentioned anywhere in the README, and the option appears to be the only way to preserve terminal formatting.

Add some tests around the logic copied out of `execFile`

#27 really only went so far as copying the execFile implementation where necessary to get tests passing using spawn.

Most of the required test aren't terribly consequential, but should still be completed. The one set of tests I can think of right now is the creation of the Error property. It builds the command by concatting args together, then uses that to build the error message. Also, it attaches a few extra properties: killed, code, etc.

Examine the diff of #27 for a complete picture of what should be tested.

I will tackle this, just recording for posterity.

Capture exit code

I'm not sure what would be the best way to implement this, but it would be great if this module also captured the exit code of whatever file or command is ran somehow.

Undefined and null argument variables are passed as strings

Undefined and null variables are passed to child process as their string representations, "undefined" and "null". I'm not sure what the expected behavior is, but when working with shell scripts, I expect the undefined or null arguments to be passed as empty strings.

Allow transform streams convert output to an array of any objects, instead of a single string

We need to think through the ideal API for this and #20 as well, but something like:

execa('./cli', [], {
  transforms: {
     // shortcut to a built-in transform we provide
    stdout: 'line',

    stderr: through2(function (chunk, encoding, callback) {
      // custom processing here
    })
  }
}).then(function (result) {
  result.stdout;
  // => an array of lines

  result.stderr;
  // => an array of whatever the custom transform emits
});

execa.shell(cmd, {shell: '/bin/bash'}) only runs the first word of command

Let's say that I have to run man ls command.

// runs correctly, with man page of 'ls' in stdout
execa.shell('man ls')
  .then(function (result) {
    console.log(`stdout: ${result.stdout}`)
    console.log(`stderr: ${result.stderr}`)
  })

but

// this just runs 'man'
execa.shell('man ls', {shell: '/bin/bash'})
  .then(function (result) {
    console.log(`stdout: ${result.stdout}`)
    console.log(`stderr: ${result.stderr}`)
  })

Output

stdout: 
stderr: What manual page do you want?

Doesn't work with electron

Feel free to close this as a wontfix but I wanted to log the issue anyway.

With standard exec:

const exec = require('child_process').exec;
exec('networksetup', console.log.bind(console));
// 'networksetup Help Information...' Works OK

With execa:

const execa = require('execa');
execa.shell('networksetup').then(console.log.bind(console));
// Error: Command failed: /bin/sh -c networksetup

Automatically wait for file streams to open

It might be possible to wait for the open event on file streams (if we can find a way to detect that a stream is in fact a file stream, and that it will indeed eventually open).

See the discussion here

stdout encoding issue.

This is more of a heads up to an interesting issue I've run into. I'm curious if others have seen it and if there's a known workaround.

foo.py (Python 2.7.10):

#!/usr/bin/env python
import sys
print(sys.stdout.encoding)

foo.js (execa v0.6.0, node 7.6.0):

'use strict'
const execa = require('execa')
const spawned = execa('./foo.py', [], { 'encoding': 'utf8' })
spawned.stdout.setEncoding('utf8')
spawned.then(({ stdout }) => console.log(stdout))

running node foo.js:

None

I'm wondering how to ensure the invoked Python script has a stdout.encoding of UTF-8.

Release?

Could you release a new version with the reject option available?

Cleanup doesn't work reliably

I'm using execa to start a child node process which simply starts an express server listening on port 5000.

But this child process often ends up running after I've exited the parent program, so when I run the parent again, I get an error saying port 5000 is already bound when it tries to start the child process. This happens about 25% of the time.

I haven't yet figured out what conditions cause the cleanup to fail. Any ideas?

readable stream 'end' event

This is not an issue, but more a question on node streams.
Just wanted to know if there is any technical difference between those 2 implementations

  function runCommandAndWriteToFile (cmd, name) {
   // cmd is an execa instance
    return new Promise((resolve) => {
      cmd.stderr.pipe(process.stdout)
      cmd.stderr.on('end', () => {
        cmd.then((res) => {
          const msg = `Command run:\n${res.cmd}`
          utils.log(chalk.magenta(msg))
          resolve()
        })
      })
    })
  }

and

  function runCommandAndWriteToFile (cmd, name) {
    cmd.stderr.pipe(process.stdout)
    return cmd.then((res) => {
      const msg = `Command run:\n${res.cmd}`
      utils.log(chalk.magenta(msg))
    })
  }

thanks

Why append the entire contents of stdout on error?

It seems just as likely the info needed will be in stderr, and it could be a whole bunch of data to print. Also, it gives you no means of extracting the original error.message property.

Is this providing any real value? If people want to log it to the console, can't they just:

console.log(error.message + error.stdout);

warning: possible EventEmitter memory leak detected

Description

My test print a warning

(node) warning: possible EventEmitter memory leak detected. 11 exit listeners added. Use emitter.setMaxListeners() to increase limit.

I use execa 16 times in my unit tests

Test Source

tests are here : https://github.com/lesspass/cli/blob/master/test.js

I try to test my command line interface with 16 tests like this one :

test('test space in password', async t => {
    const {stdout} = await execa('./cli.js', ['lesspass.com', '[email protected]', 'my Master Password']);
    t.is(stdout, 'onAV7&uvEC2=');
});

Here the output for my tests : https://travis-ci.org/lesspass/cli/jobs/169243714

Github

Here the github repository to reproduce the bug : https://github.com/lesspass/cli

Environment

Node.js v4.6.0
linux 4.4.0-45-generic
ava version 0.16.0
excea version 0.5.0
npm version 3.10.8

How can I avoid to raise this warning ?

previously post on avajs/ava#1087

Shorthand for piping stdout and stderr

I use this pattern a lot and have created a function for personal use:

const cp = execa('echo', ['foo', 'unicorns'])
cp.stdout.pipe(process.stdout)
cp.stderr.pipe(process.stderr)

Do you think it's a good idea to include it as a shorthand in the library? It could be named execa.echo or execa.log or execa.piped...

`reject` option

Currently execa rejects the promise on non-zero exit code. Sometimes though, you want to get the output regardless, like e.g. running ESLint and reading the result regardless of it having linter issues.

Not totally sold on the name. Suggestions welcome.

Does this option make sense?

Error: spawn EACCES

When I run my tests, which have this format:

import test from 'ava';
import execa from 'execa';

test('action', async (t) => {
  const { stdout } = await execa('./cli.js', ['action']);
  t.true(stdout.search('export default ClassName;') > -1);
});

I am getting the Error of Error: spawn EACCES on each test run.

image

I have a feeling it could be something simple I am overlooking, like bad formatting or something, but figured I would ask here in case I have stumbled onto something. Is this error trying to tell me that the module doesn't have access to execa's index.js file from within the node_modules directory? I deleted the node_modules directory and reinstalled everything, but I got the same set of errors.

If you want to see more of the code in context, the module that is experiencing this issue is here.

Thanks in advance for your help.

Getting the pid of a long-running process

I'm using execa to start a process that runs "forever" (a web server). I would like to send a kill signal to the process about 30 seconds after launch, but I don't know how/where to get its process id from execa. Is it possible?

At the moment, execa returns a promise that is only resolved after the process ends, which isn't really useful for indefinitely-running processes. It would be nice if execa returned some additional info along with the promise, like the pid.

execa.shell not running on windows

This code from the readme was tested on windows 7:

execa.shell('echo unicorns').then(result => {
    console.log(result.stdout);
});

and the result was no output at all. No error, unicorns or anything.

I also tested known windows commands like dir, and echo:

execa.shell('dir').then(result => {
    console.log(result.stdout);
});

execa.shell('echo unicorns').then(result => {
    console.log(result.stdout);
});

None of these commands produced any results. Just nothing in the console at all.

However, when I run the execa example, it works:

execa('echo', ['unicorns']).then(result => {
    console.log(result.stdout);
});
//=> unicorns

Am I missing something simple?

Stdio `tap` option

For stderr and stdout the choice between inherit and pipe, has tradeoffs.

Specifically, if you choose inherit, you loose the ability to observe the stream, it's piped straight to the parents stdout/stderr.

I propose we add a tap option. It will be converted internally to the pipe option, but we will grab the pipe, split it into two streams, one stream we pipe directly to stdout/stderr, the other we make available for observation.

Crash when running file without execute permissions

Steps to reproduce:

  1. Create a normal file touch foo.sh
  2. chmod -x foo.sh to be explicit.
  3. npm i execa
  4. Run execa('foo.sh') as below
// index.js
const execa = require('execa')
execa(__dirname + '/foo.sh')
  .then(() => console.log('Ran successfully'))
  .catch(err => console.error('Caught an error', err))

Crashes with error message

/repos/node_modules/execa/index.js:120
        spawned.then = promise.then.bind(promise);
                     ^

TypeError: Cannot set property 'then' of undefined
    at module.exports (/repos/node_modules/execa/index.js:120:15)
    at Object.<anonymous> (/repos/index.js:2:1)
    at Module._compile (module.js:556:32)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.runMain (module.js:590:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)

re-implement errname resolution

#27 dropped execFile in favor of using spawn directly, so we've lost some features.

One is whatever these two lines do:

  1. https://github.com/nodejs/node/blob/31600735f40e02935b936802ff42d66d49e9769b/lib/child_process.js#L8
  2. https://github.com/nodejs/node/blob/31600735f40e02935b936802ff42d66d49e9769b/lib/child_process.js#L204

Can we reliably just do process.binding('uv')? Or is that asking for problems. I know it means importing some native lib, but not much beyond that.

Maybe we look back all the way to Node 0.12 and make sure it was still imported and used the same back then?

Promisify spawn

Is there any reason that execa.spawn isn't promisified as execa?

`preferLocal` fails for globally installed CLI programs

When you have a globally installed binary that uses execa within it, the preferLocal option doesn't work correctly.

Example: foo is a js cli program that depends on a (which has a binary installed to foo's node_modules/.bin/ folder.

npm link # within foo directory
foo doSomethingThatCallsA # within another directory
output: spawn a ENOENT

The reason for this is because npmRunPath uses the CWD by default. This isn't an issue in npm-run-path necessarily though, because execa should provide an option (along with preferLocal to specify the directory to look into.

For example:

// foo.js
const execa = require('execa');

execa('a', ['some', 'options'], {
    preferLocal: true,
    localDir: __dirname 
});

More than happy to PR this, just want to get approval on the localDir option name or to have a better name suggested before I submit a PR ๐Ÿ˜„

Add `.fork()` method

Issuehunt badges

We could just use execa('node', ['becomeTheOne.js']), but that would mean not capitalising on the added optimization that happens when using fork, as mentioned here.

There is no spoon matrix


IssueHunt Summary

[
<
i
m
g

s
r
c

'
h
t
t
p
s
:
/
/
a
v
a
t
a
r
s
3
.
g
i
t
h
u
b
u
s
e
r
c
o
n
t
e
n
t
.
c
o
m
/
u
/
2
5
4
3
5
1
1
?
v

4
'

a
l
t

'
g
m
a
r
t
i
g
n
y
'

w
i
d
t
h

2
4

h
e
i
g
h
t

2
4

g
m
a
r
t
i
g
n
y
]
(
h
t
t
p
s
:
/
/
i
s
s
u
e
h
u
n
t
.
i
o
/
u
/
g
m
a
r
t
i
g
n
y
)

h
a
s

b
e
e
n

r
e
w
a
r
d
e
d
.

Backers (Total: $60.00)

Submitted pull Requests


Tips


IssueHunt has been backed by the following sponsors. Become a sponsor

Ideas?

Are there any annoyances you've had with child processes that could potentially be fixed in execa? Anything that could be done better? I'm open for any kind of suggestions.

Ability to turn off buffering

If you only pipe the output it's a waste of resources to buffer it and the user can also easily hit the maxBuffer.

Either {buffer: false} or detect when .then() is used. I would prefer the latter, but not sure how feasible it is.


This would have prevented sindresorhus/gulp-mocha#169

Better `stdio` option.

I think the stdio array is a pretty poor UI, you have to remember the order of things. I'm constantly pulling up that piece of documentation. Instead, of the three member array, let's just give each one it's own named property:

execa('./cli.js', [], {
  stdin: someReadableStream,
  stdout: 'pipe',
  stderr: 'inherit'
});

Sync methods

execa already cover the async methods and the sync methods could then take advantage of of the npm path thing, and utf8 by default instead of buffer.

Observable support with customizable through filters

var child = execa('./cli.js', [], {stdio: [null, 'observable', null]});

t.plan(5); // should get 5 lines
await child.stdin
  .filter(line => /^s/.test(line)) // only lines that start with "s"
  .forEach(line => t.true(fitsSpecifiedFormat(line)));

Additionally, it would be cool if you could use DuplexStreams to modify what reaches the observable, I'm thinking "newline-separated-json" byte stream converted to a JSON object stream, etc.

There are a number of "split-stream-XXX" modules already on NPM, but they convert to a stream, would be cool to write object-stream-to-observable, and then use that to make CLI output processing awesome.

`execa` and `execa.sync` have return type mismatches.

I think it would be better if the following were basically equivalent:

const val = execa.sync(...args);

const val = await execa(...args);

This would mean a breaking change to execa.sync, using child.spawnSync instead, but I think it's worth it to have a more consistent API.

Kill child when exiting

If I use execa.shell to spawn a long running process, but the original process dies for some reason it would be great if the spawned process gets terminated automatically.

A small example

'use strict'

const execa = require('execa')

execa.shell('ipfs daemon')
  .then((res) => {
    console.log(res.stdout)
  })
  .catch((err) => {
    console.log('failed', err)
  })

console.log('starting')
setTimeout(() => {
  console.log('exiting')
  process.exit()
}, 10 * 1000)

Running this will print exiting after 10s and exit but when I grep for ipfs dameon the process is still running.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.