bellpull
Coming from…

Coming from execa

An execa alternative with zero dependencies: bellpull's run() treats a non-zero exit as a value, not an exception, says which binary ran, and renders one result as text, --json or an agent event. Not an execa drop-in, and no execa compatibility row exists.

bellpull is an execa alternative for running subprocesses: executable resolution, a bounded run and a structured result every caller can read. It is not an execa drop-in — read this page before you plan a one-line swap.

Migrate from execa: not one import

There is no bellpull/execa. execa's streaming API and its $`cmd` template form are a different product and are out of scope, so moving is a rewrite of each call site, not of the import line:

- import { execa } from 'execa';
- const { stdout } = await execa('git', ['rev-parse', 'HEAD']);
+ import { run } from 'bellpull';
+ const runtime = { platform: process.platform, env: process.env, cwd: process.cwd() };
+ const result = await run('git', ['rev-parse', 'HEAD'], { runtime });
+ if (result.ok) console.log(result.stdout);

The one-import path bellpull does ship is for cross-spawn, the other spawner in this layer:

- import spawn from 'cross-spawn';
+ import spawn from 'bellpull/cross-spawn';

Same callable default, same .sync.

Is bellpull compatible with execa?

There is no graded execa row, and bellpull does not claim execa compatibility. The Compatibility page, generated by npm run compat:page from the oracle's last run, grades the cross-spawn path only:

passingrate
bellpull/cross-spawn68 / 68100.0%
cross-spawn itself (control)68 / 68100.0%

bellpull's README records what that 68 / 68 does not cover: on POSIX cross-spawn is a pass-through, so its suite never reaches the Windows escaping or the cmd.exe branch. That behaviour is covered by bellpull's own tests.

What you gain over execa

  • A non-zero exit is a value. execa throws when a child exits non-zero, so every caller wraps every call. run() resolves with result.ok === false and the code; it rejects only when no process ran — the executable did not resolve, or the spawn failed.
  • It says which binary ran. result.executable is the path and the PATH entry it came from — the first question of every "works on my machine" investigation.
  • Bounded by default. timeout is finite, the kill is a ladder (SIGTERM, then SIGKILL), and the output written before the kill is kept.
  • One result, three readers. format() for a person, toJson() for --json, toEvent() for an agent stream — all derived from one record, so a --json flag cannot report something the human output did not.
  • No shell, ever, to solve a Windows problem. .cmd files run through a quoted cmd.exe invocation, not { shell: true }, which would re-open command injection.

When to switch from execa

  • Your callers, human or agent, need the same result as text and as JSON.
  • You are tired of try/catch around every subprocess to read its exit code.

If you depend on execa's streaming or its template syntax, stay on execa. The entry points and the resolvers plugin host are on bellpull.

On this page