GitHub image
execa
⌘K
execa logo

Coverage Status

Process execution for humans





Why

This package improves child_process methods with:

Install

npm install execa

Documentation

Execution:

Input/output:

Advanced usage:

Usage

Promise interface

import {execa} from 'execa'; const {stdout} = await execa('echo', ['unicorns']); console.log(stdout); //=> 'unicorns'

Global/shared options

import {execa as execa_} from 'execa'; const execa = execa_({verbose: 'full'}); await execa('echo', ['unicorns']); //=> 'unicorns'

Template string syntax

Basic

import {execa} from 'execa'; const arg = 'unicorns'; const {stdout} = await execa`echo ${arg} & rainbows!`; console.log(stdout); //=> 'unicorns & rainbows!'

Multiple arguments

import {execa} from 'execa'; const args = ['unicorns', '&', 'rainbows!']; const {stdout} = await execa`echo ${args}`; console.log(stdout); //=> 'unicorns & rainbows!'

With options

import {execa} from 'execa'; await execa({verbose: 'full'})`echo unicorns`; //=> 'unicorns'

Scripts

Basic

import {$} from 'execa'; const branch = await $`git branch --show-current`; await $`dep deploy --branch=${branch}`;

Verbose mode

> node file.js unicorns rainbows > NODE_DEBUG=execa node file.js [19:49:00.360] [0] $ echo unicorns unicorns [19:49:00.383] [0] √ (done in 23ms) [19:49:00.383] [1] $ echo rainbows rainbows [19:49:00.404] [1] √ (done in 21ms)

Input/output

Redirect output to a file

import {execa} from 'execa'; // Similar to `echo unicorns > stdout.txt` in Bash await execa('echo', ['unicorns'], {stdout: {file: 'stdout.txt'}}); // Similar to `echo unicorns 2> stdout.txt` in Bash await execa('echo', ['unicorns'], {stderr: {file: 'stderr.txt'}}); // Similar to `echo unicorns &> stdout.txt` in Bash await execa('echo', ['unicorns'], {stdout: {file: 'all.txt'}, stderr: {file: 'all.txt'}});

Redirect input from a file

import {execa} from 'execa'; // Similar to `cat < stdin.txt` in Bash const {stdout} = await execa('cat', {inputFile: 'stdin.txt'}); console.log(stdout); //=> 'unicorns'

Save and pipe output from a subprocess

import {execa} from 'execa'; const {stdout} = await execa('echo', ['unicorns'], {stdout: ['pipe', 'inherit']}); // Prints `unicorns` console.log(stdout); // Also returns 'unicorns'

Pipe multiple subprocesses

import {execa} from 'execa'; // Similar to `npm run build | sort | head -n2` in Bash const {stdout, pipedFrom} = await execa('npm', ['run', 'build']) .pipe('sort') .pipe('head', ['-n2']); console.log(stdout); // Result of `head -n2` console.log(pipedFrom[0]); // Result of `sort` console.log(pipedFrom[0].pipedFrom[0]); // Result of `npm run build`

Pipe with template strings

import {execa} from 'execa'; await execa`npm run build` .pipe`sort` .pipe`head -n2`;

Iterate over output lines

import {execa} from 'execa'; for await (const line of execa`npm run build`)) { if (line.includes('ERROR')) { console.log(line); } }

Handling Errors

import {execa} from 'execa'; // Catching an error try { await execa('unknown', ['command']); } catch (error) { console.log(error); /* ExecaError: Command failed with ENOENT: unknown command spawn unknown ENOENT at ... at ... { shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT', originalMessage: 'spawn unknown ENOENT', command: 'unknown command', escapedCommand: 'unknown command', cwd: '/path/to/cwd', durationMs: 28.217566, failed: true, timedOut: false, isCanceled: false, isTerminated: false, isMaxBuffer: false, code: 'ENOENT', stdout: '', stderr: '', stdio: [undefined, '', ''], pipedFrom: [] [cause]: Error: spawn unknown ENOENT at ... at ... { errno: -2, code: 'ENOENT', syscall: 'spawn unknown', path: 'unknown', spawnargs: [ 'command' ] } } */ }

