Skip to content

uhop/dollar-shell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

158 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dollar-shell NPM version

dollar-shell is a micro-library for running OS and shell commands from JavaScript/TypeScript using template tag functions. It works in Node, Deno, Bun with the same API. Web streams, TypeScript typings, zero dependencies.

The idea is to run OS/shell commands and/or use them in stream pipelines as sources, sinks, and transformation steps using web streams. It can be used together with stream-chain and stream-json to create efficient pipelines. It helps using shell commands in utilities written in JavaScript/TypeScript running with Node, Deno, or Bun.

Available components:

  • $ — spawn a process using a template string.
    • $.from — spawn a process and use its stdout as a source stream.
    • $.to — spawn a process and use its stdin as a sink stream.
    • $.io AKA $.through — spawn a process and use it as a transformation step in our pipeline.
  • $sh — run a shell command using a template string.
    • $sh.from — run a shell command and use its stdout as a source stream.
    • $sh.to — run a shell command and use its stdin as a sink stream.
    • $sh.io AKA $sh.through — run a shell command and use it as a transformation step in our pipeline.
  • Advanced components:
    • spawn() — spawn a process with advanced ways to configure and control it.
    • $$ — spawn a process using a template string based on spawn().
    • shell() — a helper to spawn a shell command using a template string based on spawn().
    • Various helpers for them.

Introduction

Run a command:

import $ from 'dollar-shell';

const result = await $`echo hello`;
console.log(result.code, result.signal, result.killed);

Run a shell command:

import {$sh} from 'dollar-shell';

const result = await $sh`ls .`;
console.log(result.code, result.signal, result.killed);

Run a shell command (an alias or a function) and show its result:

import {$sh} from 'dollar-shell';

// custom alias that prints `stdout` and runs an interactive shell
const $p = $sh({shellArgs: ['-ic'], stdout: 'inherit'});

const result = await $p`nvm ls`;
// prints to the console the result of the command

Run a pipeline:

import $ from 'dollar-shell';
import chain from 'stream-chain';
import lines from 'stream-chain/utils/lines.js';

chain([
  $.from`ls -l .`,
  $.io`grep LICENSE`,
  $.io`wc`,
  new TextDecoderStream(),
  lines(),
  line => console.log(line)
]);

Installation

npm i --save dollar-shell

Project structure

dollar-shell/
├── src/              # Source code
│   ├── index.js      # Main entry point, wires everything together
│   ├── index.d.ts    # TypeScript declarations for the full public API
│   ├── bq-spawn.js   # Template tag factory for spawn-based functions ($, $$)
│   ├── bq-shell.js   # Template tag factory for shell-based functions ($sh, shell)
│   ├── utils.js      # Shared utilities (raw, isWindows, winCmdEscape, etc.)
│   ├── spawn/        # Runtime-specific Subprocess implementations
│   └── shell/        # Platform-specific shell escaping and command building
├── tests/            # Automated tests (tape-six): .js, .cjs, .ts
├── tests/manual/     # Manual verification scripts
└── wiki/             # GitHub wiki documentation (git submodule)

Documentation

The documentation can be found in the wiki. See how it can be used in tests/.

For AI assistants: see llms.txt and llms-full.txt for LLM-optimized documentation.

Below is the documentation for the main components: spawn(), $$, $ and $sh.

spawn()

Spawn a process with advanced ways to configure and control it.

The signature: spawn(command, options)

Arguments:

  • command — an array of strings. The first element is the command to run. The rest are its arguments.
  • options — an optional object with options to configure the process:
    • cwd — the optional current working directory as a string. Defaults to process.cwd().
    • env — the optional environment variables as an object (key-value pairs). Defaults to process.env.
    • stdin — the optional source stream. Defaults to null.
    • stdout — the optional destination stream. Defaults to null.
    • stderr — the optional destination stream. Defaults to null.

stdin, stdout and stderr can be a string (one of 'inherit', 'ignore', 'pipe' or 'piped') or null. The latter is equivalent to 'ignore'. 'piped' is an alias of 'pipe':

  • 'inherit' — inherit streams from the parent process. For output steams (stdout and stderr), it means that they will be piped to the same target, e.g., the console.
  • 'ignore' — the stream is ignored.
  • 'pipe' — the stream is available for reading or writing.

