diff --git a/examples/quiet-method-demo.mjs b/examples/quiet-method-demo.mjs new file mode 100644 index 0000000..e98e867 --- /dev/null +++ b/examples/quiet-method-demo.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +/** + * Demonstration of the .quiet() method + * Similar to zx's .quiet() functionality + * + * This example shows how .quiet() suppresses console output + * while still capturing the command's stdout/stderr + */ + +import { $ } from '../src/$.mjs'; + +console.log('=== Example 1: Without .quiet() - output is shown ==='); +const result1 = await $`echo "This will be printed to console"`; +console.log('Captured stdout:', result1.stdout.trim()); +console.log(''); + +console.log('=== Example 2: With .quiet() - output is suppressed ==='); +const result2 = await $`echo "This will NOT be printed to console"`.quiet(); +console.log('Captured stdout:', result2.stdout.trim()); +console.log(''); + +console.log('=== Example 3: Similar to the issue example ==='); +// This simulates the use case from the issue: +// await $`gh api gists/${gistId} --jq '{owner: .owner.login, files: .files, history: .history}'`.quiet(); + +// Using a simple command instead of gh api for demonstration +const jsonData = JSON.stringify({ + owner: 'test-user', + files: { 'file.txt': { content: 'Hello World' } }, + history: [] +}); + +const result3 = await $`echo ${jsonData}`.quiet(); +const parsed = JSON.parse(result3.stdout.trim()); +console.log('Parsed data (without console noise):', parsed); +console.log(''); + +console.log('=== Example 4: Chaining with other methods ==='); +const result4 = await $`echo "Line 1\nLine 2\nLine 3"`.quiet(); +console.log('Lines captured:', result4.stdout.split('\n').length); +console.log(''); + +console.log('=== All examples completed successfully! ==='); diff --git a/src/$.mjs b/src/$.mjs index 46c7258..3922c14 100755 --- a/src/$.mjs +++ b/src/$.mjs @@ -4137,6 +4137,12 @@ class ProcessRunner extends StreamEmitter { throw new Error('pipe() destination must be a ProcessRunner or $`command` result'); } + quiet() { + trace('ProcessRunner', () => `quiet() called - disabling console output`); + this.options.mirror = false; + return this; + } + // Promise interface (for await) then(onFulfilled, onRejected) { trace('ProcessRunner', () => `then() called | ${JSON.stringify({ diff --git a/tests/quiet-method.test.mjs b/tests/quiet-method.test.mjs new file mode 100644 index 0000000..a4c8f6b --- /dev/null +++ b/tests/quiet-method.test.mjs @@ -0,0 +1,150 @@ +import { test, expect, describe, beforeEach, afterEach } from 'bun:test'; +import './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup +import { $, shell } from '../src/$.mjs'; + +// Reset shell settings before each test to prevent interference +beforeEach(() => { + shell.errexit(false); + shell.verbose(false); + shell.xtrace(false); + shell.pipefail(false); + shell.nounset(false); +}); + +// Reset shell settings after each test to prevent interference with other test files +afterEach(() => { + shell.errexit(false); + shell.verbose(false); + shell.xtrace(false); + shell.pipefail(false); + shell.nounset(false); +}); + +describe('.quiet() method', () => { + test('should suppress console output when .quiet() is called', async () => { + // Capture stdout to verify output is suppressed + let capturedStdout = ''; + const originalWrite = process.stdout.write; + process.stdout.write = (chunk) => { + capturedStdout += chunk.toString(); + return true; + }; + + try { + const result = await $`echo "test output"`.quiet(); + + // Restore original stdout + process.stdout.write = originalWrite; + + // The result should still contain the output + expect(result.stdout.trim()).toBe('test output'); + + // But nothing should have been written to console + expect(capturedStdout).toBe(''); + } finally { + // Ensure stdout is restored even if test fails + process.stdout.write = originalWrite; + } + }); + + test('should work with chaining after .quiet()', async () => { + let capturedStdout = ''; + const originalWrite = process.stdout.write; + process.stdout.write = (chunk) => { + capturedStdout += chunk.toString(); + return true; + }; + + try { + const result = await $`echo "chained test"`.quiet(); + + process.stdout.write = originalWrite; + + expect(result.stdout.trim()).toBe('chained test'); + expect(capturedStdout).toBe(''); + } finally { + process.stdout.write = originalWrite; + } + }); + + test('should allow normal output without .quiet()', async () => { + // Capture stdout to verify output is shown + let capturedStdout = ''; + const originalWrite = process.stdout.write; + process.stdout.write = (chunk) => { + capturedStdout += chunk.toString(); + return true; + }; + + try { + const result = await $`echo "normal output"`; + + process.stdout.write = originalWrite; + + // The result should contain the output + expect(result.stdout.trim()).toBe('normal output'); + + // And it should have been written to console (mirrored) + expect(capturedStdout).toContain('normal output'); + } finally { + process.stdout.write = originalWrite; + } + }); + + test('should return ProcessRunner instance for chaining', () => { + const runner = $`echo "test"`.quiet(); + + // Should return a ProcessRunner that can be awaited + expect(runner).toBeDefined(); + expect(typeof runner.then).toBe('function'); + expect(typeof runner.quiet).toBe('function'); + }); + + test('should work with stderr output', async () => { + let capturedStderr = ''; + const originalWrite = process.stderr.write; + process.stderr.write = (chunk) => { + capturedStderr += chunk.toString(); + return true; + }; + + try { + const result = await $`node -e "console.error('error message')"`.quiet(); + + process.stderr.write = originalWrite; + + // The result should still contain stderr + expect(result.stderr.trim()).toContain('error message'); + + // But nothing should have been written to console + expect(capturedStderr).toBe(''); + } finally { + process.stderr.write = originalWrite; + } + }); + + test('should work similar to zx quiet() behavior', async () => { + // Test the example from the issue + let capturedStdout = ''; + const originalWrite = process.stdout.write; + process.stdout.write = (chunk) => { + capturedStdout += chunk.toString(); + return true; + }; + + try { + // Simulate the gh api command from the issue (using echo as substitute) + const result = await $`echo '{"owner": "test", "files": {}}'`.quiet(); + + process.stdout.write = originalWrite; + + // Should capture the output + expect(result.stdout).toContain('owner'); + + // But not print to console + expect(capturedStdout).toBe(''); + } finally { + process.stdout.write = originalWrite; + } + }); +});