API

Methods

execa(file, arguments?, options?)

file: string | URL
arguments: string[]
options: Options
Returns: Subprocess

Executes a command using file ...arguments.

More info on the syntax and escaping.

execa`command`

execa(options)`command`

command: string
options: Options
Returns: Subprocess

Executes a command. command is a template string that includes both the file and its arguments.

More info on the syntax and escaping.

execa(options)

options: Options
Returns: execa

Returns a new instance of Execa but with different default options. Consecutive calls are merged to previous ones.

More info.

execaSync(file, arguments?, options?)

execaSync`command`

Same as execa() but synchronous.

Returns or throws a subprocess result. The subprocess is not returned: its methods and properties are not available.

More info.

$(file, arguments?, options?)

file: string | URL
arguments: string[]
options: Options
Returns: Subprocess

Same as execa() but using script-friendly default options.

Just like execa(), this can use the template string syntax or bind options. It can also be run synchronously using $.sync() or $.s().

This is the preferred method when executing multiple commands in a script file.

More info.

execaNode(scriptPath, arguments?, options?)

scriptPath: string | URL
arguments: string[]
options: Options
Returns: Subprocess

Same as execa() but using the node: true option. Executes a Node.js file using node scriptPath ...arguments.

Just like execa(), this can use the template string syntax or bind options.

This is the preferred method when executing Node.js files.

More info.

execaCommand(command, options?)

command: string
options: Options
Returns: Subprocess

Executes a command. command is a string that includes both the file and its arguments.

This is only intended for very specific cases, such as a REPL. This should be avoided otherwise.

Just like execa(), this can bind options. It can also be run synchronously using execaCommandSync().

More info.

subprocess

The return value of all asynchronous methods is both:

More info.

subprocess.pipe(file, arguments?, options?)

file: string | URL
arguments: string[]
options: Options and PipeOptions
Returns: Promise<Result>

Pipe the subprocess' stdout to a second Execa subprocess' stdin. This resolves with that second subprocess' result. If either subprocess is rejected, this is rejected with that subprocess' error instead.

This follows the same syntax as execa(file, arguments?, options?) except both regular options and pipe-specific options can be specified.

More info.

subprocess.pipe`command`

subprocess.pipe(options)`command`

command: string
options: Options and PipeOptions
Returns: Promise<Result>

Like subprocess.pipe(file, arguments?, options?) but using a command template string instead. This follows the same syntax as execa template strings.

More info.

subprocess.pipe(secondSubprocess, pipeOptions?)

secondSubprocess: execa() return value
pipeOptions: PipeOptions
Returns: Promise<Result>

Like subprocess.pipe(file, arguments?, options?) but using the return value of another execa() call instead.

More info.

pipeOptions

Type: object

pipeOptions.from

Type: "stdout" | "stderr" | "all" | "fd3" | "fd4" | ...
Default: "stdout"

Which stream to pipe from the source subprocess. A file descriptor like "fd3" can also be passed.

"all" pipes both stdout and stderr. This requires the all option to be true.

More info.

pipeOptions.to

Type: "stdin" | "fd3" | "fd4" | ...
Default: "stdin"

Which stream to pipe to the destination subprocess. A file descriptor like "fd3" can also be passed.

More info.

pipeOptions.unpipeSignal

Type: AbortSignal

Unpipe the subprocess when the signal aborts.

More info.

subprocess.kill(signal, error?)

subprocess.kill(error?)

signal: string | number
error: Error
Returns: boolean

Sends a signal to the subprocess. The default signal is the killSignal option. killSignal defaults to SIGTERM, which terminates the subprocess.

This returns false when the signal could not be sent, for example when the subprocess has already exited.

When an error is passed as argument, it is set to the subprocess' error.cause. The subprocess is then terminated with the default signal. This does not emit the error event.

More info.

subprocess.pid

Type: number | undefined

Process identifier (PID).

This is undefined if the subprocess failed to spawn.

More info.

subprocess.send(message)

message: unknown
Returns: boolean