Returns a sub-process object with the following properties:

  • command — the command that was run as an array of strings.
  • options — the options that were passed to spawn().
  • exited — a promise that resolves to the exit code of the process. It is used to wait for the process to exit.
  • finished — a boolean. It is true when the process has finished and false otherwise.
  • killed — a boolean. It is true when the process has been killed and false otherwise.
  • exitCode — the exit code of the process as a number. It is null if the process hasn't exited yet.
  • signalCode — the signal code of the process as a string. It is null if the process hasn't exited yet.
  • stdin — the source stream of the process if options.stdin was 'pipe'. It is null otherwise.
  • stdout — the destination stream of the process if options.stdout was 'pipe'. It is null otherwise.
  • stderr — the destination stream of the process if options.stderr was 'pipe'. It is null otherwise.
  • kill() — kills the process. killed will be true as soon as the process has been killed. It can be used to pipe the input and output. See spawn()'s stdin and stdout above for more details.

Important: all streams are exposed as web streams.

Examples

import {spawn} from 'dollar-shell';

const sp = spawn(['sleep', '5']);
await new Promise(resolve => setTimeout(resolve, 1000));
sp.kill();
await sp.exited;

sp.finished === true;
sp.killed === true;

$$

The same as spawn(), but it returns a tag function that can be used as a template string.

The signatures:

const sp1 = $$`ls -l ${myFile}`; // runs a command the defaults

const sp2 = $$(options)`ls -l .`; // runs a command with custom spawn options

const $tag = $$(options); // returns a tag function
const sp3 = $tag`ls -l .`; // runs a command with custom spawn options

This function is effectively a helper for spawn(). It parses the template string into an array of string arguments. Each inserted value is included as a separate argument if it was surrounded by whitespaces.

The second signature is used to run a command with custom spawn options. See spawn() above for more details.

The first signature returns a sub-process object. See spawn() for more details. The second signature returns a tag function that can be used as a template string.

$

This function is similar to $$ but it uses different default spawn options related to streams and different (simpler) return values:

  • $ — all streams are ignored. It returns a promise that resolves to an object with the following properties:
    • code — the exit code of the process. See spawn()'s exitCode above for more details.
    • signal — the signal code of the process. See spawn()'s signalCode above for more details.
    • killed — a boolean. It is true when the process has been killed and false otherwise. See spawn()'s killed above for more details.
  • $.from — sets stdout to pipe and returns stdout of the process. It can be used to process the output. See spawn()'s stdout above for more details.
  • $.to — sets stdin to pipe and returns stdin of the process. It can be used to pipe the input. See spawn()'s stdin above for more details.
  • $.io AKA $.through — sets stdin and stdout to pipe and returns stdin and stdout of the process as a {readable, writable} pair. It can be used to create a pipeline where an external process can be used as a transform step.

$sh

This function mirrors $ but runs the command with the shell. It takes an options object that extends the spawn options with the following properties:

  • shellPath — the path to the shell.
    • On Unix-like systems it defaults to the value of the SHELL environment variable if specified. Otherwise it is '/bin/sh' or '/system/bin/sh' on Android.
    • On Windows it defaults to the value of the ComSpec environment variable if specified. Otherwise it is cmd.exe.
  • shellArgs — an array of strings that are passed to the shell as arguments.
    • On Unix-like systems it defaults to ['-c'].
    • On Windows it defaults to ['/d', '/s', '/c'] for cmd.exe or ['-c'] for pwsh.exe or powershell.exe.

The rest is identical to $: $sh, $sh.from, $sh.to and $sh.io/$sh.through.

Forcing the Node backend

Each runtime uses its own backend by default (node:child_process on Node, Bun.spawn on Bun, Deno.Command on Deno). Set the DSH_FORCE_NODE environment variable (e.g. DSH_FORCE_NODE=1) to make every runtime spawn through the Node backend — Bun and Deno then run node:child_process on their compatibility layer. This swaps only the spawn mechanism: the runtime that runs your code and how it's re-launched stay native, so a forced child of Bun/Deno is still bun run … / deno run …, never a bare node. Handy for sidestepping runtime-specific quirks (e.g. Bun intermittently dropping the last chunk of a child's piped output).

