diff --git a/js/.changeset/exitcode-alias.md b/js/.changeset/exitcode-alias.md new file mode 100644 index 0000000..8c23bb9 --- /dev/null +++ b/js/.changeset/exitcode-alias.md @@ -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. diff --git a/js/README.md b/js/README.md index a519fa3..05ab5fa 100644 --- a/js/README.md +++ b/js/README.md @@ -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!) diff --git a/js/src/$.process-runner-base.mjs b/js/src/$.process-runner-base.mjs index 80a2e19..6e69eec 100644 --- a/js/src/$.process-runner-base.mjs +++ b/js/src/$.process-runner-base.mjs @@ -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', () => diff --git a/js/src/$.result.mjs b/js/src/$.result.mjs index 9b87625..cc4525c 100644 --- a/js/src/$.result.mjs +++ b/js/src/$.result.mjs @@ -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, diff --git a/js/tests/exitcode-alias.test.mjs b/js/tests/exitcode-alias.test.mjs new file mode 100644 index 0000000..cbe226e --- /dev/null +++ b/js/tests/exitcode-alias.test.mjs @@ -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); + }); +}); diff --git a/rust/README.md b/rust/README.md index 1c69aee..af774bd 100644 --- a/rust/README.md +++ b/rust/README.md @@ -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); } ``` diff --git a/rust/changelog.d/20260610_000000_exitcode-alias.md b/rust/changelog.d/20260610_000000_exitcode-alias.md new file mode 100644 index 0000000..22b362a --- /dev/null +++ b/rust/changelog.d/20260610_000000_exitcode-alias.md @@ -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). diff --git a/rust/src/utils.rs b/rust/src/utils.rs index 163490c..359cbfa 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -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 diff --git a/rust/tests/utils.rs b/rust/tests/utils.rs index ed815ef..3058417 100644 --- a/rust/tests/utils.rs +++ b/rust/tests/utils.rs @@ -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 // ============================================================================