Skip to content
Open
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
110 changes: 110 additions & 0 deletions examples/test-exitcode-compatibility.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env node

/**
* Test script to verify that both error.code and error.exitCode work
* This validates the fix for issue #38
*/

import { $, shell } from '../src/$.mjs';

// Enable errexit to make commands throw on non-zero exit codes
shell.errexit(true);

console.log('Testing exitCode alias for error.code...\n');

// Test 1: Test that error.exitCode is available alongside error.code
async function testExitCodeAlias() {
console.log('Test 1: Checking error.exitCode alias...');

try {
// This should fail with exit code 1
await $`ls /nonexistent/directory/that/does/not/exist`;
console.log('❌ Expected command to fail');
} catch (error) {
console.log(`βœ… error.code: ${error.code} (traditional property)`);
console.log(`βœ… error.exitCode: ${error.exitCode} (Node.js standard property)`);

if (error.code === error.exitCode) {
console.log('βœ… Both properties contain the same value');
} else {
console.log(`❌ Properties don't match: code=${error.code}, exitCode=${error.exitCode}`);
}

if (error.exitCode === 2) { // ls returns exit code 2 for "No such file or directory"
console.log('βœ… Exit code is correct (2 for ls no such file)');
} else {
console.log(`ℹ️ Exit code is ${error.exitCode} (may vary by system)`);
}
}
}

// Test 2: Test specific exit codes with exit command
async function testSpecificExitCode() {
console.log('\nTest 2: Testing specific exit code (42)...');

try {
await $`exit 42`;
console.log('❌ Expected command to fail with exit code 42');
} catch (error) {
console.log(`βœ… error.code: ${error.code}`);
console.log(`βœ… error.exitCode: ${error.exitCode}`);

if (error.code === 42 && error.exitCode === 42) {
console.log('βœ… Both properties correctly contain exit code 42');
} else {
console.log(`❌ Expected both properties to be 42, got code=${error.code}, exitCode=${error.exitCode}`);
}
}
}

// Test 3: Ensure backward compatibility - existing code using error.code still works
function testBackwardCompatibility() {
console.log('\nTest 3: Testing backward compatibility...');

// This is how developers currently handle errors in command-stream
const handleErrorOldWay = (error) => {
if (error.code === 1) {
return 'Handle exit code 1';
}
return 'Unknown error';
};

// This is the new Node.js standard way
const handleErrorNewWay = (error) => {
if (error.exitCode === 1) {
return 'Handle exit code 1';
}
return 'Unknown error';
};

// Create a mock error like command-stream would
const mockError = new Error('Test error');
mockError.code = 1;
mockError.exitCode = 1;

const oldResult = handleErrorOldWay(mockError);
const newResult = handleErrorNewWay(mockError);

if (oldResult === newResult) {
console.log('βœ… Both old and new error handling patterns work identically');
} else {
console.log(`❌ Compatibility issue: old="${oldResult}", new="${newResult}"`);
}
}

// Run all tests
async function runAllTests() {
try {
await testExitCodeAlias();
await testSpecificExitCode();
testBackwardCompatibility();

console.log('\nπŸŽ‰ All tests completed! Issue #38 should be resolved.');
console.log('Both error.code and error.exitCode are now available.');
} catch (err) {
console.error('Test failed:', err);
process.exit(1);
}
}