Send a message to the subprocess. The type of message depends on the serialization option. The subprocess receives it as a message event.

This returns true on success.

This requires the ipc option to be true.

More info.

subprocess.on('message', (message) => void)

message: unknown

Receives a message from the subprocess. The type of message depends on the serialization option. The subprocess sends it using process.send(message).

This requires the ipc option to be true.

More info.

subprocess.stdin

Type: Writable | null

The subprocess stdin as a stream.

This is null if the stdin option is set to 'inherit', 'ignore', Readable or integer.

More info.

subprocess.stdout

Type: Readable | null

The subprocess stdout as a stream.

This is null if the stdout option is set to 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

More info.

subprocess.stderr

Type: Readable | null

The subprocess stderr as a stream.

This is null if the stderr option is set to 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

More info.

subprocess.all

Type: Readable | undefined

Stream combining/interleaving subprocess.stdout and subprocess.stderr.

This requires the all option to be true.

This is undefined if stdout and stderr options are set to 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

More info on interleaving and streaming.

subprocess.stdio

Type: [Writable | null, Readable | null, Readable | null, ...Array<Writable | Readable | null>]

The subprocess stdin, stdout, stderr and other files descriptors as an array of streams.

Each array item is null if the corresponding stdin, stdout, stderr or stdio option is set to 'inherit', 'ignore', Stream or integer, or if the buffer option is false.

More info.

subprocess[Symbol.asyncIterator]()

Returns: AsyncIterable

Subprocesses are async iterables. They iterate over each output line.

More info.

subprocess.iterable(readableOptions?)

readableOptions: ReadableOptions
Returns: AsyncIterable

Same as subprocess[Symbol.asyncIterator] except options can be provided.

More info.

subprocess.readable(readableOptions?)

readableOptions: ReadableOptions
Returns: Readable Node.js stream

Converts the subprocess to a readable stream.

More info.

subprocess.writable(writableOptions?)

writableOptions: WritableOptions
Returns: Writable Node.js stream

Converts the subprocess to a writable stream.

More info.

subprocess.duplex(duplexOptions?)

duplexOptions: ReadableOptions | WritableOptions
Returns: Duplex Node.js stream

Converts the subprocess to a duplex stream.

More info.

readableOptions

Type: object

readableOptions.from

Type: "stdout" | "stderr" | "all" | "fd3" | "fd4" | ...
Default: "stdout"

Which stream to read from the subprocess. A file descriptor like "fd3" can also be passed.

"all" reads both stdout and stderr. This requires the all option to be true.

More info.

readableOptions.binary

Type: boolean
Default: false with subprocess.iterable(), true with subprocess.readable()/subprocess.duplex()

If false, iterates over lines. Each line is a string.

If true, iterates over arbitrary chunks of data. Each line is an Uint8Array (with subprocess.iterable()) or a Buffer (with subprocess.readable()/subprocess.duplex()).

This is always true when the encoding option is binary.

More info for iterables and streams.

readableOptions.preserveNewlines

Type: boolean
Default: false with subprocess.iterable(), true with subprocess.readable()/subprocess.duplex()

If both this option and the binary option is false, newlines are stripped from each line.

More info.

writableOptions

Type: object

writableOptions.to

Type: "stdin" | "fd3" | "fd4" | ...
Default: "stdin"

Which stream to write to the subprocess. A file descriptor like "fd3" can also be passed.

More info.

Result

Type: object

Result of a subprocess execution.

When the subprocess fails, it is rejected with an ExecaError instead.

result.command

Type: string

The file and arguments that were run.

More info.

result.escapedCommand

Type: string

Same as command but escaped.

More info.

result.cwd

Type: string

The current directory in which the command was run.

More info.

result.durationMs

Type: number

Duration of the subprocess, in milliseconds.

More info.

result.stdout

Type: string | Uint8Array | string[] | Uint8Array[] | unknown[] | undefined

The output of the subprocess on stdout.

This is undefined if the stdout option is set to only 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

This is an array if the lines option is true, or if the stdout option is a transform in object mode.

More info.

result.stderr

Type: string | Uint8Array | string[] | Uint8Array[] | unknown[] | undefined

The output of the subprocess on stderr.

This is undefined if the stderr option is set to only 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

This is an array if the lines option is true, or if the stderr option is a transform in object mode.

More info.

result.all

Type: string | Uint8Array | string[] | Uint8Array[] | unknown[] | undefined

The output of the subprocess with result.stdout and result.stderr interleaved.

This requires the all option to be true.

This is undefined if both stdout and stderr options are set to only 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

This is an array if the lines option is true, or if either the stdout or stderr option is a transform in object mode.

More info.

result.stdio

Type: Array<string | Uint8Array | string[] | Uint8Array[] | unknown[] | undefined>

The output of the subprocess on stdin, stdout, stderr and other file descriptors.

Items are undefined when their corresponding stdio option is set to 'inherit', 'ignore', Writable or integer, or if the buffer option is false.

Items are arrays when their corresponding stdio option is a transform in object mode.

More info.

result.failed

Type: boolean

Whether the subprocess failed to run.

More info.

result.timedOut

Type: boolean

Whether the subprocess timed out due to the timeout option.

More info.

result.isCanceled

Type: boolean

Whether the subprocess was canceled using the cancelSignal option.

More info.

result.isTerminated

Type: boolean

Whether the subprocess was terminated by a signal (like SIGTERM) sent by either:

More info.

result.isMaxBuffer

Type: boolean

Whether the subprocess failed because its output was larger than the maxBuffer option.

More info.

result.exitCode

Type: number | undefined

The numeric exit code of the subprocess that was run.

This is undefined when the subprocess could not be spawned or was terminated by a signal.

More info.

result.signal

Type: string | undefined

The name of the signal (like SIGTERM) that terminated the subprocess, sent by either:

If a signal terminated the subprocess, this property is defined and included in the error message. Otherwise it is undefined.

More info.

result.signalDescription

Type: string | undefined

A human-friendly description of the signal that was used to terminate the subprocess.

If a signal terminated the subprocess, this property is defined and included in the error message. Otherwise it is undefined. It is also undefined when the signal is very uncommon which should seldomly happen.

More info.

result.pipedFrom

Type: Array<Result | ExecaError>

Results of the other subprocesses that were piped into this subprocess.

This array is initially empty and is populated each time the subprocess.pipe() method resolves.

More info.

ExecaError

ExecaSyncError

Type: Error

Exception thrown when the subprocess fails.

This has the same shape as successful results, with the following additional properties.

More info.

error.message

Type: string

Error message when the subprocess failed to run.

More info.

error.shortMessage

Type: string

This is the same as error.message except it does not include the subprocess output.

More info.

error.originalMessage

Type: string | undefined

Original error message. This is the same as error.message excluding the subprocess output and some additional information added by Execa.

More info.

error.cause

Type: unknown | undefined

Underlying error, if there is one. For example, this is set by subprocess.kill(error).

This is usually an Error instance.

More info.

error.code

Type: string | undefined

Node.js-specific error code, when available.

options

Type: object

This lists all options for execa() and the other methods.

The following options can specify different values for stdout and stderr: verbose, lines, stripFinalNewline, buffer, maxBuffer.

options.reject

Type: boolean
Default: true

Setting this to false resolves the result's promise with the error instead of rejecting it.

More info.

options.shell

Type: boolean | string | URL
Default: false

If true, runs the command inside of a shell.

Uses /bin/sh on UNIX and cmd.exe on Windows. A different shell can be specified as a string. The shell should understand the -c switch on UNIX or /d /s /c on Windows.

We recommend against using this option.

More info.

options.cwd

Type: string | URL
Default: process.cwd()

Current working directory of the subprocess.

This is also used to resolve the nodePath option when it is a relative path.

More info.

options.env

Type: object
Default: process.env

Environment variables.

Unless the extendEnv option is false, the subprocess also uses the current process' environment variables (process.env).

More info.

options.extendEnv

Type: boolean
Default: true

