Skip to content
Merged
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
5 changes: 5 additions & 0 deletions js/.changeset/exitcode-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'command-stream': minor
---

Add `exitCode` as an alias for the `code` property on all command result objects (issue #36). Code written against the `exitCode` convention (e.g. `child_process` / `execa`) now works without changes, while the existing `code` property remains fully supported.
1 change: 1 addition & 0 deletions js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ import { $ } from 'command-stream';
const result = await $`ls -la`;
console.log(result.stdout);
console.log(result.code); // exit code
console.log(result.exitCode); // alias for result.code
```

### Custom Options with $({ options }) Syntax (NEW!)
Expand Down
7 changes: 7 additions & 0 deletions js/src/$.process-runner-base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,13 @@ class ProcessRunner extends StreamEmitter {

// Centralized method to properly finish a process with correct event emission order
finish(result) {
// Ensure `exitCode` is always available as an alias for `code` (issue #36).
// This is the single choke point every result passes through before it is
// stored and returned to the user, so normalizing here covers all paths.
if (result && result.exitCode === undefined && result.code !== undefined) {
result.exitCode = result.code;
}

trace(
'ProcessRunner',
() =>
Expand Down
2 changes: 2 additions & 0 deletions js/src/$.result.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
export function createResult({ code, stdout = '', stderr = '', stdin = '' }) {
return {
code,
// `exitCode` is an alias for `code` for better compatibility (issue #36)
exitCode: code,
stdout,
stderr,
stdin,
Expand Down
50 changes: 50 additions & 0 deletions js/tests/exitcode-alias.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { test, expect, describe, beforeEach } from 'bun:test';
import './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
import { $, shell } from '../src/$.mjs';

describe('exitCode alias for code', () => {
beforeEach(() => {
// Reset all shell settings before each test
shell.errexit(false);
shell.verbose(false);
shell.xtrace(false);
shell.pipefail(false);
shell.nounset(false);
});

test('should provide exitCode as alias for code property', async () => {
const result = await $`exit 0`;
expect(result.code).toBe(0);
expect(result.exitCode).toBe(0);
expect(result.code).toBe(result.exitCode);
});

test('should have matching exitCode for non-zero exit codes', async () => {
const result = await $`exit 42`;
expect(result.code).toBe(42);
expect(result.exitCode).toBe(42);
expect(result.code).toBe(result.exitCode);
});

test('should have matching exitCode for successful commands', async () => {
const result = await $`echo "hello"`;
expect(result.code).toBe(0);
expect(result.exitCode).toBe(0);
expect(result.code).toBe(result.exitCode);
expect(result.stdout.trim()).toBe('hello');
});

test('should maintain exitCode alias in pipeline operations', async () => {
const result = await $`echo "test" | grep "test"`;
expect(result.code).toBe(0);
expect(result.exitCode).toBe(0);
expect(result.code).toBe(result.exitCode);
});

test('should maintain exitCode alias for failed commands', async () => {
const result = await $`exit 42`;
expect(result.code).toBe(42);
expect(result.exitCode).toBe(42);
expect(result.code).toBe(result.exitCode);
});
});
4 changes: 4 additions & 0 deletions rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ async fn main() {
.expect("echo should run");

assert_eq!(result.stdout.trim(), "hello from rust");

// `exit_code()` is an alias for the `code` field, mirroring the
// JavaScript `exitCode` alias.
assert_eq!(result.exit_code(), result.code);
}
```

Expand Down
6 changes: 6 additions & 0 deletions rust/changelog.d/20260610_000000_exitcode-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: minor
---

### Added
- `CommandResult::exit_code()` accessor as an alias for the `code` field, mirroring the `exitCode` alias exposed by the JavaScript implementation (issue #36).
8 changes: 8 additions & 0 deletions rust/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ impl CommandResult {
pub fn is_success(&self) -> bool {
self.code == 0
}

/// Exit code of the command.
///
/// This is an alias for the [`code`](Self::code) field, mirroring the
/// `exitCode` alias exposed by the JavaScript implementation (issue #36).
pub fn exit_code(&self) -> i32 {
self.code
}
}

/// Utility functions for virtual commands
Expand Down
12 changes: 12 additions & 0 deletions rust/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ fn test_command_result_error_with_code() {
assert_eq!(result.code, 127);
}

#[test]
fn test_command_result_exit_code_alias() {
// `exit_code()` is an alias for the `code` field (issue #36)
let success = CommandResult::success("hello");
assert_eq!(success.exit_code(), 0);
assert_eq!(success.exit_code(), success.code);

let failure = CommandResult::error_with_code("not found", 127);
assert_eq!(failure.exit_code(), 127);
assert_eq!(failure.exit_code(), failure.code);
}

// ============================================================================
// VirtualUtils Tests
// ============================================================================
Expand Down
Loading