├── .editorconfig ├── .gitattributes ├── .github ├── codecov.yml ├── security.md └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── docs ├── api.md ├── bash.md ├── binary.md ├── debugging.md ├── environment.md ├── errors.md ├── escaping.md ├── execution.md ├── input.md ├── ipc.md ├── lines.md ├── node.md ├── output.md ├── pipe.md ├── scripts.md ├── shell.md ├── small.md ├── streams.md ├── termination.md ├── transform.md ├── typescript.md └── windows.md ├── index.d.ts ├── index.js ├── lib ├── arguments │ ├── command.js │ ├── cwd.js │ ├── encoding-option.js │ ├── escape.js │ ├── fd-options.js │ ├── file-url.js │ ├── options.js │ ├── shell.js │ └── specific.js ├── convert │ ├── add.js │ ├── concurrent.js │ ├── duplex.js │ ├── iterable.js │ ├── readable.js │ ├── shared.js │ └── writable.js ├── io │ ├── contents.js │ ├── input-sync.js │ ├── iterate.js │ ├── max-buffer.js │ ├── output-async.js │ ├── output-sync.js │ ├── pipeline.js │ └── strip-newline.js ├── ipc │ ├── array.js │ ├── buffer-messages.js │ ├── forward.js │ ├── get-each.js │ ├── get-one.js │ ├── graceful.js │ ├── incoming.js │ ├── ipc-input.js │ ├── methods.js │ ├── outgoing.js │ ├── reference.js │ ├── send.js │ ├── strict.js │ └── validation.js ├── methods │ ├── bind.js │ ├── command.js │ ├── create.js │ ├── main-async.js │ ├── main-sync.js │ ├── node.js │ ├── parameters.js │ ├── promise.js │ ├── script.js │ └── template.js ├── pipe │ ├── abort.js │ ├── pipe-arguments.js │ ├── sequence.js │ ├── setup.js │ ├── streaming.js │ └── throw.js ├── resolve │ ├── all-async.js │ ├── all-sync.js │ ├── exit-async.js │ ├── exit-sync.js │ ├── stdio.js │ ├── wait-stream.js │ └── wait-subprocess.js ├── return │ ├── duration.js │ ├── early-error.js │ ├── final-error.js │ ├── message.js │ ├── reject.js │ └── result.js ├── stdio │ ├── direction.js │ ├── duplicate.js │ ├── handle-async.js │ ├── handle-sync.js │ ├── handle.js │ ├── input-option.js │ ├── native.js │ ├── stdio-option.js │ └── type.js ├── terminate │ ├── cancel.js │ ├── cleanup.js │ ├── graceful.js │ ├── kill.js │ ├── signal.js │ └── timeout.js ├── transform │ ├── encoding-transform.js │ ├── generator.js │ ├── normalize.js │ ├── object-mode.js │ ├── run-async.js │ ├── run-sync.js │ ├── split.js │ └── validate.js ├── utils │ ├── abort-signal.js │ ├── deferred.js │ ├── max-listeners.js │ ├── standard-stream.js │ └── uint-array.js └── verbose │ ├── complete.js │ ├── custom.js │ ├── default.js │ ├── error.js │ ├── info.js │ ├── ipc.js │ ├── log.js │ ├── output.js │ ├── start.js │ └── values.js ├── license ├── media ├── logo.png ├── logo.sketch ├── logo.svg ├── logo@2x.png ├── logo_dark.svg └── verbose.png ├── package.json ├── readme.md ├── test-d ├── arguments │ ├── encoding-option.test-d.ts │ ├── env.test-d.ts │ ├── options.test-d.ts │ └── specific.test-d.ts ├── convert │ ├── duplex.test-d.ts │ ├── iterable.test-d.ts │ ├── readable.test-d.ts │ └── writable.test-d.ts ├── ipc │ ├── get-each.test-d.ts │ ├── get-one.test-d.ts │ ├── graceful.ts │ ├── message.test-d.ts │ └── send.test-d.ts ├── methods │ ├── command.test-d.ts │ ├── list.test-d.ts │ ├── main-async.test-d.ts │ ├── main-sync.test-d.ts │ ├── node.test-d.ts │ ├── script-s.test-d.ts │ ├── script-sync.test-d.ts │ ├── script.test-d.ts │ └── template.test-d.ts ├── pipe.test-d.ts ├── return │ ├── ignore-option.test-d.ts │ ├── ignore-other.test-d.ts │ ├── lines-main.test-d.ts │ ├── lines-specific.test-d.ts │ ├── no-buffer-main.test-d.ts │ ├── no-buffer-specific.test-d.ts │ ├── result-all.test-d.ts │ ├── result-ipc.ts │ ├── result-main.test-d.ts │ ├── result-reject.test-d.ts │ └── result-stdio.test-d.ts ├── stdio │ ├── array.test-d.ts │ ├── direction.test-d.ts │ └── option │ │ ├── array-binary.test-d.ts │ │ ├── array-object.test-d.ts │ │ ├── array-string.test-d.ts │ │ ├── duplex-invalid.test-d.ts │ │ ├── duplex-object.test-d.ts │ │ ├── duplex-transform.test-d.ts │ │ ├── duplex.test-d.ts │ │ ├── fd-integer-0.test-d.ts │ │ ├── fd-integer-1.test-d.ts │ │ ├── fd-integer-2.test-d.ts │ │ ├── fd-integer-3.test-d.ts │ │ ├── file-append-invalid.test-d.ts │ │ ├── file-append.test-d.ts │ │ ├── file-object-invalid.test-d.ts │ │ ├── file-object.test-d.ts │ │ ├── file-url.test-d.ts │ │ ├── final-async-full.test-d.ts │ │ ├── final-invalid-full.test-d.ts │ │ ├── final-object-full.test-d.ts │ │ ├── final-unknown-full.test-d.ts │ │ ├── generator-async-full.test-d.ts │ │ ├── generator-async.test-d.ts │ │ ├── generator-binary-invalid.test-d.ts │ │ ├── generator-binary.test-d.ts │ │ ├── generator-boolean-full.test-d.ts │ │ ├── generator-boolean.test-d.ts │ │ ├── generator-empty.test-d.ts │ │ ├── generator-invalid-full.test-d.ts │ │ ├── generator-invalid.test-d.ts │ │ ├── generator-object-full.test-d.ts │ │ ├── generator-object-mode-invalid.test-d.ts │ │ ├── generator-object-mode.test-d.ts │ │ ├── generator-object.test-d.ts │ │ ├── generator-only-binary.test-d.ts │ │ ├── generator-only-final.test-d.ts │ │ ├── generator-only-object-mode.test-d.ts │ │ ├── generator-only-preserve.test-d.ts │ │ ├── generator-preserve-invalid.test-d.ts │ │ ├── generator-preserve.test-d.ts │ │ ├── generator-string-full.test-d.ts │ │ ├── generator-string.test-d.ts │ │ ├── generator-unknown-full.test-d.ts │ │ ├── generator-unknown.test-d.ts │ │ ├── ignore.test-d.ts │ │ ├── inherit.test-d.ts │ │ ├── ipc.test-d.ts │ │ ├── iterable-async-binary.test-d.ts │ │ ├── iterable-async-object.test-d.ts │ │ ├── iterable-async-string.test-d.ts │ │ ├── iterable-binary.test-d.ts │ │ ├── iterable-object.test-d.ts │ │ ├── iterable-string.test-d.ts │ │ ├── null.test-d.ts │ │ ├── overlapped.test-d.ts │ │ ├── pipe-inherit.test-d.ts │ │ ├── pipe-undefined.test-d.ts │ │ ├── pipe-wide.test-d.ts │ │ ├── pipe.test-d.ts │ │ ├── process-stderr.test-d.ts │ │ ├── process-stdin.test-d.ts │ │ ├── process-stdout.test-d.ts │ │ ├── readable-stream.test-d.ts │ │ ├── readable.test-d.ts │ │ ├── uint-array.test-d.ts │ │ ├── undefined.test-d.ts │ │ ├── unknown.test-d.ts │ │ ├── web-transform-instance.test-d.ts │ │ ├── web-transform-invalid.test-d.ts │ │ ├── web-transform-object.test-d.ts │ │ ├── web-transform.test-d.ts │ │ ├── writable-stream.test-d.ts │ │ └── writable.test-d.ts ├── subprocess │ ├── all.test-d.ts │ ├── stdio.test-d.ts │ └── subprocess.test-d.ts ├── transform │ └── object-mode.test-d.ts └── verbose.test-d.ts ├── test ├── arguments │ ├── cwd.js │ ├── encoding-option.js │ ├── env.js │ ├── escape-no-icu.js │ ├── escape.js │ ├── fd-options.js │ ├── local.js │ ├── shell.js │ └── specific.js ├── convert │ ├── concurrent.js │ ├── duplex.js │ ├── iterable.js │ ├── readable.js │ ├── shared.js │ └── writable.js ├── fixtures │ ├── all-fail.js │ ├── all.js │ ├── command with space.js │ ├── delay.js │ ├── detach.js │ ├── echo-fail.js │ ├── echo.js │ ├── empty.js │ ├── environment.js │ ├── exit.js │ ├── fail.js │ ├── fast-exit-darwin │ ├── fast-exit-linux │ ├── forever.js │ ├── graceful-disconnect.js │ ├── graceful-echo.js │ ├── graceful-listener.js │ ├── graceful-none.js │ ├── graceful-print.js │ ├── graceful-ref.js │ ├── graceful-send-echo.js │ ├── graceful-send-fast.js │ ├── graceful-send-print.js │ ├── graceful-send-string.js │ ├── graceful-send-twice.js │ ├── graceful-send.js │ ├── graceful-twice.js │ ├── graceful-wait.js │ ├── hello.cmd │ ├── hello.sh │ ├── ipc-any.js │ ├── ipc-disconnect-get.js │ ├── ipc-disconnect.js │ ├── ipc-echo-fail.js │ ├── ipc-echo-filter.js │ ├── ipc-echo-item.js │ ├── ipc-echo-twice-fail.js │ ├── ipc-echo-twice-wait.js │ ├── ipc-echo-twice.js │ ├── ipc-echo-wait.js │ ├── ipc-echo.js │ ├── ipc-get-filter-throw.js │ ├── ipc-get-io-error.js │ ├── ipc-get-ref.js │ ├── ipc-get-send-get.js │ ├── ipc-get-unref.js │ ├── ipc-get.js │ ├── ipc-iterate-back-serial.js │ ├── ipc-iterate-back.js │ ├── ipc-iterate-break.js │ ├── ipc-iterate-error.js │ ├── ipc-iterate-io-error.js │ ├── ipc-iterate-print.js │ ├── ipc-iterate-ref.js │ ├── ipc-iterate-send.js │ ├── ipc-iterate-throw.js │ ├── ipc-iterate-twice.js │ ├── ipc-iterate-unref.js │ ├── ipc-iterate.js │ ├── ipc-once-disconnect-get.js │ ├── ipc-once-disconnect-send.js │ ├── ipc-once-disconnect.js │ ├── ipc-once-message-get.js │ ├── ipc-once-message-send.js │ ├── ipc-once-message.js │ ├── ipc-print-many-each.js │ ├── ipc-print-many.js │ ├── ipc-process-error.js │ ├── ipc-process-send-get.js │ ├── ipc-process-send-send.js │ ├── ipc-process-send.js │ ├── ipc-replay.js │ ├── ipc-send-argv.js │ ├── ipc-send-disconnect.js │ ├── ipc-send-echo-strict.js │ ├── ipc-send-echo-wait.js │ ├── ipc-send-error.js │ ├── ipc-send-fail.js │ ├── ipc-send-forever.js │ ├── ipc-send-get.js │ ├── ipc-send-io-error.js │ ├── ipc-send-json.js │ ├── ipc-send-many.js │ ├── ipc-send-native.js │ ├── ipc-send-pid.js │ ├── ipc-send-print.js │ ├── ipc-send-repeat.js │ ├── ipc-send-strict-catch.js │ ├── ipc-send-strict-get.js │ ├── ipc-send-strict-listen.js │ ├── ipc-send-strict.js │ ├── ipc-send-twice.js │ ├── ipc-send-wait-print.js │ ├── ipc-send.js │ ├── max-buffer.js │ ├── nested-double.js │ ├── nested-fail.js │ ├── nested-inherit.js │ ├── nested-multiple-stdin.js │ ├── nested-multiple-stdio-output.js │ ├── nested-node.js │ ├── nested-pipe-file.js │ ├── nested-pipe-script.js │ ├── nested-pipe-stream.js │ ├── nested-pipe-subprocess.js │ ├── nested-pipe-subprocesses.js │ ├── nested-pipe-verbose.js │ ├── nested-stdio.js │ ├── nested-sync-tty.js │ ├── nested-write.js │ ├── nested.js │ ├── nested │ │ ├── custom-event.js │ │ ├── custom-json.js │ │ ├── custom-object-stdout.js │ │ ├── custom-option.js │ │ ├── custom-print-function.js │ │ ├── custom-print-multiple.js │ │ ├── custom-print.js │ │ ├── custom-result.js │ │ ├── custom-return.js │ │ ├── custom-throw.js │ │ ├── custom-uppercase.js │ │ ├── file-url.js │ │ ├── generator-big-array.js │ │ ├── generator-duplex.js │ │ ├── generator-object.js │ │ ├── generator-string-object.js │ │ ├── generator-uppercase.js │ │ ├── writable-web.js │ │ └── writable.js │ ├── no-await.js │ ├── no-killable.js │ ├── non-executable.js │ ├── noop-132.js │ ├── noop-both-fail-strict.js │ ├── noop-both-fail.js │ ├── noop-both.js │ ├── noop-continuous.js │ ├── noop-delay.js │ ├── noop-fail.js │ ├── noop-fd-ipc.js │ ├── noop-fd.js │ ├── noop-forever.js │ ├── noop-progressive.js │ ├── noop-repeat.js │ ├── noop-stdin-double.js │ ├── noop-stdin-fail.js │ ├── noop-stdin-fd.js │ ├── noop-verbose.js │ ├── noop.js │ ├── stdin-both.js │ ├── stdin-fail.js │ ├── stdin-fd-both.js │ ├── stdin-fd.js │ ├── stdin-script.js │ ├── stdin-twice-both.js │ ├── stdin.js │ ├── verbose-script.js │ ├── wait-fail.js │ └── worker.js ├── helpers │ ├── convert.js │ ├── duplex.js │ ├── early-error.js │ ├── encoding.js │ ├── file-path.js │ ├── fixtures-directory.js │ ├── fs.js │ ├── generator.js │ ├── graceful.js │ ├── input.js │ ├── ipc.js │ ├── lines.js │ ├── listeners.js │ ├── map.js │ ├── max-buffer.js │ ├── nested.js │ ├── node-version.js │ ├── override-promise.js │ ├── parallel.js │ ├── pipe.js │ ├── run.js │ ├── stdio.js │ ├── stream.js │ ├── verbose.js │ ├── wait.js │ └── web-transform.js ├── io │ ├── input-option.js │ ├── input-sync.js │ ├── iterate.js │ ├── max-buffer.js │ ├── output-async.js │ ├── output-sync.js │ ├── pipeline.js │ └── strip-newline.js ├── ipc │ ├── buffer-messages.js │ ├── forward.js │ ├── get-each.js │ ├── get-one.js │ ├── graceful.js │ ├── incoming.js │ ├── ipc-input.js │ ├── outgoing.js │ ├── pending.js │ ├── reference.js │ ├── send.js │ ├── strict.js │ └── validation.js ├── methods │ ├── bind.js │ ├── command.js │ ├── create.js │ ├── main-async.js │ ├── node.js │ ├── override-promise.js │ ├── parameters-args.js │ ├── parameters-command.js │ ├── parameters-options.js │ ├── promise.js │ ├── script.js │ └── template.js ├── pipe │ ├── abort.js │ ├── pipe-arguments.js │ ├── sequence.js │ ├── setup.js │ ├── streaming.js │ └── throw.js ├── resolve │ ├── all.js │ ├── buffer-end.js │ ├── exit.js │ ├── no-buffer.js │ ├── stdio.js │ ├── wait-abort.js │ ├── wait-epipe.js │ ├── wait-error.js │ └── wait-subprocess.js ├── return │ ├── duration.js │ ├── early-error.js │ ├── final-error.js │ ├── message.js │ ├── output.js │ ├── reject.js │ └── result.js ├── stdio │ ├── direction.js │ ├── duplex.js │ ├── duplicate.js │ ├── file-descriptor.js │ ├── file-path-error.js │ ├── file-path-main.js │ ├── file-path-mixed.js │ ├── handle-invalid.js │ ├── handle-normal.js │ ├── handle-options.js │ ├── iterable.js │ ├── lines-main.js │ ├── lines-max-buffer.js │ ├── lines-mixed.js │ ├── lines-noop.js │ ├── native-fd.js │ ├── native-inherit-pipe.js │ ├── native-redirect.js │ ├── node-stream-custom.js │ ├── node-stream-native.js │ ├── stdio-option.js │ ├── type-invalid.js │ ├── type-undefined.js │ ├── typed-array.js │ ├── web-stream.js │ └── web-transform.js ├── terminate │ ├── cancel.js │ ├── cleanup.js │ ├── graceful.js │ ├── kill-error.js │ ├── kill-force.js │ ├── kill-signal.js │ ├── signal.js │ └── timeout.js ├── transform │ ├── encoding-final.js │ ├── encoding-ignored.js │ ├── encoding-multibyte.js │ ├── encoding-transform.js │ ├── generator-all.js │ ├── generator-error.js │ ├── generator-final.js │ ├── generator-input.js │ ├── generator-main.js │ ├── generator-mixed.js │ ├── generator-output.js │ ├── generator-return.js │ ├── normalize-transform.js │ ├── split-binary.js │ ├── split-lines.js │ ├── split-newline.js │ ├── split-transform.js │ └── validate.js └── verbose │ ├── complete.js │ ├── custom-command.js │ ├── custom-common.js │ ├── custom-complete.js │ ├── custom-error.js │ ├── custom-event.js │ ├── custom-id.js │ ├── custom-ipc.js │ ├── custom-options.js │ ├── custom-output.js │ ├── custom-reject.js │ ├── custom-result.js │ ├── custom-start.js │ ├── custom-throw.js │ ├── error.js │ ├── info.js │ ├── ipc.js │ ├── log.js │ ├── output-buffer.js │ ├── output-enable.js │ ├── output-mixed.js │ ├── output-noop.js │ ├── output-pipe.js │ ├── output-progressive.js │ └── start.js ├── tsconfig.json └── types ├── arguments ├── encoding-option.d.ts ├── fd-options.d.ts ├── options.d.ts └── specific.d.ts ├── convert.d.ts ├── ipc.d.ts ├── methods ├── command.d.ts ├── main-async.d.ts ├── main-sync.d.ts ├── node.d.ts ├── script.d.ts └── template.d.ts ├── pipe.d.ts ├── return ├── final-error.d.ts ├── ignore.d.ts ├── result-all.d.ts ├── result-ipc.d.ts ├── result-stdio.d.ts ├── result-stdout.d.ts └── result.d.ts ├── stdio ├── array.d.ts ├── direction.d.ts ├── option.d.ts └── type.d.ts ├── subprocess ├── all.d.ts ├── stdio.d.ts ├── stdout.d.ts └── subprocess.d.ts ├── transform ├── normalize.d.ts └── object-mode.d.ts ├── utils.d.ts └── verbose.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | after_n_builds: 6 4 | -------------------------------------------------------------------------------- /.github/security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }}-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 24 14 | - 18 15 | os: 16 | - ubuntu 17 | - macos 18 | - windows 19 | steps: 20 | - uses: actions/cache@v4 21 | with: 22 | path: .lycheecache 23 | key: cache-lychee-${{ github.sha }} 24 | restore-keys: cache-lychee- 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - run: npm install 30 | - uses: lycheeverse/lychee-action@v1 31 | with: 32 | args: --cache --verbose --no-progress --include-fragments --exclude packagephobia --exclude /pull/ --exclude linkedin --exclude stackoverflow --exclude stackexchange --exclude github.com/nodejs/node --exclude file:///test --exclude invalid.com '*.md' 'docs/*.md' '.github/**/*.md' '*.json' '*.js' 'lib/**/*.js' 'test/**/*.js' '*.ts' 'test-d/**/*.ts' 33 | fail: true 34 | if: ${{ matrix.os == 'ubuntu' && matrix.node-version == 24 }} 35 | - run: npm run lint 36 | - run: npm run type 37 | - run: npm run unit 38 | - uses: codecov/codecov-action@v4 39 | with: 40 | token: ${{ secrets.CODECOV_TOKEN }} 41 | flags: '${{ matrix.os }}, node-${{ matrix.node-version }}' 42 | fail_ci_if_error: false 43 | verbose: true 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | .nyc_output 4 | coverage 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /docs/node.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | execa logo 4 | 5 |
6 | 7 | # 🐢 Node.js files 8 | 9 | ## Run Node.js files 10 | 11 | ```js 12 | import {execaNode, execa} from 'execa'; 13 | 14 | await execaNode`file.js argument`; 15 | // Is the same as: 16 | await execa({node: true})`file.js argument`; 17 | // Or: 18 | await execa`node file.js argument`; 19 | ``` 20 | 21 | ## Node.js CLI flags 22 | 23 | When using the [`node`](api.md#optionsnode) option or [`execaNode()`](api.md#execanodescriptpath-arguments-options), the current Node.js [CLI flags](https://nodejs.org/api/cli.html#options) are inherited. For example, the subprocess will use [`--allow-fs-read`](https://nodejs.org/api/cli.html#--allow-fs-read) if the current process does. 24 | 25 | The [`nodeOptions`](api.md#optionsnodeoptions) option can be used to set different CLI flags. 26 | 27 | ```js 28 | await execaNode({nodeOptions: ['--allow-fs-write']})`file.js argument`; 29 | ``` 30 | 31 | ## Node.js version 32 | 33 | The same applies to the Node.js version, which is inherited too. 34 | 35 | [`get-node`](https://github.com/ehmicky/get-node) and the [`nodePath`](api.md#optionsnodepath) option can be used to run a specific Node.js version. Alternatively, [`nvexeca`](https://github.com/ehmicky/nvexeca) or [`nve`](https://github.com/ehmicky/nve) can be used. 36 | 37 | ```js 38 | import {execaNode} from 'execa'; 39 | import getNode from 'get-node'; 40 | 41 | const {path: nodePath} = await getNode('16.2.0'); 42 | await execaNode({nodePath})`file.js argument`; 43 | ``` 44 | 45 |
46 | 47 | [**Next**: 🌐 Environment](environment.md)\ 48 | [**Previous**: 📜 Scripts](scripts.md)\ 49 | [**Top**: Table of contents](../readme.md#documentation) 50 | -------------------------------------------------------------------------------- /docs/shell.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | execa logo 4 | 5 |
6 | 7 | # 💻 Shell 8 | 9 | ## Avoiding shells 10 | 11 | In general, [shells](https://en.wikipedia.org/wiki/Shell_(computing)) should be avoided because they are: 12 | - Not cross-platform, encouraging shell-specific syntax. 13 | - Slower, because of the additional shell interpretation. 14 | - Unsafe, potentially allowing [command injection](https://en.wikipedia.org/wiki/Code_injection#Shell_injection) (see the [escaping section](escaping.md#shells)). 15 | 16 | In almost all cases, plain JavaScript is a better alternative to shells. The [following page](bash.md) shows how to convert Bash into JavaScript. 17 | 18 | ## Specific shell 19 | 20 | ```js 21 | import {execa} from 'execa'; 22 | 23 | await execa({shell: '/bin/bash'})`npm run "$TASK" && npm run test`; 24 | ``` 25 | 26 | ## OS-specific shell 27 | 28 | When the [`shell`](api.md#optionsshell) option is `true`, `sh` is used on Unix and [`cmd.exe`](https://en.wikipedia.org/wiki/Cmd.exe) is used on Windows. 29 | 30 | `sh` and `cmd.exe` syntaxes are very different. Therefore, this is usually not useful. 31 | 32 | ```js 33 | await execa({shell: true})`npm run build`; 34 | ``` 35 | 36 |
37 | 38 | [**Next**: 📜 Scripts](scripts.md)\ 39 | [**Previous**: 💬 Escaping/quoting](escaping.md)\ 40 | [**Top**: Table of contents](../readme.md#documentation) 41 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | StdinOption, 3 | StdinSyncOption, 4 | StdoutStderrOption, 5 | StdoutStderrSyncOption, 6 | } from './types/stdio/type.js'; 7 | export type {Options, SyncOptions} from './types/arguments/options.js'; 8 | export type {TemplateExpression} from './types/methods/template.js'; 9 | 10 | export type {Result, SyncResult} from './types/return/result.js'; 11 | export type {ResultPromise, Subprocess} from './types/subprocess/subprocess.js'; 12 | export {ExecaError, ExecaSyncError} from './types/return/final-error.js'; 13 | 14 | export {execa, type ExecaMethod} from './types/methods/main-async.js'; 15 | export {execaSync, type ExecaSyncMethod} from './types/methods/main-sync.js'; 16 | export {execaCommand, execaCommandSync, parseCommandString} from './types/methods/command.js'; 17 | export {$, type ExecaScriptMethod, type ExecaScriptSyncMethod} from './types/methods/script.js'; 18 | export {execaNode, type ExecaNodeMethod} from './types/methods/node.js'; 19 | 20 | export { 21 | sendMessage, 22 | getOneMessage, 23 | getEachMessage, 24 | getCancelSignal, 25 | type Message, 26 | } from './types/ipc.js'; 27 | export type {VerboseObject, SyncVerboseObject} from './types/verbose.js'; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {createExeca} from './lib/methods/create.js'; 2 | import {mapCommandAsync, mapCommandSync} from './lib/methods/command.js'; 3 | import {mapNode} from './lib/methods/node.js'; 4 | import {mapScriptAsync, setScriptSync, deepScriptOptions} from './lib/methods/script.js'; 5 | import {getIpcExport} from './lib/ipc/methods.js'; 6 | 7 | export {parseCommandString} from './lib/methods/command.js'; 8 | export {ExecaError, ExecaSyncError} from './lib/return/final-error.js'; 9 | 10 | export const execa = createExeca(() => ({})); 11 | export const execaSync = createExeca(() => ({isSync: true})); 12 | export const execaCommand = createExeca(mapCommandAsync); 13 | export const execaCommandSync = createExeca(mapCommandSync); 14 | export const execaNode = createExeca(mapNode); 15 | export const $ = createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync); 16 | 17 | const { 18 | sendMessage, 19 | getOneMessage, 20 | getEachMessage, 21 | getCancelSignal, 22 | } = getIpcExport(); 23 | export { 24 | sendMessage, 25 | getOneMessage, 26 | getEachMessage, 27 | getCancelSignal, 28 | }; 29 | -------------------------------------------------------------------------------- /lib/arguments/command.js: -------------------------------------------------------------------------------- 1 | import {logCommand} from '../verbose/start.js'; 2 | import {getVerboseInfo} from '../verbose/info.js'; 3 | import {getStartTime} from '../return/duration.js'; 4 | import {joinCommand} from './escape.js'; 5 | import {normalizeFdSpecificOption} from './specific.js'; 6 | 7 | // Compute `result.command`, `result.escapedCommand` and `verbose`-related information 8 | export const handleCommand = (filePath, rawArguments, rawOptions) => { 9 | const startTime = getStartTime(); 10 | const {command, escapedCommand} = joinCommand(filePath, rawArguments); 11 | const verbose = normalizeFdSpecificOption(rawOptions, 'verbose'); 12 | const verboseInfo = getVerboseInfo(verbose, escapedCommand, {...rawOptions}); 13 | logCommand(escapedCommand, verboseInfo); 14 | return { 15 | command, 16 | escapedCommand, 17 | startTime, 18 | verboseInfo, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/arguments/cwd.js: -------------------------------------------------------------------------------- 1 | import {statSync} from 'node:fs'; 2 | import path from 'node:path'; 3 | import process from 'node:process'; 4 | import {safeNormalizeFileUrl} from './file-url.js'; 5 | 6 | // Normalize `cwd` option 7 | export const normalizeCwd = (cwd = getDefaultCwd()) => { 8 | const cwdString = safeNormalizeFileUrl(cwd, 'The "cwd" option'); 9 | return path.resolve(cwdString); 10 | }; 11 | 12 | const getDefaultCwd = () => { 13 | try { 14 | return process.cwd(); 15 | } catch (error) { 16 | error.message = `The current directory does not exist.\n${error.message}`; 17 | throw error; 18 | } 19 | }; 20 | 21 | // When `cwd` option has an invalid value, provide with a better error message 22 | export const fixCwdError = (originalMessage, cwd) => { 23 | if (cwd === getDefaultCwd()) { 24 | return originalMessage; 25 | } 26 | 27 | let cwdStat; 28 | try { 29 | cwdStat = statSync(cwd); 30 | } catch (error) { 31 | return `The "cwd" option is invalid: ${cwd}.\n${error.message}\n${originalMessage}`; 32 | } 33 | 34 | if (!cwdStat.isDirectory()) { 35 | return `The "cwd" option is not a directory: ${cwd}.\n${originalMessage}`; 36 | } 37 | 38 | return originalMessage; 39 | }; 40 | -------------------------------------------------------------------------------- /lib/arguments/encoding-option.js: -------------------------------------------------------------------------------- 1 | // Validate `encoding` option 2 | export const validateEncoding = ({encoding}) => { 3 | if (ENCODINGS.has(encoding)) { 4 | return; 5 | } 6 | 7 | const correctEncoding = getCorrectEncoding(encoding); 8 | if (correctEncoding !== undefined) { 9 | throw new TypeError(`Invalid option \`encoding: ${serializeEncoding(encoding)}\`. 10 | Please rename it to ${serializeEncoding(correctEncoding)}.`); 11 | } 12 | 13 | const correctEncodings = [...ENCODINGS].map(correctEncoding => serializeEncoding(correctEncoding)).join(', '); 14 | throw new TypeError(`Invalid option \`encoding: ${serializeEncoding(encoding)}\`. 15 | Please rename it to one of: ${correctEncodings}.`); 16 | }; 17 | 18 | const TEXT_ENCODINGS = new Set(['utf8', 'utf16le']); 19 | export const BINARY_ENCODINGS = new Set(['buffer', 'hex', 'base64', 'base64url', 'latin1', 'ascii']); 20 | const ENCODINGS = new Set([...TEXT_ENCODINGS, ...BINARY_ENCODINGS]); 21 | 22 | const getCorrectEncoding = encoding => { 23 | if (encoding === null) { 24 | return 'buffer'; 25 | } 26 | 27 | if (typeof encoding !== 'string') { 28 | return; 29 | } 30 | 31 | const lowerEncoding = encoding.toLowerCase(); 32 | if (lowerEncoding in ENCODING_ALIASES) { 33 | return ENCODING_ALIASES[lowerEncoding]; 34 | } 35 | 36 | if (ENCODINGS.has(lowerEncoding)) { 37 | return lowerEncoding; 38 | } 39 | }; 40 | 41 | const ENCODING_ALIASES = { 42 | // eslint-disable-next-line unicorn/text-encoding-identifier-case 43 | 'utf-8': 'utf8', 44 | 'utf-16le': 'utf16le', 45 | 'ucs-2': 'utf16le', 46 | ucs2: 'utf16le', 47 | binary: 'latin1', 48 | }; 49 | 50 | const serializeEncoding = encoding => typeof encoding === 'string' ? `"${encoding}"` : String(encoding); 51 | -------------------------------------------------------------------------------- /lib/arguments/file-url.js: -------------------------------------------------------------------------------- 1 | import {fileURLToPath} from 'node:url'; 2 | 3 | // Allow some arguments/options to be either a file path string or a file URL 4 | export const safeNormalizeFileUrl = (file, name) => { 5 | const fileString = normalizeFileUrl(normalizeDenoExecPath(file)); 6 | 7 | if (typeof fileString !== 'string') { 8 | throw new TypeError(`${name} must be a string or a file URL: ${fileString}.`); 9 | } 10 | 11 | return fileString; 12 | }; 13 | 14 | // In Deno node:process execPath is a special object, not just a string: 15 | // https://github.com/denoland/deno/blob/f460188e583f00144000aa0d8ade08218d47c3c1/ext/node/polyfills/process.ts#L344 16 | const normalizeDenoExecPath = file => isDenoExecPath(file) 17 | ? file.toString() 18 | : file; 19 | 20 | export const isDenoExecPath = file => typeof file !== 'string' 21 | && file 22 | && Object.getPrototypeOf(file) === String.prototype; 23 | 24 | // Same but also allows other values, e.g. `boolean` for the `shell` option 25 | export const normalizeFileUrl = file => file instanceof URL ? fileURLToPath(file) : file; 26 | -------------------------------------------------------------------------------- /lib/arguments/shell.js: -------------------------------------------------------------------------------- 1 | // When the `shell` option is set, any command argument is concatenated as a single string by Node.js: 2 | // https://github.com/nodejs/node/blob/e38ce27f3ca0a65f68a31cedd984cddb927d4002/lib/child_process.js#L614-L624 3 | // However, since Node 24, it also prints a deprecation warning. 4 | // To avoid this warning, we perform that same operation before calling `node:child_process`. 5 | // Shells only understand strings, which is why Node.js performs that concatenation. 6 | // However, we rely on users splitting command arguments as an array. 7 | // For example, this allows us to easily detect which arguments are passed. 8 | // So we do want users to pass array of arguments even with `shell: true`, but we also want to avoid any warning. 9 | export const concatenateShell = (file, commandArguments, options) => options.shell && commandArguments.length > 0 10 | ? [[file, ...commandArguments].join(' '), [], options] 11 | : [file, commandArguments, options]; 12 | -------------------------------------------------------------------------------- /lib/convert/add.js: -------------------------------------------------------------------------------- 1 | import {initializeConcurrentStreams} from './concurrent.js'; 2 | import {createReadable} from './readable.js'; 3 | import {createWritable} from './writable.js'; 4 | import {createDuplex} from './duplex.js'; 5 | import {createIterable} from './iterable.js'; 6 | 7 | // Add methods to convert the subprocess to a stream or iterable 8 | export const addConvertedStreams = (subprocess, {encoding}) => { 9 | const concurrentStreams = initializeConcurrentStreams(); 10 | subprocess.readable = createReadable.bind(undefined, {subprocess, concurrentStreams, encoding}); 11 | subprocess.writable = createWritable.bind(undefined, {subprocess, concurrentStreams}); 12 | subprocess.duplex = createDuplex.bind(undefined, {subprocess, concurrentStreams, encoding}); 13 | subprocess.iterable = createIterable.bind(undefined, subprocess, encoding); 14 | subprocess[Symbol.asyncIterator] = createIterable.bind(undefined, subprocess, encoding, {}); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/convert/concurrent.js: -------------------------------------------------------------------------------- 1 | import {createDeferred} from '../utils/deferred.js'; 2 | 3 | // When using multiple `.readable()`/`.writable()`/`.duplex()`, `final` and `destroy` should wait for other streams 4 | export const initializeConcurrentStreams = () => ({ 5 | readableDestroy: new WeakMap(), 6 | writableFinal: new WeakMap(), 7 | writableDestroy: new WeakMap(), 8 | }); 9 | 10 | // Each file descriptor + `waitName` has its own array of promises. 11 | // Each promise is a single `.readable()`/`.writable()`/`.duplex()` call. 12 | export const addConcurrentStream = (concurrentStreams, stream, waitName) => { 13 | const weakMap = concurrentStreams[waitName]; 14 | if (!weakMap.has(stream)) { 15 | weakMap.set(stream, []); 16 | } 17 | 18 | const promises = weakMap.get(stream); 19 | const promise = createDeferred(); 20 | promises.push(promise); 21 | const resolve = promise.resolve.bind(promise); 22 | return {resolve, promises}; 23 | }; 24 | 25 | // Wait for other streams, but stop waiting when subprocess ends 26 | export const waitForConcurrentStreams = async ({resolve, promises}, subprocess) => { 27 | resolve(); 28 | const [isSubprocessExit] = await Promise.race([ 29 | Promise.allSettled([true, subprocess]), 30 | Promise.all([false, ...promises]), 31 | ]); 32 | return !isSubprocessExit; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/convert/iterable.js: -------------------------------------------------------------------------------- 1 | import {BINARY_ENCODINGS} from '../arguments/encoding-option.js'; 2 | import {getFromStream} from '../arguments/fd-options.js'; 3 | import {iterateOnSubprocessStream} from '../io/iterate.js'; 4 | 5 | // Convert the subprocess to an async iterable 6 | export const createIterable = (subprocess, encoding, { 7 | from, 8 | binary: binaryOption = false, 9 | preserveNewlines = false, 10 | } = {}) => { 11 | const binary = binaryOption || BINARY_ENCODINGS.has(encoding); 12 | const subprocessStdout = getFromStream(subprocess, from); 13 | const onStdoutData = iterateOnSubprocessStream({ 14 | subprocessStdout, 15 | subprocess, 16 | binary, 17 | shouldEncode: true, 18 | encoding, 19 | preserveNewlines, 20 | }); 21 | return iterateOnStdoutData(onStdoutData, subprocessStdout, subprocess); 22 | }; 23 | 24 | const iterateOnStdoutData = async function * (onStdoutData, subprocessStdout, subprocess) { 25 | try { 26 | yield * onStdoutData; 27 | } finally { 28 | if (subprocessStdout.readable) { 29 | subprocessStdout.destroy(); 30 | } 31 | 32 | await subprocess; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /lib/convert/shared.js: -------------------------------------------------------------------------------- 1 | import {finished} from 'node:stream/promises'; 2 | import {isStreamAbort} from '../resolve/wait-stream.js'; 3 | 4 | export const safeWaitForSubprocessStdin = async subprocessStdin => { 5 | if (subprocessStdin === undefined) { 6 | return; 7 | } 8 | 9 | try { 10 | await waitForSubprocessStdin(subprocessStdin); 11 | } catch {} 12 | }; 13 | 14 | export const safeWaitForSubprocessStdout = async subprocessStdout => { 15 | if (subprocessStdout === undefined) { 16 | return; 17 | } 18 | 19 | try { 20 | await waitForSubprocessStdout(subprocessStdout); 21 | } catch {} 22 | }; 23 | 24 | export const waitForSubprocessStdin = async subprocessStdin => { 25 | await finished(subprocessStdin, {cleanup: true, readable: false, writable: true}); 26 | }; 27 | 28 | export const waitForSubprocessStdout = async subprocessStdout => { 29 | await finished(subprocessStdout, {cleanup: true, readable: true, writable: false}); 30 | }; 31 | 32 | // When `readable` or `writable` aborts/errors, awaits the subprocess, for the reason mentioned above 33 | export const waitForSubprocess = async (subprocess, error) => { 34 | await subprocess; 35 | if (error) { 36 | throw error; 37 | } 38 | }; 39 | 40 | export const destroyOtherStream = (stream, isOpen, error) => { 41 | if (error && !isStreamAbort(error)) { 42 | stream.destroy(error); 43 | } else if (isOpen) { 44 | stream.destroy(); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /lib/io/pipeline.js: -------------------------------------------------------------------------------- 1 | import {finished} from 'node:stream/promises'; 2 | import {isStandardStream} from '../utils/standard-stream.js'; 3 | 4 | // Similar to `Stream.pipeline(source, destination)`, but does not destroy standard streams 5 | export const pipeStreams = (source, destination) => { 6 | source.pipe(destination); 7 | onSourceFinish(source, destination); 8 | onDestinationFinish(source, destination); 9 | }; 10 | 11 | // `source.pipe(destination)` makes `destination` end when `source` ends. 12 | // But it does not propagate aborts or errors. This function does it. 13 | const onSourceFinish = async (source, destination) => { 14 | if (isStandardStream(source) || isStandardStream(destination)) { 15 | return; 16 | } 17 | 18 | try { 19 | await finished(source, {cleanup: true, readable: true, writable: false}); 20 | } catch {} 21 | 22 | endDestinationStream(destination); 23 | }; 24 | 25 | export const endDestinationStream = destination => { 26 | if (destination.writable) { 27 | destination.end(); 28 | } 29 | }; 30 | 31 | // We do the same thing in the other direction as well. 32 | const onDestinationFinish = async (source, destination) => { 33 | if (isStandardStream(source) || isStandardStream(destination)) { 34 | return; 35 | } 36 | 37 | try { 38 | await finished(destination, {cleanup: true, readable: false, writable: true}); 39 | } catch {} 40 | 41 | abortSourceStream(source); 42 | }; 43 | 44 | export const abortSourceStream = source => { 45 | if (source.readable) { 46 | source.destroy(); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /lib/io/strip-newline.js: -------------------------------------------------------------------------------- 1 | import stripFinalNewlineFunction from 'strip-final-newline'; 2 | 3 | // Apply `stripFinalNewline` option, which applies to `result.stdout|stderr|all|stdio[*]`. 4 | // If the `lines` option is used, it is applied on each line, but using a different function. 5 | export const stripNewline = (value, {stripFinalNewline}, fdNumber) => getStripFinalNewline(stripFinalNewline, fdNumber) && value !== undefined && !Array.isArray(value) 6 | ? stripFinalNewlineFunction(value) 7 | : value; 8 | 9 | // Retrieve `stripFinalNewline` option value, including with `subprocess.all` 10 | export const getStripFinalNewline = (stripFinalNewline, fdNumber) => fdNumber === 'all' 11 | ? stripFinalNewline[1] || stripFinalNewline[2] 12 | : stripFinalNewline[fdNumber]; 13 | -------------------------------------------------------------------------------- /lib/ipc/array.js: -------------------------------------------------------------------------------- 1 | // The `ipc` option adds an `ipc` item to the `stdio` option 2 | export const normalizeIpcStdioArray = (stdioArray, ipc) => ipc && !stdioArray.includes('ipc') 3 | ? [...stdioArray, 'ipc'] 4 | : stdioArray; 5 | -------------------------------------------------------------------------------- /lib/ipc/buffer-messages.js: -------------------------------------------------------------------------------- 1 | import {checkIpcMaxBuffer} from '../io/max-buffer.js'; 2 | import {shouldLogIpc, logIpcOutput} from '../verbose/ipc.js'; 3 | import {getFdSpecificValue} from '../arguments/specific.js'; 4 | import {loopOnMessages} from './get-each.js'; 5 | 6 | // Iterate through IPC messages sent by the subprocess 7 | export const waitForIpcOutput = async ({ 8 | subprocess, 9 | buffer: bufferArray, 10 | maxBuffer: maxBufferArray, 11 | ipc, 12 | ipcOutput, 13 | verboseInfo, 14 | }) => { 15 | if (!ipc) { 16 | return ipcOutput; 17 | } 18 | 19 | const isVerbose = shouldLogIpc(verboseInfo); 20 | const buffer = getFdSpecificValue(bufferArray, 'ipc'); 21 | const maxBuffer = getFdSpecificValue(maxBufferArray, 'ipc'); 22 | 23 | for await (const message of loopOnMessages({ 24 | anyProcess: subprocess, 25 | channel: subprocess.channel, 26 | isSubprocess: false, 27 | ipc, 28 | shouldAwait: false, 29 | reference: true, 30 | })) { 31 | if (buffer) { 32 | checkIpcMaxBuffer(subprocess, ipcOutput, maxBuffer); 33 | ipcOutput.push(message); 34 | } 35 | 36 | if (isVerbose) { 37 | logIpcOutput(message, verboseInfo); 38 | } 39 | } 40 | 41 | return ipcOutput; 42 | }; 43 | 44 | export const getBufferedIpcOutput = async (ipcOutputPromise, ipcOutput) => { 45 | await Promise.allSettled([ipcOutputPromise]); 46 | return ipcOutput; 47 | }; 48 | -------------------------------------------------------------------------------- /lib/ipc/ipc-input.js: -------------------------------------------------------------------------------- 1 | import {serialize} from 'node:v8'; 2 | 3 | // Validate the `ipcInput` option 4 | export const validateIpcInputOption = ({ipcInput, ipc, serialization}) => { 5 | if (ipcInput === undefined) { 6 | return; 7 | } 8 | 9 | if (!ipc) { 10 | throw new Error('The `ipcInput` option cannot be set unless the `ipc` option is `true`.'); 11 | } 12 | 13 | validateIpcInput[serialization](ipcInput); 14 | }; 15 | 16 | const validateAdvancedInput = ipcInput => { 17 | try { 18 | serialize(ipcInput); 19 | } catch (error) { 20 | throw new Error('The `ipcInput` option is not serializable with a structured clone.', {cause: error}); 21 | } 22 | }; 23 | 24 | const validateJsonInput = ipcInput => { 25 | try { 26 | JSON.stringify(ipcInput); 27 | } catch (error) { 28 | throw new Error('The `ipcInput` option is not serializable with JSON.', {cause: error}); 29 | } 30 | }; 31 | 32 | const validateIpcInput = { 33 | advanced: validateAdvancedInput, 34 | json: validateJsonInput, 35 | }; 36 | 37 | // When the `ipcInput` option is set, it is sent as an initial IPC message to the subprocess 38 | export const sendIpcInput = async (subprocess, ipcInput) => { 39 | if (ipcInput === undefined) { 40 | return; 41 | } 42 | 43 | await subprocess.sendMessage(ipcInput); 44 | }; 45 | -------------------------------------------------------------------------------- /lib/ipc/methods.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import {sendMessage} from './send.js'; 3 | import {getOneMessage} from './get-one.js'; 4 | import {getEachMessage} from './get-each.js'; 5 | import {getCancelSignal} from './graceful.js'; 6 | 7 | // Add promise-based IPC methods in current process 8 | export const addIpcMethods = (subprocess, {ipc}) => { 9 | Object.assign(subprocess, getIpcMethods(subprocess, false, ipc)); 10 | }; 11 | 12 | // Get promise-based IPC in the subprocess 13 | export const getIpcExport = () => { 14 | const anyProcess = process; 15 | const isSubprocess = true; 16 | const ipc = process.channel !== undefined; 17 | 18 | return { 19 | ...getIpcMethods(anyProcess, isSubprocess, ipc), 20 | getCancelSignal: getCancelSignal.bind(undefined, { 21 | anyProcess, 22 | channel: anyProcess.channel, 23 | isSubprocess, 24 | ipc, 25 | }), 26 | }; 27 | }; 28 | 29 | // Retrieve the `ipc` shared by both the current process and the subprocess 30 | const getIpcMethods = (anyProcess, isSubprocess, ipc) => ({ 31 | sendMessage: sendMessage.bind(undefined, { 32 | anyProcess, 33 | channel: anyProcess.channel, 34 | isSubprocess, 35 | ipc, 36 | }), 37 | getOneMessage: getOneMessage.bind(undefined, { 38 | anyProcess, 39 | channel: anyProcess.channel, 40 | isSubprocess, 41 | ipc, 42 | }), 43 | getEachMessage: getEachMessage.bind(undefined, { 44 | anyProcess, 45 | channel: anyProcess.channel, 46 | isSubprocess, 47 | ipc, 48 | }), 49 | }); 50 | -------------------------------------------------------------------------------- /lib/methods/bind.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from 'is-plain-obj'; 2 | import {FD_SPECIFIC_OPTIONS} from '../arguments/specific.js'; 3 | 4 | // Deep merge specific options like `env`. Shallow merge the other ones. 5 | export const mergeOptions = (boundOptions, options) => { 6 | const newOptions = Object.fromEntries( 7 | Object.entries(options).map(([optionName, optionValue]) => [ 8 | optionName, 9 | mergeOption(optionName, boundOptions[optionName], optionValue), 10 | ]), 11 | ); 12 | return {...boundOptions, ...newOptions}; 13 | }; 14 | 15 | const mergeOption = (optionName, boundOptionValue, optionValue) => { 16 | if (DEEP_OPTIONS.has(optionName) && isPlainObject(boundOptionValue) && isPlainObject(optionValue)) { 17 | return {...boundOptionValue, ...optionValue}; 18 | } 19 | 20 | return optionValue; 21 | }; 22 | 23 | const DEEP_OPTIONS = new Set(['env', ...FD_SPECIFIC_OPTIONS]); 24 | -------------------------------------------------------------------------------- /lib/methods/command.js: -------------------------------------------------------------------------------- 1 | // Main logic for `execaCommand()` 2 | export const mapCommandAsync = ({file, commandArguments}) => parseCommand(file, commandArguments); 3 | 4 | // Main logic for `execaCommandSync()` 5 | export const mapCommandSync = ({file, commandArguments}) => ({...parseCommand(file, commandArguments), isSync: true}); 6 | 7 | // Convert `execaCommand(command)` into `execa(file, ...commandArguments)` 8 | const parseCommand = (command, unusedArguments) => { 9 | if (unusedArguments.length > 0) { 10 | throw new TypeError(`The command and its arguments must be passed as a single string: ${command} ${unusedArguments}.`); 11 | } 12 | 13 | const [file, ...commandArguments] = parseCommandString(command); 14 | return {file, commandArguments}; 15 | }; 16 | 17 | // Convert `command` string into an array of file or arguments to pass to $`${...fileOrCommandArguments}` 18 | export const parseCommandString = command => { 19 | if (typeof command !== 'string') { 20 | throw new TypeError(`The command must be a string: ${String(command)}.`); 21 | } 22 | 23 | const trimmedCommand = command.trim(); 24 | if (trimmedCommand === '') { 25 | return []; 26 | } 27 | 28 | const tokens = []; 29 | for (const token of trimmedCommand.split(SPACES_REGEXP)) { 30 | // Allow spaces to be escaped by a backslash if not meant as a delimiter 31 | const previousToken = tokens.at(-1); 32 | if (previousToken && previousToken.endsWith('\\')) { 33 | // Merge previous token with current one 34 | tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`; 35 | } else { 36 | tokens.push(token); 37 | } 38 | } 39 | 40 | return tokens; 41 | }; 42 | 43 | const SPACES_REGEXP = / +/g; 44 | -------------------------------------------------------------------------------- /lib/methods/node.js: -------------------------------------------------------------------------------- 1 | import {execPath, execArgv} from 'node:process'; 2 | import path from 'node:path'; 3 | import {safeNormalizeFileUrl} from '../arguments/file-url.js'; 4 | 5 | // `execaNode()` is a shortcut for `execa(..., {node: true})` 6 | export const mapNode = ({options}) => { 7 | if (options.node === false) { 8 | throw new TypeError('The "node" option cannot be false with `execaNode()`.'); 9 | } 10 | 11 | return {options: {...options, node: true}}; 12 | }; 13 | 14 | // Applies the `node: true` option, and the related `nodePath`/`nodeOptions` options. 15 | // Modifies the file commands/arguments to ensure the same Node binary and flags are re-used. 16 | // Also adds `ipc: true` and `shell: false`. 17 | export const handleNodeOption = (file, commandArguments, { 18 | node: shouldHandleNode = false, 19 | nodePath = execPath, 20 | nodeOptions = execArgv.filter(nodeOption => !nodeOption.startsWith('--inspect')), 21 | cwd, 22 | execPath: formerNodePath, 23 | ...options 24 | }) => { 25 | if (formerNodePath !== undefined) { 26 | throw new TypeError('The "execPath" option has been removed. Please use the "nodePath" option instead.'); 27 | } 28 | 29 | const normalizedNodePath = safeNormalizeFileUrl(nodePath, 'The "nodePath" option'); 30 | const resolvedNodePath = path.resolve(cwd, normalizedNodePath); 31 | const newOptions = { 32 | ...options, 33 | nodePath: resolvedNodePath, 34 | node: shouldHandleNode, 35 | cwd, 36 | }; 37 | 38 | if (!shouldHandleNode) { 39 | return [file, commandArguments, newOptions]; 40 | } 41 | 42 | if (path.basename(file, '.exe') === 'node') { 43 | throw new TypeError('When the "node" option is true, the first argument does not need to be "node".'); 44 | } 45 | 46 | return [ 47 | resolvedNodePath, 48 | [...nodeOptions, file, ...commandArguments], 49 | {ipc: true, ...newOptions, shell: false}, 50 | ]; 51 | }; 52 | -------------------------------------------------------------------------------- /lib/methods/parameters.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from 'is-plain-obj'; 2 | import {safeNormalizeFileUrl} from '../arguments/file-url.js'; 3 | 4 | // The command `arguments` and `options` are both optional. 5 | // This also does basic validation on them and on the command file. 6 | export const normalizeParameters = (rawFile, rawArguments = [], rawOptions = {}) => { 7 | const filePath = safeNormalizeFileUrl(rawFile, 'First argument'); 8 | const [commandArguments, options] = isPlainObject(rawArguments) 9 | ? [[], rawArguments] 10 | : [rawArguments, rawOptions]; 11 | 12 | if (!Array.isArray(commandArguments)) { 13 | throw new TypeError(`Second argument must be either an array of arguments or an options object: ${commandArguments}`); 14 | } 15 | 16 | if (commandArguments.some(commandArgument => typeof commandArgument === 'object' && commandArgument !== null)) { 17 | throw new TypeError(`Second argument must be an array of strings: ${commandArguments}`); 18 | } 19 | 20 | const normalizedArguments = commandArguments.map(String); 21 | const nullByteArgument = normalizedArguments.find(normalizedArgument => normalizedArgument.includes('\0')); 22 | if (nullByteArgument !== undefined) { 23 | throw new TypeError(`Arguments cannot contain null bytes ("\\0"): ${nullByteArgument}`); 24 | } 25 | 26 | if (!isPlainObject(options)) { 27 | throw new TypeError(`Last argument must be an options object: ${options}`); 28 | } 29 | 30 | return [filePath, normalizedArguments, options]; 31 | }; 32 | -------------------------------------------------------------------------------- /lib/methods/promise.js: -------------------------------------------------------------------------------- 1 | // The return value is a mixin of `subprocess` and `Promise` 2 | export const mergePromise = (subprocess, promise) => { 3 | for (const [property, descriptor] of descriptors) { 4 | const value = descriptor.value.bind(promise); 5 | Reflect.defineProperty(subprocess, property, {...descriptor, value}); 6 | } 7 | }; 8 | 9 | // eslint-disable-next-line unicorn/prefer-top-level-await 10 | const nativePromisePrototype = (async () => {})().constructor.prototype; 11 | 12 | const descriptors = ['then', 'catch', 'finally'].map(property => [ 13 | property, 14 | Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property), 15 | ]); 16 | -------------------------------------------------------------------------------- /lib/methods/script.js: -------------------------------------------------------------------------------- 1 | // Sets `$.sync` and `$.s` 2 | export const setScriptSync = (boundExeca, createNested, boundOptions) => { 3 | boundExeca.sync = createNested(mapScriptSync, boundOptions); 4 | boundExeca.s = boundExeca.sync; 5 | }; 6 | 7 | // Main logic for `$` 8 | export const mapScriptAsync = ({options}) => getScriptOptions(options); 9 | 10 | // Main logic for `$.sync` 11 | const mapScriptSync = ({options}) => ({...getScriptOptions(options), isSync: true}); 12 | 13 | // `$` is like `execa` but with script-friendly options: `{stdin: 'inherit', preferLocal: true}` 14 | const getScriptOptions = options => ({options: {...getScriptStdinOption(options), ...options}}); 15 | 16 | const getScriptStdinOption = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined 17 | ? {stdin: 'inherit'} 18 | : {}; 19 | 20 | // When using $(...).pipe(...), most script-friendly options should apply to both commands. 21 | // However, some options (like `stdin: 'inherit'`) would create issues with piping, i.e. cannot be deep. 22 | export const deepScriptOptions = {preferLocal: true}; 23 | -------------------------------------------------------------------------------- /lib/pipe/abort.js: -------------------------------------------------------------------------------- 1 | import {aborted} from 'node:util'; 2 | import {createNonCommandError} from './throw.js'; 3 | 4 | // When passing an `unpipeSignal` option, abort piping when the signal is aborted. 5 | // However, do not terminate the subprocesses. 6 | export const unpipeOnAbort = (unpipeSignal, unpipeContext) => unpipeSignal === undefined 7 | ? [] 8 | : [unpipeOnSignalAbort(unpipeSignal, unpipeContext)]; 9 | 10 | const unpipeOnSignalAbort = async (unpipeSignal, {sourceStream, mergedStream, fileDescriptors, sourceOptions, startTime}) => { 11 | await aborted(unpipeSignal, sourceStream); 12 | await mergedStream.remove(sourceStream); 13 | const error = new Error('Pipe canceled by `unpipeSignal` option.'); 14 | throw createNonCommandError({ 15 | error, 16 | fileDescriptors, 17 | sourceOptions, 18 | startTime, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/pipe/sequence.js: -------------------------------------------------------------------------------- 1 | // Like Bash, we await both subprocesses. This is unlike some other shells which only await the destination subprocess. 2 | // Like Bash with the `pipefail` option, if either subprocess fails, the whole pipe fails. 3 | // Like Bash, if both subprocesses fail, we return the failure of the destination. 4 | // This ensures both subprocesses' errors are present, using `error.pipedFrom`. 5 | export const waitForBothSubprocesses = async subprocessPromises => { 6 | const [ 7 | {status: sourceStatus, reason: sourceReason, value: sourceResult = sourceReason}, 8 | {status: destinationStatus, reason: destinationReason, value: destinationResult = destinationReason}, 9 | ] = await subprocessPromises; 10 | 11 | if (!destinationResult.pipedFrom.includes(sourceResult)) { 12 | destinationResult.pipedFrom.push(sourceResult); 13 | } 14 | 15 | if (destinationStatus === 'rejected') { 16 | throw destinationResult; 17 | } 18 | 19 | if (sourceStatus === 'rejected') { 20 | throw sourceResult; 21 | } 22 | 23 | return destinationResult; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/pipe/throw.js: -------------------------------------------------------------------------------- 1 | import {makeEarlyError} from '../return/result.js'; 2 | import {abortSourceStream, endDestinationStream} from '../io/pipeline.js'; 3 | 4 | // When passing invalid arguments to `source.pipe()`, throw asynchronously. 5 | // We also abort both subprocesses. 6 | export const handlePipeArgumentsError = ({ 7 | sourceStream, 8 | sourceError, 9 | destinationStream, 10 | destinationError, 11 | fileDescriptors, 12 | sourceOptions, 13 | startTime, 14 | }) => { 15 | const error = getPipeArgumentsError({ 16 | sourceStream, 17 | sourceError, 18 | destinationStream, 19 | destinationError, 20 | }); 21 | if (error !== undefined) { 22 | throw createNonCommandError({ 23 | error, 24 | fileDescriptors, 25 | sourceOptions, 26 | startTime, 27 | }); 28 | } 29 | }; 30 | 31 | const getPipeArgumentsError = ({sourceStream, sourceError, destinationStream, destinationError}) => { 32 | if (sourceError !== undefined && destinationError !== undefined) { 33 | return destinationError; 34 | } 35 | 36 | if (destinationError !== undefined) { 37 | abortSourceStream(sourceStream); 38 | return destinationError; 39 | } 40 | 41 | if (sourceError !== undefined) { 42 | endDestinationStream(destinationStream); 43 | return sourceError; 44 | } 45 | }; 46 | 47 | // Specific error return value when passing invalid arguments to `subprocess.pipe()` or when using `unpipeSignal` 48 | export const createNonCommandError = ({error, fileDescriptors, sourceOptions, startTime}) => makeEarlyError({ 49 | error, 50 | command: PIPE_COMMAND_MESSAGE, 51 | escapedCommand: PIPE_COMMAND_MESSAGE, 52 | fileDescriptors, 53 | options: sourceOptions, 54 | startTime, 55 | isSync: false, 56 | }); 57 | 58 | const PIPE_COMMAND_MESSAGE = 'source.pipe(destination)'; 59 | -------------------------------------------------------------------------------- /lib/resolve/all-sync.js: -------------------------------------------------------------------------------- 1 | import {isUint8Array, concatUint8Arrays} from '../utils/uint-array.js'; 2 | import {stripNewline} from '../io/strip-newline.js'; 3 | 4 | // Retrieve `result.all` with synchronous methods 5 | export const getAllSync = ([, stdout, stderr], options) => { 6 | if (!options.all) { 7 | return; 8 | } 9 | 10 | if (stdout === undefined) { 11 | return stderr; 12 | } 13 | 14 | if (stderr === undefined) { 15 | return stdout; 16 | } 17 | 18 | if (Array.isArray(stdout)) { 19 | return Array.isArray(stderr) 20 | ? [...stdout, ...stderr] 21 | : [...stdout, stripNewline(stderr, options, 'all')]; 22 | } 23 | 24 | if (Array.isArray(stderr)) { 25 | return [stripNewline(stdout, options, 'all'), ...stderr]; 26 | } 27 | 28 | if (isUint8Array(stdout) && isUint8Array(stderr)) { 29 | return concatUint8Arrays([stdout, stderr]); 30 | } 31 | 32 | return `${stdout}${stderr}`; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/resolve/exit-sync.js: -------------------------------------------------------------------------------- 1 | import {DiscardedError} from '../return/final-error.js'; 2 | import {isMaxBufferSync} from '../io/max-buffer.js'; 3 | import {isFailedExit} from './exit-async.js'; 4 | 5 | // Retrieve exit code, signal name and error information, with synchronous methods 6 | export const getExitResultSync = ({error, status: exitCode, signal, output}, {maxBuffer}) => { 7 | const resultError = getResultError(error, exitCode, signal); 8 | const timedOut = resultError?.code === 'ETIMEDOUT'; 9 | const isMaxBuffer = isMaxBufferSync(resultError, output, maxBuffer); 10 | return { 11 | resultError, 12 | exitCode, 13 | signal, 14 | timedOut, 15 | isMaxBuffer, 16 | }; 17 | }; 18 | 19 | const getResultError = (error, exitCode, signal) => { 20 | if (error !== undefined) { 21 | return error; 22 | } 23 | 24 | return isFailedExit(exitCode, signal) ? new DiscardedError() : undefined; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/resolve/stdio.js: -------------------------------------------------------------------------------- 1 | import {getStreamOutput} from '../io/contents.js'; 2 | import {waitForStream, isInputFileDescriptor} from './wait-stream.js'; 3 | 4 | // Read the contents of `subprocess.std*` and|or wait for its completion 5 | export const waitForStdioStreams = ({subprocess, encoding, buffer, maxBuffer, lines, stripFinalNewline, verboseInfo, streamInfo}) => subprocess.stdio.map((stream, fdNumber) => waitForSubprocessStream({ 6 | stream, 7 | fdNumber, 8 | encoding, 9 | buffer: buffer[fdNumber], 10 | maxBuffer: maxBuffer[fdNumber], 11 | lines: lines[fdNumber], 12 | allMixed: false, 13 | stripFinalNewline, 14 | verboseInfo, 15 | streamInfo, 16 | })); 17 | 18 | // Read the contents of `subprocess.std*` or `subprocess.all` and|or wait for its completion 19 | export const waitForSubprocessStream = async ({stream, fdNumber, encoding, buffer, maxBuffer, lines, allMixed, stripFinalNewline, verboseInfo, streamInfo}) => { 20 | if (!stream) { 21 | return; 22 | } 23 | 24 | const onStreamEnd = waitForStream(stream, fdNumber, streamInfo); 25 | if (isInputFileDescriptor(streamInfo, fdNumber)) { 26 | await onStreamEnd; 27 | return; 28 | } 29 | 30 | const [output] = await Promise.all([ 31 | getStreamOutput({ 32 | stream, 33 | onStreamEnd, 34 | fdNumber, 35 | encoding, 36 | buffer, 37 | maxBuffer, 38 | lines, 39 | allMixed, 40 | stripFinalNewline, 41 | verboseInfo, 42 | streamInfo, 43 | }), 44 | onStreamEnd, 45 | ]); 46 | return output; 47 | }; 48 | -------------------------------------------------------------------------------- /lib/return/duration.js: -------------------------------------------------------------------------------- 1 | import {hrtime} from 'node:process'; 2 | 3 | // Start counting time before spawning the subprocess 4 | export const getStartTime = () => hrtime.bigint(); 5 | 6 | // Compute duration after the subprocess ended. 7 | // Printed by the `verbose` option. 8 | export const getDurationMs = startTime => Number(hrtime.bigint() - startTime) / 1e6; 9 | -------------------------------------------------------------------------------- /lib/return/final-error.js: -------------------------------------------------------------------------------- 1 | // When the subprocess fails, this is the error instance being returned. 2 | // If another error instance is being thrown, it is kept as `error.cause`. 3 | export const getFinalError = (originalError, message, isSync) => { 4 | const ErrorClass = isSync ? ExecaSyncError : ExecaError; 5 | const options = originalError instanceof DiscardedError ? {} : {cause: originalError}; 6 | return new ErrorClass(message, options); 7 | }; 8 | 9 | // Indicates that the error is used only to interrupt control flow, but not in the return value 10 | export class DiscardedError extends Error {} 11 | 12 | // Proper way to set `error.name`: it should be inherited and non-enumerable 13 | const setErrorName = (ErrorClass, value) => { 14 | Object.defineProperty(ErrorClass.prototype, 'name', { 15 | value, 16 | writable: true, 17 | enumerable: false, 18 | configurable: true, 19 | }); 20 | Object.defineProperty(ErrorClass.prototype, execaErrorSymbol, { 21 | value: true, 22 | writable: false, 23 | enumerable: false, 24 | configurable: false, 25 | }); 26 | }; 27 | 28 | // Unlike `instanceof`, this works across realms 29 | export const isExecaError = error => isErrorInstance(error) && execaErrorSymbol in error; 30 | 31 | const execaErrorSymbol = Symbol('isExecaError'); 32 | 33 | export const isErrorInstance = value => Object.prototype.toString.call(value) === '[object Error]'; 34 | 35 | // We use two different Error classes for async/sync methods since they have slightly different shape and types 36 | export class ExecaError extends Error {} 37 | setErrorName(ExecaError, ExecaError.name); 38 | 39 | export class ExecaSyncError extends Error {} 40 | setErrorName(ExecaSyncError, ExecaSyncError.name); 41 | -------------------------------------------------------------------------------- /lib/return/reject.js: -------------------------------------------------------------------------------- 1 | import {logResult} from '../verbose/complete.js'; 2 | 3 | // Applies the `reject` option. 4 | // Also print the final log line with `verbose`. 5 | export const handleResult = (result, verboseInfo, {reject}) => { 6 | logResult(result, verboseInfo); 7 | 8 | if (result.failed && reject) { 9 | throw result; 10 | } 11 | 12 | return result; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/stdio/input-option.js: -------------------------------------------------------------------------------- 1 | import {isReadableStream} from 'is-stream'; 2 | import {isUint8Array} from '../utils/uint-array.js'; 3 | import {isUrl, isFilePathString} from './type.js'; 4 | 5 | // Append the `stdin` option with the `input` and `inputFile` options 6 | export const handleInputOptions = ({input, inputFile}, fdNumber) => fdNumber === 0 7 | ? [ 8 | ...handleInputOption(input), 9 | ...handleInputFileOption(inputFile), 10 | ] 11 | : []; 12 | 13 | const handleInputOption = input => input === undefined ? [] : [{ 14 | type: getInputType(input), 15 | value: input, 16 | optionName: 'input', 17 | }]; 18 | 19 | const getInputType = input => { 20 | if (isReadableStream(input, {checkOpen: false})) { 21 | return 'nodeStream'; 22 | } 23 | 24 | if (typeof input === 'string') { 25 | return 'string'; 26 | } 27 | 28 | if (isUint8Array(input)) { 29 | return 'uint8Array'; 30 | } 31 | 32 | throw new Error('The `input` option must be a string, a Uint8Array or a Node.js Readable stream.'); 33 | }; 34 | 35 | const handleInputFileOption = inputFile => inputFile === undefined ? [] : [{ 36 | ...getInputFileType(inputFile), 37 | optionName: 'inputFile', 38 | }]; 39 | 40 | const getInputFileType = inputFile => { 41 | if (isUrl(inputFile)) { 42 | return {type: 'fileUrl', value: inputFile}; 43 | } 44 | 45 | if (isFilePathString(inputFile)) { 46 | return {type: 'filePath', value: {file: inputFile}}; 47 | } 48 | 49 | throw new Error('The `inputFile` option must be a file path string or a file URL.'); 50 | }; 51 | -------------------------------------------------------------------------------- /lib/terminate/cancel.js: -------------------------------------------------------------------------------- 1 | import {onAbortedSignal} from '../utils/abort-signal.js'; 2 | 3 | // Validate the `cancelSignal` option 4 | export const validateCancelSignal = ({cancelSignal}) => { 5 | if (cancelSignal !== undefined && Object.prototype.toString.call(cancelSignal) !== '[object AbortSignal]') { 6 | throw new Error(`The \`cancelSignal\` option must be an AbortSignal: ${String(cancelSignal)}`); 7 | } 8 | }; 9 | 10 | // Terminate the subprocess when aborting the `cancelSignal` option and `gracefulSignal` is `false` 11 | export const throwOnCancel = ({subprocess, cancelSignal, gracefulCancel, context, controller}) => cancelSignal === undefined || gracefulCancel 12 | ? [] 13 | : [terminateOnCancel(subprocess, cancelSignal, context, controller)]; 14 | 15 | const terminateOnCancel = async (subprocess, cancelSignal, context, {signal}) => { 16 | await onAbortedSignal(cancelSignal, signal); 17 | context.terminationReason ??= 'cancel'; 18 | subprocess.kill(); 19 | throw cancelSignal.reason; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/terminate/cleanup.js: -------------------------------------------------------------------------------- 1 | import {addAbortListener} from 'node:events'; 2 | import {onExit} from 'signal-exit'; 3 | 4 | // If the `cleanup` option is used, call `subprocess.kill()` when the parent process exits 5 | export const cleanupOnExit = (subprocess, {cleanup, detached}, {signal}) => { 6 | if (!cleanup || detached) { 7 | return; 8 | } 9 | 10 | const removeExitHandler = onExit(() => { 11 | subprocess.kill(); 12 | }); 13 | addAbortListener(signal, () => { 14 | removeExitHandler(); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/terminate/timeout.js: -------------------------------------------------------------------------------- 1 | import {setTimeout} from 'node:timers/promises'; 2 | import {DiscardedError} from '../return/final-error.js'; 3 | 4 | // Validate `timeout` option 5 | export const validateTimeout = ({timeout}) => { 6 | if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) { 7 | throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); 8 | } 9 | }; 10 | 11 | // Fails when the `timeout` option is exceeded 12 | export const throwOnTimeout = (subprocess, timeout, context, controller) => timeout === 0 || timeout === undefined 13 | ? [] 14 | : [killAfterTimeout(subprocess, timeout, context, controller)]; 15 | 16 | const killAfterTimeout = async (subprocess, timeout, context, {signal}) => { 17 | await setTimeout(timeout, undefined, {signal}); 18 | context.terminationReason ??= 'timeout'; 19 | subprocess.kill(); 20 | throw new DiscardedError(); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/transform/encoding-transform.js: -------------------------------------------------------------------------------- 1 | import {Buffer} from 'node:buffer'; 2 | import {StringDecoder} from 'node:string_decoder'; 3 | import {isUint8Array, bufferToUint8Array} from '../utils/uint-array.js'; 4 | 5 | /* 6 | When using binary encodings, add an internal generator that converts chunks from `Buffer` to `string` or `Uint8Array`. 7 | Chunks might be Buffer, Uint8Array or strings since: 8 | - `subprocess.stdout|stderr` emits Buffers 9 | - `subprocess.stdin.write()` accepts Buffer, Uint8Array or string 10 | - Previous generators might return Uint8Array or string 11 | 12 | However, those are converted to Buffer: 13 | - on writes: `Duplex.writable` `decodeStrings: true` default option 14 | - on reads: `Duplex.readable` `readableEncoding: null` default option 15 | */ 16 | export const getEncodingTransformGenerator = (binary, encoding, skipped) => { 17 | if (skipped) { 18 | return; 19 | } 20 | 21 | if (binary) { 22 | return {transform: encodingUint8ArrayGenerator.bind(undefined, new TextEncoder())}; 23 | } 24 | 25 | const stringDecoder = new StringDecoder(encoding); 26 | return { 27 | transform: encodingStringGenerator.bind(undefined, stringDecoder), 28 | final: encodingStringFinal.bind(undefined, stringDecoder), 29 | }; 30 | }; 31 | 32 | const encodingUint8ArrayGenerator = function * (textEncoder, chunk) { 33 | if (Buffer.isBuffer(chunk)) { 34 | yield bufferToUint8Array(chunk); 35 | } else if (typeof chunk === 'string') { 36 | yield textEncoder.encode(chunk); 37 | } else { 38 | yield chunk; 39 | } 40 | }; 41 | 42 | const encodingStringGenerator = function * (stringDecoder, chunk) { 43 | yield isUint8Array(chunk) ? stringDecoder.write(chunk) : chunk; 44 | }; 45 | 46 | const encodingStringFinal = function * (stringDecoder) { 47 | const lastChunk = stringDecoder.end(); 48 | if (lastChunk !== '') { 49 | yield lastChunk; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /lib/transform/run-sync.js: -------------------------------------------------------------------------------- 1 | // Duplicate the code from `run-async.js` but as synchronous functions 2 | export const pushChunksSync = (getChunksSync, getChunksArguments, transformStream, done) => { 3 | try { 4 | for (const chunk of getChunksSync(...getChunksArguments)) { 5 | transformStream.push(chunk); 6 | } 7 | 8 | done(); 9 | } catch (error) { 10 | done(error); 11 | } 12 | }; 13 | 14 | // Run synchronous generators with `execaSync()` 15 | export const runTransformSync = (generators, chunks) => [ 16 | ...chunks.flatMap(chunk => [...transformChunkSync(chunk, generators, 0)]), 17 | ...finalChunksSync(generators), 18 | ]; 19 | 20 | export const transformChunkSync = function * (chunk, generators, index) { 21 | if (index === generators.length) { 22 | yield chunk; 23 | return; 24 | } 25 | 26 | const {transform = identityGenerator} = generators[index]; 27 | for (const transformedChunk of transform(chunk)) { 28 | yield * transformChunkSync(transformedChunk, generators, index + 1); 29 | } 30 | }; 31 | 32 | export const finalChunksSync = function * (generators) { 33 | for (const [index, {final}] of Object.entries(generators)) { 34 | yield * generatorFinalChunksSync(final, Number(index), generators); 35 | } 36 | }; 37 | 38 | const generatorFinalChunksSync = function * (final, index, generators) { 39 | if (final === undefined) { 40 | return; 41 | } 42 | 43 | for (const finalChunk of final()) { 44 | yield * transformChunkSync(finalChunk, generators, index + 1); 45 | } 46 | }; 47 | 48 | const identityGenerator = function * (chunk) { 49 | yield chunk; 50 | }; 51 | -------------------------------------------------------------------------------- /lib/transform/validate.js: -------------------------------------------------------------------------------- 1 | import {Buffer} from 'node:buffer'; 2 | import {isUint8Array} from '../utils/uint-array.js'; 3 | 4 | // Validate the type of chunk argument passed to transform generators 5 | export const getValidateTransformInput = (writableObjectMode, optionName) => writableObjectMode 6 | ? undefined 7 | : validateStringTransformInput.bind(undefined, optionName); 8 | 9 | const validateStringTransformInput = function * (optionName, chunk) { 10 | if (typeof chunk !== 'string' && !isUint8Array(chunk) && !Buffer.isBuffer(chunk)) { 11 | throw new TypeError(`The \`${optionName}\` option's transform must use "objectMode: true" to receive as input: ${typeof chunk}.`); 12 | } 13 | 14 | yield chunk; 15 | }; 16 | 17 | // Validate the type of the value returned by transform generators 18 | export const getValidateTransformReturn = (readableObjectMode, optionName) => readableObjectMode 19 | ? validateObjectTransformReturn.bind(undefined, optionName) 20 | : validateStringTransformReturn.bind(undefined, optionName); 21 | 22 | const validateObjectTransformReturn = function * (optionName, chunk) { 23 | validateEmptyReturn(optionName, chunk); 24 | yield chunk; 25 | }; 26 | 27 | const validateStringTransformReturn = function * (optionName, chunk) { 28 | validateEmptyReturn(optionName, chunk); 29 | 30 | if (typeof chunk !== 'string' && !isUint8Array(chunk)) { 31 | throw new TypeError(`The \`${optionName}\` option's function must yield a string or an Uint8Array, not ${typeof chunk}.`); 32 | } 33 | 34 | yield chunk; 35 | }; 36 | 37 | const validateEmptyReturn = (optionName, chunk) => { 38 | if (chunk === null || chunk === undefined) { 39 | throw new TypeError(`The \`${optionName}\` option's function must not call \`yield ${chunk}\`. 40 | Instead, \`yield\` should either be called with a value, or not be called at all. For example: 41 | if (condition) { yield value; }`); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /lib/utils/abort-signal.js: -------------------------------------------------------------------------------- 1 | import {once} from 'node:events'; 2 | 3 | // Combines `util.aborted()` and `events.addAbortListener()`: promise-based and cleaned up with a stop signal 4 | export const onAbortedSignal = async (mainSignal, stopSignal) => { 5 | if (!mainSignal.aborted) { 6 | await once(mainSignal, 'abort', {signal: stopSignal}); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /lib/utils/deferred.js: -------------------------------------------------------------------------------- 1 | export const createDeferred = () => { 2 | const methods = {}; 3 | const promise = new Promise((resolve, reject) => { 4 | Object.assign(methods, {resolve, reject}); 5 | }); 6 | return Object.assign(promise, methods); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/utils/max-listeners.js: -------------------------------------------------------------------------------- 1 | import {addAbortListener} from 'node:events'; 2 | 3 | // Temporarily increase the maximum number of listeners on an eventEmitter 4 | export const incrementMaxListeners = (eventEmitter, maxListenersIncrement, signal) => { 5 | const maxListeners = eventEmitter.getMaxListeners(); 6 | if (maxListeners === 0 || maxListeners === Number.POSITIVE_INFINITY) { 7 | return; 8 | } 9 | 10 | eventEmitter.setMaxListeners(maxListeners + maxListenersIncrement); 11 | addAbortListener(signal, () => { 12 | eventEmitter.setMaxListeners(eventEmitter.getMaxListeners() - maxListenersIncrement); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /lib/utils/standard-stream.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | 3 | export const isStandardStream = stream => STANDARD_STREAMS.includes(stream); 4 | export const STANDARD_STREAMS = [process.stdin, process.stdout, process.stderr]; 5 | export const STANDARD_STREAMS_ALIASES = ['stdin', 'stdout', 'stderr']; 6 | export const getStreamName = fdNumber => STANDARD_STREAMS_ALIASES[fdNumber] ?? `stdio[${fdNumber}]`; 7 | -------------------------------------------------------------------------------- /lib/verbose/complete.js: -------------------------------------------------------------------------------- 1 | import prettyMs from 'pretty-ms'; 2 | import {isVerbose} from './values.js'; 3 | import {verboseLog} from './log.js'; 4 | import {logError} from './error.js'; 5 | 6 | // When `verbose` is `short|full|custom`, print each command's completion, duration and error 7 | export const logResult = (result, verboseInfo) => { 8 | if (!isVerbose(verboseInfo)) { 9 | return; 10 | } 11 | 12 | logError(result, verboseInfo); 13 | logDuration(result, verboseInfo); 14 | }; 15 | 16 | const logDuration = (result, verboseInfo) => { 17 | const verboseMessage = `(done in ${prettyMs(result.durationMs)})`; 18 | verboseLog({ 19 | type: 'duration', 20 | verboseMessage, 21 | verboseInfo, 22 | result, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/verbose/custom.js: -------------------------------------------------------------------------------- 1 | import {getVerboseFunction} from './values.js'; 2 | 3 | // Apply the `verbose` function on each line 4 | export const applyVerboseOnLines = (printedLines, verboseInfo, fdNumber) => { 5 | const verboseFunction = getVerboseFunction(verboseInfo, fdNumber); 6 | return printedLines 7 | .map(({verboseLine, verboseObject}) => applyVerboseFunction(verboseLine, verboseObject, verboseFunction)) 8 | .filter(printedLine => printedLine !== undefined) 9 | .map(printedLine => appendNewline(printedLine)) 10 | .join(''); 11 | }; 12 | 13 | const applyVerboseFunction = (verboseLine, verboseObject, verboseFunction) => { 14 | if (verboseFunction === undefined) { 15 | return verboseLine; 16 | } 17 | 18 | const printedLine = verboseFunction(verboseLine, verboseObject); 19 | if (typeof printedLine === 'string') { 20 | return printedLine; 21 | } 22 | }; 23 | 24 | const appendNewline = printedLine => printedLine.endsWith('\n') 25 | ? printedLine 26 | : `${printedLine}\n`; 27 | -------------------------------------------------------------------------------- /lib/verbose/default.js: -------------------------------------------------------------------------------- 1 | import figures from 'figures'; 2 | import { 3 | gray, 4 | bold, 5 | redBright, 6 | yellowBright, 7 | } from 'yoctocolors'; 8 | 9 | // Default when `verbose` is not a function 10 | export const defaultVerboseFunction = ({ 11 | type, 12 | message, 13 | timestamp, 14 | piped, 15 | commandId, 16 | result: {failed = false} = {}, 17 | options: {reject = true}, 18 | }) => { 19 | const timestampString = serializeTimestamp(timestamp); 20 | const icon = ICONS[type]({failed, reject, piped}); 21 | const color = COLORS[type]({reject}); 22 | return `${gray(`[${timestampString}]`)} ${gray(`[${commandId}]`)} ${color(icon)} ${color(message)}`; 23 | }; 24 | 25 | // Prepending the timestamp allows debugging the slow paths of a subprocess 26 | const serializeTimestamp = timestamp => `${padField(timestamp.getHours(), 2)}:${padField(timestamp.getMinutes(), 2)}:${padField(timestamp.getSeconds(), 2)}.${padField(timestamp.getMilliseconds(), 3)}`; 27 | 28 | const padField = (field, padding) => String(field).padStart(padding, '0'); 29 | 30 | const getFinalIcon = ({failed, reject}) => { 31 | if (!failed) { 32 | return figures.tick; 33 | } 34 | 35 | return reject ? figures.cross : figures.warning; 36 | }; 37 | 38 | const ICONS = { 39 | command: ({piped}) => piped ? '|' : '$', 40 | output: () => ' ', 41 | ipc: () => '*', 42 | error: getFinalIcon, 43 | duration: getFinalIcon, 44 | }; 45 | 46 | const identity = string => string; 47 | 48 | const COLORS = { 49 | command: () => bold, 50 | output: () => identity, 51 | ipc: () => identity, 52 | error: ({reject}) => reject ? redBright : yellowBright, 53 | duration: () => gray, 54 | }; 55 | -------------------------------------------------------------------------------- /lib/verbose/error.js: -------------------------------------------------------------------------------- 1 | import {verboseLog} from './log.js'; 2 | 3 | // When `verbose` is `short|full|custom`, print each command's error when it fails 4 | export const logError = (result, verboseInfo) => { 5 | if (result.failed) { 6 | verboseLog({ 7 | type: 'error', 8 | verboseMessage: result.shortMessage, 9 | verboseInfo, 10 | result, 11 | }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/verbose/info.js: -------------------------------------------------------------------------------- 1 | import {isVerbose, VERBOSE_VALUES, isVerboseFunction} from './values.js'; 2 | 3 | // Information computed before spawning, used by the `verbose` option 4 | export const getVerboseInfo = (verbose, escapedCommand, rawOptions) => { 5 | validateVerbose(verbose); 6 | const commandId = getCommandId(verbose); 7 | return { 8 | verbose, 9 | escapedCommand, 10 | commandId, 11 | rawOptions, 12 | }; 13 | }; 14 | 15 | const getCommandId = verbose => isVerbose({verbose}) ? COMMAND_ID++ : undefined; 16 | 17 | // Prepending the `pid` is useful when multiple commands print their output at the same time. 18 | // However, we cannot use the real PID since this is not available with `child_process.spawnSync()`. 19 | // Also, we cannot use the real PID if we want to print it before `child_process.spawn()` is run. 20 | // As a pro, it is shorter than a normal PID and never re-uses the same id. 21 | // As a con, it cannot be used to send signals. 22 | let COMMAND_ID = 0n; 23 | 24 | const validateVerbose = verbose => { 25 | for (const fdVerbose of verbose) { 26 | if (fdVerbose === false) { 27 | throw new TypeError('The "verbose: false" option was renamed to "verbose: \'none\'".'); 28 | } 29 | 30 | if (fdVerbose === true) { 31 | throw new TypeError('The "verbose: true" option was renamed to "verbose: \'short\'".'); 32 | } 33 | 34 | if (!VERBOSE_VALUES.includes(fdVerbose) && !isVerboseFunction(fdVerbose)) { 35 | const allowedValues = VERBOSE_VALUES.map(allowedValue => `'${allowedValue}'`).join(', '); 36 | throw new TypeError(`The "verbose" option must not be ${fdVerbose}. Allowed values are: ${allowedValues} or a function.`); 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /lib/verbose/ipc.js: -------------------------------------------------------------------------------- 1 | import {verboseLog, serializeVerboseMessage} from './log.js'; 2 | import {isFullVerbose} from './values.js'; 3 | 4 | // When `verbose` is `'full'`, print IPC messages from the subprocess 5 | export const shouldLogIpc = verboseInfo => isFullVerbose(verboseInfo, 'ipc'); 6 | 7 | export const logIpcOutput = (message, verboseInfo) => { 8 | const verboseMessage = serializeVerboseMessage(message); 9 | verboseLog({ 10 | type: 'ipc', 11 | verboseMessage, 12 | fdNumber: 'ipc', 13 | verboseInfo, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/verbose/start.js: -------------------------------------------------------------------------------- 1 | import {isVerbose} from './values.js'; 2 | import {verboseLog} from './log.js'; 3 | 4 | // When `verbose` is `short|full|custom`, print each command 5 | export const logCommand = (escapedCommand, verboseInfo) => { 6 | if (!isVerbose(verboseInfo)) { 7 | return; 8 | } 9 | 10 | verboseLog({ 11 | type: 'command', 12 | verboseMessage: escapedCommand, 13 | verboseInfo, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/verbose/values.js: -------------------------------------------------------------------------------- 1 | import {getFdSpecificValue} from '../arguments/specific.js'; 2 | 3 | // The `verbose` option can have different values for `stdout`/`stderr` 4 | export const isVerbose = ({verbose}, fdNumber) => getFdVerbose(verbose, fdNumber) !== 'none'; 5 | 6 | // Whether IPC and output and logged 7 | export const isFullVerbose = ({verbose}, fdNumber) => !['none', 'short'].includes(getFdVerbose(verbose, fdNumber)); 8 | 9 | // The `verbose` option can be a function to customize logging 10 | export const getVerboseFunction = ({verbose}, fdNumber) => { 11 | const fdVerbose = getFdVerbose(verbose, fdNumber); 12 | return isVerboseFunction(fdVerbose) ? fdVerbose : undefined; 13 | }; 14 | 15 | // When using `verbose: {stdout, stderr, fd3, ipc}`: 16 | // - `verbose.stdout|stderr|fd3` is used for 'output' 17 | // - `verbose.ipc` is only used for 'ipc' 18 | // - highest `verbose.*` value is used for 'command', 'error' and 'duration' 19 | const getFdVerbose = (verbose, fdNumber) => fdNumber === undefined 20 | ? getFdGenericVerbose(verbose) 21 | : getFdSpecificValue(verbose, fdNumber); 22 | 23 | // When using `verbose: {stdout, stderr, fd3, ipc}` and logging is not specific to a file descriptor. 24 | // We then use the highest `verbose.*` value, using the following order: 25 | // - function > 'full' > 'short' > 'none' 26 | // - if several functions are defined: stdout > stderr > fd3 > ipc 27 | const getFdGenericVerbose = verbose => verbose.find(fdVerbose => isVerboseFunction(fdVerbose)) 28 | ?? VERBOSE_VALUES.findLast(fdVerbose => verbose.includes(fdVerbose)); 29 | 30 | // Whether the `verbose` option is customized using a function 31 | export const isVerboseFunction = fdVerbose => typeof fdVerbose === 'function'; 32 | 33 | export const VERBOSE_VALUES = ['none', 'short', 'full']; 34 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/execa/a31fe55782993f2483d30955a8799ab88e20687c/media/logo.png -------------------------------------------------------------------------------- /media/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/execa/a31fe55782993f2483d30955a8799ab88e20687c/media/logo.sketch -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /media/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/execa/a31fe55782993f2483d30955a8799ab88e20687c/media/logo@2x.png -------------------------------------------------------------------------------- /media/verbose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/execa/a31fe55782993f2483d30955a8799ab88e20687c/media/verbose.png -------------------------------------------------------------------------------- /test-d/arguments/env.test-d.ts: -------------------------------------------------------------------------------- 1 | import process, {type env} from 'node:process'; 2 | import {expectType, expectAssignable} from 'tsd'; 3 | import {execa, type Options, type Result} from '../../index.js'; 4 | 5 | type NodeEnv = 'production' | 'development' | 'test'; 6 | 7 | // Libraries like Next.js or Remix do the following type augmentation. 8 | // The following type tests ensure this works with Execa. 9 | // See https://github.com/sindresorhus/execa/pull/1141 and https://github.com/sindresorhus/execa/issues/1132 10 | declare global { 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | namespace NodeJS { 13 | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions 14 | interface ProcessEnv { 15 | readonly NODE_ENV: NodeEnv; 16 | } 17 | } 18 | } 19 | 20 | // The global types are impacted 21 | expectType(process.env.NODE_ENV); 22 | expectType('' as (typeof env)['NODE_ENV']); 23 | expectType('' as NodeJS.ProcessEnv['NODE_ENV']); 24 | expectType('' as globalThis.NodeJS.ProcessEnv['NODE_ENV']); 25 | 26 | // But Execa's types are not impacted 27 | expectType('' as Exclude['NODE_ENV']); 28 | expectAssignable(await execa({env: {test: 'example'}})`unicorns`); 29 | expectAssignable(await execa({env: {test: 'example'} as const})`unicorns`); 30 | expectAssignable(await execa({env: {test: undefined}})`unicorns`); 31 | expectAssignable(await execa({env: {test: undefined} as const})`unicorns`); 32 | -------------------------------------------------------------------------------- /test-d/convert/duplex.test-d.ts: -------------------------------------------------------------------------------- 1 | import type {Duplex} from 'node:stream'; 2 | import {expectType, expectError} from 'tsd'; 3 | import {execa} from '../../index.js'; 4 | 5 | const subprocess = execa('unicorns'); 6 | 7 | expectType(subprocess.duplex()); 8 | 9 | subprocess.duplex({from: 'stdout'}); 10 | subprocess.duplex({from: 'stderr'}); 11 | subprocess.duplex({from: 'all'}); 12 | subprocess.duplex({from: 'fd3'}); 13 | subprocess.duplex({to: 'fd3'}); 14 | subprocess.duplex({from: 'stdout', to: 'stdin'}); 15 | subprocess.duplex({from: 'stdout', to: 'fd3'}); 16 | expectError(subprocess.duplex({from: 'stdout' as string})); 17 | expectError(subprocess.duplex({to: 'fd3' as string})); 18 | expectError(subprocess.duplex({from: 'stdin'})); 19 | expectError(subprocess.duplex({from: 'stderr', to: 'stdout'})); 20 | expectError(subprocess.duplex({from: 'fd'})); 21 | expectError(subprocess.duplex({from: 'fdNotANumber'})); 22 | expectError(subprocess.duplex({to: 'fd'})); 23 | expectError(subprocess.duplex({to: 'fdNotANumber'})); 24 | 25 | subprocess.duplex({binary: false}); 26 | expectError(subprocess.duplex({binary: 'false'})); 27 | 28 | subprocess.duplex({preserveNewlines: false}); 29 | expectError(subprocess.duplex({preserveNewlines: 'false'})); 30 | 31 | expectError(subprocess.duplex('stdout')); 32 | expectError(subprocess.duplex({other: 'stdout'})); 33 | -------------------------------------------------------------------------------- /test-d/convert/readable.test-d.ts: -------------------------------------------------------------------------------- 1 | import type {Readable} from 'node:stream'; 2 | import {expectType, expectError} from 'tsd'; 3 | import {execa} from '../../index.js'; 4 | 5 | const subprocess = execa('unicorns'); 6 | 7 | expectType(subprocess.readable()); 8 | 9 | subprocess.readable({from: 'stdout'}); 10 | subprocess.readable({from: 'stderr'}); 11 | subprocess.readable({from: 'all'}); 12 | subprocess.readable({from: 'fd3'}); 13 | expectError(subprocess.readable({from: 'fd3' as string})); 14 | expectError(subprocess.readable({from: 'stdin'})); 15 | expectError(subprocess.readable({from: 'fd'})); 16 | expectError(subprocess.readable({from: 'fdNotANumber'})); 17 | expectError(subprocess.readable({to: 'stdin'})); 18 | 19 | subprocess.readable({binary: false}); 20 | expectError(subprocess.readable({binary: 'false'})); 21 | 22 | subprocess.readable({preserveNewlines: false}); 23 | expectError(subprocess.readable({preserveNewlines: 'false'})); 24 | 25 | expectError(subprocess.readable('stdout')); 26 | expectError(subprocess.readable({other: 'stdout'})); 27 | -------------------------------------------------------------------------------- /test-d/convert/writable.test-d.ts: -------------------------------------------------------------------------------- 1 | import type {Writable} from 'node:stream'; 2 | import {expectType, expectError} from 'tsd'; 3 | import {execa} from '../../index.js'; 4 | 5 | const subprocess = execa('unicorns'); 6 | 7 | expectType(subprocess.writable()); 8 | 9 | subprocess.writable({to: 'stdin'}); 10 | subprocess.writable({to: 'fd3'}); 11 | expectError(subprocess.writable({to: 'fd3' as string})); 12 | expectError(subprocess.writable({to: 'stdout'})); 13 | expectError(subprocess.writable({to: 'fd'})); 14 | expectError(subprocess.writable({to: 'fdNotANumber'})); 15 | expectError(subprocess.writable({from: 'stdout'})); 16 | 17 | expectError(subprocess.writable({binary: false})); 18 | 19 | expectError(subprocess.writable({preserveNewlines: false})); 20 | 21 | expectError(subprocess.writable('stdin')); 22 | expectError(subprocess.writable({other: 'stdin'})); 23 | -------------------------------------------------------------------------------- /test-d/ipc/graceful.ts: -------------------------------------------------------------------------------- 1 | import {expectType, expectError} from 'tsd'; 2 | import {getCancelSignal, execa} from '../../index.js'; 3 | 4 | expectType>(getCancelSignal()); 5 | 6 | expectError(await getCancelSignal('')); 7 | 8 | expectError(execa('test').getCancelSignal); 9 | -------------------------------------------------------------------------------- /test-d/methods/template.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectAssignable, expectNotAssignable} from 'tsd'; 2 | import {execa, type TemplateExpression} from '../../index.js'; 3 | 4 | const stringArray = ['foo', 'bar'] as const; 5 | const numberArray = [1, 2] as const; 6 | 7 | expectAssignable('unicorns'); 8 | expectAssignable(1); 9 | expectAssignable(stringArray); 10 | expectAssignable(numberArray); 11 | expectAssignable(false.toString()); 12 | expectNotAssignable(false); 13 | 14 | expectAssignable(await execa`echo foo`); 15 | expectAssignable(await execa({reject: false})`echo foo`); 16 | expectNotAssignable(execa`echo foo`); 17 | expectAssignable([await execa`echo foo`, 'bar']); 18 | expectNotAssignable([execa`echo foo`, 'bar']); 19 | -------------------------------------------------------------------------------- /test-d/return/result-all.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type ExecaError, 6 | type ExecaSyncError, 7 | } from '../../index.js'; 8 | 9 | const allResult = await execa('unicorns', {all: true}); 10 | expectType(allResult.all); 11 | 12 | const noAllResult = await execa('unicorns'); 13 | expectType(noAllResult.all); 14 | 15 | const allResultSync = execaSync('unicorns', {all: true}); 16 | expectType(allResultSync.all); 17 | 18 | const noAllResultSync = execaSync('unicorns'); 19 | expectType(noAllResultSync.all); 20 | 21 | const allError = new Error('.') as ExecaError<{all: true}>; 22 | expectType(allError.all); 23 | 24 | const noAllError = new Error('.') as ExecaError<{}>; 25 | expectType(noAllError.all); 26 | 27 | const allErrorSync = new Error('.') as ExecaError<{all: true}>; 28 | expectType(allErrorSync.all); 29 | 30 | const noAllErrorSync = new Error('.') as ExecaSyncError<{}>; 31 | expectType(noAllErrorSync.all); 32 | -------------------------------------------------------------------------------- /test-d/stdio/array.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError} from 'tsd'; 2 | import {execa, execaSync} from '../../index.js'; 3 | 4 | expectError(await execa('unicorns', {stdio: []})); 5 | expectError(execaSync('unicorns', {stdio: []})); 6 | expectError(await execa('unicorns', {stdio: ['pipe']})); 7 | expectError(execaSync('unicorns', {stdio: ['pipe']})); 8 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe']})); 9 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe']})); 10 | 11 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'pipe']}); 12 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'pipe']}); 13 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'unknown']})); 14 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'unknown']})); 15 | -------------------------------------------------------------------------------- /test-d/stdio/option/array-binary.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const binaryArray = [new Uint8Array(), new Uint8Array()] as const; 12 | 13 | await execa('unicorns', {stdin: [binaryArray]}); 14 | execaSync('unicorns', {stdin: [binaryArray]}); 15 | 16 | expectError(await execa('unicorns', {stdout: [binaryArray]})); 17 | expectError(execaSync('unicorns', {stdout: [binaryArray]})); 18 | 19 | expectError(await execa('unicorns', {stderr: [binaryArray]})); 20 | expectError(execaSync('unicorns', {stderr: [binaryArray]})); 21 | 22 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [binaryArray]]}); 23 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [binaryArray]]})); 24 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [[binaryArray]]]}); 25 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [[binaryArray]]]})); 26 | 27 | expectAssignable([binaryArray]); 28 | expectAssignable([binaryArray]); 29 | 30 | expectNotAssignable([binaryArray]); 31 | expectNotAssignable([binaryArray]); 32 | -------------------------------------------------------------------------------- /test-d/stdio/option/array-object.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const objectArray = [{}, {}] as const; 12 | 13 | await execa('unicorns', {stdin: [objectArray]}); 14 | execaSync('unicorns', {stdin: [objectArray]}); 15 | 16 | expectError(await execa('unicorns', {stdout: [objectArray]})); 17 | expectError(execaSync('unicorns', {stdout: [objectArray]})); 18 | 19 | expectError(await execa('unicorns', {stderr: [objectArray]})); 20 | expectError(execaSync('unicorns', {stderr: [objectArray]})); 21 | 22 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [objectArray]]}); 23 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [objectArray]]})); 24 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [[objectArray]]]}); 25 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [[objectArray]]]})); 26 | 27 | expectAssignable([objectArray]); 28 | expectAssignable([objectArray]); 29 | 30 | expectNotAssignable([objectArray]); 31 | expectNotAssignable([objectArray]); 32 | -------------------------------------------------------------------------------- /test-d/stdio/option/array-string.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const stringArray = ['foo', 'bar'] as const; 12 | 13 | await execa('unicorns', {stdin: [stringArray]}); 14 | execaSync('unicorns', {stdin: [stringArray]}); 15 | 16 | expectError(await execa('unicorns', {stdout: [stringArray]})); 17 | expectError(execaSync('unicorns', {stdout: [stringArray]})); 18 | 19 | expectError(await execa('unicorns', {stderr: [stringArray]})); 20 | expectError(execaSync('unicorns', {stderr: [stringArray]})); 21 | 22 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [stringArray]]}); 23 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [stringArray]]})); 24 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [[stringArray]]]}); 25 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [[stringArray]]]})); 26 | 27 | expectAssignable([stringArray]); 28 | expectAssignable([stringArray]); 29 | 30 | expectNotAssignable([stringArray]); 31 | expectNotAssignable([stringArray]); 32 | -------------------------------------------------------------------------------- /test-d/stdio/option/fd-integer-1.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | expectError(await execa('unicorns', {stdin: 1})); 12 | expectError(execaSync('unicorns', {stdin: 1})); 13 | expectError(await execa('unicorns', {stdin: [1]})); 14 | expectError(execaSync('unicorns', {stdin: [1]})); 15 | 16 | await execa('unicorns', {stdout: 1}); 17 | execaSync('unicorns', {stdout: 1}); 18 | await execa('unicorns', {stdout: [1]}); 19 | execaSync('unicorns', {stdout: [1]}); 20 | 21 | await execa('unicorns', {stderr: 1}); 22 | execaSync('unicorns', {stderr: 1}); 23 | await execa('unicorns', {stderr: [1]}); 24 | execaSync('unicorns', {stderr: [1]}); 25 | 26 | expectError(await execa('unicorns', {stdio: 1})); 27 | expectError(execaSync('unicorns', {stdio: 1})); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 1]}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 1]}); 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [1]]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [1]]}); 33 | 34 | expectNotAssignable(1); 35 | expectNotAssignable(1); 36 | expectNotAssignable([1]); 37 | expectNotAssignable([1]); 38 | 39 | expectAssignable(1); 40 | expectAssignable(1); 41 | expectAssignable([1]); 42 | expectAssignable([1]); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/fd-integer-2.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | expectError(await execa('unicorns', {stdin: 2})); 12 | expectError(execaSync('unicorns', {stdin: 2})); 13 | expectError(await execa('unicorns', {stdin: [2]})); 14 | expectError(execaSync('unicorns', {stdin: [2]})); 15 | 16 | await execa('unicorns', {stdout: 2}); 17 | execaSync('unicorns', {stdout: 2}); 18 | await execa('unicorns', {stdout: [2]}); 19 | execaSync('unicorns', {stdout: [2]}); 20 | 21 | await execa('unicorns', {stderr: 2}); 22 | execaSync('unicorns', {stderr: 2}); 23 | await execa('unicorns', {stderr: [2]}); 24 | execaSync('unicorns', {stderr: [2]}); 25 | 26 | expectError(await execa('unicorns', {stdio: 2})); 27 | expectError(execaSync('unicorns', {stdio: 2})); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 2]}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 2]}); 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [2]]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [2]]}); 33 | 34 | expectNotAssignable(2); 35 | expectNotAssignable(2); 36 | expectNotAssignable([2]); 37 | expectNotAssignable([2]); 38 | 39 | expectAssignable(2); 40 | expectAssignable(2); 41 | expectAssignable([2]); 42 | expectAssignable([2]); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/fd-integer-3.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | await execa('unicorns', {stdin: 3}); 12 | execaSync('unicorns', {stdin: 3}); 13 | expectError(await execa('unicorns', {stdin: [3]})); 14 | execaSync('unicorns', {stdin: [3]}); 15 | 16 | await execa('unicorns', {stdout: 3}); 17 | execaSync('unicorns', {stdout: 3}); 18 | expectError(await execa('unicorns', {stdout: [3]})); 19 | execaSync('unicorns', {stdout: [3]}); 20 | 21 | await execa('unicorns', {stderr: 3}); 22 | execaSync('unicorns', {stderr: 3}); 23 | expectError(await execa('unicorns', {stderr: [3]})); 24 | execaSync('unicorns', {stderr: [3]}); 25 | 26 | expectError(await execa('unicorns', {stdio: 3})); 27 | expectError(execaSync('unicorns', {stdio: 3})); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 3]}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 3]}); 31 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [3]]})); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [3]]}); 33 | 34 | expectAssignable(3); 35 | expectAssignable(3); 36 | expectNotAssignable([3]); 37 | expectAssignable([3]); 38 | 39 | expectAssignable(3); 40 | expectAssignable(3); 41 | expectNotAssignable([3]); 42 | expectAssignable([3]); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/file-append.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const fileAppend = {file: './test', append: true} as const; 12 | 13 | await execa('unicorns', {stdin: fileAppend}); 14 | execaSync('unicorns', {stdin: fileAppend}); 15 | await execa('unicorns', {stdin: [fileAppend]}); 16 | execaSync('unicorns', {stdin: [fileAppend]}); 17 | 18 | await execa('unicorns', {stdout: fileAppend}); 19 | execaSync('unicorns', {stdout: fileAppend}); 20 | await execa('unicorns', {stdout: [fileAppend]}); 21 | execaSync('unicorns', {stdout: [fileAppend]}); 22 | 23 | await execa('unicorns', {stderr: fileAppend}); 24 | execaSync('unicorns', {stderr: fileAppend}); 25 | await execa('unicorns', {stderr: [fileAppend]}); 26 | execaSync('unicorns', {stderr: [fileAppend]}); 27 | 28 | expectError(await execa('unicorns', {stdio: fileAppend})); 29 | expectError(execaSync('unicorns', {stdio: fileAppend})); 30 | 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', fileAppend]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', fileAppend]}); 33 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [fileAppend]]}); 34 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [fileAppend]]}); 35 | 36 | expectAssignable(fileAppend); 37 | expectAssignable(fileAppend); 38 | expectAssignable([fileAppend]); 39 | expectAssignable([fileAppend]); 40 | 41 | expectAssignable(fileAppend); 42 | expectAssignable(fileAppend); 43 | expectAssignable([fileAppend]); 44 | expectAssignable([fileAppend]); 45 | -------------------------------------------------------------------------------- /test-d/stdio/option/file-object.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const fileObject = {file: './test'} as const; 12 | 13 | await execa('unicorns', {stdin: fileObject}); 14 | execaSync('unicorns', {stdin: fileObject}); 15 | await execa('unicorns', {stdin: [fileObject]}); 16 | execaSync('unicorns', {stdin: [fileObject]}); 17 | 18 | await execa('unicorns', {stdout: fileObject}); 19 | execaSync('unicorns', {stdout: fileObject}); 20 | await execa('unicorns', {stdout: [fileObject]}); 21 | execaSync('unicorns', {stdout: [fileObject]}); 22 | 23 | await execa('unicorns', {stderr: fileObject}); 24 | execaSync('unicorns', {stderr: fileObject}); 25 | await execa('unicorns', {stderr: [fileObject]}); 26 | execaSync('unicorns', {stderr: [fileObject]}); 27 | 28 | expectError(await execa('unicorns', {stdio: fileObject})); 29 | expectError(execaSync('unicorns', {stdio: fileObject})); 30 | 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', fileObject]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', fileObject]}); 33 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [fileObject]]}); 34 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [fileObject]]}); 35 | 36 | expectAssignable(fileObject); 37 | expectAssignable(fileObject); 38 | expectAssignable([fileObject]); 39 | expectAssignable([fileObject]); 40 | 41 | expectAssignable(fileObject); 42 | expectAssignable(fileObject); 43 | expectAssignable([fileObject]); 44 | expectAssignable([fileObject]); 45 | -------------------------------------------------------------------------------- /test-d/stdio/option/file-url.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const fileUrl = new URL('file:///test'); 12 | 13 | await execa('unicorns', {stdin: fileUrl}); 14 | execaSync('unicorns', {stdin: fileUrl}); 15 | await execa('unicorns', {stdin: [fileUrl]}); 16 | execaSync('unicorns', {stdin: [fileUrl]}); 17 | 18 | await execa('unicorns', {stdout: fileUrl}); 19 | execaSync('unicorns', {stdout: fileUrl}); 20 | await execa('unicorns', {stdout: [fileUrl]}); 21 | execaSync('unicorns', {stdout: [fileUrl]}); 22 | 23 | await execa('unicorns', {stderr: fileUrl}); 24 | execaSync('unicorns', {stderr: fileUrl}); 25 | await execa('unicorns', {stderr: [fileUrl]}); 26 | execaSync('unicorns', {stderr: [fileUrl]}); 27 | 28 | expectError(await execa('unicorns', {stdio: fileUrl})); 29 | expectError(execaSync('unicorns', {stdio: fileUrl})); 30 | 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', fileUrl]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', fileUrl]}); 33 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [fileUrl]]}); 34 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [fileUrl]]}); 35 | 36 | expectAssignable(fileUrl); 37 | expectAssignable(fileUrl); 38 | expectAssignable([fileUrl]); 39 | expectAssignable([fileUrl]); 40 | 41 | expectAssignable(fileUrl); 42 | expectAssignable(fileUrl); 43 | expectAssignable([fileUrl]); 44 | expectAssignable([fileUrl]); 45 | -------------------------------------------------------------------------------- /test-d/stdio/option/generator-empty.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | expectError(await execa('unicorns', {stdin: {}})); 12 | expectError(execaSync('unicorns', {stdin: {}})); 13 | expectError(await execa('unicorns', {stdin: [{}]})); 14 | expectError(execaSync('unicorns', {stdin: [{}]})); 15 | 16 | expectError(await execa('unicorns', {stdout: {}})); 17 | expectError(execaSync('unicorns', {stdout: {}})); 18 | expectError(await execa('unicorns', {stdout: [{}]})); 19 | expectError(execaSync('unicorns', {stdout: [{}]})); 20 | 21 | expectError(await execa('unicorns', {stderr: {}})); 22 | expectError(execaSync('unicorns', {stderr: {}})); 23 | expectError(await execa('unicorns', {stderr: [{}]})); 24 | expectError(execaSync('unicorns', {stderr: [{}]})); 25 | 26 | expectError(await execa('unicorns', {stdio: {}})); 27 | expectError(execaSync('unicorns', {stdio: {}})); 28 | 29 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {}]})); 30 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {}]})); 31 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [{}]]})); 32 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [{}]]})); 33 | 34 | expectNotAssignable({}); 35 | expectNotAssignable({}); 36 | expectNotAssignable([{}]); 37 | expectNotAssignable([{}]); 38 | 39 | expectNotAssignable({}); 40 | expectNotAssignable({}); 41 | expectNotAssignable([{}]); 42 | expectNotAssignable([{}]); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/ignore.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | await execa('unicorns', {stdin: 'ignore'}); 12 | execaSync('unicorns', {stdin: 'ignore'}); 13 | expectError(await execa('unicorns', {stdin: ['ignore']})); 14 | expectError(execaSync('unicorns', {stdin: ['ignore']})); 15 | 16 | await execa('unicorns', {stdout: 'ignore'}); 17 | execaSync('unicorns', {stdout: 'ignore'}); 18 | expectError(await execa('unicorns', {stdout: ['ignore']})); 19 | expectError(execaSync('unicorns', {stdout: ['ignore']})); 20 | 21 | await execa('unicorns', {stderr: 'ignore'}); 22 | execaSync('unicorns', {stderr: 'ignore'}); 23 | expectError(await execa('unicorns', {stderr: ['ignore']})); 24 | expectError(execaSync('unicorns', {stderr: ['ignore']})); 25 | 26 | await execa('unicorns', {stdio: 'ignore'}); 27 | execaSync('unicorns', {stdio: 'ignore'}); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'ignore']}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'ignore']}); 31 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['ignore']]})); 32 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['ignore']]})); 33 | 34 | expectAssignable('ignore'); 35 | expectAssignable('ignore'); 36 | expectNotAssignable(['ignore']); 37 | expectNotAssignable(['ignore']); 38 | 39 | expectAssignable('ignore'); 40 | expectAssignable('ignore'); 41 | expectNotAssignable(['ignore']); 42 | expectNotAssignable(['ignore']); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/inherit.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | await execa('unicorns', {stdin: 'inherit'}); 12 | execaSync('unicorns', {stdin: 'inherit'}); 13 | await execa('unicorns', {stdin: ['inherit']}); 14 | execaSync('unicorns', {stdin: ['inherit']}); 15 | 16 | await execa('unicorns', {stdout: 'inherit'}); 17 | execaSync('unicorns', {stdout: 'inherit'}); 18 | await execa('unicorns', {stdout: ['inherit']}); 19 | execaSync('unicorns', {stdout: ['inherit']}); 20 | 21 | await execa('unicorns', {stderr: 'inherit'}); 22 | execaSync('unicorns', {stderr: 'inherit'}); 23 | await execa('unicorns', {stderr: ['inherit']}); 24 | execaSync('unicorns', {stderr: ['inherit']}); 25 | 26 | await execa('unicorns', {stdio: 'inherit'}); 27 | execaSync('unicorns', {stdio: 'inherit'}); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'inherit']}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'inherit']}); 31 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['inherit']]})); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['inherit']]}); 33 | 34 | expectAssignable('inherit'); 35 | expectAssignable('inherit'); 36 | expectAssignable(['inherit']); 37 | expectAssignable(['inherit']); 38 | 39 | expectAssignable('inherit'); 40 | expectAssignable('inherit'); 41 | expectAssignable(['inherit']); 42 | expectAssignable(['inherit']); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/ipc.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | await execa('unicorns', {stdin: 'ipc'}); 12 | expectError(execaSync('unicorns', {stdin: 'ipc'})); 13 | expectError(await execa('unicorns', {stdin: ['ipc']})); 14 | expectError(execaSync('unicorns', {stdin: ['ipc']})); 15 | 16 | await execa('unicorns', {stdout: 'ipc'}); 17 | expectError(execaSync('unicorns', {stdout: 'ipc'})); 18 | expectError(await execa('unicorns', {stdout: ['ipc']})); 19 | expectError(execaSync('unicorns', {stdout: ['ipc']})); 20 | 21 | await execa('unicorns', {stderr: 'ipc'}); 22 | expectError(execaSync('unicorns', {stderr: 'ipc'})); 23 | expectError(await execa('unicorns', {stderr: ['ipc']})); 24 | expectError(execaSync('unicorns', {stderr: ['ipc']})); 25 | 26 | expectError(await execa('unicorns', {stdio: 'ipc'})); 27 | expectError(execaSync('unicorns', {stdio: 'ipc'})); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'ipc']}); 30 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'ipc']})); 31 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['ipc']]})); 32 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['ipc']]})); 33 | 34 | expectAssignable('ipc'); 35 | expectNotAssignable('ipc'); 36 | expectNotAssignable(['ipc']); 37 | expectNotAssignable(['ipc']); 38 | 39 | expectAssignable('ipc'); 40 | expectNotAssignable('ipc'); 41 | expectNotAssignable(['ipc']); 42 | expectNotAssignable(['ipc']); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/null.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | expectError(await execa('unicorns', {stdin: null})); 12 | expectError(execaSync('unicorns', {stdin: null})); 13 | expectError(await execa('unicorns', {stdin: [null]})); 14 | expectError(execaSync('unicorns', {stdin: [null]})); 15 | 16 | expectError(await execa('unicorns', {stdout: null})); 17 | expectError(execaSync('unicorns', {stdout: null})); 18 | expectError(await execa('unicorns', {stdout: [null]})); 19 | expectError(execaSync('unicorns', {stdout: [null]})); 20 | 21 | expectError(await execa('unicorns', {stderr: null})); 22 | expectError(execaSync('unicorns', {stderr: null})); 23 | expectError(await execa('unicorns', {stderr: [null]})); 24 | expectError(execaSync('unicorns', {stderr: [null]})); 25 | 26 | expectError(await execa('unicorns', {stdio: null})); 27 | expectError(execaSync('unicorns', {stdio: null})); 28 | 29 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', null]})); 30 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', null]})); 31 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [null]]})); 32 | expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [null]]})); 33 | 34 | expectNotAssignable(null); 35 | expectNotAssignable(null); 36 | expectNotAssignable([null]); 37 | expectNotAssignable([null]); 38 | 39 | expectNotAssignable(null); 40 | expectNotAssignable(null); 41 | expectNotAssignable([null]); 42 | expectNotAssignable([null]); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/pipe-inherit.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const pipeInherit = ['pipe', 'inherit'] as const; 12 | 13 | await execa('unicorns', {stdin: pipeInherit}); 14 | execaSync('unicorns', {stdin: pipeInherit}); 15 | 16 | await execa('unicorns', {stdout: pipeInherit}); 17 | execaSync('unicorns', {stdout: pipeInherit}); 18 | 19 | await execa('unicorns', {stderr: pipeInherit}); 20 | execaSync('unicorns', {stderr: pipeInherit}); 21 | 22 | expectError(await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', pipeInherit]})); 23 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', pipeInherit]}); 24 | 25 | expectAssignable(pipeInherit); 26 | expectAssignable(pipeInherit); 27 | 28 | expectAssignable(pipeInherit); 29 | expectAssignable(pipeInherit); 30 | -------------------------------------------------------------------------------- /test-d/stdio/option/pipe-undefined.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const pipeUndefined = ['pipe', undefined] as const; 12 | 13 | await execa('unicorns', {stdin: pipeUndefined}); 14 | execaSync('unicorns', {stdin: pipeUndefined}); 15 | 16 | await execa('unicorns', {stdout: pipeUndefined}); 17 | execaSync('unicorns', {stdout: pipeUndefined}); 18 | 19 | await execa('unicorns', {stderr: pipeUndefined}); 20 | execaSync('unicorns', {stderr: pipeUndefined}); 21 | 22 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', pipeUndefined]}); 23 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', pipeUndefined]}); 24 | 25 | expectAssignable(pipeUndefined); 26 | expectAssignable(pipeUndefined); 27 | 28 | expectAssignable(pipeUndefined); 29 | expectAssignable(pipeUndefined); 30 | -------------------------------------------------------------------------------- /test-d/stdio/option/pipe-wide.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectError, expectNotAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | const pipe = 'pipe' as string; 12 | const pipes = ['pipe'] as string[]; 13 | const pipesOfPipes = [['pipe']] as string[][]; 14 | 15 | expectError(await execa('unicorns', {stdin: pipe})); 16 | expectError(execaSync('unicorns', {stdin: pipe})); 17 | expectError(await execa('unicorns', {stdin: pipes})); 18 | expectError(execaSync('unicorns', {stdin: pipes})); 19 | 20 | expectError(await execa('unicorns', {stdout: pipe})); 21 | expectError(execaSync('unicorns', {stdout: pipe})); 22 | expectError(await execa('unicorns', {stdout: pipes})); 23 | expectError(execaSync('unicorns', {stdout: pipes})); 24 | 25 | expectError(await execa('unicorns', {stderr: pipe})); 26 | expectError(execaSync('unicorns', {stderr: pipe})); 27 | expectError(await execa('unicorns', {stderr: pipes})); 28 | expectError(execaSync('unicorns', {stderr: pipes})); 29 | 30 | expectError(await execa('unicorns', {stdio: pipe})); 31 | expectError(execaSync('unicorns', {stdio: pipe})); 32 | 33 | expectError(await execa('unicorns', {stdio: pipes})); 34 | expectError(execaSync('unicorns', {stdio: pipes})); 35 | expectError(await execa('unicorns', {stdio: pipesOfPipes})); 36 | expectError(execaSync('unicorns', {stdio: pipesOfPipes})); 37 | 38 | expectNotAssignable(pipe); 39 | expectNotAssignable(pipe); 40 | expectNotAssignable(pipes); 41 | expectNotAssignable(pipes); 42 | 43 | expectNotAssignable(pipe); 44 | expectNotAssignable(pipe); 45 | expectNotAssignable(pipes); 46 | expectNotAssignable(pipes); 47 | -------------------------------------------------------------------------------- /test-d/stdio/option/pipe.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | await execa('unicorns', {stdin: 'pipe'}); 12 | execaSync('unicorns', {stdin: 'pipe'}); 13 | await execa('unicorns', {stdin: ['pipe']}); 14 | execaSync('unicorns', {stdin: ['pipe']}); 15 | 16 | await execa('unicorns', {stdout: 'pipe'}); 17 | execaSync('unicorns', {stdout: 'pipe'}); 18 | await execa('unicorns', {stdout: ['pipe']}); 19 | execaSync('unicorns', {stdout: ['pipe']}); 20 | 21 | await execa('unicorns', {stderr: 'pipe'}); 22 | execaSync('unicorns', {stderr: 'pipe'}); 23 | await execa('unicorns', {stderr: ['pipe']}); 24 | execaSync('unicorns', {stderr: ['pipe']}); 25 | 26 | await execa('unicorns', {stdio: 'pipe'}); 27 | execaSync('unicorns', {stdio: 'pipe'}); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe']}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe']}); 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['pipe']]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['pipe']]}); 33 | 34 | expectAssignable('pipe'); 35 | expectAssignable('pipe'); 36 | expectAssignable(['pipe']); 37 | expectAssignable(['pipe']); 38 | 39 | expectAssignable('pipe'); 40 | expectAssignable('pipe'); 41 | expectAssignable(['pipe']); 42 | expectAssignable(['pipe']); 43 | -------------------------------------------------------------------------------- /test-d/stdio/option/undefined.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectAssignable} from 'tsd'; 2 | import { 3 | execa, 4 | execaSync, 5 | type StdinOption, 6 | type StdinSyncOption, 7 | type StdoutStderrOption, 8 | type StdoutStderrSyncOption, 9 | } from '../../../index.js'; 10 | 11 | await execa('unicorns', {stdin: undefined}); 12 | execaSync('unicorns', {stdin: undefined}); 13 | await execa('unicorns', {stdin: [undefined]}); 14 | execaSync('unicorns', {stdin: [undefined]}); 15 | 16 | await execa('unicorns', {stdout: undefined}); 17 | execaSync('unicorns', {stdout: undefined}); 18 | await execa('unicorns', {stdout: [undefined]}); 19 | execaSync('unicorns', {stdout: [undefined]}); 20 | 21 | await execa('unicorns', {stderr: undefined}); 22 | execaSync('unicorns', {stderr: undefined}); 23 | await execa('unicorns', {stderr: [undefined]}); 24 | execaSync('unicorns', {stderr: [undefined]}); 25 | 26 | await execa('unicorns', {stdio: undefined}); 27 | execaSync('unicorns', {stdio: undefined}); 28 | 29 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', undefined]}); 30 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', undefined]}); 31 | await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [undefined]]}); 32 | execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', [undefined]]}); 33 | 34 | expectAssignable(undefined); 35 | expectAssignable(undefined); 36 | expectAssignable([undefined]); 37 | expectAssignable([undefined]); 38 | 39 | expectAssignable(undefined); 40 | expectAssignable(undefined); 41 | expectAssignable([undefined]); 42 | expectAssignable([undefined]); 43 | -------------------------------------------------------------------------------- /test-d/subprocess/all.test-d.ts: -------------------------------------------------------------------------------- 1 | import type {Readable} from 'node:stream'; 2 | import {expectType} from 'tsd'; 3 | import {execa} from '../../index.js'; 4 | 5 | const allSubprocess = execa('unicorns', {all: true}); 6 | expectType(allSubprocess.all); 7 | 8 | const noAllSubprocess = execa('unicorns'); 9 | expectType(noAllSubprocess.all); 10 | -------------------------------------------------------------------------------- /test-d/subprocess/subprocess.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType, expectError, expectAssignable} from 'tsd'; 2 | import {execa, type Subprocess} from '../../index.js'; 3 | 4 | const subprocess = execa('unicorns'); 5 | expectAssignable(subprocess); 6 | 7 | expectType(subprocess.pid); 8 | 9 | expectType(subprocess.kill()); 10 | subprocess.kill('SIGKILL'); 11 | subprocess.kill(undefined); 12 | subprocess.kill(new Error('test')); 13 | subprocess.kill('SIGKILL', new Error('test')); 14 | subprocess.kill(undefined, new Error('test')); 15 | expectError(subprocess.kill(null)); 16 | expectError(subprocess.kill(0n)); 17 | expectError(subprocess.kill('Sigkill')); 18 | expectError(subprocess.kill('sigkill')); 19 | expectError(subprocess.kill('SIGOTHER')); 20 | expectError(subprocess.kill('SIGEMT')); 21 | expectError(subprocess.kill('SIGCLD')); 22 | expectError(subprocess.kill('SIGRT1')); 23 | expectError(subprocess.kill([new Error('test')])); 24 | expectError(subprocess.kill({message: 'test'})); 25 | expectError(subprocess.kill(undefined, {})); 26 | expectError(subprocess.kill('SIGKILL', {})); 27 | expectError(subprocess.kill(null, new Error('test'))); 28 | 29 | const ipcSubprocess = execa('unicorns', {ipc: true}); 30 | expectAssignable(subprocess); 31 | -------------------------------------------------------------------------------- /test/arguments/env.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import test from 'ava'; 3 | import {execa} from '../../index.js'; 4 | import {setFixtureDirectory, PATH_KEY} from '../helpers/fixtures-directory.js'; 5 | 6 | setFixtureDirectory(); 7 | process.env.FOO = 'foo'; 8 | 9 | const isWindows = process.platform === 'win32'; 10 | 11 | test('use environment variables by default', async t => { 12 | const {stdout} = await execa('environment.js'); 13 | t.deepEqual(stdout.split('\n'), ['foo', 'undefined']); 14 | }); 15 | 16 | test('extend environment variables by default', async t => { 17 | const {stdout} = await execa('environment.js', [], {env: {BAR: 'bar', [PATH_KEY]: process.env[PATH_KEY]}}); 18 | t.deepEqual(stdout.split('\n'), ['foo', 'bar']); 19 | }); 20 | 21 | test('do not extend environment with `extendEnv: false`', async t => { 22 | const {stdout} = await execa('environment.js', [], {env: {BAR: 'bar', [PATH_KEY]: process.env[PATH_KEY]}, extendEnv: false}); 23 | t.deepEqual(stdout.split('\n'), ['undefined', 'bar']); 24 | }); 25 | 26 | test('use extend environment with `extendEnv: true` and `shell: true`', async t => { 27 | process.env.TEST = 'test'; 28 | const command = isWindows ? 'echo %TEST%' : 'echo $TEST'; 29 | const {stdout} = await execa(command, {shell: true, env: {}, extendEnv: true}); 30 | t.is(stdout, 'test'); 31 | delete process.env.TEST; 32 | }); 33 | -------------------------------------------------------------------------------- /test/arguments/escape-no-icu.js: -------------------------------------------------------------------------------- 1 | // Mimics Node.js when built without ICU support 2 | // See https://github.com/sindresorhus/execa/issues/1143 3 | globalThis.RegExp = class extends RegExp { 4 | constructor(regExpString, flags) { 5 | if (flags?.includes('u') && regExpString.includes('\\p{')) { 6 | throw new Error('Invalid property name'); 7 | } 8 | 9 | super(regExpString, flags); 10 | } 11 | 12 | static isMocked = true; 13 | }; 14 | 15 | // Execa computes the RegExp when first loaded, so we must delay this import 16 | await import('./escape.js'); 17 | -------------------------------------------------------------------------------- /test/arguments/shell.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import {pathToFileURL} from 'node:url'; 3 | import test from 'ava'; 4 | import which from 'which'; 5 | import {execa} from '../../index.js'; 6 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 7 | import {identity} from '../helpers/stdio.js'; 8 | 9 | setFixtureDirectory(); 10 | process.env.FOO = 'foo'; 11 | 12 | const isWindows = process.platform === 'win32'; 13 | 14 | test('can use `options.shell: true`', async t => { 15 | const {stdout} = await execa('node test/fixtures/noop.js foo', {shell: true}); 16 | t.is(stdout, 'foo'); 17 | }); 18 | 19 | const testShellPath = async (t, mapPath) => { 20 | const shellPath = isWindows ? 'cmd.exe' : 'bash'; 21 | const shell = mapPath(await which(shellPath)); 22 | const {stdout} = await execa('node test/fixtures/noop.js foo', {shell}); 23 | t.is(stdout, 'foo'); 24 | }; 25 | 26 | test('can use `options.shell: string`', testShellPath, identity); 27 | test('can use `options.shell: file URL`', testShellPath, pathToFileURL); 28 | -------------------------------------------------------------------------------- /test/fixtures/all-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | console.log('std\nout'); 5 | console.error('std\nerr'); 6 | process.exitCode = 1; 7 | -------------------------------------------------------------------------------- /test/fixtures/all.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | console.log('std\nout'); 3 | console.error('std\nerr'); 4 | -------------------------------------------------------------------------------- /test/fixtures/command with space.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | console.log(process.argv.slice(2).join('\n')); 5 | -------------------------------------------------------------------------------- /test/fixtures/delay.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | const delay = Number(process.argv[2]); 5 | setTimeout(() => {}, delay); 6 | -------------------------------------------------------------------------------- /test/fixtures/detach.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {execa} from '../../index.js'; 4 | 5 | const subprocess = execa('forever.js', {detached: true}); 6 | console.log(subprocess.pid); 7 | process.exit(0); 8 | -------------------------------------------------------------------------------- /test/fixtures/echo-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | 5 | console.log('stdout'); 6 | console.error('stderr'); 7 | getWriteStream(3).write('fd3'); 8 | process.exitCode = 1; 9 | -------------------------------------------------------------------------------- /test/fixtures/echo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | console.log(process.argv.slice(2).join('\n')); 5 | -------------------------------------------------------------------------------- /test/fixtures/empty.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | -------------------------------------------------------------------------------- /test/fixtures/environment.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | console.log(process.env.FOO); 5 | console.log(process.env.BAR); 6 | -------------------------------------------------------------------------------- /test/fixtures/exit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.exitCode = Number(process.argv[2]); 5 | -------------------------------------------------------------------------------- /test/fixtures/fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.exitCode = 2; 5 | -------------------------------------------------------------------------------- /test/fixtures/fast-exit-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/execa/a31fe55782993f2483d30955a8799ab88e20687c/test/fixtures/fast-exit-darwin -------------------------------------------------------------------------------- /test/fixtures/fast-exit-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/execa/a31fe55782993f2483d30955a8799ab88e20687c/test/fixtures/fast-exit-linux -------------------------------------------------------------------------------- /test/fixtures/forever.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | setTimeout(() => {}, 1e8); 3 | -------------------------------------------------------------------------------- /test/fixtures/graceful-disconnect.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {once} from 'node:events'; 4 | import {getCancelSignal, sendMessage} from 'execa'; 5 | import {onAbortedSignal} from '../helpers/graceful.js'; 6 | 7 | const cancelSignal = await getCancelSignal(); 8 | await onAbortedSignal(cancelSignal); 9 | await Promise.all([ 10 | once(process, 'disconnect'), 11 | sendMessage(cancelSignal.reason), 12 | ]); 13 | -------------------------------------------------------------------------------- /test/fixtures/graceful-echo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage, getOneMessage} from 'execa'; 3 | 4 | await getCancelSignal(); 5 | await sendMessage(await getOneMessage()); 6 | -------------------------------------------------------------------------------- /test/fixtures/graceful-listener.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | 4 | const id = setTimeout(() => {}, 1e8); 5 | const cancelSignal = await getCancelSignal(); 6 | // eslint-disable-next-line unicorn/prefer-add-event-listener 7 | cancelSignal.onabort = async () => { 8 | await sendMessage(cancelSignal.reason); 9 | clearTimeout(id); 10 | }; 11 | 12 | await sendMessage('.'); 13 | -------------------------------------------------------------------------------- /test/fixtures/graceful-none.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal} from 'execa'; 3 | 4 | await getCancelSignal(); 5 | -------------------------------------------------------------------------------- /test/fixtures/graceful-print.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | const cancelSignal = await getCancelSignal(); 6 | await onAbortedSignal(cancelSignal); 7 | console.log(cancelSignal.reason); 8 | -------------------------------------------------------------------------------- /test/fixtures/graceful-ref.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal} from 'execa'; 3 | 4 | const cancelSignal = await getCancelSignal(); 5 | cancelSignal.addEventListener('abort', () => {}); 6 | -------------------------------------------------------------------------------- /test/fixtures/graceful-send-echo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, getOneMessage, sendMessage} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | const message = await getOneMessage(); 6 | const cancelSignal = await getCancelSignal(); 7 | await sendMessage(message); 8 | await onAbortedSignal(cancelSignal); 9 | await sendMessage(cancelSignal.reason); 10 | -------------------------------------------------------------------------------- /test/fixtures/graceful-send-fast.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | 4 | const cancelSignal = await getCancelSignal(); 5 | await sendMessage(cancelSignal.aborted); 6 | -------------------------------------------------------------------------------- /test/fixtures/graceful-send-print.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | const cancelSignal = await getCancelSignal(); 6 | await sendMessage(cancelSignal.aborted); 7 | await onAbortedSignal(cancelSignal); 8 | console.log(cancelSignal.reason); 9 | -------------------------------------------------------------------------------- /test/fixtures/graceful-send-string.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | await getCancelSignal(); 6 | await sendMessage(foobarString); 7 | -------------------------------------------------------------------------------- /test/fixtures/graceful-send-twice.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | const cancelSignal = await getCancelSignal(); 6 | await sendMessage(cancelSignal.aborted); 7 | await onAbortedSignal(cancelSignal); 8 | await sendMessage(cancelSignal.reason); 9 | -------------------------------------------------------------------------------- /test/fixtures/graceful-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | const cancelSignal = await getCancelSignal(); 6 | await onAbortedSignal(cancelSignal); 7 | await sendMessage(cancelSignal.reason); 8 | -------------------------------------------------------------------------------- /test/fixtures/graceful-twice.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal, sendMessage} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | await getCancelSignal(); 6 | const cancelSignal = await getCancelSignal(); 7 | await onAbortedSignal(cancelSignal); 8 | await sendMessage(cancelSignal.reason); 9 | -------------------------------------------------------------------------------- /test/fixtures/graceful-wait.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getCancelSignal} from 'execa'; 3 | import {onAbortedSignal} from '../helpers/graceful.js'; 4 | 5 | const cancelSignal = await getCancelSignal(); 6 | await onAbortedSignal(cancelSignal); 7 | -------------------------------------------------------------------------------- /test/fixtures/hello.cmd: -------------------------------------------------------------------------------- 1 | ECHO Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/hello.sh: -------------------------------------------------------------------------------- 1 | echo Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/ipc-any.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import * as execaExports from '../../index.js'; 4 | 5 | const methodName = process.argv[2]; 6 | await execaExports[methodName](); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-disconnect-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getOneMessage} from '../../index.js'; 4 | 5 | process.disconnect(); 6 | console.log(process.channel); 7 | await getOneMessage(); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-disconnect.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {once} from 'node:events'; 4 | import * as execaExports from '../../index.js'; 5 | 6 | const methodName = process.argv[2]; 7 | 8 | if (process.channel !== null) { 9 | await once(process, 'disconnect'); 10 | } 11 | 12 | await execaExports[methodName](); 13 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | await sendMessage(await getOneMessage()); 6 | process.exitCode = 1; 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-filter.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | import {foobarArray} from '../helpers/input.js'; 4 | 5 | const message = await getOneMessage({filter: message => message === foobarArray[1]}); 6 | await sendMessage(message); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-item.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | 4 | const [message] = await getOneMessage(); 5 | await sendMessage(message); 6 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-twice-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | const message = await getOneMessage(); 6 | await sendMessage(message); 7 | await sendMessage(message); 8 | process.exitCode = 1; 9 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-twice-wait.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {setTimeout} from 'node:timers/promises'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | const message = await getOneMessage(); 6 | await sendMessage(message); 7 | const secondMessage = await getOneMessage(); 8 | await sendMessage(secondMessage); 9 | await setTimeout(1e3); 10 | await sendMessage('.'); 11 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-twice.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | 4 | const message = await getOneMessage(); 5 | await sendMessage(message); 6 | const secondMessage = await getOneMessage(); 7 | await sendMessage(secondMessage); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo-wait.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {setTimeout} from 'node:timers/promises'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | await setTimeout(1e3); 6 | await sendMessage(await getOneMessage()); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-echo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | 4 | await sendMessage(await getOneMessage()); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-get-filter-throw.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getOneMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | await getOneMessage({ 6 | filter() { 7 | throw new Error(foobarString); 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-get-io-error.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getOneMessage} from '../../index.js'; 4 | import {mockSendIoError} from '../helpers/ipc.js'; 5 | 6 | mockSendIoError(process); 7 | console.log(await getOneMessage()); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-get-ref.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getOneMessage} from '../../index.js'; 3 | 4 | getOneMessage(); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-get-send-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | import {alwaysPass} from '../helpers/ipc.js'; 5 | 6 | const filter = argv[2] === 'true' ? alwaysPass : undefined; 7 | 8 | const message = await getOneMessage({filter}); 9 | await Promise.all([ 10 | getOneMessage({filter}), 11 | sendMessage(message), 12 | ]); 13 | -------------------------------------------------------------------------------- /test/fixtures/ipc-get-unref.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getOneMessage} from '../../index.js'; 3 | 4 | getOneMessage({reference: false}); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getOneMessage} from '../../index.js'; 3 | 4 | await getOneMessage(); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-back-serial.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | import {PARALLEL_COUNT} from '../helpers/parallel.js'; 5 | import {alwaysPass, getFirst} from '../helpers/ipc.js'; 6 | 7 | const filter = process.argv[2] === 'true' ? alwaysPass : undefined; 8 | 9 | await sendMessage(await getOneMessage({filter})); 10 | 11 | const promise = sendMessage(1); 12 | process.emit('message', '.'); 13 | await promise; 14 | 15 | const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index + 2); 16 | for (const message of messages) { 17 | // eslint-disable-next-line no-await-in-loop 18 | await sendMessage(message); 19 | } 20 | 21 | const secondPromise = process.argv[3] === 'true' 22 | ? getFirst() 23 | : getOneMessage({filter}); 24 | await sendMessage(await secondPromise); 25 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-back.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | import {PARALLEL_COUNT} from '../helpers/parallel.js'; 5 | import {alwaysPass, getFirst} from '../helpers/ipc.js'; 6 | 7 | const filter = process.argv[2] === 'true' ? alwaysPass : undefined; 8 | 9 | await sendMessage(await getOneMessage({filter})); 10 | 11 | const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index + 1); 12 | await Promise.all([ 13 | ...messages.map(message => sendMessage(message)), 14 | process.emit('message', '.'), 15 | ]); 16 | 17 | const promise = process.argv[3] === 'true' 18 | ? getFirst() 19 | : getOneMessage({filter}); 20 | await sendMessage(await promise); 21 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-break.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getEachMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const iterable = getEachMessage(); 6 | await sendMessage(foobarString); 7 | 8 | // eslint-disable-next-line no-unreachable-loop 9 | for await (const _ of iterable) { 10 | break; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-error.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getEachMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | const echoMessages = async () => { 7 | for await (const message of getEachMessage()) { 8 | if (message === foobarString) { 9 | break; 10 | } 11 | 12 | await sendMessage(message); 13 | } 14 | }; 15 | 16 | process.on('error', () => {}); 17 | // eslint-disable-next-line unicorn/prefer-top-level-await 18 | const promise = echoMessages(); 19 | process.emit('error', new Error(foobarString)); 20 | await promise; 21 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-io-error.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getEachMessage} from '../../index.js'; 4 | import {mockSendIoError} from '../helpers/ipc.js'; 5 | 6 | mockSendIoError(process); 7 | for await (const message of getEachMessage()) { 8 | console.log(message); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-print.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getEachMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | const iterable = getEachMessage(); 7 | 8 | await sendMessage(foobarString); 9 | 10 | for await (const message of iterable) { 11 | if (message === foobarString) { 12 | break; 13 | } 14 | 15 | process.stdout.write(`${message}`); 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-ref.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getEachMessage} from '../../index.js'; 3 | 4 | getEachMessage(); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getEachMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const iterable = getEachMessage(); 6 | await sendMessage(foobarString); 7 | 8 | for await (const message of iterable) { 9 | console.log(message); 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-throw.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getEachMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const iterable = getEachMessage(); 6 | await sendMessage(foobarString); 7 | 8 | // eslint-disable-next-line no-unreachable-loop 9 | for await (const message of iterable) { 10 | throw new Error(message); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-twice.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getEachMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | for (let index = 0; index < 2; index += 1) { 6 | // Intentionally not awaiting `sendMessage()` to avoid a race condition 7 | sendMessage(foobarString); 8 | // eslint-disable-next-line no-await-in-loop 9 | for await (const message of getEachMessage()) { 10 | if (message === foobarString) { 11 | break; 12 | } 13 | 14 | await sendMessage(`${index}${message}`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate-unref.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getEachMessage} from '../../index.js'; 3 | 4 | getEachMessage({reference: false}); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-iterate.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {getEachMessage, sendMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | for await (const message of getEachMessage()) { 6 | if (message === foobarString) { 7 | break; 8 | } 9 | 10 | await sendMessage(message); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-once-disconnect-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getOneMessage} from '../../index.js'; 4 | 5 | await getOneMessage(); 6 | 7 | process.once('disconnect', () => { 8 | console.log('.'); 9 | }); 10 | 11 | process.send('.'); 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-once-disconnect-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | await sendMessage('.'); 6 | 7 | process.once('disconnect', () => { 8 | console.log('.'); 9 | }); 10 | 11 | process.send('.'); 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-once-disconnect.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.send('.'); 5 | 6 | process.once('disconnect', () => { 7 | console.log('.'); 8 | }); 9 | -------------------------------------------------------------------------------- /test/fixtures/ipc-once-message-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getOneMessage} from '../../index.js'; 4 | 5 | await getOneMessage(); 6 | 7 | process.once('message', message => { 8 | console.log(message); 9 | }); 10 | 11 | process.send('.'); 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-once-message-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | await sendMessage('.'); 6 | 7 | process.once('message', message => { 8 | console.log(message); 9 | }); 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-once-message.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.send('.'); 5 | 6 | process.once('message', message => { 7 | console.log(message); 8 | }); 9 | -------------------------------------------------------------------------------- /test/fixtures/ipc-print-many-each.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {getEachMessage} from '../../index.js'; 4 | 5 | const count = Number(argv[2]); 6 | 7 | for (let index = 0; index < count; index += 1) { 8 | // eslint-disable-next-line no-await-in-loop, no-unreachable-loop 9 | for await (const message of getEachMessage()) { 10 | console.log(message); 11 | break; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/ipc-print-many.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {getOneMessage} from '../../index.js'; 4 | import {alwaysPass} from '../helpers/ipc.js'; 5 | 6 | const count = Number(argv[2]); 7 | const filter = argv[3] === 'true' ? alwaysPass : undefined; 8 | 9 | for (let index = 0; index < count; index += 1) { 10 | // eslint-disable-next-line no-await-in-loop 11 | console.log(await getOneMessage({filter})); 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/ipc-process-error.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getOneMessage, sendMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | import {alwaysPass} from '../helpers/ipc.js'; 6 | 7 | process.on('error', () => {}); 8 | const filter = process.argv[2] === 'true' ? alwaysPass : undefined; 9 | const promise = getOneMessage({filter}); 10 | process.emit('error', new Error(foobarString)); 11 | await sendMessage(await promise); 12 | -------------------------------------------------------------------------------- /test/fixtures/ipc-process-send-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {foobarString} from '../helpers/input.js'; 4 | import {getOneMessage} from '../../index.js'; 5 | 6 | await getOneMessage(); 7 | 8 | process.send(foobarString, () => { 9 | console.log('.'); 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixtures/ipc-process-send-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {foobarString} from '../helpers/input.js'; 4 | import {sendMessage} from '../../index.js'; 5 | 6 | await sendMessage('.'); 7 | 8 | process.send(foobarString, () => { 9 | console.log('.'); 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixtures/ipc-process-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | process.send(foobarString, () => { 6 | console.log('.'); 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-replay.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {setTimeout} from 'node:timers/promises'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | await sendMessage(await getOneMessage()); 6 | await setTimeout(1e3); 7 | await sendMessage(await getOneMessage()); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-argv.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | const message = argv[2]; 6 | await sendMessage(message); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-disconnect.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | await sendMessage(foobarString); 7 | 8 | process.disconnect(); 9 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-echo-strict.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {setTimeout} from 'node:timers/promises'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | await sendMessage('.', {strict: true}); 6 | await setTimeout(10); 7 | await sendMessage(await getOneMessage()); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-echo-wait.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {setTimeout} from 'node:timers/promises'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | 5 | await sendMessage('.'); 6 | await setTimeout(1e3); 7 | await sendMessage(await getOneMessage()); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-error.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage} from '../../index.js'; 3 | 4 | await sendMessage(0n); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | await sendMessage(foobarString); 7 | process.exitCode = 1; 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-forever.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | await sendMessage(foobarString); 6 | 7 | setTimeout(() => {}, 1e8); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | await Promise.all([ 6 | getOneMessage(), 7 | sendMessage(foobarString), 8 | ]); 9 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-io-error.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | import {mockSendIoError} from '../helpers/ipc.js'; 5 | 6 | mockSendIoError(process); 7 | await sendMessage('.'); 8 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-json.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | const message = JSON.parse(argv[2]); 6 | await sendMessage(message); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-many.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | const count = Number(argv[2]); 6 | await Promise.all(Array.from({length: count}, (_, index) => sendMessage(index))); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-native.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.send('.'); 5 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-pid.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {execa, sendMessage} from '../../index.js'; 4 | 5 | const cleanup = process.argv[2] === 'true'; 6 | const detached = process.argv[3] === 'true'; 7 | const subprocess = execa('forever.js', {cleanup, detached}); 8 | await sendMessage(subprocess.pid); 9 | await subprocess; 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-print.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage, getOneMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | await sendMessage(foobarString); 7 | 8 | process.stdout.write('.'); 9 | 10 | await getOneMessage(); 11 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-repeat.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | const count = Number(argv[2]); 6 | for (let index = 0; index < count; index += 1) { 7 | // eslint-disable-next-line no-await-in-loop 8 | await sendMessage(index); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-strict-catch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | try { 6 | await sendMessage(foobarString, {strict: true}); 7 | } catch { 8 | await sendMessage(foobarString); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-strict-get.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | await sendMessage(foobarString, {strict: true}); 6 | await sendMessage(await getOneMessage()); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-strict-listen.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage, getOneMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const [message] = await Promise.all([ 6 | getOneMessage(), 7 | sendMessage(foobarString, {strict: true}), 8 | ]); 9 | await sendMessage(message); 10 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-strict.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | await sendMessage(foobarString, {strict: true}); 6 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-twice.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {sendMessage} from '../../index.js'; 3 | import {foobarArray} from '../helpers/input.js'; 4 | 5 | await sendMessage(foobarArray[0]); 6 | await sendMessage(foobarArray[1]); 7 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send-wait-print.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {setTimeout} from 'node:timers/promises'; 3 | import {sendMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | await sendMessage(foobarString); 7 | await setTimeout(100); 8 | console.log('.'); 9 | -------------------------------------------------------------------------------- /test/fixtures/ipc-send.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {argv} from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | const message = argv[2] || foobarString; 7 | await sendMessage(message); 8 | -------------------------------------------------------------------------------- /test/fixtures/max-buffer.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | 5 | const fdNumber = Number(process.argv[2]); 6 | const bytes = '.'.repeat(Number(process.argv[3] || 1e7)); 7 | getWriteStream(fdNumber).write(bytes); 8 | -------------------------------------------------------------------------------- /test/fixtures/nested-double.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execa, getOneMessage} from '../../index.js'; 3 | 4 | const {file, commandArguments, options} = await getOneMessage(); 5 | const firstArguments = commandArguments.slice(0, -1); 6 | const lastArgument = commandArguments.at(-1); 7 | await Promise.all([ 8 | execa(file, [...firstArguments, lastArgument], options), 9 | execa(file, [...firstArguments, lastArgument.toUpperCase()], options), 10 | ]); 11 | -------------------------------------------------------------------------------- /test/fixtures/nested-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execa, getOneMessage} from '../../index.js'; 3 | 4 | const {file, commandArguments, options} = await getOneMessage(); 5 | const subprocess = execa(file, commandArguments, options); 6 | subprocess.kill(new Error(commandArguments[0])); 7 | await subprocess; 8 | -------------------------------------------------------------------------------- /test/fixtures/nested-inherit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {execa} from '../../index.js'; 4 | import {generatorsMap} from '../helpers/map.js'; 5 | 6 | const type = process.argv[2]; 7 | await execa('noop-fd.js', ['1'], {stdout: ['inherit', generatorsMap[type].uppercase()]}); 8 | -------------------------------------------------------------------------------- /test/fixtures/nested-multiple-stdin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {execa, execaSync} from '../../index.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | import {parseStdioOption} from '../helpers/stdio.js'; 6 | 7 | const [stdioOption, isSyncString] = process.argv.slice(2); 8 | const stdin = parseStdioOption(stdioOption); 9 | const execaMethod = isSyncString === 'true' ? execaSync : execa; 10 | await execaMethod('stdin.js', {input: foobarString, stdin, stdout: 'inherit'}); 11 | -------------------------------------------------------------------------------- /test/fixtures/nested-multiple-stdio-output.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {Buffer} from 'node:buffer'; 3 | import process from 'node:process'; 4 | import {execa, execaSync} from '../../index.js'; 5 | import {foobarString} from '../helpers/input.js'; 6 | import {getStdio, parseStdioOption} from '../helpers/stdio.js'; 7 | import {getWriteStream} from '../helpers/fs.js'; 8 | 9 | const [stdioOption, fdNumber, outerFdNumber, isSyncString, encoding] = process.argv.slice(2); 10 | const stdioValue = parseStdioOption(stdioOption); 11 | const execaMethod = isSyncString === 'true' ? execaSync : execa; 12 | const {stdio} = await execaMethod('noop-fd.js', [fdNumber, foobarString], {...getStdio(Number(fdNumber), stdioValue), encoding}); 13 | getWriteStream(Number(outerFdNumber)).write(`nested ${Buffer.from(stdio[fdNumber]).toString()}`); 14 | -------------------------------------------------------------------------------- /test/fixtures/nested-node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | import {execa, execaNode} from '../../index.js'; 5 | 6 | const [fakeExecArgv, execaMethod, nodeOptions, file, ...commandArguments] = process.argv.slice(2); 7 | 8 | if (fakeExecArgv !== '') { 9 | process.execArgv = [fakeExecArgv]; 10 | } 11 | 12 | const filteredNodeOptions = [nodeOptions].filter(Boolean); 13 | const {stdout, stderr} = await (execaMethod === 'execaNode' 14 | ? execaNode(file, commandArguments, {nodeOptions: filteredNodeOptions}) 15 | : execa(file, commandArguments, {nodeOptions: filteredNodeOptions, node: true})); 16 | console.log(stdout); 17 | getWriteStream(3).write(stderr); 18 | -------------------------------------------------------------------------------- /test/fixtures/nested-pipe-file.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execa, getOneMessage} from '../../index.js'; 3 | 4 | const { 5 | file, 6 | commandArguments = [], 7 | options: { 8 | sourceOptions = {}, 9 | destinationFile, 10 | destinationArguments = [], 11 | destinationOptions = {}, 12 | }, 13 | } = await getOneMessage(); 14 | await execa(file, commandArguments, sourceOptions) 15 | .pipe(destinationFile, destinationArguments, destinationOptions); 16 | -------------------------------------------------------------------------------- /test/fixtures/nested-pipe-script.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {$, getOneMessage} from '../../index.js'; 3 | 4 | const { 5 | file, 6 | commandArguments = [], 7 | options: { 8 | sourceOptions = {}, 9 | destinationFile, 10 | destinationArguments = [], 11 | destinationOptions = {}, 12 | }, 13 | } = await getOneMessage(); 14 | await $(sourceOptions)`${file} ${commandArguments}` 15 | .pipe(destinationOptions)`${destinationFile} ${destinationArguments}`; 16 | -------------------------------------------------------------------------------- /test/fixtures/nested-pipe-stream.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {execa, getOneMessage} from '../../index.js'; 4 | 5 | const {file, commandArguments, options: {unpipe, ...options}} = await getOneMessage(); 6 | const subprocess = execa(file, commandArguments, options); 7 | subprocess.stdout.pipe(process.stdout); 8 | if (unpipe) { 9 | subprocess.stdout.unpipe(process.stdout); 10 | } 11 | 12 | await subprocess; 13 | -------------------------------------------------------------------------------- /test/fixtures/nested-pipe-subprocess.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execa, getOneMessage} from '../../index.js'; 3 | 4 | const {file, commandArguments, options: {unpipe, ...options}} = await getOneMessage(); 5 | const source = execa(file, commandArguments, options); 6 | const destination = execa('stdin.js'); 7 | const controller = new AbortController(); 8 | const subprocess = source.pipe(destination, {unpipeSignal: controller.signal}); 9 | if (unpipe) { 10 | controller.abort(); 11 | destination.stdin.end(); 12 | } 13 | 14 | try { 15 | await subprocess; 16 | } catch {} 17 | -------------------------------------------------------------------------------- /test/fixtures/nested-pipe-subprocesses.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execa, getOneMessage} from '../../index.js'; 3 | 4 | const { 5 | file, 6 | commandArguments = [], 7 | options: { 8 | sourceOptions = {}, 9 | destinationFile, 10 | destinationArguments = [], 11 | destinationOptions = {}, 12 | }, 13 | } = await getOneMessage(); 14 | await execa(file, commandArguments, sourceOptions) 15 | .pipe(execa(destinationFile, destinationArguments, destinationOptions)); 16 | -------------------------------------------------------------------------------- /test/fixtures/nested-pipe-verbose.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execa, getOneMessage, sendMessage} from '../../index.js'; 3 | import {getNestedOptions} from '../helpers/nested.js'; 4 | 5 | const { 6 | file, 7 | commandArguments = [], 8 | options: {destinationFile, destinationArguments, ...options}, 9 | optionsFixture, 10 | optionsInput, 11 | } = await getOneMessage(); 12 | 13 | const commandOptions = await getNestedOptions(options, optionsFixture, optionsInput); 14 | 15 | try { 16 | const result = await execa(file, commandArguments, commandOptions) 17 | .pipe(destinationFile, destinationArguments, commandOptions); 18 | await sendMessage(result); 19 | } catch (error) { 20 | await sendMessage(error); 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/nested-stdio.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {execa, execaSync} from '../../index.js'; 4 | import {parseStdioOption} from '../helpers/stdio.js'; 5 | 6 | const [stdioOption, fdNumber, isSyncString, file, ...commandArguments] = process.argv.slice(2); 7 | const optionValue = parseStdioOption(stdioOption); 8 | const isSync = isSyncString === 'true'; 9 | const stdio = ['ignore', 'inherit', 'inherit']; 10 | stdio[fdNumber] = optionValue; 11 | const execaMethod = isSync ? execaSync : execa; 12 | const returnValue = execaMethod(file, [`${fdNumber}`, ...commandArguments], {stdio}); 13 | 14 | const shouldPipe = Array.isArray(optionValue) && optionValue.includes('pipe'); 15 | const fdReturnValue = returnValue.stdio[fdNumber]; 16 | const hasPipe = fdReturnValue !== undefined && fdReturnValue !== null; 17 | 18 | if (shouldPipe && !hasPipe) { 19 | throw new Error(`subprocess.stdio[${fdNumber}] is null.`); 20 | } 21 | 22 | if (!shouldPipe && hasPipe) { 23 | throw new Error(`subprocess.stdio[${fdNumber}] should be null.`); 24 | } 25 | 26 | if (!isSync) { 27 | await returnValue; 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/nested-sync-tty.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import tty from 'node:tty'; 4 | import {execa, execaSync} from '../../index.js'; 5 | 6 | const mockIsatty = fdNumber => { 7 | tty.isatty = fdNumberArgument => fdNumber === fdNumberArgument; 8 | }; 9 | 10 | const originalIsatty = tty.isatty; 11 | const unmockIsatty = () => { 12 | tty.isatty = originalIsatty; 13 | }; 14 | 15 | const [options, isSync, file, fdNumber, ...commandArguments] = process.argv.slice(2); 16 | mockIsatty(Number(fdNumber)); 17 | 18 | try { 19 | if (isSync === 'true') { 20 | execaSync(file, [fdNumber, ...commandArguments], JSON.parse(options)); 21 | } else { 22 | await execa(file, [fdNumber, ...commandArguments], JSON.parse(options)); 23 | } 24 | } finally { 25 | unmockIsatty(); 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/nested-write.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {writeFile} from 'node:fs/promises'; 3 | import {text} from 'node:stream/consumers'; 4 | import process from 'node:process'; 5 | 6 | const [filePath, bytes] = process.argv.slice(2); 7 | const stdinString = await text(process.stdin); 8 | await writeFile(filePath, `${stdinString} ${bytes}`); 9 | -------------------------------------------------------------------------------- /test/fixtures/nested.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { 3 | execa, 4 | execaSync, 5 | getOneMessage, 6 | sendMessage, 7 | } from '../../index.js'; 8 | import {getNestedOptions} from '../helpers/nested.js'; 9 | 10 | const { 11 | isSync, 12 | file, 13 | commandArguments, 14 | options, 15 | optionsFixture, 16 | optionsInput, 17 | } = await getOneMessage(); 18 | 19 | const commandOptions = await getNestedOptions(options, optionsFixture, optionsInput); 20 | 21 | try { 22 | const result = isSync 23 | ? execaSync(file, commandArguments, commandOptions) 24 | : await execa(file, commandArguments, commandOptions); 25 | await sendMessage(result); 26 | } catch (error) { 27 | await sendMessage(error); 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-event.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type, eventProperty}) => ({ 2 | verbose: (verboseLine, verboseObject) => verboseObject.type === type ? `${verboseObject[eventProperty]}` : undefined, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-json.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type}) => ({ 2 | verbose: (verboseLine, verboseObject) => verboseObject.type === type ? JSON.stringify(verboseObject) : undefined, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-object-stdout.js: -------------------------------------------------------------------------------- 1 | import {foobarObject} from '../../helpers/input.js'; 2 | 3 | export const getOptions = () => ({ 4 | verbose: (verboseLine, {type}) => type === 'output' ? verboseLine : undefined, 5 | stdout: { 6 | * transform() { 7 | yield foobarObject; 8 | }, 9 | objectMode: true, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-option.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type, optionName}) => ({ 2 | verbose: (verboseLine, verboseObject) => verboseObject.type === type ? `${verboseObject.options[optionName]}` : undefined, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-print-function.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type, fdNumber, secondFdNumber}) => ({ 2 | verbose: { 3 | [fdNumber](verboseLine, verboseObject) { 4 | if (verboseObject.type === type) { 5 | console.warn(verboseLine); 6 | } 7 | }, 8 | [secondFdNumber]: 'none', 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-print-multiple.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type, fdNumber, secondFdNumber}) => ({ 2 | verbose: { 3 | [fdNumber](verboseLine, verboseObject) { 4 | if (verboseObject.type === type) { 5 | console.warn(verboseLine); 6 | } 7 | }, 8 | [secondFdNumber]() {}, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-print.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type, fdNumber}) => ({ 2 | verbose: setFdSpecific( 3 | fdNumber, 4 | (verboseLine, verboseObject) => verboseObject.type === type ? verboseLine : undefined, 5 | ), 6 | }); 7 | 8 | const setFdSpecific = (fdNumber, option) => fdNumber === undefined 9 | ? option 10 | : {[fdNumber]: option}; 11 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-result.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type}) => ({ 2 | verbose: (verboseLine, verboseObject) => verboseObject.type === type ? JSON.stringify(verboseObject.result) : undefined, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-return.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({verboseOutput}) => ({ 2 | verbose(verboseLine, {type}) { 3 | return type === 'command' ? verboseOutput : undefined; 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-throw.js: -------------------------------------------------------------------------------- 1 | export const getOptions = ({type, errorMessage}) => ({ 2 | verbose(verboseLine, verboseObject) { 3 | if (verboseObject.type === type) { 4 | throw new Error(errorMessage); 5 | } 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/nested/custom-uppercase.js: -------------------------------------------------------------------------------- 1 | export const getOptions = () => ({ 2 | verbose(verboseLine, {type}) { 3 | return type === 'command' ? verboseLine.replace('noop', 'NOOP') : undefined; 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/nested/file-url.js: -------------------------------------------------------------------------------- 1 | import {pathToFileURL} from 'node:url'; 2 | 3 | export const getOptions = ({stdout: {file}}) => ({stdout: pathToFileURL(file)}); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/generator-big-array.js: -------------------------------------------------------------------------------- 1 | import {getOutputGenerator} from '../../helpers/generator.js'; 2 | 3 | const bigArray = Array.from({length: 100}, (_, index) => index); 4 | export const getOptions = () => ({stdout: getOutputGenerator(bigArray)(true)}); 5 | -------------------------------------------------------------------------------- /test/fixtures/nested/generator-duplex.js: -------------------------------------------------------------------------------- 1 | import {uppercaseBufferDuplex} from '../../helpers/duplex.js'; 2 | 3 | export const getOptions = () => ({stdout: uppercaseBufferDuplex()}); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/generator-object.js: -------------------------------------------------------------------------------- 1 | import {outputObjectGenerator} from '../../helpers/generator.js'; 2 | 3 | export const getOptions = () => ({stdout: outputObjectGenerator()}); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/generator-string-object.js: -------------------------------------------------------------------------------- 1 | import {getOutputGenerator} from '../../helpers/generator.js'; 2 | import {simpleFull} from '../../helpers/lines.js'; 3 | 4 | export const getOptions = () => ({stdout: getOutputGenerator(simpleFull)(true)}); 5 | -------------------------------------------------------------------------------- /test/fixtures/nested/generator-uppercase.js: -------------------------------------------------------------------------------- 1 | import {uppercaseGenerator} from '../../helpers/generator.js'; 2 | 3 | export const getOptions = () => ({stdout: uppercaseGenerator()}); 4 | -------------------------------------------------------------------------------- /test/fixtures/nested/writable-web.js: -------------------------------------------------------------------------------- 1 | export const getOptions = () => ({stdout: new WritableStream()}); 2 | -------------------------------------------------------------------------------- /test/fixtures/nested/writable.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | 3 | export const getOptions = () => ({stdout: process.stdout}); 4 | -------------------------------------------------------------------------------- /test/fixtures/no-await.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {once} from 'node:events'; 4 | import {execa} from '../../index.js'; 5 | 6 | const [options, file, ...commandArguments] = process.argv.slice(2); 7 | execa(file, commandArguments, JSON.parse(options)); 8 | const [error] = await once(process, 'unhandledRejection'); 9 | console.log(error.shortMessage); 10 | -------------------------------------------------------------------------------- /test/fixtures/no-killable.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | const noop = () => {}; 6 | 7 | process.on('SIGTERM', noop); 8 | process.on('SIGINT', noop); 9 | 10 | await sendMessage(''); 11 | console.log('.'); 12 | 13 | setTimeout(noop, 1e8); 14 | -------------------------------------------------------------------------------- /test/fixtures/non-executable.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | -------------------------------------------------------------------------------- /test/fixtures/noop-132.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | console.log(1); 3 | console.warn(2); 4 | 5 | setTimeout(() => { 6 | console.log(3); 7 | }, 1000); 8 | -------------------------------------------------------------------------------- /test/fixtures/noop-both-fail-strict.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | const stdoutBytes = process.argv[2]; 5 | const stderrBytes = process.argv[3]; 6 | process.stdout.write(stdoutBytes); 7 | process.stderr.write(stderrBytes); 8 | process.exitCode = 1; 9 | -------------------------------------------------------------------------------- /test/fixtures/noop-both-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const bytes = process.argv[2] || foobarString; 6 | console.log(bytes); 7 | console.error(bytes); 8 | process.exitCode = 1; 9 | -------------------------------------------------------------------------------- /test/fixtures/noop-both.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const bytes = process.argv[2] || foobarString; 6 | const bytesStderr = process.argv[3] || bytes; 7 | console.log(bytes); 8 | console.error(bytesStderr); 9 | -------------------------------------------------------------------------------- /test/fixtures/noop-continuous.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {setTimeout} from 'node:timers/promises'; 4 | 5 | const bytes = process.argv[2]; 6 | for (const character of bytes) { 7 | console.log(character); 8 | // eslint-disable-next-line no-await-in-loop 9 | await setTimeout(100); 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/noop-delay.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {setTimeout} from 'node:timers/promises'; 4 | import {getWriteStream} from '../helpers/fs.js'; 5 | import {foobarString} from '../helpers/input.js'; 6 | 7 | const fdNumber = Number(process.argv[2]); 8 | const bytes = process.argv[3] || foobarString; 9 | getWriteStream(fdNumber).write(bytes); 10 | await setTimeout(100); 11 | -------------------------------------------------------------------------------- /test/fixtures/noop-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | const fdNumber = Number(process.argv[2]); 7 | const bytes = process.argv[3] || foobarString; 8 | getWriteStream(fdNumber).write(bytes); 9 | process.exitCode = 2; 10 | -------------------------------------------------------------------------------- /test/fixtures/noop-fd-ipc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {promisify} from 'node:util'; 4 | import {sendMessage} from '../../index.js'; 5 | import {getWriteStream} from '../helpers/fs.js'; 6 | import {foobarString} from '../helpers/input.js'; 7 | 8 | const fdNumber = Number(process.argv[2]); 9 | const bytes = process.argv[3] || foobarString; 10 | const stream = getWriteStream(fdNumber); 11 | await promisify(stream.write.bind(stream))(bytes); 12 | await sendMessage(''); 13 | -------------------------------------------------------------------------------- /test/fixtures/noop-fd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | const fdNumber = Number(process.argv[2]); 7 | const bytes = process.argv[3] || foobarString; 8 | getWriteStream(fdNumber).write(bytes); 9 | -------------------------------------------------------------------------------- /test/fixtures/noop-forever.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | const bytes = process.argv[2]; 5 | console.log(bytes); 6 | setTimeout(() => {}, 1e8); 7 | -------------------------------------------------------------------------------- /test/fixtures/noop-progressive.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {setTimeout} from 'node:timers/promises'; 4 | 5 | const bytes = process.argv[2]; 6 | for (const character of bytes) { 7 | process.stdout.write(character); 8 | // eslint-disable-next-line no-await-in-loop 9 | await setTimeout(10); 10 | } 11 | 12 | process.stdout.write('\n'); 13 | -------------------------------------------------------------------------------- /test/fixtures/noop-repeat.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | import {foobarString} from '../helpers/input.js'; 5 | 6 | const fdNumber = Number(process.argv[2]) || 1; 7 | const bytes = process.argv[3] || foobarString; 8 | setInterval(() => { 9 | getWriteStream(fdNumber).write(bytes); 10 | }, 10); 11 | -------------------------------------------------------------------------------- /test/fixtures/noop-stdin-double.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {text} from 'node:stream/consumers'; 4 | 5 | const bytes = process.argv[2]; 6 | const stdinString = await text(process.stdin); 7 | console.log(`${stdinString} ${bytes}`); 8 | -------------------------------------------------------------------------------- /test/fixtures/noop-stdin-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {text} from 'node:stream/consumers'; 4 | import {getWriteStream} from '../helpers/fs.js'; 5 | 6 | const fdNumber = Number(process.argv[2]); 7 | const stdinString = await text(process.stdin); 8 | getWriteStream(fdNumber).write(stdinString); 9 | process.exitCode = 2; 10 | -------------------------------------------------------------------------------- /test/fixtures/noop-stdin-fd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getWriteStream} from '../helpers/fs.js'; 4 | 5 | const fdNumber = Number(process.argv[2]); 6 | process.stdin.pipe(getWriteStream(fdNumber)); 7 | -------------------------------------------------------------------------------- /test/fixtures/noop-verbose.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {sendMessage} from '../../index.js'; 4 | 5 | const bytes = process.argv[2]; 6 | console.log(bytes); 7 | 8 | try { 9 | await sendMessage(bytes); 10 | } catch {} 11 | 12 | process.exitCode = 2; 13 | -------------------------------------------------------------------------------- /test/fixtures/noop.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {foobarString} from '../helpers/input.js'; 4 | 5 | const bytes = process.argv[2] || foobarString; 6 | console.log(bytes); 7 | -------------------------------------------------------------------------------- /test/fixtures/stdin-both.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.stdin.pipe(process.stdout); 5 | process.stdin.pipe(process.stderr); 6 | -------------------------------------------------------------------------------- /test/fixtures/stdin-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.stdin.pipe(process.stdout); 5 | process.exitCode = 2; 6 | -------------------------------------------------------------------------------- /test/fixtures/stdin-fd-both.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getReadStream} from '../helpers/fs.js'; 4 | 5 | const fdNumber = Number(process.argv[2]); 6 | process.stdin.pipe(process.stdout); 7 | getReadStream(fdNumber).pipe(process.stdout); 8 | -------------------------------------------------------------------------------- /test/fixtures/stdin-fd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getReadStream} from '../helpers/fs.js'; 4 | 5 | const fdNumber = Number(process.argv[2]); 6 | getReadStream(fdNumber).pipe(process.stdout); 7 | -------------------------------------------------------------------------------- /test/fixtures/stdin-script.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {$} from '../../index.js'; 3 | import {FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js'; 4 | 5 | await $({stdout: 'inherit'})`node ${`${FIXTURES_DIRECTORY}/stdin.js`}`; 6 | -------------------------------------------------------------------------------- /test/fixtures/stdin-twice-both.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {getReadStream} from '../helpers/fs.js'; 4 | 5 | const fdNumber = Number(process.argv[2]); 6 | 7 | process.stdin.pipe(process.stdout); 8 | getReadStream(fdNumber).pipe(process.stderr); 9 | -------------------------------------------------------------------------------- /test/fixtures/stdin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | 4 | process.stdin.pipe(process.stdout); 5 | -------------------------------------------------------------------------------- /test/fixtures/verbose-script.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {$} from '../../index.js'; 3 | 4 | const $$ = $({stdio: 'inherit'}); 5 | await $$`node -e console.error(1)`; 6 | await $$({reject: false})`node -e process.exit(2)`; 7 | -------------------------------------------------------------------------------- /test/fixtures/wait-fail.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import process from 'node:process'; 3 | import {setTimeout} from 'node:timers/promises'; 4 | 5 | await setTimeout(1e3); 6 | process.exitCode = 2; 7 | -------------------------------------------------------------------------------- /test/fixtures/worker.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {workerData, parentPort} from 'node:worker_threads'; 3 | import {spawnParentProcess} from '../helpers/nested.js'; 4 | 5 | try { 6 | const result = await spawnParentProcess(workerData); 7 | parentPort.postMessage(result); 8 | } catch (error) { 9 | parentPort.postMessage(error); 10 | } 11 | -------------------------------------------------------------------------------- /test/helpers/early-error.js: -------------------------------------------------------------------------------- 1 | import {execa, execaSync} from '../../index.js'; 2 | 3 | export const earlyErrorOptions = {detached: 'true'}; 4 | export const getEarlyErrorSubprocess = options => execa('empty.js', {...earlyErrorOptions, ...options}); 5 | export const earlyErrorOptionsSync = {maxBuffer: false}; 6 | export const getEarlyErrorSubprocessSync = options => execaSync('empty.js', {...earlyErrorOptionsSync, ...options}); 7 | 8 | export const expectedEarlyError = {code: 'ERR_INVALID_ARG_TYPE'}; 9 | export const expectedEarlyErrorSync = {code: 'ERR_OUT_OF_RANGE'}; 10 | -------------------------------------------------------------------------------- /test/helpers/encoding.js: -------------------------------------------------------------------------------- 1 | const textEncoder = new TextEncoder(); 2 | 3 | export const multibyteChar = '\u{1F984}'; 4 | export const multibyteString = `${multibyteChar}${multibyteChar}`; 5 | export const multibyteUint8Array = textEncoder.encode(multibyteString); 6 | export const breakingLength = multibyteUint8Array.length * 0.75; 7 | export const brokenSymbol = '\uFFFD'; 8 | -------------------------------------------------------------------------------- /test/helpers/file-path.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import process from 'node:process'; 3 | 4 | export const getAbsolutePath = file => ({file}); 5 | export const getRelativePath = filePath => ({file: path.relative('.', filePath)}); 6 | // Defined as getter so call to toString is not cached 7 | export const getDenoNodePath = () => Object.freeze({ 8 | __proto__: String.prototype, 9 | toString() { 10 | return process.execPath; 11 | }, 12 | get length() { 13 | return this.toString().length; 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /test/helpers/fixtures-directory.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import process from 'node:process'; 3 | import {fileURLToPath} from 'node:url'; 4 | import logProcessErrors from 'log-process-errors'; 5 | import pathKey from 'path-key'; 6 | 7 | // Make tests fail if any warning (such as a deprecation warning) is emitted 8 | logProcessErrors({ 9 | onError(error, event) { 10 | if (event === 'warning') { 11 | throw error; 12 | } 13 | }, 14 | }); 15 | 16 | export const PATH_KEY = pathKey(); 17 | export const FIXTURES_DIRECTORY_URL = new URL('../fixtures/', import.meta.url); 18 | // @todo: use import.meta.dirname after dropping support for Node <20.11.0 19 | export const FIXTURES_DIRECTORY = path.resolve(fileURLToPath(FIXTURES_DIRECTORY_URL)); 20 | 21 | // Add the fixtures directory to PATH so fixtures can be executed without adding 22 | // `node`. This is only meant to make writing tests simpler. 23 | export const setFixtureDirectory = () => { 24 | process.env[PATH_KEY] = FIXTURES_DIRECTORY + path.delimiter + process.env[PATH_KEY]; 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /test/helpers/fs.js: -------------------------------------------------------------------------------- 1 | import {createReadStream, createWriteStream} from 'node:fs'; 2 | import process from 'node:process'; 3 | 4 | export const getReadStream = fdNumber => fdNumber === 0 5 | ? process.stdin 6 | : createReadStream(undefined, {fd: fdNumber}); 7 | 8 | export const getWriteStream = fdNumber => { 9 | if (fdNumber === 1) { 10 | return process.stdout; 11 | } 12 | 13 | if (fdNumber === 2) { 14 | return process.stderr; 15 | } 16 | 17 | return createWriteStream(undefined, {fd: fdNumber}); 18 | }; 19 | -------------------------------------------------------------------------------- /test/helpers/graceful.js: -------------------------------------------------------------------------------- 1 | import {setTimeout} from 'node:timers/promises'; 2 | 3 | // Combines `util.aborted()` and `events.addAbortListener()`: promise-based and cleaned up with a stop signal 4 | export const onAbortedSignal = async signal => { 5 | try { 6 | await setTimeout(1e8, undefined, {signal}); 7 | } catch {} 8 | }; 9 | -------------------------------------------------------------------------------- /test/helpers/input.js: -------------------------------------------------------------------------------- 1 | import {Buffer} from 'node:buffer'; 2 | import {inspect} from 'node:util'; 3 | 4 | const textEncoder = new TextEncoder(); 5 | 6 | export const foobarString = 'foobar'; 7 | export const foobarArray = ['foo', 'bar']; 8 | export const foobarUint8Array = textEncoder.encode(foobarString); 9 | export const foobarArrayBuffer = foobarUint8Array.buffer; 10 | export const foobarUint16Array = new Uint16Array(foobarArrayBuffer); 11 | export const foobarBuffer = Buffer.from(foobarString); 12 | const foobarUtf16Buffer = Buffer.from(foobarString, 'utf16le'); 13 | export const foobarUtf16Uint8Array = new Uint8Array(foobarUtf16Buffer); 14 | export const foobarDataView = new DataView(foobarArrayBuffer); 15 | export const foobarHex = foobarBuffer.toString('hex'); 16 | export const foobarUppercase = foobarString.toUpperCase(); 17 | export const foobarUppercaseUint8Array = textEncoder.encode(foobarUppercase); 18 | export const foobarUppercaseHex = Buffer.from(foobarUppercase).toString('hex'); 19 | export const foobarObject = {foo: 'bar'}; 20 | export const foobarObjectString = JSON.stringify(foobarObject); 21 | export const foobarObjectInspect = inspect(foobarObject); 22 | -------------------------------------------------------------------------------- /test/helpers/ipc.js: -------------------------------------------------------------------------------- 1 | import {getEachMessage} from '../../index.js'; 2 | import {foobarString} from './input.js'; 3 | 4 | // @todo: replace with Array.fromAsync(subprocess.getEachMessage()) after dropping support for Node <22.0.0 5 | export const iterateAllMessages = async subprocess => { 6 | const messages = []; 7 | for await (const message of subprocess.getEachMessage()) { 8 | messages.push(message); 9 | } 10 | 11 | return messages; 12 | }; 13 | 14 | export const subprocessGetFirst = async subprocess => { 15 | const [firstMessage] = await iterateAllMessages(subprocess); 16 | return firstMessage; 17 | }; 18 | 19 | export const getFirst = async () => { 20 | // eslint-disable-next-line no-unreachable-loop 21 | for await (const message of getEachMessage()) { 22 | return message; 23 | } 24 | }; 25 | 26 | export const subprocessGetOne = (subprocess, options) => subprocess.getOneMessage(options); 27 | 28 | export const alwaysPass = () => true; 29 | 30 | // `process.send()` can fail due to I/O errors. 31 | // However, I/O errors are seldom and hard to trigger predictably. 32 | // So we mock them. 33 | export const mockSendIoError = anyProcess => { 34 | const error = new Error(foobarString); 35 | anyProcess.send = () => { 36 | throw error; 37 | }; 38 | 39 | return error; 40 | }; 41 | -------------------------------------------------------------------------------- /test/helpers/listeners.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | 3 | export const assertMaxListeners = t => { 4 | let warning; 5 | const captureWarning = warningArgument => { 6 | warning = warningArgument; 7 | }; 8 | 9 | process.once('warning', captureWarning); 10 | return () => { 11 | t.is(warning, undefined); 12 | process.removeListener('warning', captureWarning); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /test/helpers/max-buffer.js: -------------------------------------------------------------------------------- 1 | import {execa, execaSync} from '../../index.js'; 2 | 3 | export const maxBuffer = 10; 4 | 5 | export const assertErrorMessage = (t, shortMessage, {execaMethod = execa, length = maxBuffer, fdNumber = 1, unit = 'characters'} = {}) => { 6 | const [expectedStreamName, expectedUnit] = execaMethod === execaSync 7 | ? ['output', 'bytes'] 8 | : [STREAM_NAMES[fdNumber], unit]; 9 | t.true(shortMessage.includes(`${expectedStreamName} was larger than ${length} ${expectedUnit}`)); 10 | }; 11 | 12 | const STREAM_NAMES = ['stdin', 'stdout', 'stderr', 'stdio[3]']; 13 | -------------------------------------------------------------------------------- /test/helpers/node-version.js: -------------------------------------------------------------------------------- 1 | import {version} from 'node:process'; 2 | 3 | export const majorNodeVersion = Number(version.split('.')[0].slice(1)); 4 | -------------------------------------------------------------------------------- /test/helpers/override-promise.js: -------------------------------------------------------------------------------- 1 | // Can't use `test.before`, because `ava` needs `Promise`. 2 | const nativePromise = Promise; 3 | globalThis.Promise = class BrokenPromise { 4 | then() { // eslint-disable-line unicorn/no-thenable 5 | throw new Error('error'); 6 | } 7 | }; 8 | 9 | export function restorePromise() { 10 | globalThis.Promise = nativePromise; 11 | } 12 | -------------------------------------------------------------------------------- /test/helpers/parallel.js: -------------------------------------------------------------------------------- 1 | import isInCi from 'is-in-ci'; 2 | 3 | export const PARALLEL_COUNT = isInCi ? 10 : 100; 4 | -------------------------------------------------------------------------------- /test/helpers/pipe.js: -------------------------------------------------------------------------------- 1 | export const assertPipeError = async (t, pipePromise, message) => { 2 | const error = await t.throwsAsync(pipePromise); 3 | 4 | t.is(error.command, 'source.pipe(destination)'); 5 | t.is(error.escapedCommand, error.command); 6 | 7 | t.is(typeof error.cwd, 'string'); 8 | t.true(error.failed); 9 | t.false(error.timedOut); 10 | t.false(error.isCanceled); 11 | t.false(error.isTerminated); 12 | t.is(error.exitCode, undefined); 13 | t.is(error.signal, undefined); 14 | t.is(error.signalDescription, undefined); 15 | t.is(error.stdout, undefined); 16 | t.is(error.stderr, undefined); 17 | t.is(error.all, undefined); 18 | t.deepEqual(error.stdio, Array.from({length: error.stdio.length})); 19 | t.deepEqual(error.pipedFrom, []); 20 | 21 | t.true(error.shortMessage.includes(`Command failed: ${error.command}`)); 22 | t.true(error.shortMessage.includes(error.originalMessage)); 23 | t.true(error.message.includes(error.shortMessage)); 24 | 25 | t.true(error.originalMessage.includes(message)); 26 | }; 27 | -------------------------------------------------------------------------------- /test/helpers/run.js: -------------------------------------------------------------------------------- 1 | import {execa, execaSync, $} from '../../index.js'; 2 | 3 | export const runExeca = (file, options) => execa(file, options); 4 | export const runExecaSync = (file, options) => execaSync(file, options); 5 | export const runScript = (file, options) => $(options)`${file}`; 6 | export const runScriptSync = (file, options) => $(options).sync`${file}`; 7 | -------------------------------------------------------------------------------- /test/helpers/stdio.js: -------------------------------------------------------------------------------- 1 | import process, {platform} from 'node:process'; 2 | import {noopReadable} from './stream.js'; 3 | 4 | export const identity = value => value; 5 | 6 | export const getStdio = (fdNumberOrName, stdioOption, length = 3) => { 7 | if (typeof fdNumberOrName === 'string') { 8 | return {[fdNumberOrName]: stdioOption}; 9 | } 10 | 11 | const stdio = Array.from({length}).fill('pipe'); 12 | stdio[fdNumberOrName] = stdioOption; 13 | return {stdio}; 14 | }; 15 | 16 | export const fullStdio = getStdio(3, 'pipe'); 17 | export const fullReadableStdio = () => getStdio(3, ['pipe', noopReadable()]); 18 | 19 | export const STANDARD_STREAMS = [process.stdin, process.stdout, process.stderr]; 20 | 21 | export const prematureClose = {code: 'ERR_STREAM_PREMATURE_CLOSE'}; 22 | 23 | const isWindows = platform === 'win32'; 24 | 25 | export const assertEpipe = (t, stderr, fdNumber = 1) => { 26 | if (fdNumber === 1 && !isWindows) { 27 | t.true(stderr.includes('EPIPE')); 28 | } 29 | }; 30 | 31 | export const parseStdioOption = stdioOption => { 32 | const optionValue = JSON.parse(stdioOption); 33 | if (typeof optionValue === 'string' && optionValue in process) { 34 | return process[optionValue]; 35 | } 36 | 37 | if (Array.isArray(optionValue) && typeof optionValue[0] === 'string' && optionValue[0] in process) { 38 | return [process[optionValue[0]], ...optionValue.slice(1)]; 39 | } 40 | 41 | return optionValue; 42 | }; 43 | -------------------------------------------------------------------------------- /test/helpers/stream.js: -------------------------------------------------------------------------------- 1 | import { 2 | Readable, 3 | Writable, 4 | PassThrough, 5 | getDefaultHighWaterMark, 6 | } from 'node:stream'; 7 | import {foobarString} from './input.js'; 8 | 9 | export const noopReadable = () => new Readable({read() {}}); 10 | export const noopWritable = () => new Writable({write() {}}); 11 | export const noopDuplex = () => new PassThrough().resume(); 12 | export const simpleReadable = () => Readable.from([foobarString]); 13 | 14 | export const defaultHighWaterMark = getDefaultHighWaterMark(false); 15 | export const defaultObjectHighWaterMark = getDefaultHighWaterMark(true); 16 | -------------------------------------------------------------------------------- /test/helpers/wait.js: -------------------------------------------------------------------------------- 1 | import {getStdio} from '../helpers/stdio.js'; 2 | import {noopGenerator} from '../helpers/generator.js'; 3 | 4 | export const endOptionStream = ({stream}) => { 5 | stream.end(); 6 | }; 7 | 8 | export const destroyOptionStream = ({stream, error}) => { 9 | stream.destroy(error); 10 | }; 11 | 12 | export const destroySubprocessStream = ({subprocess, fdNumber, error}) => { 13 | subprocess.stdio[fdNumber].destroy(error); 14 | }; 15 | 16 | export const getStreamStdio = (fdNumber, stream, useTransform) => 17 | getStdio(fdNumber, [stream, useTransform ? noopGenerator(false) : 'pipe']); 18 | -------------------------------------------------------------------------------- /test/io/input-sync.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {execaSync} from '../../index.js'; 3 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 4 | import {getStdio} from '../helpers/stdio.js'; 5 | 6 | setFixtureDirectory(); 7 | 8 | const getFd3InputMessage = type => `not \`stdio[3]\`, can be ${type}`; 9 | 10 | const testFd3InputSync = (t, stdioOption, expectedMessage) => { 11 | const {message} = t.throws(() => { 12 | execaSync('empty.js', getStdio(3, stdioOption)); 13 | }); 14 | t.true(message.includes(expectedMessage)); 15 | }; 16 | 17 | test('Cannot use Uint8Array with stdio[*], sync', testFd3InputSync, new Uint8Array(), getFd3InputMessage('a Uint8Array')); 18 | test('Cannot use iterable with stdio[*], sync', testFd3InputSync, [[]], getFd3InputMessage('an iterable')); 19 | -------------------------------------------------------------------------------- /test/io/output-sync.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {execaSync} from '../../index.js'; 3 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 4 | import {throwingGenerator} from '../helpers/generator.js'; 5 | import {foobarString} from '../helpers/input.js'; 6 | 7 | setFixtureDirectory(); 8 | 9 | test('Handles errors with stdout generator, sync', t => { 10 | const cause = new Error(foobarString); 11 | const error = t.throws(() => { 12 | execaSync('noop.js', {stdout: throwingGenerator(cause)()}); 13 | }); 14 | t.is(error.cause, cause); 15 | }); 16 | 17 | test('Handles errors with stdout generator, spawn failure, sync', t => { 18 | const cause = new Error(foobarString); 19 | const error = t.throws(() => { 20 | execaSync('noop.js', {cwd: 'does_not_exist', stdout: throwingGenerator(cause)()}); 21 | }); 22 | t.true(error.failed); 23 | t.is(error.cause.code, 'ENOENT'); 24 | }); 25 | 26 | test('Handles errors with stdout generator, subprocess failure, sync', t => { 27 | const cause = new Error(foobarString); 28 | const error = t.throws(() => { 29 | execaSync('noop-fail.js', ['1'], {stdout: throwingGenerator(cause)()}); 30 | }); 31 | t.true(error.failed); 32 | t.is(error.cause, cause); 33 | }); 34 | -------------------------------------------------------------------------------- /test/methods/main-async.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import test from 'ava'; 3 | import {execa} from '../../index.js'; 4 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 5 | 6 | setFixtureDirectory(); 7 | 8 | const isWindows = process.platform === 'win32'; 9 | 10 | if (isWindows) { 11 | test('execa() - cmd file', async t => { 12 | const {stdout} = await execa('hello.cmd'); 13 | t.is(stdout, 'Hello World'); 14 | }); 15 | 16 | test('execa() - run cmd command', async t => { 17 | const {stdout} = await execa('cmd', ['/c', 'hello.cmd']); 18 | t.is(stdout, 'Hello World'); 19 | }); 20 | } 21 | 22 | test('execa() returns a promise with pid', async t => { 23 | const subprocess = execa('noop.js', ['foo']); 24 | t.is(typeof subprocess.pid, 'number'); 25 | await subprocess; 26 | }); 27 | -------------------------------------------------------------------------------- /test/methods/override-promise.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | // The helper module overrides Promise on import so has to be imported before `execa`. 3 | import {restorePromise} from '../helpers/override-promise.js'; 4 | import {execa} from '../../index.js'; 5 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 6 | 7 | restorePromise(); 8 | setFixtureDirectory(); 9 | 10 | test('should work with third-party Promise', async t => { 11 | const {stdout} = await execa('noop.js', ['foo']); 12 | t.is(stdout, 'foo'); 13 | }); 14 | -------------------------------------------------------------------------------- /test/pipe/throw.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {execa} from '../../index.js'; 3 | import {foobarString} from '../helpers/input.js'; 4 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 5 | import {assertPipeError} from '../helpers/pipe.js'; 6 | 7 | setFixtureDirectory(); 8 | 9 | test('Destination stream is ended when first argument is invalid', async t => { 10 | const source = execa('empty.js', {stdout: 'ignore'}); 11 | const destination = execa('stdin.js'); 12 | const pipePromise = source.pipe(destination); 13 | 14 | await assertPipeError(t, pipePromise, 'option is incompatible'); 15 | await source; 16 | t.like(await destination, {stdout: ''}); 17 | }); 18 | 19 | test('Destination stream is ended when first argument is invalid - $', async t => { 20 | const pipePromise = execa('empty.js', {stdout: 'ignore'}).pipe`stdin.js`; 21 | await assertPipeError(t, pipePromise, 'option is incompatible'); 22 | }); 23 | 24 | test('Source stream is aborted when second argument is invalid', async t => { 25 | const source = execa('noop.js', [foobarString]); 26 | const pipePromise = source.pipe(false); 27 | 28 | await assertPipeError(t, pipePromise, 'an Execa subprocess'); 29 | t.like(await source, {stdout: ''}); 30 | }); 31 | 32 | test('Both arguments might be invalid', async t => { 33 | const source = execa('empty.js', {stdout: 'ignore'}); 34 | const pipePromise = source.pipe(false); 35 | 36 | await assertPipeError(t, pipePromise, 'an Execa subprocess'); 37 | t.like(await source, {stdout: undefined}); 38 | }); 39 | -------------------------------------------------------------------------------- /test/return/reject.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {execa, execaSync} from '../../index.js'; 3 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 4 | 5 | setFixtureDirectory(); 6 | 7 | test('skip throwing when using reject option', async t => { 8 | const {exitCode} = await execa('fail.js', {reject: false}); 9 | t.is(exitCode, 2); 10 | }); 11 | 12 | test('skip throwing when using reject option in sync mode', t => { 13 | const {exitCode} = execaSync('fail.js', {reject: false}); 14 | t.is(exitCode, 2); 15 | }); 16 | -------------------------------------------------------------------------------- /test/stdio/file-descriptor.js: -------------------------------------------------------------------------------- 1 | import {readFile, open, rm} from 'node:fs/promises'; 2 | import test from 'ava'; 3 | import tempfile from 'tempfile'; 4 | import {execa, execaSync} from '../../index.js'; 5 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 6 | import {getStdio} from '../helpers/stdio.js'; 7 | 8 | setFixtureDirectory(); 9 | 10 | const testFileDescriptorOption = async (t, fdNumber, execaMethod) => { 11 | const filePath = tempfile(); 12 | const fileDescriptor = await open(filePath, 'w'); 13 | await execaMethod('noop-fd.js', [`${fdNumber}`, 'foobar'], getStdio(fdNumber, fileDescriptor)); 14 | t.is(await readFile(filePath, 'utf8'), 'foobar'); 15 | await rm(filePath); 16 | await fileDescriptor.close(); 17 | }; 18 | 19 | test('pass `stdout` to a file descriptor', testFileDescriptorOption, 1, execa); 20 | test('pass `stderr` to a file descriptor', testFileDescriptorOption, 2, execa); 21 | test('pass `stdio[*]` to a file descriptor', testFileDescriptorOption, 3, execa); 22 | test('pass `stdout` to a file descriptor - sync', testFileDescriptorOption, 1, execaSync); 23 | test('pass `stderr` to a file descriptor - sync', testFileDescriptorOption, 2, execaSync); 24 | test('pass `stdio[*]` to a file descriptor - sync', testFileDescriptorOption, 3, execaSync); 25 | 26 | const testStdinWrite = async (t, fdNumber) => { 27 | const subprocess = execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, 'pipe')); 28 | subprocess.stdio[fdNumber].end('unicorns'); 29 | const {stdout} = await subprocess; 30 | t.is(stdout, 'unicorns'); 31 | }; 32 | 33 | test('you can write to subprocess.stdin', testStdinWrite, 0); 34 | test('you can write to subprocess.stdio[*]', testStdinWrite, 3); 35 | -------------------------------------------------------------------------------- /test/stdio/web-transform.js: -------------------------------------------------------------------------------- 1 | import {promisify} from 'node:util'; 2 | import {gunzip} from 'node:zlib'; 3 | import test from 'ava'; 4 | import {execa} from '../../index.js'; 5 | import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; 6 | import {foobarString, foobarUtf16Uint8Array, foobarUint8Array} from '../helpers/input.js'; 7 | 8 | setFixtureDirectory(); 9 | 10 | test('Can use CompressionStream()', async t => { 11 | const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: new CompressionStream('gzip'), encoding: 'buffer'}); 12 | const decompressedStdout = await promisify(gunzip)(stdout); 13 | t.is(decompressedStdout.toString(), foobarString); 14 | }); 15 | 16 | test('Can use TextDecoderStream()', async t => { 17 | const {stdout} = await execa('stdin.js', { 18 | input: foobarUtf16Uint8Array, 19 | stdout: new TextDecoderStream('utf-16le'), 20 | encoding: 'buffer', 21 | }); 22 | t.deepEqual(stdout, foobarUint8Array); 23 | }); 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "target": "ES2022", 6 | "strict": true 7 | }, 8 | "files": [ 9 | "index.d.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /types/arguments/encoding-option.d.ts: -------------------------------------------------------------------------------- 1 | type DefaultEncodingOption = 'utf8'; 2 | type TextEncodingOption = 3 | | DefaultEncodingOption 4 | | 'utf16le'; 5 | 6 | export type BufferEncodingOption = 'buffer'; 7 | export type BinaryEncodingOption = 8 | | BufferEncodingOption 9 | | 'hex' 10 | | 'base64' 11 | | 'base64url' 12 | | 'latin1' 13 | | 'ascii'; 14 | 15 | // `options.encoding` 16 | export type EncodingOption = 17 | | TextEncodingOption 18 | | BinaryEncodingOption 19 | | undefined; 20 | -------------------------------------------------------------------------------- /types/arguments/fd-options.d.ts: -------------------------------------------------------------------------------- 1 | type FileDescriptorOption = `fd${number}`; 2 | 3 | // `from` option of `subprocess.readable|duplex|iterable|pipe()` 4 | // Also used by fd-specific options 5 | export type FromOption = 'stdout' | 'stderr' | 'all' | FileDescriptorOption; 6 | 7 | // `to` option of `subprocess.writable|duplex|pipe()` 8 | export type ToOption = 'stdin' | FileDescriptorOption; 9 | -------------------------------------------------------------------------------- /types/arguments/specific.d.ts: -------------------------------------------------------------------------------- 1 | import type {FromOption} from './fd-options.js'; 2 | 3 | // Options which can be fd-specific like `{verbose: {stdout: 'none', stderr: 'full'}}` 4 | export type FdGenericOption = OptionType | GenericOptionObject; 5 | 6 | type GenericOptionObject = { 7 | readonly [FdName in GenericFromOption]?: OptionType 8 | }; 9 | 10 | type GenericFromOption = FromOption | 'ipc'; 11 | 12 | // Retrieve fd-specific option's value 13 | export type FdSpecificOption< 14 | GenericOption extends FdGenericOption, 15 | FdNumber extends string, 16 | > = GenericOption extends GenericOptionObject 17 | ? FdSpecificObjectOption 18 | : GenericOption; 19 | 20 | type FdSpecificObjectOption< 21 | GenericOption extends GenericOptionObject, 22 | FdNumber extends string, 23 | > = keyof GenericOption extends GenericFromOption 24 | ? FdNumberToFromOption extends never 25 | ? undefined 26 | : GenericOption[FdNumberToFromOption] 27 | : GenericOption; 28 | 29 | type FdNumberToFromOption< 30 | FdNumber extends string, 31 | GenericOptionKeys extends GenericFromOption, 32 | > = FdNumber extends '1' 33 | ? 'stdout' extends GenericOptionKeys 34 | ? 'stdout' 35 | : 'fd1' extends GenericOptionKeys 36 | ? 'fd1' 37 | : 'all' extends GenericOptionKeys 38 | ? 'all' 39 | : never 40 | : FdNumber extends '2' 41 | ? 'stderr' extends GenericOptionKeys 42 | ? 'stderr' 43 | : 'fd2' extends GenericOptionKeys 44 | ? 'fd2' 45 | : 'all' extends GenericOptionKeys 46 | ? 'all' 47 | : never 48 | : `fd${FdNumber}` extends GenericOptionKeys 49 | ? `fd${FdNumber}` 50 | : 'ipc' extends GenericOptionKeys 51 | ? 'ipc' 52 | : never; 53 | -------------------------------------------------------------------------------- /types/methods/template.d.ts: -------------------------------------------------------------------------------- 1 | import type {Result, SyncResult} from '../return/result.js'; 2 | 3 | type TemplateExpressionItem = 4 | | string 5 | | number 6 | | Result 7 | | SyncResult; 8 | 9 | /** 10 | Value allowed inside `${...}` when using the template string syntax. 11 | */ 12 | export type TemplateExpression = TemplateExpressionItem | readonly TemplateExpressionItem[]; 13 | 14 | // `...${...}...` template syntax 15 | export type TemplateString = readonly [TemplateStringsArray, ...readonly TemplateExpression[]]; 16 | 17 | // `...${...}...` template syntax, but only allowing a single argument, for `execaCommand()` 18 | export type SimpleTemplateString = readonly [TemplateStringsArray, string?]; 19 | -------------------------------------------------------------------------------- /types/return/ignore.d.ts: -------------------------------------------------------------------------------- 1 | import type {NoStreamStdioOption} from '../stdio/type.js'; 2 | import type {IsInputFd} from '../stdio/direction.js'; 3 | import type {FdStdioOption} from '../stdio/option.js'; 4 | import type {FdSpecificOption} from '../arguments/specific.js'; 5 | import type {CommonOptions} from '../arguments/options.js'; 6 | 7 | // Whether `result.stdin|stdout|stderr|all|stdio[*]` is `undefined` 8 | export type IgnoresResultOutput< 9 | FdNumber extends string, 10 | OptionsType extends CommonOptions, 11 | > = FdSpecificOption extends false 12 | ? true 13 | : IsInputFd extends true 14 | ? true 15 | : IgnoresSubprocessOutput; 16 | 17 | // Whether `subprocess.stdout|stderr|all` is `undefined|null` 18 | export type IgnoresSubprocessOutput< 19 | FdNumber extends string, 20 | OptionsType extends CommonOptions, 21 | > = IgnoresOutput>; 22 | 23 | type IgnoresOutput< 24 | FdNumber extends string, 25 | StdioOptionType, 26 | > = StdioOptionType extends NoStreamStdioOption ? true : false; 27 | -------------------------------------------------------------------------------- /types/return/result-all.d.ts: -------------------------------------------------------------------------------- 1 | import type {IsObjectFd} from '../transform/object-mode.js'; 2 | import type {CommonOptions} from '../arguments/options.js'; 3 | import type {FdSpecificOption} from '../arguments/specific.js'; 4 | import type {IgnoresResultOutput} from './ignore.js'; 5 | import type {ResultStdio} from './result-stdout.js'; 6 | 7 | // `result.all` 8 | export type ResultAll = 9 | ResultAllProperty; 10 | 11 | type ResultAllProperty< 12 | AllOption extends CommonOptions['all'], 13 | OptionsType extends CommonOptions, 14 | > = AllOption extends true 15 | ? ResultStdio< 16 | AllMainFd, 17 | AllObjectFd, 18 | AllLinesFd, 19 | OptionsType 20 | > 21 | : undefined; 22 | 23 | type AllMainFd = 24 | IgnoresResultOutput<'1', OptionsType> extends true ? '2' : '1'; 25 | 26 | type AllObjectFd = 27 | IsObjectFd<'1', OptionsType> extends true ? '1' : '2'; 28 | 29 | type AllLinesFd = 30 | FdSpecificOption extends true ? '1' : '2'; 31 | -------------------------------------------------------------------------------- /types/return/result-ipc.d.ts: -------------------------------------------------------------------------------- 1 | import type {FdSpecificOption} from '../arguments/specific.js'; 2 | import type {CommonOptions, Options, StricterOptions} from '../arguments/options.js'; 3 | import type {Message, HasIpc} from '../ipc.js'; 4 | 5 | // `result.ipcOutput` 6 | // This is empty unless the `ipc` option is `true`. 7 | // Also, this is empty if the `buffer` option is `false`. 8 | export type ResultIpcOutput< 9 | IsSync, 10 | OptionsType extends CommonOptions, 11 | > = IsSync extends true 12 | ? [] 13 | : ResultIpcAsync< 14 | FdSpecificOption, 15 | HasIpc>, 16 | OptionsType['serialization'] 17 | >; 18 | 19 | type ResultIpcAsync< 20 | BufferOption extends boolean | undefined, 21 | IpcEnabled extends boolean, 22 | SerializationOption extends CommonOptions['serialization'], 23 | > = BufferOption extends false 24 | ? [] 25 | : IpcEnabled extends true 26 | ? Array> 27 | : []; 28 | -------------------------------------------------------------------------------- /types/return/result-stdio.d.ts: -------------------------------------------------------------------------------- 1 | import type {StdioOptionNormalizedArray} from '../stdio/array.js'; 2 | import type {CommonOptions} from '../arguments/options.js'; 3 | import type {ResultStdioNotAll} from './result-stdout.js'; 4 | 5 | // `result.stdio` 6 | export type ResultStdioArray = 7 | MapResultStdio, OptionsType>; 8 | 9 | type MapResultStdio< 10 | StdioOptionsArrayType, 11 | OptionsType extends CommonOptions, 12 | > = { 13 | -readonly [FdNumber in keyof StdioOptionsArrayType]: ResultStdioNotAll< 14 | FdNumber extends string ? FdNumber : string, 15 | OptionsType 16 | > 17 | }; 18 | -------------------------------------------------------------------------------- /types/return/result-stdout.d.ts: -------------------------------------------------------------------------------- 1 | import type {BufferEncodingOption, BinaryEncodingOption} from '../arguments/encoding-option.js'; 2 | import type {IsObjectFd} from '../transform/object-mode.js'; 3 | import type {FdSpecificOption} from '../arguments/specific.js'; 4 | import type {CommonOptions} from '../arguments/options.js'; 5 | import type {IgnoresResultOutput} from './ignore.js'; 6 | 7 | // `result.stdout|stderr|stdio` 8 | export type ResultStdioNotAll< 9 | FdNumber extends string, 10 | OptionsType extends CommonOptions, 11 | > = ResultStdio; 12 | 13 | // `result.stdout|stderr|stdio|all` 14 | export type ResultStdio< 15 | MainFdNumber extends string, 16 | ObjectFdNumber extends string, 17 | LinesFdNumber extends string, 18 | OptionsType extends CommonOptions, 19 | > = ResultStdioProperty< 20 | ObjectFdNumber, 21 | LinesFdNumber, 22 | IgnoresResultOutput, 23 | OptionsType 24 | >; 25 | 26 | type ResultStdioProperty< 27 | ObjectFdNumber extends string, 28 | LinesFdNumber extends string, 29 | StreamOutputIgnored, 30 | OptionsType extends CommonOptions, 31 | > = StreamOutputIgnored extends true 32 | ? undefined 33 | : ResultStdioItem< 34 | IsObjectFd, 35 | FdSpecificOption, 36 | OptionsType['encoding'] 37 | >; 38 | 39 | type ResultStdioItem< 40 | IsObjectResult, 41 | LinesOption extends boolean | undefined, 42 | Encoding extends CommonOptions['encoding'], 43 | > = IsObjectResult extends true ? unknown[] 44 | : Encoding extends BufferEncodingOption 45 | ? Uint8Array 46 | : LinesOption extends true 47 | ? Encoding extends BinaryEncodingOption 48 | ? string 49 | : string[] 50 | : string; 51 | -------------------------------------------------------------------------------- /types/stdio/array.d.ts: -------------------------------------------------------------------------------- 1 | import type {CommonOptions} from '../arguments/options.js'; 2 | import type {StdinOptionCommon, StdoutStderrOptionCommon, StdioOptionsArray} from './type.js'; 3 | 4 | // `options.stdio`, normalized as an array 5 | export type StdioOptionNormalizedArray = StdioOptionNormalized; 6 | 7 | type StdioOptionNormalized = StdioOption extends StdioOptionsArray 8 | ? StdioOption 9 | : StdioOption extends StdinOptionCommon 10 | ? StdioOption extends StdoutStderrOptionCommon 11 | ? readonly [StdioOption, StdioOption, StdioOption] 12 | : DefaultStdioOption 13 | : DefaultStdioOption; 14 | 15 | // `options.stdio` default value 16 | type DefaultStdioOption = readonly ['pipe', 'pipe', 'pipe']; 17 | -------------------------------------------------------------------------------- /types/stdio/direction.d.ts: -------------------------------------------------------------------------------- 1 | import type {CommonOptions} from '../arguments/options.js'; 2 | import type {Intersects} from '../utils.js'; 3 | import type {StdioSingleOptionItems, InputStdioOption} from './type.js'; 4 | import type {FdStdioArrayOption} from './option.js'; 5 | 6 | // Whether `result.stdio[FdNumber]` is an input stream 7 | export type IsInputFd< 8 | FdNumber extends string, 9 | OptionsType extends CommonOptions, 10 | > = FdNumber extends '0' 11 | ? true 12 | : Intersects>, InputStdioOption>; 13 | -------------------------------------------------------------------------------- /types/stdio/option.d.ts: -------------------------------------------------------------------------------- 1 | import type {CommonOptions} from '../arguments/options.js'; 2 | import type {StdioOptionNormalizedArray} from './array.js'; 3 | import type {StandardStreams, StdioOptionCommon, StdioOptionsArray} from './type.js'; 4 | 5 | // `options.stdin|stdout|stderr|stdio` for a given file descriptor 6 | export type FdStdioOption< 7 | FdNumber extends string, 8 | OptionsType extends CommonOptions, 9 | > = FdStdioOptionProperty; 10 | 11 | type FdStdioOptionProperty< 12 | FdNumber extends string, 13 | OptionsType extends CommonOptions, 14 | > = string extends FdNumber ? StdioOptionCommon 15 | : FdNumber extends keyof StandardStreams 16 | ? StandardStreams[FdNumber] extends keyof OptionsType 17 | ? OptionsType[StandardStreams[FdNumber]] extends undefined 18 | ? FdStdioArrayOption 19 | : OptionsType[StandardStreams[FdNumber]] 20 | : FdStdioArrayOption 21 | : FdStdioArrayOption; 22 | 23 | // `options.stdio[FdNumber]`, excluding `options.stdin|stdout|stderr` 24 | export type FdStdioArrayOption< 25 | FdNumber extends string, 26 | OptionsType extends CommonOptions, 27 | > = FdStdioArrayOptionProperty>; 28 | 29 | type FdStdioArrayOptionProperty< 30 | FdNumber extends string, 31 | StdioOptionsType, 32 | > = string extends FdNumber 33 | ? StdioOptionCommon | undefined 34 | : StdioOptionsType extends StdioOptionsArray 35 | ? FdNumber extends keyof StdioOptionsType 36 | ? StdioOptionsType[FdNumber] 37 | : StdioOptionNormalizedArray extends StdioOptionsType 38 | ? StdioOptionsType[number] 39 | : undefined 40 | : undefined; 41 | -------------------------------------------------------------------------------- /types/subprocess/all.d.ts: -------------------------------------------------------------------------------- 1 | import type {Readable} from 'node:stream'; 2 | import type {IgnoresSubprocessOutput} from '../return/ignore.js'; 3 | import type {Options} from '../arguments/options.js'; 4 | 5 | // `subprocess.all` 6 | export type SubprocessAll = AllStream>; 7 | 8 | type AllStream = IsIgnored extends true ? undefined : Readable; 9 | 10 | type AllIgnored< 11 | AllOption, 12 | OptionsType extends Options, 13 | > = AllOption extends true 14 | ? IgnoresSubprocessOutput<'1', OptionsType> extends true 15 | ? IgnoresSubprocessOutput<'2', OptionsType> 16 | : false 17 | : true; 18 | -------------------------------------------------------------------------------- /types/subprocess/stdio.d.ts: -------------------------------------------------------------------------------- 1 | import type {StdioOptionNormalizedArray} from '../stdio/array.js'; 2 | import type {Options} from '../arguments/options.js'; 3 | import type {SubprocessStdioStream} from './stdout.js'; 4 | 5 | // `subprocess.stdio` 6 | export type SubprocessStdioArray = MapStdioStreams, OptionsType>; 7 | 8 | // We cannot use mapped types because it must be compatible with Node.js `ChildProcess["stdio"]` which uses a tuple with exactly 5 items 9 | type MapStdioStreams< 10 | StdioOptionsArrayType, 11 | OptionsType extends Options, 12 | > = [ 13 | SubprocessStdioStream<'0', OptionsType>, 14 | SubprocessStdioStream<'1', OptionsType>, 15 | SubprocessStdioStream<'2', OptionsType>, 16 | '3' extends keyof StdioOptionsArrayType ? SubprocessStdioStream<'3', OptionsType> : never, 17 | '4' extends keyof StdioOptionsArrayType ? SubprocessStdioStream<'4', OptionsType> : never, 18 | ]; 19 | -------------------------------------------------------------------------------- /types/subprocess/stdout.d.ts: -------------------------------------------------------------------------------- 1 | import type {Readable, Writable} from 'node:stream'; 2 | import type {IsInputFd} from '../stdio/direction.js'; 3 | import type {IgnoresSubprocessOutput} from '../return/ignore.js'; 4 | import type {Options} from '../arguments/options.js'; 5 | 6 | // `subprocess.stdin|stdout|stderr|stdio` 7 | export type SubprocessStdioStream< 8 | FdNumber extends string, 9 | OptionsType extends Options, 10 | > = SubprocessStream, OptionsType>; 11 | 12 | type SubprocessStream< 13 | FdNumber extends string, 14 | StreamResultIgnored, 15 | OptionsType extends Options, 16 | > = StreamResultIgnored extends true 17 | ? null 18 | : InputOutputStream>; 19 | 20 | type InputOutputStream = IsInput extends true 21 | ? Writable 22 | : Readable; 23 | -------------------------------------------------------------------------------- /types/transform/object-mode.d.ts: -------------------------------------------------------------------------------- 1 | import type {StdioSingleOptionItems} from '../stdio/type.js'; 2 | import type {FdStdioOption} from '../stdio/option.js'; 3 | import type {CommonOptions} from '../arguments/options.js'; 4 | import type {DuplexTransform, TransformCommon} from './normalize.js'; 5 | 6 | // Whether a file descriptor is in object mode 7 | // I.e. whether `result.stdout|stderr|stdio|all` is an array of `unknown` due to `objectMode: true` 8 | export type IsObjectFd< 9 | FdNumber extends string, 10 | OptionsType extends CommonOptions, 11 | > = IsObjectStdioOption>; 12 | 13 | type IsObjectStdioOption = IsObjectStdioSingleOption>; 14 | 15 | type IsObjectStdioSingleOption = StdioSingleOptionType extends TransformCommon 16 | ? BooleanObjectMode 17 | : StdioSingleOptionType extends DuplexTransform 18 | ? StdioSingleOptionType['transform']['readableObjectMode'] 19 | : false; 20 | 21 | type BooleanObjectMode = ObjectModeOption extends true ? true : false; 22 | -------------------------------------------------------------------------------- /types/utils.d.ts: -------------------------------------------------------------------------------- 1 | export type Not = Value extends true ? false : true; 2 | 3 | export type And = First extends true ? Second : false; 4 | 5 | export type Or = First extends true ? true : Second; 6 | 7 | export type Unless = Condition extends true ? ElseValue : ThenValue; 8 | 9 | export type AndUnless = Condition extends true ? ElseValue : ThenValue; 10 | 11 | // Whether any of T's union element is the same as one of U's union element. 12 | // `&` does not work here. 13 | export type Intersects = true extends (T extends U ? true : false) ? true : false; 14 | --------------------------------------------------------------------------------