runAllTests();
12 changes: 12 additions & 0 deletions src/$.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2104,8 +2104,9 @@
hasStderr: !!this.result.stderr
}, null, 2)}`);

const error = new Error(`Command failed with exit code ${this.result.code}`);

Check failure on line 2107 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 143

at _doStartAsync (/home/runner/work/command-stream/command-stream/src/$.mjs:2107:21)
error.code = this.result.code;
error.exitCode = this.result.code;
error.stdout = this.result.stdout;
error.stderr = this.result.stderr;
error.result = this.result;
Expand Down Expand Up @@ -2532,8 +2533,10 @@
this.finish(result);

if (globalShellSettings.errexit && result.code !== 0) {
const error = new Error(`Command failed with exit code ${result.code}`);

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)

Check failure on line 2536 in src/$.mjs

View workflow job for this annotation

GitHub Actions / test (latest)

error: Command failed with exit code 1

at _runVirtual (/home/runner/work/command-stream/command-stream/src/$.mjs:2536:23)
error.code = result.code;
error.exitCode = result.code;
error.exitCode = result.code;
error.stdout = result.stdout;
error.stderr = result.stderr;
error.result = result;
Expand Down Expand Up @@ -2746,6 +2749,7 @@
if (failedIndex !== -1) {
const error = new Error(`Pipeline command at index ${failedIndex} failed with exit code ${exitCodes[failedIndex]}`);
error.code = exitCodes[failedIndex];
error.exitCode = exitCodes[failedIndex];
throw error;
}
}
Expand All @@ -2764,6 +2768,7 @@
if (globalShellSettings.errexit && result.code !== 0) {
const error = new Error(`Pipeline failed with exit code ${result.code}`);
error.code = result.code;
error.exitCode = result.code;
error.stdout = result.stdout;
error.stderr = result.stderr;
error.result = result;
Expand Down Expand Up @@ -2922,6 +2927,7 @@
if (failedIndex !== -1) {
const error = new Error(`Pipeline command at index ${failedIndex} failed with exit code ${exitCodes[failedIndex]}`);
error.code = exitCodes[failedIndex];
error.exitCode = exitCodes[failedIndex];
throw error;
}
}
Expand All @@ -2940,6 +2946,7 @@
if (globalShellSettings.errexit && result.code !== 0) {
const error = new Error(`Pipeline failed with exit code ${result.code}`);
error.code = result.code;
error.exitCode = result.code;
error.stdout = result.stdout;
error.stderr = result.stderr;
error.result = result;
Expand Down Expand Up @@ -3271,6 +3278,7 @@
if (globalShellSettings.errexit && finalResult.code !== 0) {
const error = new Error(`Pipeline failed with exit code ${finalResult.code}`);
error.code = finalResult.code;
error.exitCode = finalResult.code;
error.stdout = finalResult.stdout;
error.stderr = finalResult.stderr;
error.result = finalResult;
Expand All @@ -3283,6 +3291,7 @@
if (globalShellSettings.errexit && result.code !== 0) {
const error = new Error(`Pipeline command failed with exit code ${result.code}`);
error.code = result.code;
error.exitCode = result.code;
error.stdout = result.stdout;
error.stderr = result.stderr;
error.result = result;
Expand Down Expand Up @@ -3480,6 +3489,7 @@
if (globalShellSettings.pipefail && result.code !== 0) {
const error = new Error(`Pipeline command '${commandStr}' failed with exit code ${result.code}`);
error.code = result.code;
error.exitCode = result.code;
error.stdout = result.stdout;
error.stderr = result.stderr;
throw error;
Expand Down Expand Up @@ -3520,6 +3530,7 @@
if (globalShellSettings.errexit && finalResult.code !== 0) {
const error = new Error(`Pipeline failed with exit code ${finalResult.code}`);
error.code = finalResult.code;
error.exitCode = finalResult.code;
error.stdout = finalResult.stdout;
error.stderr = finalResult.stderr;
error.result = finalResult;
Expand Down Expand Up @@ -4289,6 +4300,7 @@
if (globalShellSettings.errexit && result.code !== 0) {
const error = new Error(`Command failed with exit code ${result.code}`);
error.code = result.code;
error.exitCode = result.code;
error.stdout = result.stdout;
error.stderr = result.stderr;
error.result = result;
Expand Down
5 changes: 4 additions & 1 deletion src/commands/$.exit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ export default function createExitCommand(globalShellSettings) {
return async function exit({ args }) {
const code = parseInt(args[0] || 0);
if (globalShellSettings.errexit || code !== 0) {
throw { code, message: `Command failed with exit code ${code}` };
const error = new Error(`Command failed with exit code ${code}`);
error.code = code;
error.exitCode = code;
throw error;
}
return { stdout: '', code };
};
Expand Down
95 changes: 95 additions & 0 deletions tests/exitcode-compatibility.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Tests for issue #38: The library uses error.code instead of error.exitCode
* Verifies that both error.code and error.exitCode are available for backward compatibility
* and Node.js standard compatibility.
*/

import { describe, test, expect } from 'bun:test';
import { $, shell } from '../src/$.mjs';

describe('exitCode compatibility (issue #38)', () => {
test('should provide both error.code and error.exitCode properties', async () => {
shell.errexit(true);

try {
await $`exit 42`;
expect(true).toBe(false); // Should not reach here
} catch (error) {
// Both properties should exist and be equal
expect(error.code).toBe(42);
expect(error.exitCode).toBe(42);
expect(error.code).toBe(error.exitCode);

// Standard Node.js error properties should also exist
expect(error.message).toContain('Command failed with exit code 42');
expect(error.result).toBeDefined();
expect(error.result.code).toBe(42);
}
});

test('should maintain backward compatibility with existing error.code usage', async () => {
shell.errexit(true);

try {
await $`exit 5`;
expect(true).toBe(false);
} catch (error) {
// Traditional command-stream pattern should still work
if (error.code === 5) {
expect(true).toBe(true); // This should execute
} else {
expect(true).toBe(false); // This should not execute
}

// New Node.js standard pattern should also work
if (error.exitCode === 5) {
expect(true).toBe(true); // This should execute
} else {
expect(true).toBe(false); // This should not execute
}
}
});

test('should provide exitCode in pipeline errors', async () => {
shell.errexit(true);
shell.pipefail(true);

try {
await $`echo "test" | exit 3 | echo "after"`;
expect(true).toBe(false);
} catch (error) {
expect(error.code).toBe(3);
expect(error.exitCode).toBe(3);
expect(error.code).toBe(error.exitCode);
}
});

test('should work with different exit codes', async () => {
shell.errexit(true);
const testCodes = [1, 2, 127, 255];

for (const code of testCodes) {
try {
await $`exit ${code}`;
expect(true).toBe(false);
} catch (error) {
expect(error.code).toBe(code);
expect(error.exitCode).toBe(code);
expect(error.code).toBe(error.exitCode);
}
}
});

test('should handle file system errors with both properties', async () => {
try {
await $`ls /nonexistent/directory/path/that/should/not/exist`;
} catch (error) {
// Both properties should exist for file system errors
expect(error.code).toBeDefined();
expect(error.exitCode).toBeDefined();
expect(error.code).toBe(error.exitCode);
expect(typeof error.code).toBe('number');
expect(typeof error.exitCode).toBe('number');
}
});
});
Loading