Because dollar-shell spawns children with env defaulting to process.env, the variable is inherited by those children — so it forces the Node backend across the whole process tree. To force only the current process (no leak to spawned children), set globalThis.DSH_FORCE_NODE = true before importing instead; it's process-local, but requires a dynamic import() (the backend is chosen once, at import time). See the Cross-runtime notes for details.

Node streams (dollar-shell/node)

The default entry exposes web streams on stdin/stdout/stderr. If you'd rather work with Node streams — to pipe straight into fs/zlib/etc. with no Web↔Node adapter, or to skip the conversion — import from dollar-shell/node instead:

import {spawn} from 'dollar-shell/node';

const sp = spawn(['cat', 'file.txt'], {stdout: 'pipe'});
sp.stdout.pipe(process.stdout); // sp.stdout is a Node Readable

The API is identical to the main entry — same $, $$, $sh, shell, helpers, and .from/.to/.through/.io — only the stream types differ (stdin is a Node Writable, stdout/stderr are Node Readables, and asDuplex / .io / .through return a Node Duplex). It always spawns through the Node backend, so it also runs on Bun and Deno through their node:child_process compatibility layer (only the spawn mechanism changes — the runtime launch stays native).

For AI Agents

This package ships with files to help AI coding agents and LLMs find, understand, and use it:

  • AGENTS.md — Project conventions, architecture, commands, and coding guidelines for AI agents.
  • CLAUDE.md — Claude Code specific instructions (redirects to AGENTS.md).
  • CONTRIBUTING.md — Contribution guidelines for humans and AI agents.
  • llms.txt — Concise project overview following the llms.txt standard.
  • llms-full.txt — Self-contained complete API reference (no external links needed).

The machine-readable llms.txt and llms-full.txt ship inside the npm package, so AI tools can read them straight from node_modules. AGENTS.md and CLAUDE.md are authoring-side docs kept in the repository.

License

BSD-3-Clause

Release History

  • 1.2.1 Bugfix: DSH_FORCE_NODE and dollar-shell/node now switch only the spawn mechanism — spawned children stay native (bun run … / deno run …).
  • 1.2.0 Added dollar-shell/node with Node streams and a DSH_FORCE_NODE flag to force the Node backend on any runtime.
  • 1.1.14 Fixed Bun stdin abort path, added js-check, Bun + Deno wired into CI.
  • 1.1.13 Updated dev dependencies.
  • 1.1.12 Consolidated TypeScript tests into tests/, removed ts-check/, added CJS test, improved test coverage and documentation.
  • 1.1.11 Updated dev dependencies.
  • 1.1.10 Fixed a bug with options chaining for attached functions, fixed Bun spawn on invalid commands, Windows-compatible tests, updated dev dependencies.
  • 1.1.9 Updated dev dependencies, cleaned up docs, added info for AI agents.
  • 1.1.8 Updated dev dependencies.
  • 1.1.7 Updated dev dependencies.
  • 1.1.6 Updated dev dependencies.
  • 1.1.5 Updated dev dependencies.
  • 1.1.4 Updated dev dependencies.
  • 1.1.3 Updated dev dependencies.
  • 1.1.2 Updated dev dependencies.
  • 1.1.1 Updated dev dependencies.
  • 1.1.0 Added asDuplex to the sub-process object.
  • 1.0.5 Updated dev dependencies.
  • 1.0.4 Fixed raw() for spawn commands.
  • 1.0.3 Added TSDoc comments, improved docs, fixed typos, added the missing copying of properties.
  • 1.0.2 Technical release: fixed references in the package file.
  • 1.0.1 Technical release: more tests, better documentation.
  • 1.0.0 The initial release.

The full release notes are in the wiki: Release notes.

About

Run OS and shell commands using template tag functions. Same API in Node, Deno, and Bun. Web streams, TypeScript typings, zero dependencies.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors