Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 116 additions & 1 deletion docs/lines.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Alternatively, [`subprocess.iterable()`](api.md#subprocessiterablereadableoption
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](api.md#result). This means you do not need to `await` the subprocess' [promise](execution.md#result).

```js
for await (const line of execa`npm run build`.iterable())) {
for await (const line of execa`npm run build`.iterable()) {
/* ... */
}
```
Expand All @@ -51,6 +51,121 @@ for await (const stderrLine of execa`npm run build`.iterable({from: 'stderr'}))
}
```

## Custom delimiters

By default, lines are split using newline characters (`\n` or `\r\n`). To use a custom delimiter instead, combine the [`lines: false`](api.md#optionslines) option with a [transform](transform.md) that splits on your desired delimiter.

### Array output with custom delimiter

```js
import {execa} from 'execa';

// Split output by null character (useful for filenames with spaces)
const splitByNull = function * (chunk) {
if (typeof chunk !== 'string') {
yield chunk;
return;
}

const parts = chunk.split('\0');
for (const part of parts) {
if (part.length > 0) {
yield part;
}
}
};

const {stdout: filenames} = await execa({
stdout: {transform: splitByNull, objectMode: true},
})`find . -print0`;
// filenames is now an array of strings, split by null character
console.log(filenames); // ['.', './file1.txt', './file2.txt', ...]
```

### Progressive iteration with custom delimiter

```js
import {execa} from 'execa';

// Custom transform that splits by a specific delimiter
const createDelimiterTransform = delimiter => function * (chunk) {
if (typeof chunk !== 'string') {
yield chunk;
return;
}

// Keep track of partial chunks between iterations
if (!this.buffer) {
this.buffer = '';
}

this.buffer += chunk;
const parts = this.buffer.split(delimiter);

// Yield all complete parts except the last one
for (let i = 0; i < parts.length - 1; i++) {
yield parts[i];
}

// Keep the last (potentially incomplete) part in the buffer
this.buffer = parts[parts.length - 1];
};

// Usage example: processing CSV-like output with semicolon delimiter
const subprocess = execa({
stdout: {transform: createDelimiterTransform(';'), objectMode: true},
})`some-command-outputting-semicolon-separated-values`;

for await (const value of subprocess) {
console.log('Got value:', value);
}
```

### Common delimiter examples

```js
import {execa} from 'execa';

// Comma-separated values
const csvTransform = function * (chunk) {
if (typeof chunk !== 'string') {
yield chunk;
return;
}
for (const value of chunk.split(',')) {
if (value.trim()) yield value.trim();
}
};

// Tab-separated values
const tsvTransform = function * (chunk) {
if (typeof chunk !== 'string') {
yield chunk;
return;
}
for (const value of chunk.split('\t')) {
if (value) yield value;
}
};

// Double-newline (paragraph) separator
const paragraphTransform = function * (chunk) {
if (typeof chunk !== 'string') {
yield chunk;
return;
}
for (const para of chunk.split('\n\n')) {
if (para.trim()) yield para.trim();
}
};

