diff --git a/README.md b/README.md index fc45e26..d25bcd8 100644 --- a/README.md +++ b/README.md @@ -431,6 +431,53 @@ console.log(traditional.stdout); // "still works\n" - **`streams.*`** - Available **immediately** when command starts, for real-time interaction - **`buffers.*` & `strings.*`** - Complete **snapshots** available only **after** command finishes +### Process ID (PID) Access + +Get the process ID of running commands for monitoring and management: + +```javascript +import { $ } from 'command-stream'; + +// Method 1: Access PID after starting via streams (recommended) +const command = $`ping -c 5 google.com`; +const stdout = await command.streams.stdout; + +if (command.child && command.child.pid) { + console.log(`Command PID: ${command.child.pid}`); + console.log('Process is running, you can monitor it externally'); +} + +const result = await command; + +// Method 2: Access PID with explicit start +const longCmd = $`sleep 10`; +await longCmd.start(); + +console.log(`Sleep PID: ${longCmd.child.pid}`); +// PID remains available even after completion + +// Method 3: Safe PID access with error handling +function getPidSafely(cmd, name) { + if (cmd.child && cmd.child.pid) { + return cmd.child.pid; + } else { + console.log(`PID not available for ${name}`); + return null; + } +} + +const echoCmd = $`echo "Hello"`; +await echoCmd.streams.stdout; +const pid = getPidSafely(echoCmd, 'echo command'); +``` + +**Key Points:** +- Access PID via: `command.child.pid` +- Process must be started first (use `.streams.*`, `.start()`, or `.stream()`) +- Always check if `command.child` and `command.child.pid` exist +- PID remains accessible even after command completion +- Very fast commands may finish before PID access + ### Shell Replacement (.sh โ†’ .mjs) Replace bash scripts with JavaScript while keeping shell semantics: @@ -828,6 +875,8 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event - `stdout`: Direct access to child process stdout stream - `stderr`: Direct access to child process stderr stream - `stdin`: Direct access to child process stdin stream +- `child`: Reference to the underlying Node.js ChildProcess object +- `child.pid`: Process ID (PID) of the running command (available after process starts) ### Default Options diff --git a/examples/README.md b/examples/README.md index fbe01d3..c44416d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -149,6 +149,11 @@ The simplest examples to get started: - `syntax-piping-comparison.mjs` - Command chaining comparison - `syntax-multiple-listeners.mjs` - Multiple event listeners comparison +### ๐Ÿ†” Process Management + +**PID Access:** +- `process-pid-access.mjs` - Complete guide to accessing Process IDs (PIDs) of started commands + ### ๐Ÿงช Testing and Debugging **Core Functionality Tests:** @@ -279,12 +284,21 @@ The simplest examples to get started: - โœ… **No resource leaks** - Virtual commands are properly closed - โœ… **Clean exit** - No hanging processes after iteration stops +### ๐Ÿ†” Process Management +- โœ… **PID access** - Access process IDs via `command.child.pid` +- โœ… **Process lifecycle** - PID available after process starts, remains after completion +- โœ… **Multiple startup methods** - Access via streams, start(), or stream() +- โœ… **Safe PID handling** - Best practices for checking PID availability + ## Usage Examples ```bash # Run a basic example bun examples/ping-streaming-simple.mjs +# Learn how to get process PIDs +node examples/process-pid-access.mjs + # Test ANSI color handling node examples/colors-default-preserved.mjs diff --git a/examples/process-pid-access.mjs b/examples/process-pid-access.mjs new file mode 100755 index 0000000..f599ff3 --- /dev/null +++ b/examples/process-pid-access.mjs @@ -0,0 +1,191 @@ +#!/usr/bin/env node + +// Example: How to get PID of started commands +// This demonstrates different ways to access the process ID of running commands + +import { $ } from '../src/$.mjs'; + +console.log('๐Ÿ†” Process ID (PID) Access Examples\n'); + +// Example 1: Basic PID access with auto-start via streams +console.log('1๏ธโƒฃ Basic PID Access (streams auto-start):'); +const echoCmd = $`echo "Hello World"`; + +// Accessing streams automatically starts the process +const stdout = await echoCmd.streams.stdout; + +// Now the PID should be available +if (echoCmd.child && echoCmd.child.pid) { + console.log(` โœ… Command PID: ${echoCmd.child.pid}`); + console.log(` Command: echo "Hello World"`); +} else { + console.log(' โš ๏ธ PID not available'); +} + +// Wait for completion and show output +const result1 = await echoCmd; +console.log(` Output: ${result1.stdout.trim()}`); +console.log(` Exit code: ${result1.code}\n`); + +// Example 2: PID access with explicit start +console.log('2๏ธโƒฃ PID Access with Explicit Start:'); +const sleepCmd = $`sleep 2`; + +// Start the command explicitly +await sleepCmd.start(); + +// Give it a moment to fully initialize +await new Promise(resolve => setTimeout(resolve, 10)); + +if (sleepCmd.child && sleepCmd.child.pid) { + console.log(` โœ… Sleep command PID: ${sleepCmd.child.pid}`); + console.log(` Command: sleep 2`); + console.log(` Status: running...`); +} else { + console.log(' โš ๏ธ PID not available'); +} + +// Wait for completion +const result2 = await sleepCmd; +console.log(` Sleep completed with exit code: ${result2.code}\n`); + +// Example 3: Multiple commands with PID tracking using streams +console.log('3๏ธโƒฃ Multiple Commands PID Tracking:'); +const commands = [ + $`sleep 0.5`, // Use sleep to keep process alive longer + $`sleep 0.5`, + $`sleep 0.5` +]; + +const pids = []; + +// Start all commands and collect PIDs using streams access +for (let i = 0; i < commands.length; i++) { + const cmd = commands[i]; + // Access streams to auto-start the process + const stdout = await cmd.streams.stdout; + + if (cmd.child && cmd.child.pid) { + pids.push(cmd.child.pid); + console.log(` โœ… Command ${i + 1} PID: ${cmd.child.pid}`); + } else { + console.log(` โš ๏ธ Command ${i + 1} PID: not available`); + } +} + +// Wait for all to complete +const results = await Promise.all(commands); +console.log(` All ${results.length} commands completed\n`); + +// Example 4: PID access with streaming +console.log('4๏ธโƒฃ Streaming with PID Access:'); +const pingCmd = $`ping -c 3 127.0.0.1`; + +// Start streaming - this auto-starts the process +const stream = pingCmd.stream(); + +// Small delay to let the process fully initialize +await new Promise(resolve => setTimeout(resolve, 100)); + +if (pingCmd.child && pingCmd.child.pid) { + console.log(` โœ… Ping command PID: ${pingCmd.child.pid}`); + console.log(` Streaming ping output:`); + + // Process streaming output + for await (const chunk of stream) { + if (chunk.type === 'stdout') { + const line = chunk.data.toString().trim(); + if (line && line.includes('ping') || line.includes('bytes') || line.includes('time=')) { + console.log(` ๐Ÿ“ก ${line}`); + } + } + } +} else { + console.log(' โš ๏ธ Could not access PID for streaming command'); +} + +console.log('\n'); + +// Example 5: PID with event-based processing +console.log('5๏ธโƒฃ Event-based Processing with PID:'); +const eventCmd = $`sleep 1` // Use sleep for a longer-running process + .on('stdout', (chunk) => { + console.log(` ๐Ÿ“‹ Event: Received output: ${chunk.toString().trim()}`); + }) + .on('end', (result) => { + console.log(` ๐Ÿ“‹ Event: Command finished with exit code ${result.code}`); + }); + +// Access streams to start the process, then check PID +const eventStdout = await eventCmd.streams.stdout; +if (eventCmd.child && eventCmd.child.pid) { + console.log(` ๐Ÿ“‹ โœ… Event-based command PID: ${eventCmd.child.pid}`); +} + +// Wait for completion +await eventCmd; + +console.log('\n'); + +// Example 6: PID availability timeline with proper initialization +console.log('6๏ธโƒฃ PID Availability Timeline:'); +const timelineCmd = $`sleep 0.5`; + +console.log(' ๐Ÿ• Before accessing streams: PID available?', !!(timelineCmd.child && timelineCmd.child.pid)); + +// Access streams to start the process +const timelineStdout = await timelineCmd.streams.stdout; +console.log(' ๐Ÿ• After accessing streams: PID available?', !!(timelineCmd.child && timelineCmd.child.pid)); + +if (timelineCmd.child && timelineCmd.child.pid) { + console.log(` ๐Ÿ• โœ… PID during execution: ${timelineCmd.child.pid}`); +} + +await timelineCmd; +console.log(' ๐Ÿ• After completion: PID available?', !!(timelineCmd.child && timelineCmd.child.pid)); + +console.log('\n'); + +// Example 7: Error handling and best practices +console.log('7๏ธโƒฃ Best Practices for PID Access:'); + +function getPidSafely(command, commandName) { + try { + if (command.child && command.child.pid) { + return command.child.pid; + } else { + console.log(` โš ๏ธ PID not available for ${commandName}`); + console.log(` ๐Ÿ’ก Tip: Access .streams or call .start() first`); + return null; + } + } catch (error) { + console.log(` โŒ Error accessing PID for ${commandName}:`, error.message); + return null; + } +} + +const safeCmd = $`sleep 0.2`; + +// Method 1: Access streams to initialize +const safeStdout = await safeCmd.streams.stdout; +const pid = getPidSafely(safeCmd, 'sleep command'); +if (pid) { + console.log(` โœ… Successfully got PID: ${pid}`); +} + +await safeCmd; + +console.log('\n๐Ÿ All PID examples completed!'); +console.log('\n๐Ÿ“š Key Takeaways:'); +console.log(' โ€ข Access PID via: command.child.pid'); +console.log(' โ€ข Process must be started first - use command.streams.* or command.start()'); +console.log(' โ€ข PID becomes available once child process is created'); +console.log(' โ€ข Always check if command.child and command.child.pid exist'); +console.log(' โ€ข PID remains available even after command completion'); +console.log(' โ€ข Use getPidSafely() pattern for robust error handling'); +console.log('\n๐Ÿ”ง Three ways to start a process and access PID:'); +console.log(' 1. await command.streams.stdout (recommended)'); +console.log(' 2. await command.start()'); +console.log(' 3. command.stream() (for streaming)'); +console.log('\n๐Ÿ’ก Pro tip: For very fast commands, consider using sleep or long-running'); +console.log(' commands to ensure PID remains accessible long enough.'); \ No newline at end of file