If true, the subprocess uses both the env option and the current process' environment variables (process.env). If false, only the env option is used, not process.env.

More info.

options.preferLocal

Type: boolean
Default: true with $, false otherwise

Prefer locally installed binaries when looking for a binary to execute.

More info.

options.localDir

Type: string | URL
Default: cwd option

Preferred path to find locally installed binaries, when using the preferLocal option.

More info.

options.node

Type: boolean
Default: true with execaNode(), false otherwise

If true, runs with Node.js. The first argument must be a Node.js file.

More info.

options.nodeOptions

Type: string[]
Default: process.execArgv (current Node.js CLI options)

List of CLI flags passed to the Node.js executable.

Requires the node option to be true.

More info.

options.nodePath

Type: string | URL
Default: process.execPath (current Node.js executable)

Path to the Node.js executable.

Requires the node option to be true.

More info.

options.verbose

Type: 'none' | 'short' | 'full'
Default: 'none'

If verbose is 'short', prints the command on stderr: its file, arguments, duration and (if it failed) error message.

If verbose is 'full', the command's stdout and stderr are also printed.

By default, this applies to both stdout and stderr, but different values can also be passed.

More info.

options.buffer

Type: boolean
Default: true

When buffer is false, the result.stdout, result.stderr, result.all and result.stdio properties are not set.

By default, this applies to both stdout and stderr, but different values can also be passed.

More info.

options.input

Type: string | Uint8Array | stream.Readable

Write some input to the subprocess' stdin.

See also the inputFile and stdin options.

More info.

options.inputFile

Type: string | URL

Use a file as input to the subprocess' stdin.

See also the input and stdin options.

More info.

options.stdin

Type: string | number | stream.Readable | ReadableStream | TransformStream | URL | {file: string} | Uint8Array | Iterable<string | Uint8Array | unknown> | AsyncIterable<string | Uint8Array | unknown> | GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown> | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream} (or a tuple of those types)
Default: 'inherit' with $, 'pipe' otherwise

How to setup the subprocess' standard input. This can be 'pipe', 'overlapped', 'ignore, 'inherit', a file descriptor integer, a Node.js Readable stream, a web ReadableStream, a { file: 'path' } object, a file URL, an Iterable (including an array of strings), an AsyncIterable, an Uint8Array, a generator function, a Duplex or a web TransformStream.

This can be an array of values such as ['inherit', 'pipe'] or [fileUrl, 'pipe'].

More info on available values, streaming and transforms.

options.stdout

Type: string | number | stream.Writable | WritableStream | TransformStream | URL | {file: string} | GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown> | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream} (or a tuple of those types)
Default: pipe

How to setup the subprocess' standard output. This can be 'pipe', 'overlapped', 'ignore, 'inherit', a file descriptor integer, a Node.js Writable stream, a web WritableStream, a { file: 'path' } object, a file URL, a generator function, a Duplex or a web TransformStream.

This can be an array of values such as ['inherit', 'pipe'] or [fileUrl, 'pipe'].

More info on available values, streaming and transforms.

options.stderr

Type: string | number | stream.Writable | WritableStream | TransformStream | URL | {file: string} | GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown> | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream} (or a tuple of those types)
Default: pipe

How to setup the subprocess' standard error. This can be 'pipe', 'overlapped', 'ignore, 'inherit', a file descriptor integer, a Node.js Writable stream, a web WritableStream, a { file: 'path' } object, a file URL, a generator function, a Duplex or a web TransformStream.

This can be an array of values such as ['inherit', 'pipe'] or [fileUrl, 'pipe'].

More info on available values, streaming and transforms.

options.stdio

Type: string | Array<string | number | stream.Readable | stream.Writable | ReadableStream | WritableStream | TransformStream | URL | {file: string} | Uint8Array | Iterable<string> | Iterable<Uint8Array> | Iterable<unknown> | AsyncIterable<string | Uint8Array | unknown> | GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown> | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream}> (or a tuple of those types)
Default: pipe

Like the stdin, stdout and stderr options but for all file descriptors at once. For example, {stdio: ['ignore', 'pipe', 'pipe']} is the same as {stdin: 'ignore', stdout: 'pipe', stderr: 'pipe'}.

