diff --git a/index.d.ts b/index.d.ts index 5910390391..576e7cffb9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -27,7 +27,7 @@ export type CommonOptions = { If you `$ npm install foo`, you can then `execa('foo')`. - @default `true` with `$`/`$.sync`, `false` otherwise + @default `true` with `$`, `false` otherwise */ readonly preferLocal?: boolean; @@ -63,7 +63,7 @@ export type CommonOptions = { /** Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio). - @default 'pipe' + @default `inherit` with `$`, `pipe` otherwise */ readonly stdin?: StdioOption; diff --git a/index.js b/index.js index 963c96dc78..fa417620f3 100644 --- a/index.js +++ b/index.js @@ -232,6 +232,16 @@ export function execaSync(file, args, options) { }; } +const normalizeScriptStdin = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined + ? {stdin: 'inherit'} + : {}; + +const normalizeScriptOptions = (options = {}) => ({ + preferLocal: true, + ...normalizeScriptStdin(options), + ...options, +}); + function create$(options) { function $(templatesOrOptions, ...expressions) { if (!Array.isArray(templatesOrOptions)) { @@ -239,7 +249,7 @@ function create$(options) { } const [file, ...args] = parseTemplates(templatesOrOptions, expressions); - return execa(file, args, options); + return execa(file, args, normalizeScriptOptions(options)); } $.sync = (templates, ...expressions) => { @@ -248,13 +258,13 @@ function create$(options) { } const [file, ...args] = parseTemplates(templates, expressions); - return execaSync(file, args, options); + return execaSync(file, args, normalizeScriptOptions(options)); }; return $; } -export const $ = create$({preferLocal: true}); +export const $ = create$(); export function execaCommand(command, options) { const [file, ...args] = parseCommand(command); diff --git a/readme.md b/readme.md index 4b756c64e9..6b1f43eb7d 100644 --- a/readme.md +++ b/readme.md @@ -467,7 +467,7 @@ Kill the spawned process when the parent process exits unless either: #### preferLocal Type: `boolean`\ -Default: `true` with [`$`](#command)/[`$.sync`](#synccommand), `false` otherwise +Default: `true` with [`$`](#command), `false` otherwise Prefer locally installed binaries when looking for a binary to execute.\ If you `$ npm install foo`, you can then `execa('foo')`. @@ -521,7 +521,7 @@ If the input is not a file, use the [`input` option](#input) instead. #### stdin Type: `string | number | Stream | undefined`\ -Default: `pipe` +Default: `inherit` with [`$`](#command), `pipe` otherwise Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio). diff --git a/test/command.js b/test/command.js index affcc58dbe..787b35884c 100644 --- a/test/command.js +++ b/test/command.js @@ -1,5 +1,6 @@ import {inspect} from 'node:util'; import test from 'ava'; +import {isStream} from 'is-stream'; import {execa, execaSync, execaCommand, execaCommandSync, $} from '../index.js'; import {setFixtureDir} from './helpers/fixtures-dir.js'; @@ -273,3 +274,17 @@ test('[ $`noop.js` ]', invalidExpression, [$`noop.js`], 'Unexpected "object" in test('$({stdio: \'inherit\'}).sync`noop.js`', invalidExpression, $({stdio: 'inherit'}).sync`noop.js`, 'Unexpected "undefined" stdout in template expression'); test('[ $({stdio: \'inherit\'}).sync`noop.js` ]', invalidExpression, [$({stdio: 'inherit'}).sync`noop.js`], 'Unexpected "undefined" stdout in template expression'); + +test('$ stdin defaults to "inherit"', async t => { + const {stdout} = await $({input: 'foo'})`stdin-script.js`; + t.is(stdout, 'foo'); +}); + +test('$.sync stdin defaults to "inherit"', t => { + const {stdout} = $({input: 'foo'}).sync`stdin-script.js`; + t.is(stdout, 'foo'); +}); + +test('$ stdin has no default value when stdio is set', t => { + t.true(isStream($({stdio: 'pipe'})`noop.js`.stdin)); +}); diff --git a/test/fixtures/stdin-script.js b/test/fixtures/stdin-script.js new file mode 100755 index 0000000000..ca3047ef62 --- /dev/null +++ b/test/fixtures/stdin-script.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +import {$} from '../../index.js'; +import {FIXTURES_DIR} from '../helpers/fixtures-dir.js'; + +await $({stdout: 'inherit'})`node ${`${FIXTURES_DIR}/stdin.js`}`; diff --git a/test/helpers/fixtures-dir.js b/test/helpers/fixtures-dir.js index 15a978b445..c57362783e 100644 --- a/test/helpers/fixtures-dir.js +++ b/test/helpers/fixtures-dir.js @@ -4,7 +4,7 @@ import {fileURLToPath} from 'node:url'; import pathKey from 'path-key'; export const PATH_KEY = pathKey(); -const FIXTURES_DIR = fileURLToPath(new URL('../fixtures', import.meta.url)); +export const FIXTURES_DIR = fileURLToPath(new URL('../fixtures', import.meta.url)); // Add the fixtures directory to PATH so fixtures can be executed without adding // `node`. This is only meant to make writing tests simpler. diff --git a/test/stream.js b/test/stream.js index 6ce70be992..44ad0919e6 100644 --- a/test/stream.js +++ b/test/stream.js @@ -6,7 +6,7 @@ import test from 'ava'; import getStream from 'get-stream'; import {pEvent} from 'p-event'; import tempfile from 'tempfile'; -import {execa, execaSync} from '../index.js'; +import {execa, execaSync, $} from '../index.js'; import {setFixtureDir} from './helpers/fixtures-dir.js'; setFixtureDir(); @@ -71,6 +71,11 @@ test('input can be a Stream', async t => { t.is(stdout, 'howdy'); }); +test('input option can be used with $', async t => { + const {stdout} = await $({input: 'foobar'})`stdin.js`; + t.is(stdout, 'foobar'); +}); + test('inputFile can be set', async t => { const inputFile = tempfile(); fs.writeFileSync(inputFile, 'howdy'); @@ -78,6 +83,13 @@ test('inputFile can be set', async t => { t.is(stdout, 'howdy'); }); +test('inputFile can be set with $', async t => { + const inputFile = tempfile(); + fs.writeFileSync(inputFile, 'howdy'); + const {stdout} = await $({inputFile})`stdin.js`; + t.is(stdout, 'howdy'); +}); + test('inputFile and input cannot be both set', t => { t.throws(() => execa('stdin.js', {inputFile: '', input: ''}), { message: /cannot be both set/, @@ -96,6 +108,11 @@ test('input option can be a String - sync', t => { t.is(stdout, 'foobar'); }); +test('input option can be used with $.sync', t => { + const {stdout} = $({input: 'foobar'}).sync`stdin.js`; + t.is(stdout, 'foobar'); +}); + test('input option can be a Buffer - sync', t => { const {stdout} = execaSync('stdin.js', {input: Buffer.from('testing12', 'utf8')}); t.is(stdout, 'testing12'); @@ -125,6 +142,13 @@ test('inputFile can be set - sync', t => { t.is(stdout, 'howdy'); }); +test('inputFile option can be used with $.sync', t => { + const inputFile = tempfile(); + fs.writeFileSync(inputFile, 'howdy'); + const {stdout} = $({inputFile}).sync`stdin.js`; + t.is(stdout, 'howdy'); +}); + test('inputFile and input cannot be both set - sync', t => { t.throws(() => execaSync('stdin.js', {inputFile: '', input: ''}), { message: /cannot be both set/, diff --git a/test/test.js b/test/test.js index 14bcabebee..44dfe61893 100644 --- a/test/test.js +++ b/test/test.js @@ -100,6 +100,10 @@ test('preferLocal: undefined with $', async t => { await t.notThrowsAsync($({env: getPathWithoutLocalDir()})`ava --version`); }); +test('preferLocal: undefined with $.sync', t => { + t.notThrows(() => $({env: getPathWithoutLocalDir()}).sync`ava --version`); +}); + test('localDir option', async t => { const command = process.platform === 'win32' ? 'echo %PATH%' : 'echo $PATH'; const {stdout} = await execa(command, {shell: true, preferLocal: true, localDir: '/test'});