diff --git a/readme.md b/readme.md
index 329d665..f63258a 100644
--- a/readme.md
+++ b/readme.md
@@ -2,24 +2,29 @@
-[![Install size](https://packagephobia.com/badge?p=nano-spawn)](https://packagephobia.com/result?p=nano-spawn)
-![npm package minzipped size](https://img.shields.io/bundlejs/size/nano-spawn)
![Test coverage](https://img.shields.io/badge/coverage-100%25-green)
> Tiny process execution for humans — a better [`child_process`](https://nodejs.org/api/child_process.html)
-> [!WARNING]
-> This package is still a work in progress.
-
-Check out [`execa`](https://github.com/sindresorhus/execa) for more features.
-
## Features
-- Outputs combined result of stdout and stderr, similar to what you get in terminals
-- Outputs lines
-- No dependencies
+No dependencies. Small package size: ![npm package minzipped size](https://img.shields.io/bundlejs/size/nano-spawn) [![Install size](https://packagephobia.com/badge?p=nano-spawn)](https://packagephobia.com/result?p=nano-spawn)
+
+Despite the small size, this is packed with some essential features:
+- [Promise-based](#nanospawnfile-arguments-options) interface.
+- Execute [locally installed binaries](#optionspreferlocal) without `npx`.
+- Improved [Windows support](#windows-support).
+- Proper handling of [subprocess failures](#subprocesserror) and better error messages.
+- [Pipe](#subprocesspipefile-arguments-options) multiple subprocesses and retrieve [intermediate results](#resultpipedfrom).
+- [Iterate](#subprocesssymbolasynciterator) over the output lines.
+- Get [interleaved output](#resultoutput) from stdout and stderr similar to what is printed on the terminal.
+- Strip [unnecessary newlines](#resultstdout).
+- Pass strings as [`stdin` input](#optionsstdin-optionsstdout-optionsstderr) to the subprocess.
+- Preserve the current [Node.js version and flags](#nanospawnfile-arguments-options).
+- Simpler syntax to set [environment variables](#optionsenv) or [`stdin`/`stdout`/`stderr`](#optionsstdin-optionsstdout-optionsstderr).
+- Compute the command [duration](#resultdurationms).
## Install
@@ -35,21 +40,20 @@ npm install nano-spawn
## Usage
-```js
-import $ from 'nano-spawn';
+### Run commands
-const result = await $('echo', ['🦄']);
+```js
+import spawn from 'nano-spawn';
-console.log(result.exitCode);
-//=> 0
+const result = await spawn('echo', ['🦄']);
+console.log(result.output);
+//=> '🦄'
```
-**Advanced**
+### Iterate over output lines
```js
-import $ from 'nano-spawn';
-
-for await (const line of $('ls', ['--oneline'])) {
+for await (const line of spawn('ls', ['--oneline'])) {
console.log(line);
}
//=> index.d.ts
@@ -57,13 +61,200 @@ for await (const line of $('ls', ['--oneline'])) {
//=> …
```
+### Pipe commands
+
+```js
+const result = await spawn('npm', ['run', 'build'])
+ .pipe('sort')
+ .pipe('head', ['-n', '2']);
+```
+
## API
-See the [types](source/index.d.ts) for now.
+### nanoSpawn(file, arguments?, options?)
+
+`file`: `string`\
+`arguments`: `string[]`\
+`options`: [`Options`](#options)\
+_Returns_: [`Subprocess`](#subprocess)
+
+Executes a command using `file ...arguments`.
+
+This has the same syntax as [`child_process.spawn()`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options).
+
+If `file` is `'node'`, the current Node.js version and [flags](https://nodejs.org/api/cli.html#options) are inherited.
+
+#### Options
+
+##### options.stdio, options.shell, options.timeout, options.signal, options.cwd, options.killSignal, options.serialization, options.detached, options.uid, options.gid, options.windowsVerbatimArguments, options.windowsHide, options.argv0
+
+All `child_process.spawn()` options can be passed to `nanoSpawn()`. Please see [the `node:child_process` documentation](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options) for a description of each option.
+
+##### options.env
+
+_Type_: `object`\
+_Default_: `{}`
+
+Override specific [environment variables](https://en.wikipedia.org/wiki/Environment_variable). Other environment variables are inherited from the current process ([`process.env`](https://nodejs.org/api/process.html#processenv)).
+
+##### options.preferLocal
+
+_Type_: `boolean`\
+_Default_: `false`
+
+Allows executing binaries installed locally with `npm` (or `yarn`, etc.).
+
+##### options.stdin, options.stdout, options.stderr
+
+_Type_: `string | number | Stream | {string: string}`
+
+Subprocess's standard [input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin))/[output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout))/[error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)).
+
+[All values supported](https://nodejs.org/api/child_process.html#optionsstdio) by `node:child_process` are available. The most common ones are:
+- `'pipe'` (default value): returns the output using [`result.stdout`](#resultstdout), [`result.stderr`](#resultstderr) and [`result.output`](#resultoutput).
+- `'inherit'`: uses the current process's [input](https://nodejs.org/api/process.html#processstdin)/[output](https://nodejs.org/api/process.html#processstdout). This is useful when running in a terminal.
+- `'ignore'`: discards the input/output.
+- [`Stream`](https://nodejs.org/api/stream.html#stream): redirects the input/output from/to a stream. For example, [`fs.createReadStream()`](https://nodejs.org/api/fs.html#fscreatereadstreampath-options)/[`fs.createWriteStream()`](https://nodejs.org/api/fs.html#fscreatewritestreampath-options) can be used, once the stream's [`open`](https://nodejs.org/api/fs.html#event-open) event has been emitted.
+- `{string: '...'}`: passes a string as input to `stdin`.
+
+#### Subprocess
+
+Subprocess started by [`nanoSpawn()`](#nanospawnfile-arguments-options).
+
+##### await subprocess
+
+_Returns_: [`Result`](#result)\
+_Throws_: [`SubprocessError`](#subprocesserror)
+
+A subprocess is a promise that is either resolved with a successful [`result` object](#result) or rejected with a [`subprocessError`](#error).
+
+##### subprocess.stdout
+
+_Returns_: `AsyncIterable`\
+_Throws_: [`SubprocessError`](#subprocesserror)
+
+Iterates over each [`stdout`](#resultstdout) line, as soon as it is available.
+
+The iteration waits for the subprocess to end (even when using [`break`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break) or [`return`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return)). It throws if the subprocess [fails](#subprocesserror). This means you do not need to call [`await subprocess`](#await-subprocess).
+
+##### subprocess.stderr
+
+_Returns_: `AsyncIterable`\
+_Throws_: [`SubprocessError`](#subprocesserror)
+
+Same as [`subprocess.stdout`](#subprocessstdout) but for [`stderr`](#resultstderr) instead.
+
+##### subprocess[Symbol.asyncIterator]\()
+
+_Returns_: `AsyncIterable`\
+_Throws_: [`SubprocessError`](#subprocesserror)
+
+Same as [`subprocess.stdout`](#subprocessstdout) but for both [`stdout` and `stderr`](#resultoutput).
+
+##### subprocess.pipe(file, arguments?, options?)
+
+`file`: `string`\
+`arguments`: `string[]`\
+`options`: [`Options`](#options)\
+_Returns_: [`Subprocess`](#subprocess)
+
+Similar to the `|` symbol in shells. [Pipe](https://nodejs.org/api/stream.html#readablepipedestination-options) the subprocess's[`stdout`](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)) to a second subprocess's [`stdin`](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)).
+
+This resolves with that second subprocess's [result](#result). If either subprocess is rejected, this is rejected with that subprocess's [error](#subprocesserror) instead.
+
+This follows the same syntax as [`nanoSpawn(file, arguments?, options?)`](#nanospawnfile-arguments-options). It can be done multiple times in a row.
+
+##### await subprocess.nodeChildProcess
+
+_Type_: `ChildProcess`
+
+Underlying [Node.js child process](https://nodejs.org/api/child_process.html#class-childprocess).
+
+Among other things, this can be used to terminate the subprocess using [`.kill()`](https://nodejs.org/api/child_process.html#subprocesskillsignal) or exchange IPC message using [`.send()`](https://nodejs.org/api/child_process.html#subprocesssendmessage-sendhandle-options-callback).
+
+#### Result
+
+When the subprocess succeeds, its [promise](#await-subprocess) is resolved with an object with the following properties.
+
+##### result.stdout
+
+_Type_: `string`
+
+The output of the subprocess on [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)).
+
+If the output ends with a [newline](https://en.wikipedia.org/wiki/Newline), that newline is automatically stripped.
+
+This is an empty string if either:
+- The [`stdout`](#optionsstdin-optionsstdout-optionsstderr) option is set to another value than `'pipe'` (its default value).
+- The output is being iterated using [`subprocess.stdout`](#subprocessstdout) or [`subprocess[Symbol.asyncIterator]`](#subprocesssymbolasynciterator).
+
+##### result.stderr
+
+_Type_: `string`
+
+Like [`result.stdout`](#resultstdout) but for the [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)) instead.
+
+##### result.output
+
+_Type_: `string`
+
+Like [`result.stdout`](#resultstdout) but for both the [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)) and [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)), interleaved.
+
+##### result.command
+
+_Type_: `string`
+
+The file and arguments that were run.
+
+It is intended for logging or debugging. Since the escaping is fairly basic, it should not be executed directly.
+
+##### result.durationMs
+
+_Type_: `number`
+
+Duration of the subprocess, in milliseconds.
+
+##### result.pipedFrom
+
+_Type_: `Result | SubprocessError | undefined`
+
+If [`subprocess.pipe()`](#subprocesspipefile-arguments-options) was used, the [result](#result) or [error](#subprocesserror) of the other subprocess that was piped into this subprocess.
+
+#### SubprocessError
+
+_Type_: `Error`
+
+When the subprocess fails, its [promise](#await-subprocess) is rejected with this error.
+
+Subprocesses fail either when their [exit code](#subprocesserrorexitcode) is not `0` or when terminated by a [signal](#subprocesserrorsignalname). Other failure reasons include misspelling the command name or using the [`timeout`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options) option.
+
+Subprocess errors have the same shape as [successful results](#result), with the following additional properties.
+
+##### subprocessError.exitCode
+
+_Type_: `number | undefined`
+
+The numeric [exit code](https://en.wikipedia.org/wiki/Exit_status) of the subprocess that was run.
+
+This is `undefined` when the subprocess could not be spawned or was terminated by a [signal](#subprocesserrorsignalname).
+
+##### subprocessError.signalName
+
+_Type_: `string | undefined`
+
+The name of the [signal](https://en.wikipedia.org/wiki/Signal_(IPC)) (like [`SIGTERM`](https://en.wikipedia.org/wiki/Signal_(IPC)#SIGTERM)) that terminated the subprocess, sent by either:
+- The current process.
+- Another process. This case is [not supported on Windows](https://nodejs.org/api/process.html#signal-events).
+
+If a signal terminated the subprocess, this property is defined and included in the [error message](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message). Otherwise it is `undefined`.
-## Limitations
+## Windows support
-- It does not handle binary output. Use [`execa`](https://github.com/sindresorhus/execa) for that.
+This package fixes several cross-platform issues with [`node:child_process`](https://nodejs.org/api/child_process.html). It brings full Windows support for:
+- Node modules binaries (without requiring the [`shell`](https://nodejs.org/api/child_process.html#default-windows-shell) option).
+- `.cmd`, `.bat` and other shell files.
+- The [`PATHEXT`](https://wiki.tcl-lang.org/page/PATHEXT) environment variable.
+- Windows-specific [newlines](https://en.wikipedia.org/wiki/Newline#Representation).
## Maintainers