A single string can be used as a shortcut.

The array can have more than 3 items, to create additional file descriptors beyond stdin/stdout/stderr.

More info on available values, streaming and transforms.

options.all

Type: boolean
Default: false

Add a subprocess.all stream and a result.all property.

More info.

options.lines

Type: boolean
Default: false

Set result.stdout, result.stderr, result.all and result.stdio as arrays of strings, splitting the subprocess' output into lines.

This cannot be used if the encoding option is binary.

By default, this applies to both stdout and stderr, but different values can also be passed.

More info.

options.encoding

Type: 'utf8' | 'utf16le' | 'buffer' | 'hex' | 'base64' | 'base64url' | 'latin1' | 'ascii'
Default: 'utf8'

If the subprocess outputs text, specifies its character encoding, either 'utf8' or 'utf16le'.

If it outputs binary data instead, this should be either:

The output is available with result.stdout, result.stderr and result.stdio.

More info.

options.stripFinalNewline

Type: boolean
Default: true

Strip the final newline character from the output.

If the lines option is true, this applies to each output line instead.

By default, this applies to both stdout and stderr, but different values can also be passed.

More info.

options.maxBuffer

Type: number
Default: 100_000_000

Largest amount of data allowed on stdout, stderr and stdio.

By default, this applies to both stdout and stderr, but different values can also be passed.

More info.

options.ipc

Type: boolean
Default: true if the node option is enabled, false otherwise

Enables exchanging messages with the subprocess using subprocess.send(message) and subprocess.on('message', (message) => {}).

More info.

options.serialization

Type: 'json' | 'advanced'
Default: 'advanced'

Specify the kind of serialization used for sending messages between subprocesses when using the ipc option.

More info.

options.detached

Type: boolean
Default: false

Run the subprocess independently from the current process.

More info.

options.cleanup

Type: boolean
Default: true

Kill the subprocess when the current process exits.

More info.

options.timeout

Type: number
Default: 0

If timeout is greater than 0, the subprocess will be terminated if it runs for longer than that amount of milliseconds.

On timeout, result.timedOut becomes true.

More info.

options.cancelSignal

Type: AbortSignal

You can abort the subprocess using AbortController.

When AbortController.abort() is called, result.isCanceled becomes true.

More info.

options.forceKillAfterDelay

Type: number | false
Default: 5000

If the subprocess is terminated but does not exit, forcefully exit it by sending SIGKILL.

More info.

options.killSignal

Type: string | number
Default: 'SIGTERM'

Default signal used to terminate the subprocess.

This can be either a name (like 'SIGTERM') or a number (like 9).

More info.

options.argv0

Type: string
Default: file being executed

Value of argv[0] sent to the subprocess.

options.uid

Type: number
Default: current user identifier

Sets the user identifier of the subprocess.

More info.

options.gid

Type: number
Default: current group identifier

Sets the group identifier of the subprocess.

More info.

options.windowsVerbatimArguments

Type: boolean
Default: true if the shell option is true, false otherwise

If false, escapes the command arguments on Windows.

More info.

options.windowsHide

Type: boolean
Default: true

On Windows, do not create a new console window.

More info.

Transform options

A transform or an array of transforms can be passed to the stdin, stdout, stderr or stdio option.

A transform is either a generator function or a plain object with the following members.

More info.

transformOptions.transform

Type: GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown>

Map or filter the input or output of the subprocess.

More info here and there.

transformOptions.final

Type: GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown>

Create additional lines after the last one.

More info.

transformOptions.binary

Type: boolean
Default: false

If true, iterate over arbitrary chunks of Uint8Arrays instead of line strings.

More info.

transformOptions.preserveNewlines

Type: boolean
Default: false

If true, keep newlines in each line argument. Also, this allows multiple yields to produces a single line.

More info.

transformOptions.objectMode

Type: boolean
Default: false

If true, allow transformOptions.transform and transformOptions.final to return any type, not just string or Uint8Array.

More info.

Related

Maintainers