diff --git a/examples/build-script.js b/examples/build-script.js new file mode 100644 index 0000000000..33a7da2230 --- /dev/null +++ b/examples/build-script.js @@ -0,0 +1,39 @@ +import { execa } from 'execa'; + +/** + * Build automation example with error handling + * Demonstrates running build commands with proper error handling + */ + +async function runBuild() { + console.log('๐Ÿ”จ Starting build process...\n'); + + try { + // Clean previous build + console.log('๐Ÿงน Cleaning previous build...'); + await execa('rm', ['-rf', 'dist']); + console.log('โœ… Clean complete\n'); + + // Type checking + console.log('๐Ÿ” Running type check...'); + await execa('tsc', ['--noEmit'], { stdio: 'inherit' }); + console.log('โœ… Type check passed\n'); + + // Building + console.log('๐Ÿ“ฆ Building project...'); + const { stdout } = await execa('npm', ['run', 'build']); + console.log(stdout); + console.log('โœ… Build successful!\n'); + + } catch (error) { + console.error('โŒ Build failed:', error.message); + process.exit(1); + } +} + +// Run if executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + runBuild(); +} + +export { runBuild }; diff --git a/examples/git-helpers.js b/examples/git-helpers.js new file mode 100644 index 0000000000..1050b4c4ce --- /dev/null +++ b/examples/git-helpers.js @@ -0,0 +1,70 @@ +import { execa } from 'execa'; + +/** + * Git helper functions using execa + * Wrappers for common git operations + */ + +/** + * Get the current git branch + */ +async function getCurrentBranch() { + const { stdout } = await execa('git', ['branch', '--show-current']); + return stdout.trim(); +} + +/** + * Get the latest commit message + */ +async function getLatestCommit() { + const { stdout } = await execa('git', ['log', '-1', '--pretty=%s']); + return stdout.trim(); +} + +/** + * Check if working directory is clean + */ +async function isWorkingDirectoryClean() { + try { + await execa('git', ['diff', '--quiet']); + await execa('git', ['diff', '--staged', '--quiet']); + return true; + } catch { + return false; + } +} + +/** + * Get repository status + */ +async function getStatus() { + const { stdout } = await execa('git', ['status', '--short']); + return stdout || 'Working directory clean'; +} + +/** + * Stage all changes and commit + */ +async function stageAndCommit(message) { + await execa('git', ['add', '.']); + await execa('git', ['commit', '-m', message]); + console.log(`โœ… Committed: ${message}`); +} + +// Demo +if (import.meta.url === `file://${process.argv[1]}`) { + console.log('๐Ÿ“ Git Repository Info\n'); + + console.log(`Branch: ${await getCurrentBranch()}`); + console.log(`Last commit: ${await getLatestCommit()}`); + console.log(`Working directory: ${await isWorkingDirectoryClean() ? 'โœ… Clean' : 'โš ๏ธ Dirty'}`); + console.log(`Status:\n${await getStatus()}`); +} + +export { + getCurrentBranch, + getLatestCommit, + isWorkingDirectoryClean, + getStatus, + stageAndCommit, +}; diff --git a/examples/parallel-tasks.js b/examples/parallel-tasks.js new file mode 100644 index 0000000000..d9f9e24ffc --- /dev/null +++ b/examples/parallel-tasks.js @@ -0,0 +1,80 @@ +import { execa } from 'execa'; + +/** + * Running commands in parallel + * Demonstrates concurrent execution with proper error handling + */ + +/** + * Run multiple linting tasks in parallel + */ +async function runParallelLints() { + console.log('๐Ÿ” Running linters in parallel...\n'); + + try { + const tasks = [ + { name: 'ESLint', cmd: ['eslint', '.'] }, + { name: 'Prettier', cmd: ['prettier', '--check', '.'] }, + { name: 'TypeScript', cmd: ['tsc', '--noEmit'] }, + ]; + + const results = await Promise.allSettled( + tasks.map(async ({ name, cmd }) => { + console.log(`๐Ÿš€ Starting ${name}...`); + await execa('npx', cmd); + return { name, status: 'passed' }; + }) + ); + + console.log('\n๐Ÿ“Š Results:'); + let hasErrors = false; + + for (const result of results) { + if (result.status === 'fulfilled') { + console.log(` โœ… ${result.value.name}`); + } else { + console.log(` โŒ ${result.reason.name || 'Task'}`); + hasErrors = true; + } + } + + if (hasErrors) { + process.exit(1); + } + + } catch (error) { + console.error('Unexpected error:', error); + process.exit(1); + } +} + +/** + * Run multiple independent commands in parallel + * with a limit on concurrency + */ +async function runWithLimit(tasks, limit = 3) { + const results = []; + const executing = []; + + for (const [index, task] of tasks.entries()) { + const promise = task().then(result => ({ index, result })); + results.push(promise); + + if (tasks.length >= limit) { + executing.push(promise); + if (executing.length >= limit) { + await Promise.race(executing); + executing.splice(executing.findIndex(p => p === promise), 1); + } + } + } + + return Promise.all(results); +} + +// Demo +if (import.meta.url === `file://${process.argv[1]}`) { + runParallelLints(); +} + +export { runParallelLints, runWithLimit }; diff --git a/examples/readme.md b/examples/readme.md new file mode 100644 index 0000000000..a996dbb9d8 --- /dev/null +++ b/examples/readme.md @@ -0,0 +1,39 @@ +# Execa Examples + +This directory contains practical examples demonstrating common use cases with `execa`. + +## Examples + +### build-script.js +Build automation with error handling. Shows how to chain build commands and handle failures gracefully. + +```bash +node examples/build-script.js +``` + +### git-helpers.js +Git operation wrappers. Demonstrates async helper functions for common git operations. + +```bash +node examples/git-helpers.js +``` + +### parallel-tasks.js +Running commands in parallel. Shows concurrent execution with `Promise.allSettled()` and progress tracking. + +```bash +node examples/parallel-tasks.js +``` + +### stream-processing.js +Output filtering with transforms. Demonstrates using Node.js Transform streams to process command output. + +```bash +node examples/stream-processing.js +``` + +## Notes + +- All examples use ES modules (`.js` extension with `import` syntax) +- Each example can be run directly or imported as a module +- Error handling is included in all examples diff --git a/examples/stream-processing.js b/examples/stream-processing.js new file mode 100644 index 0000000000..8937d58f06 --- /dev/null +++ b/examples/stream-processing.js @@ -0,0 +1,95 @@ +import { execa } from 'execa'; +import { Transform } from 'node:stream'; + +/** + * Stream processing with transforms + * Demonstrates filtering and transforming command output + */ + +/** + * Filter lines containing specific keywords + */ +function createKeywordFilter(keywords) { + return new Transform({ + transform(chunk, encoding, callback) { + const lines = chunk.toString().split('\n'); + const filtered = lines + .filter(line => keywords.some(kw => line.toLowerCase().includes(kw.toLowerCase()))) + .join('\n'); + callback(null, filtered ? filtered + '\n' : ''); + }, + }); +} + +/** + * Prefix each line with a timestamp + */ +function createTimestampPrefix() { + return new Transform({ + transform(chunk, encoding, callback) { + const lines = chunk.toString().split('\n'); + const prefixed = lines + .filter(line => line.trim()) + .map(line => `[${new Date().toISOString()}] ${line}`) + .join('\n'); + callback(null, prefixed + '\n'); + }, + }); +} + +/** + * Count lines and show progress + */ +function createProgressCounter() { + let count = 0; + return new Transform({ + transform(chunk, encoding, callback) { + const lines = chunk.toString().split('\n'); + const nonEmpty = lines.filter(line => line.trim()); + count += nonEmpty.length; + process.stdout.write(`\r๐Ÿ“Š Processed ${count} lines...`); + callback(null, chunk); + }, + flush(callback) { + console.log(`\nโœ… Total: ${count} lines`); + callback(); + }, + }); +} + +/** + * Run a command and process its output through transforms + */ +async function runWithTransforms(command, args, transforms) { + const subprocess = execa(command, args); + + let stream = subprocess.stdout; + for (const transform of transforms) { + stream = stream.pipe(transform); + } + + stream.pipe(process.stdout); + await subprocess; +} + +// Demo +if (import.meta.url === `file://${process.argv[1]}`) { + console.log('๐Ÿ” Running npm audit with filtered output:\n'); + + try { + await runWithTransforms( + 'npm', + ['audit', '--json'], + [createKeywordFilter(['severity', 'critical', 'high'])] + ); + } catch { + // npm audit exits with non-zero on vulnerabilities + } +} + +export { + createKeywordFilter, + createTimestampPrefix, + createProgressCounter, + runWithTransforms, +};