From fa6b786c9210638ee786b56ac338db106a103b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 20 May 2026 13:53:12 +0200 Subject: [PATCH] feat: add POSIX null command (`:`) as a builtin `:` is POSIX's null command: it discards its arguments and always exits 0. Bash treats it as a builtin. We had `true` and `false` already; add `:` alongside them (same `ExitCodeCommand(0)` underneath). Without this, `deno task` for a `package.json` script defined as `"test": ":"` (a common no-op pattern, e.g. used in the aube benchmark fixture's ~1400-package project) fails with `:: command not found` and exit code 127, while bash / sh / dash all return 0 cleanly. Adds an integration test covering the bare `:`, argument discarding, and `&&` / `||` short-circuit behavior. --- src/shell/commands/mod.rs | 6 ++++++ tests/integration_test.rs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/shell/commands/mod.rs b/src/shell/commands/mod.rs index 0c6dbdd..df3498e 100644 --- a/src/shell/commands/mod.rs +++ b/src/shell/commands/mod.rs @@ -98,6 +98,12 @@ pub fn builtin_commands() -> HashMap> { "false".to_string(), Rc::new(ExitCodeCommand(1)) as Rc, ), + // POSIX null command: discards its arguments and always exits 0. + // Commonly seen as `"test": ":"` in package.json no-op scripts. + ( + ":".to_string(), + Rc::new(ExitCodeCommand(0)) as Rc, + ), ( "unset".to_string(), Rc::new(unset::UnsetCommand) as Rc, diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 2a8f905..a471bdc 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -154,6 +154,24 @@ async fn boolean_logic() { .await; } +#[tokio::test] +async fn null_command() { + // POSIX `:` is the null command — always succeeds, discards args. + TestBuilder::new().command(":").run().await; + + TestBuilder::new() + .command(": ignored args && echo ok") + .assert_stdout("ok\n") + .run() + .await; + + TestBuilder::new() + .command(": && echo yes || echo no") + .assert_stdout("yes\n") + .run() + .await; +} + #[tokio::test] async fn exit() { TestBuilder::new()