Skip to content

Commit

Permalink
Remove execa.shell() and execa.shellSync() (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky authored and sindresorhus committed May 8, 2019
1 parent f6db264 commit 06f21c6
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 176 deletions.
89 changes: 5 additions & 84 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ declare namespace execa {
/**
If `true`, runs `command` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows.
We recommend against using this option since it is:
- not cross-platform, encouraging shell-specific syntax.
- slower, because of the additional shell interpretation.
- unsafe, potentially allowing command injection.
@default false
*/
readonly shell?: boolean | string;
Expand Down Expand Up @@ -388,51 +393,6 @@ declare const execa: {
stderr(file: string, options?: execa.Options): Promise<string>;
stderr(file: string, options?: execa.Options<null>): Promise<Buffer>;

/**
Execute a command through the system shell.
Prefer `execa()` whenever possible, as it's both faster and safer.
@param command - The command to execute.
@returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess).
@example
```
import execa from 'execa';
(async => {
// Run a shell command
const {stdout} = await execa.shell('echo unicorns');
//=> 'unicorns'
// Catching an error
try {
await execa.shell('exit 3');
} catch (error) {
console.log(error);
//{
// message: 'Command failed with exit code 3 (ESRCH): exit 3',
// code: 3,
// exitCode: 3,
// exitCodeName: 'ESRCH',
// stdout: '',
// stderr: '',
// all: '',
// failed: true,
// command: 'exit 3',
// timedOut: false,
// killed: false
//}
}
})();
```
*/
shell(command: string, options?: execa.Options): execa.ExecaChildProcess;
shell(
command: string,
options?: execa.Options<null>
): execa.ExecaChildProcess<Buffer>;

/**
Execute a file synchronously.
Expand All @@ -457,45 +417,6 @@ declare const execa: {
file: string,
options?: execa.SyncOptions<null>
): execa.ExecaSyncReturnValue<Buffer>;

/**
Execute a command synchronously through the system shell.
This method throws an `Error` if the command fails.
@param command - The command to execute.
@returns The same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).
@example
```
import execa from 'execa';
try {
execa.shellSync('exit 3');
} catch (error) {
console.log(error);
//{
// message: 'Command failed with exit code 3 (ESRCH): exit 3',
// code: 3,
// exitCode: 3,
// exitCodeName: 'ESRCH',
// stdout: '',
// stderr: '',
// failed: true,
// command: 'exit 3',
// timedOut: false
//}
}
```
*/
shellSync(
command: string,
options?: execa.Options
): execa.ExecaSyncReturnValue;
shellSync(
command: string,
options?: execa.Options<null>
): execa.ExecaSyncReturnValue<Buffer>;
};

export = execa;
8 changes: 0 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@ function handleOutput(options, value) {
return value;
}

function handleShell(fn, command, options) {
return fn(command, {...options, shell: true});
}

function makeAllStream(spawned) {
if (!spawned.stdout && !spawned.stderr) {
return;
Expand Down Expand Up @@ -446,8 +442,6 @@ module.exports.stderr = async (...args) => {
return stderr;
};

module.exports.shell = (command, options) => handleShell(execa, command, options);

module.exports.sync = (command, args, options) => {
const parsed = handleArgs(command, args, options);
const joinedCommand = joinCommand(command, args);
Expand Down Expand Up @@ -483,5 +477,3 @@ module.exports.sync = (command, args, options) => {
timedOut: false
};
};

module.exports.shellSync = (command, options) => handleShell(execa.sync, command, options);
17 changes: 0 additions & 17 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,6 @@ expectType<Buffer>(await execa.stderr('unicorns', {encoding: null}));
expectType<string>(await execa.stderr('unicorns', ['foo'], {encoding: 'utf8'}));
expectType<Buffer>(await execa.stderr('unicorns', ['foo'], {encoding: null}));

expectType<ExecaChildProcess<string>>(execa.shell('unicorns'));
expectType<ExecaReturnValue<string>>(await execa.shell('unicorns'));
expectType<ExecaReturnValue<string>>(
await execa.shell('unicorns', {encoding: 'utf8'})
);
expectType<ExecaReturnValue<Buffer>>(
await execa.shell('unicorns', {encoding: null})
);

expectType<ExecaSyncReturnValue<string>>(execa.sync('unicorns'));
expectType<ExecaSyncReturnValue<string>>(
execa.sync('unicorns', {encoding: 'utf8'})
Expand All @@ -150,11 +141,3 @@ expectType<ExecaSyncReturnValue<string>>(
expectType<ExecaSyncReturnValue<Buffer>>(
execa.sync('unicorns', ['foo'], {encoding: null})
);

expectType<ExecaSyncReturnValue<string>>(execa.shellSync('unicorns'));
expectType<ExecaSyncReturnValue<string>>(
execa.shellSync('unicorns', {encoding: 'utf8'})
);
expectType<ExecaSyncReturnValue<Buffer>>(
execa.shellSync('unicorns', {encoding: null})
);
60 changes: 22 additions & 38 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,25 @@ const execa = require('execa');
execa('echo', ['unicorns']).stdout.pipe(process.stdout);


// Run a shell command
const {stdout} = await execa.shell('echo unicorns');
//=> 'unicorns'

// Catching an error
try {
await execa.shell('exit 3');
await execa('wrong command');
} catch (error) {
console.log(error);
/*
{
message: 'Command failed with exit code 3 (ESRCH): exit 3',
code: 3,
exitCode: 3,
exitCodeName: 'ESRCH',
message: 'spawn wrong command ENOENT',
errno: 'ENOENT',
code: 'ENOENT',
syscall: 'spawn wrong command',
path: 'wrong command',
killed: false,
stdout: '',
stderr: '',
all: '',
failed: true,
command: 'exit 3',
timedOut: false,
killed: false
signal: null,
cmd: 'wrong command',
timedOut: false
}
*/
}
Expand All @@ -93,20 +90,16 @@ const execa = require('execa');

// Catching an error with a sync method
try {
execa.shellSync('exit 3');
execa.sync('wrong command');
} catch (error) {
console.log(error);
/*
{
message: 'Command failed with exit code 3 (ESRCH): exit 3',
code: 3,
exitCode: 3,
exitCodeName: 'ESRCH',
stdout: '',
stderr: '',
failed: true,
command: 'exit 3',
timedOut: false
message: 'spawnSync wrong command ENOENT',
errno: 'ENOENT',
code: 'ENOENT',
syscall: 'spawnSync wrong command',
path: 'wrong command',
}
*/
}
Expand All @@ -128,7 +121,7 @@ Arguments should not be escaped nor quoted. Exception: inside `command`, spaces

Think of this as a mix of `child_process.execFile` and `child_process.spawn`.

As opposed to [`execa.shell()`](#execashellcommand-options), no shell interpreter (Bash, `cmd.exe`, etc.) is used, so shell features such as variables substitution (`echo $PATH`) are not allowed.
Unless the [`shell`](#shell) option is used, no shell interpreter (Bash, `cmd.exe`, etc.) is used, so shell features such as variables substitution (`echo $PATH`) are not allowed.

Returns a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) which is enhanced to be a `Promise`.

Expand All @@ -148,14 +141,6 @@ Same as `execa()`, but returns only `stdout`.

Same as `execa()`, but returns only `stderr`.

### execa.shell(command, [options])

Execute a command through the system shell. Prefer `execa()` whenever possible, as it's faster, safer and more cross-platform.

Returns a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess).

The `child_process` instance is enhanced to also be promise for a result object with `stdout` and `stderr` properties.

### execa.sync(file, [arguments], [options])
### execa.sync(command, [options])

Expand All @@ -167,12 +152,6 @@ It does not have the `.all` property that `execa()` has because the [underlying

This method throws an `Error` if the command fails.

### execa.shellSync(command, [options])

Execute a command synchronously through the system shell.

Returns the same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).

### options

Type: `object`
Expand Down Expand Up @@ -236,6 +215,11 @@ Default: `false`

If `true`, runs `command` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows.

We recommend against using this option since it is:
- not cross-platform, encouraging shell-specific syntax.
- slower, because of the additional shell interpretation.
- unsafe, potentially allowing command injection.

#### stripFinalNewline

Type: `boolean`<br>
Expand Down
41 changes: 12 additions & 29 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,6 @@ test('trim string arguments', async t => {
t.is(stdout, 'foo\nbar');
});

test('execa.shell()', async t => {
const {stdout} = await execa.shell('node fixtures/noop foo');
t.is(stdout, 'foo');
});

test('execa.sync()', t => {
const {stdout} = execa.sync('noop', ['foo']);
t.is(stdout, 'foo');
Expand All @@ -165,23 +160,6 @@ test('skip throwing when using reject option in execa.sync()', t => {
t.is(typeof error.stderr, 'string');
});

test('execa.shellSync()', t => {
const {stdout} = execa.shellSync('node fixtures/noop foo');
t.is(stdout, 'foo');
});

test('execa.shellSync() includes stdout and stderr in errors for improved debugging', t => {
t.throws(() => {
execa.shellSync('node fixtures/error-message.js');
}, {message: STDERR_STDOUT_REGEXP, code: 1});
});

test('skip throwing when using reject option in execa.shellSync()', t => {
const error = execa.shellSync('node fixtures/error-message.js', {reject: false});
t.is(typeof error.stdout, 'string');
t.is(typeof error.stderr, 'string');
});

test('stripEof option (legacy)', async t => {
const {stdout} = await execa('noop', ['foo'], {stripEof: false});
t.is(stdout, 'foo\n');
Expand Down Expand Up @@ -512,13 +490,6 @@ test('cleanup false - SIGTERM', spawnAndKill, 'SIGTERM', 'false', exitIfWindows)
test('cleanup true - SIGKILL', spawnAndKill, 'SIGKILL', 'true', exitIfWindows);
test('cleanup false - SIGKILL', spawnAndKill, 'SIGKILL', 'false', exitIfWindows);

test('execa.shell() supports the `shell` option', async t => {
const {stdout} = await execa.shell('node fixtures/noop foo', {
shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/bash'
});
t.is(stdout, 'foo');
});

if (process.platform !== 'win32') {
test('write to fast-exit process', async t => {
// Try-catch here is necessary, because this test is not 100% accurate
Expand Down Expand Up @@ -559,6 +530,18 @@ test('do not extend environment with `extendEnv: false`', async t => {
]);
});

test('can use `options.shell: true`', async t => {
const {stdout} = await execa('node fixtures/noop foo', {shell: true});
t.is(stdout, 'foo');
});

test('can use `options.shell: string`', async t => {
const {stdout} = await execa('node fixtures/noop foo', {
shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/bash'
});
t.is(stdout, 'foo');
});

test('use extend environment with `extendEnv: true` and `shell: true`', async t => {
process.env.TEST = 'test';
const command = process.platform === 'win32' ? 'echo %TEST%' : 'echo $TEST';
Expand Down

0 comments on commit 06f21c6

Please sign in to comment.