// Example: parse comma-separated output
const {stdout: items} = await execa({
stdout: {transform: csvTransform, objectMode: true},
})`echo "apple,banana,cherry,date"`;
console.log(items); // ['apple', 'banana', 'cherry', 'date']
```
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those 3 examples are a little bigger in size and more specific than the other examples in our documentation, which are trying to explain the feature with as few lines as possible.

Would you please move them to the examples directory instead, then include some link to them from lines.md? Thanks!


## Newlines

### Final newline
Expand Down
33 changes: 33 additions & 0 deletions examples/build-script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {$} from 'execa';

// Example: Build script with error handling
// Demonstrates common patterns for build automation

async function build() {
console.log('🚀 Starting build process...\n');

try {
// Clean previous build
console.log('🧹 Cleaning previous build...');
await $`rm -rf dist`;

// Run linting
console.log('🔍 Running linter...');
await $`eslint src/`;

// Run tests
console.log('🧪 Running tests...');
await $`npm test`;

// Build the project
console.log('📦 Building project...');
await $`npm run build`;

console.log('\n✅ Build completed successfully!');
} catch (error) {
console.error('\n❌ Build failed:', error.shortMessage || error.message);
process.exit(1);

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Only use `process.exit()` in CLI apps. Throw an error instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Only use `process.exit()` in CLI apps. Throw an error instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Only use `process.exit()` in CLI apps. Throw an error instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Only use `process.exit()` in CLI apps. Throw an error instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Only use `process.exit()` in CLI apps. Throw an error instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Only use `process.exit()` in CLI apps. Throw an error instead.

Check failure on line 29 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Unexpected use of the global variable 'process'. Use 'require("process")' instead.
}
}

build();

Check failure on line 33 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Prefer top-level await over an async function `build` call.

Check failure on line 33 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Prefer top-level await over an async function `build` call.

Check failure on line 33 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Prefer top-level await over an async function `build` call.

Check failure on line 33 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Prefer top-level await over an async function `build` call.

Check failure on line 33 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Prefer top-level await over an async function `build` call.

Check failure on line 33 in examples/build-script.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Prefer top-level await over an async function `build` call.
58 changes: 58 additions & 0 deletions examples/git-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {execa} from 'execa';

// Example: Git helper functions
// Common git operations wrapped for programmatic usage

/**
* Get the current git branch
*/
export async function getCurrentBranch() {
const {stdout} = await execa`git branch --show-current`;
return stdout;
}

/**
* Check if working directory is clean
*/
export async function isWorkingDirectoryClean() {
try {
await execa`git diff --quiet`;
return true;
} catch {
return false;
}
}

/**
* Get the latest commit message
*/
export async function getLastCommitMessage() {
const {stdout} = await execa`git log -1 --pretty=%B`;
return stdout.trim();
}

/**
* Get list of changed files
*/
export async function getChangedFiles() {
const {stdout} = await execa`git status --porcelain`;
if (!stdout) return [];

Check failure on line 39 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Expected { after 'if' condition.

Check failure on line 39 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Expected { after 'if' condition.

Check failure on line 39 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Expected { after 'if' condition.

Check failure on line 39 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Expected { after 'if' condition.

Check failure on line 39 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Expected { after 'if' condition.

Check failure on line 39 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Expected { after 'if' condition.
return stdout.split('\n').map(line => line.slice(3).trim()).filter(Boolean);
}

// Demo: Run examples if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {

Check failure on line 44 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 44 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 44 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 44 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 44 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Unexpected use of the global variable 'process'. Use 'require("process")' instead.

Check failure on line 44 in examples/git-helpers.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Unexpected use of the global variable 'process'. Use 'require("process")' instead.
console.log('📁 Git Helpers Demo\n');

const branch = await getCurrentBranch();
console.log(`Current branch: ${branch}`);

const isClean = await isWorkingDirectoryClean();
console.log(`Working directory clean: ${isClean ? '✅' : '⚠️ Has uncommitted changes'}`);

const lastCommit = await getLastCommitMessage();
console.log(`Last commit: ${lastCommit.slice(0, 50)}${lastCommit.length > 50 ? '...' : ''}`);

const changedFiles = await getChangedFiles();
console.log(`Changed files: ${changedFiles.length > 0 ? changedFiles.join(', ') : 'None'}`);
}
50 changes: 50 additions & 0 deletions examples/parallel-tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {execa} from 'execa';

// Example: Running multiple commands in parallel
// Demonstrates Promise.all() patterns with Execa

async function runParallel() {
console.log('⚡ Running parallel tasks...\n');

// Example 1: Run independent commands in parallel
console.log('1️⃣ Running independent commands in parallel:');
const startTime = Date.now();

const [nodeVersion, npmVersion, gitVersion] = await Promise.all([
execa`node --version`,
execa`npm --version`,
execa`git --version`,
]);

console.log(` Node: ${nodeVersion.stdout}`);
console.log(` npm: v${npmVersion.stdout}`);
console.log(` Git: ${gitVersion.stdout}`);
console.log(` ⏱️ Time: ${Date.now() - startTime}ms\n`);

// Example 2: Parallel with error handling
console.log('2️⃣ Parallel with individual error handling:');
const results = await Promise.allSettled([
execa`node --version`,
execa`nonexistent-command`,
execa`git --version`,
]);

results.forEach((result, index) => {

Check failure on line 32 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Use `for…of` instead of `.forEach(…)`.

Check failure on line 32 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Use `for…of` instead of `.forEach(…)`.

Check failure on line 32 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Use `for…of` instead of `.forEach(…)`.

Check failure on line 32 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Use `for…of` instead of `.forEach(…)`.

Check failure on line 32 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Use `for…of` instead of `.forEach(…)`.

Check failure on line 32 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Use `for…of` instead of `.forEach(…)`.
if (result.status === 'fulfilled') {
console.log(` ✅ Task ${index + 1}: ${result.value.stdout.slice(0, 30)}`);
} else {
console.log(` ❌ Task ${index + 1}: Failed`);
}
});

// Example 3: Parallel with timeout
console.log('\n3️⃣ Parallel with timeout:');
try {
const {stdout} = await execa({timeout: 5000})`sleep 1 && echo "Completed"`;
console.log(` ✅ Result: ${stdout}`);
} catch (error) {
console.log(` ⏱️ Timed out or failed: ${error.shortMessage}`);
}
}

runParallel().catch(console.error);

Check failure on line 50 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Prefer top-level await over using a promise chain.

Check failure on line 50 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Prefer top-level await over using a promise chain.

Check failure on line 50 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Prefer top-level await over using a promise chain.

Check failure on line 50 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Prefer top-level await over using a promise chain.

Check failure on line 50 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Prefer top-level await over using a promise chain.

Check failure on line 50 in examples/parallel-tasks.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Prefer top-level await over using a promise chain.
24 changes: 24 additions & 0 deletions examples/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Execa Examples

This directory contains practical examples demonstrating common use cases of Execa.

## Examples

- [`build-script.js`](build-script.js) - A build script with error handling and logging
- [`git-helpers.js`](git-helpers.js) - Git command wrappers for Node.js projects
- [`parallel-tasks.js`](parallel-tasks.js) - Running multiple commands in parallel
- [`stream-processing.js`](stream-processing.js) - Processing command output with transforms

## Running Examples

All examples use ES modules. Run them with:

```bash
node example-name.js
```

Make sure you have `execa` installed in your project first:

```bash
npm install execa
```
53 changes: 53 additions & 0 deletions examples/stream-processing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {execa} from 'execa';

// Example: Processing command output with transforms
// Shows how to filter and transform stdout/stderr streams

async function streamProcessing() {
console.log('🔄 Stream Processing Examples\n');

// Example 1: Simple transform - prefix each line
console.log('1️⃣ Prefixing output lines:');
const prefixTransform = function* (line) {

Check failure on line 11 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Missing space before *.

Check failure on line 11 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Missing space before *.

Check failure on line 11 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Missing space before *.

Check failure on line 11 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Missing space before *.

Check failure on line 11 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Missing space before *.

Check failure on line 11 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Missing space before *.
yield `[${new Date().toISOString()}] ${line}`;
};

await execa({stdout: prefixTransform})`echo -e "line1\nline2\nline3"`;

// Example 2: Filter lines containing specific text
console.log('\n2️⃣ Filtering npm audit output for high severity:');
const filterHighSeverity = function* (line) {

Check failure on line 19 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Missing space before *.

Check failure on line 19 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Missing space before *.

Check failure on line 19 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Missing space before *.

Check failure on line 19 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Missing space before *.

Check failure on line 19 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Missing space before *.

Check failure on line 19 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Missing space before *.
if (line.includes('high') || line.includes('critical')) {
yield line;
}
};

try {
// Simulated: in real usage, this would be `npm audit`
await execa({stdout: filterHighSeverity})`echo -e "low: package1\nhigh: vulnerable-package\ninfo: package2"`;
} catch (error) {

Check failure on line 28 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on ubuntu

Remove unused catch binding `error`.

Check failure on line 28 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on ubuntu

Remove unused catch binding `error`.

Check failure on line 28 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on macos

Remove unused catch binding `error`.

Check failure on line 28 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on macos

Remove unused catch binding `error`.

Check failure on line 28 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 24 on windows

Remove unused catch binding `error`.

Check failure on line 28 in examples/stream-processing.js

View workflow job for this annotation

GitHub Actions / Node.js 18 on windows

Remove unused catch binding `error`.
// npm audit exits with non-zero on vulnerabilities
console.log(' (Example output only)');
}

// Example 3: Count lines transform
console.log('\n3️⃣ Line counter transform:');
let lineCount = 0;
const countTransform = function* (line) {
lineCount++;
yield `${lineCount}: ${line}`;
};

const {stdout} = await execa({stdout: countTransform, lines: true})`ls -la`;
console.log(` Total lines processed: ${lineCount}`);

// Example 4: Transform to uppercase
console.log('\n4️⃣ Uppercase transform:');
const upperTransform = function* (line) {
yield line.toUpperCase();
};

await execa({stdout: upperTransform})`echo "hello world"`;
}

streamProcessing().catch(console.error);
Loading