Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove execa.shell() and execa.shellSync() #219

Merged
merged 8 commits into from May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 5 additions & 84 deletions index.d.ts
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
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
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
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.
ehmicky marked this conversation as resolved.
Show resolved Hide resolved

#### stripFinalNewline

Type: `boolean`<br>
Expand Down
41 changes: 12 additions & 29 deletions test.js
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