dollar-shell 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,97 @@
1
+ # AGENTS.md — dollar-shell
2
+
3
+ > `dollar-shell` is a micro-library for running OS and shell commands from JavaScript/TypeScript using template tag functions. It works on Node, Deno, and Bun with the same API. All streams are web streams. Zero dependencies.
4
+
5
+ For detailed usage docs and API references see the [wiki](https://github.com/uhop/dollar-shell/wiki).
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ git clone --recursive git@github.com:uhop/dollar-shell.git
11
+ cd dollar-shell
12
+ npm install
13
+ ```
14
+
15
+ The wiki is a git submodule in `wiki/`.
16
+
17
+ ## Commands
18
+
19
+ - **Test (Node):** `npm test` (runs `tape6 --flags FO`)
20
+ - **Test (Bun):** `npm run test:bun`
21
+ - **Test (Deno):** `npm run test:deno`
22
+ - **TypeScript check:** `npm run ts-check` (`tsc --noEmit`)
23
+ - **Lint:** `npm run lint` (Prettier check)
24
+ - **Lint fix:** `npm run lint:fix` (Prettier write)
25
+
26
+ ## Project structure
27
+
28
+ ```
29
+ dollar-shell/
30
+ ├── package.json # Package config
31
+ ├── tsconfig.json # TypeScript config (noEmit check only)
32
+ ├── src/ # Source code
33
+ │ ├── index.js # Main entry point, wires everything together
34
+ │ ├── index.d.ts # TypeScript declarations for the full public API
35
+ │ ├── bq-spawn.js # Template tag factory for spawn-based functions ($, $$)
36
+ │ ├── bq-shell.js # Template tag factory for shell-based functions ($sh, shell)
37
+ │ ├── utils.js # Shared utilities (raw, isWindows, winCmdEscape, etc.)
38
+ │ ├── spawn/ # Runtime-specific Subprocess implementations
39
+ │ │ ├── node.js # Node.js: uses child_process + stream.Writable/Readable
40
+ │ │ ├── deno.js # Deno: uses Deno.Command
41
+ │ │ └── bun.js # Bun: uses Bun.spawn
42
+ │ └── shell/ # Platform-specific shell escaping and command building
43
+ │ ├── unix.js # Unix: single-quote escaping, $SHELL detection
44
+ │ └── windows.js # Windows: cmd.exe and PowerShell escaping
45
+ ├── tests/ # Automated tests (tape-six)
46
+ ├── tests/manual/ # Manual verification scripts
47
+ ├── ts-check/ # TypeScript usage examples (compiled but not executed)
48
+ └── wiki/ # GitHub wiki documentation (git submodule)
49
+ ```
50
+
51
+ ## Quick reference
52
+
53
+ ```js
54
+ import $, {$$, $sh, shell, sh, spawn} from 'dollar-shell';
55
+
56
+ // Run a command, get exit info
57
+ const result = await $`echo hello`;
58
+ // result: {code: 0, signal: null, killed: false}
59
+
60
+ // Run a command, get full Subprocess
61
+ const sp = $$`sleep 5`;
62
+ sp.kill();
63
+ await sp.exited;
64
+
65
+ // Shell command (supports pipes, aliases)
66
+ await $sh`ls -l . | grep LICENSE | wc`;
67
+
68
+ // Stream pipelines
69
+ $.from`ls -l .`.pipeThrough($.io`grep LIC`).pipeTo($.to({stdout: 'inherit'})`wc`);
70
+
71
+ // Custom options (returns new tag function with updated defaults)
72
+ const $verbose = $({stdout: 'inherit', stderr: 'inherit'});
73
+ await $verbose`ls -l .`;
74
+ ```
75
+
76
+ ## Code style
77
+
78
+ - **ES modules** throughout (`"type": "module"` in package.json).
79
+ - **No transpilation** — code runs directly in all target runtimes.
80
+ - **Prettier** for formatting — run `npm run lint:fix` before committing.
81
+ - Imports at the top of files, using `import` syntax.
82
+
83
+ ## Architecture
84
+
85
+ - `src/index.js` is the main entry point. It dynamically imports the correct `spawn` implementation (Node/Deno/Bun) and the correct `shell` implementation (Unix/Windows) at import time.
86
+ - All tag functions (`$`, `$$`, `$sh`, `shell`) are built by factory functions in `bq-spawn.js` and `bq-shell.js`.
87
+ - **Tag function + options pattern**: calling a tag function with an options object returns a new tag function with updated defaults while preserving `.from`, `.to`, `.io`, `.through` properties.
88
+ - **raw()**: wraps a value to bypass escaping (shell) or argument splitting (spawn).
89
+ - **Platform detection**: `isWindows` boolean is exported. Runtime detection (Node/Deno/Bun) happens at import via dynamic imports.
90
+
91
+ ## Key conventions
92
+
93
+ - Do not add dependencies — the library is intentionally zero-dependency.
94
+ - All public API is exported from `src/index.js` and typed in `src/index.d.ts`. Keep them in sync.
95
+ - Wiki documentation lives in the `wiki/` submodule — update it alongside code changes.
96
+ - Tests are in `tests/` (automated, tape-six) and `tests/manual/` (manual verification scripts).
97
+ - TypeScript usage examples are in `ts-check/` — they are compiled but not executed during `npm run ts-check`.
package/CLAUDE.md ADDED
@@ -0,0 +1,3 @@
1
+ <!-- Claude Code project instructions — canonical source is AGENTS.md -->
2
+
3
+ See [AGENTS.md](./AGENTS.md) for all AI agent rules and project conventions.
@@ -0,0 +1,34 @@
1
+ # Contributing to dollar-shell
2
+
3
+ Thank you for your interest in contributing!
4
+
5
+ ## Getting started
6
+
7
+ This project uses git submodules. Clone and set up:
8
+
9
+ ```bash
10
+ git clone --recursive git@github.com:uhop/dollar-shell.git
11
+ cd dollar-shell
12
+ npm install
13
+ ```
14
+
15
+ See the [wiki](https://github.com/uhop/dollar-shell/wiki) for API documentation.
16
+
17
+ ## Development workflow
18
+
19
+ 1. Make your changes.
20
+ 2. Format: `npm run lint:fix`
21
+ 3. Test: `npm test`
22
+ 4. Type-check: `npm run ts-check`
23
+
24
+ ## Code style
25
+
26
+ - ES modules (`import`/`export`), no CommonJS in source.
27
+ - Formatted with Prettier — run `npm run lint:fix` before committing.
28
+ - No dependencies — the library is intentionally zero-dependency.
29
+ - Keep `src/index.js` and `src/index.d.ts` in sync.
30
+ - Update wiki documentation alongside code changes.
31
+
32
+ ## AI agents
33
+
34
+ If you are an AI coding agent, see [AGENTS.md](./AGENTS.md) for detailed project conventions, commands, and architecture.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [npm-image]: https://img.shields.io/npm/v/dollar-shell.svg
4
4
  [npm-url]: https://npmjs.org/package/dollar-shell
5
5
 
6
- `dollar-shell` is a micro-library for running shell commands and using them in streams with ease in Node, Deno, Bun. It is a tiny, simple, no dependency package with TypeScript typings.
6
+ `dollar-shell` is a micro-library for running OS and shell commands from JavaScript/TypeScript using template tag functions. It works in [Node](https://nodejs.org/), [Deno](https://deno.land/), [Bun](https://bun.sh/) with the same API. **Web streams, TypeScript typings, zero dependencies.**
7
7
 
8
8
  The idea is to run OS/shell commands and/or use them in stream pipelines as sources, sinks,
9
9
  and transformation steps using [web streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).
@@ -85,10 +85,32 @@ chain([
85
85
  npm i --save dollar-shell
86
86
  ```
87
87
 
88
+ ## Project structure
89
+
90
+ ```
91
+ dollar-shell/
92
+ ├── src/ # Source code
93
+ │ ├── index.js # Main entry point, wires everything together
94
+ │ ├── index.d.ts # TypeScript declarations for the full public API
95
+ │ ├── bq-spawn.js # Template tag factory for spawn-based functions ($, $$)
96
+ │ ├── bq-shell.js # Template tag factory for shell-based functions ($sh, shell)
97
+ │ ├── utils.js # Shared utilities (raw, isWindows, winCmdEscape, etc.)
98
+ │ ├── spawn/ # Runtime-specific Subprocess implementations
99
+ │ └── shell/ # Platform-specific shell escaping and command building
100
+ ├── tests/ # Automated tests (tape-six)
101
+ ├── tests/manual/ # Manual verification scripts
102
+ ├── ts-check/ # TypeScript usage examples (compiled but not executed)
103
+ └── wiki/ # GitHub wiki documentation (git submodule)
104
+ ```
105
+
88
106
  ## Documentation
89
107
 
108
+ The documentation can be found in the [wiki](https://github.com/uhop/dollar-shell/wiki).
109
+ See how it can be used in [tests/](https://github.com/uhop/dollar-shell/tree/main/tests).
110
+
111
+ For AI assistants: see [llms.txt](https://github.com/uhop/dollar-shell/blob/main/llms.txt) and [llms-full.txt](https://github.com/uhop/dollar-shell/blob/main/llms-full.txt) for LLM-optimized documentation.
112
+
90
113
  Below is the documentation for the main components: `spawn()`, `$$`, `$` and `$sh`.
91
- Additional information can be found in the [wiki](https://github.com/uhop/dollar-shell/wiki).
92
114
 
93
115
  ### `spawn()`
94
116
 
@@ -194,16 +216,30 @@ the spawn options with the following properties:
194
216
  - `shellArgs` &mdash; an array of strings that are passed to the shell as arguments.
195
217
  - On Unix-like systems it defaults to `['-c']`.
196
218
  - On Windows it defaults to `['/d', '/s', '/c']` for `cmd.exe`
197
- or `['-e']` for `pwsh.exe` or `powershell.exe`.
219
+ or `['-c']` for `pwsh.exe` or `powershell.exe`.
198
220
 
199
221
  The rest is identical to `$`: `$sh`, `$sh.from`, `$sh.to` and `$sh.io`/`$sh.through`.
200
222
 
223
+ ## For AI Agents
224
+
225
+ This package ships with files to help AI coding agents and LLMs find, understand, and use it:
226
+
227
+ - **[AGENTS.md](./AGENTS.md)** — Project conventions, architecture, commands, and coding guidelines for AI agents.
228
+ - **[CLAUDE.md](./CLAUDE.md)** — Claude Code specific instructions (redirects to AGENTS.md).
229
+ - **[CONTRIBUTING.md](./CONTRIBUTING.md)** — Contribution guidelines for humans and AI agents.
230
+ - **[llms.txt](./llms.txt)** — Concise project overview following the [llms.txt standard](https://llmstxt.org/).
231
+ - **[llms-full.txt](./llms-full.txt)** — Self-contained complete API reference (no external links needed).
232
+
233
+ All files are included in the npm package.
234
+
201
235
  ## License
202
236
 
203
237
  BSD-3-Clause
204
238
 
205
239
  ## Release History
206
240
 
241
+ - 1.1.10 _Fixed a bug with options chaining for attached functions, fixed Bun spawn on invalid commands, Windows-compatible tests, updated dev dependencies._
242
+ - 1.1.9 _Updated dev dependencies, cleaned up docs, added info for AI agents._
207
243
  - 1.1.8 _Updated dev dependencies._
208
244
  - 1.1.7 _Updated dev dependencies._
209
245
  - 1.1.6 _Updated dev dependencies._
package/llms-full.txt ADDED
@@ -0,0 +1,317 @@
1
+ # dollar-shell
2
+
3
+ > A micro-library for running OS and shell commands from JavaScript/TypeScript using template tag functions. Works in Node, Deno, and Bun with the same API. Web streams, TypeScript typings, zero dependencies.
4
+
5
+ The package provides template-tag functions to spawn OS processes (`$`, `$$`) and shell processes (`$sh`, `shell`/`sh`). Each has variants for stream pipelines: `.from` (stdout as ReadableStream), `.to` (stdin as WritableStream), `.io`/`.through` (duplex). All tag functions accept an options object to customize spawn/shell behavior and return a new tag function with updated defaults.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i --save dollar-shell
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import $, {$$, $sh, shell, sh, spawn} from 'dollar-shell';
17
+ ```
18
+
19
+ ## spawn()
20
+
21
+ Low-level function to spawn a process with full control.
22
+
23
+ Signature: `spawn(command: string[], options?: SpawnOptions): Subprocess`
24
+
25
+ ### SpawnOptions
26
+
27
+ ```ts
28
+ type SpawnStreamState = 'pipe' | 'ignore' | 'inherit' | 'piped' | null;
29
+
30
+ interface SpawnOptions {
31
+ cwd?: string; // working directory, defaults to process.cwd()
32
+ env?: {[key: string]: string | undefined}; // environment variables
33
+ stdin?: SpawnStreamState; // default: null (ignored)
34
+ stdout?: SpawnStreamState; // default: null (ignored)
35
+ stderr?: SpawnStreamState; // default: null (ignored)
36
+ }
37
+ ```
38
+
39
+ Stream states: `'pipe'`/`'piped'` makes the stream available, `'inherit'` inherits from parent, `'ignore'`/`null` ignores it.
40
+
41
+ ### Subprocess
42
+
43
+ ```ts
44
+ interface Subprocess<R = any> {
45
+ readonly command: string[];
46
+ readonly options: SpawnOptions | undefined;
47
+ readonly exited: Promise<number>; // resolves to exit code
48
+ readonly finished: boolean;
49
+ readonly killed: boolean;
50
+ readonly exitCode: number | null;
51
+ readonly signalCode: string | null;
52
+ readonly stdin: WritableStream<R> | null; // non-null when stdin is 'pipe'
53
+ readonly stdout: ReadableStream<R> | null; // non-null when stdout is 'pipe'
54
+ readonly stderr: ReadableStream<R> | null; // non-null when stderr is 'pipe'
55
+ readonly asDuplex: {readable: ReadableStream<R>, writable: WritableStream<R>};
56
+ kill(): void;
57
+ }
58
+ ```
59
+
60
+ All streams are web streams (ReadableStream/WritableStream).
61
+
62
+ Example:
63
+ ```js
64
+ import {spawn} from 'dollar-shell';
65
+
66
+ const sp = spawn(['sleep', '5']);
67
+ await new Promise(resolve => setTimeout(resolve, 1000));
68
+ sp.kill();
69
+ await sp.exited;
70
+ // sp.finished === true, sp.killed === true
71
+ ```
72
+
73
+ ## $$ (double dollar)
74
+
75
+ Template tag function wrapping `spawn()`. Parses the template string into an array of arguments (split by whitespace). Interpolated values are kept as separate arguments when surrounded by whitespace.
76
+
77
+ ```ts
78
+ type Backticks<R> = (strings: TemplateStringsArray, ...args: unknown[]) => R;
79
+
80
+ interface Dollar<R, O = SpawnOptions> extends Backticks<R> {
81
+ (options: O): Dollar<R, O>;
82
+ }
83
+
84
+ declare const $$: Dollar<Subprocess>;
85
+ ```
86
+
87
+ Signatures:
88
+ ```js
89
+ import {$$} from 'dollar-shell';
90
+
91
+ const sp = $$`ls -l ${myFile}`; // returns Subprocess
92
+ const sp2 = $$(options)`ls -l .`; // with custom options
93
+ const $tag = $$(options); // returns reusable tag function
94
+ const sp3 = $tag`ls -l .`;
95
+ ```
96
+
97
+ ## $ (dollar)
98
+
99
+ Simpler interface than `$$`. Returns a promise with exit info instead of a Subprocess.
100
+
101
+ ```ts
102
+ interface DollarResult {
103
+ code: number | null;
104
+ signal: string | null;
105
+ killed: boolean;
106
+ }
107
+
108
+ interface DollarImpl<R = any> extends Dollar<Promise<DollarResult>> {
109
+ from: Dollar<ReadableStream<R>>; // stdout as source stream
110
+ to: Dollar<WritableStream<R>>; // stdin as sink stream
111
+ through: Dollar<DuplexPair<R>>; // {readable, writable} pair
112
+ io: Dollar<DuplexPair<R>>; // alias of through
113
+ }
114
+
115
+ declare const $: DollarImpl;
116
+ export default $;
117
+ ```
118
+
119
+ `$` is the default export.
120
+
121
+ Examples:
122
+ ```js
123
+ import $ from 'dollar-shell';
124
+
125
+ // Run a command
126
+ const result = await $`echo hello`;
127
+ console.log(result.code, result.signal, result.killed);
128
+
129
+ // With custom options
130
+ const result2 = await $({stdout: 'inherit'})`ls -l .`;
131
+
132
+ // Stream pipeline
133
+ import chain from 'stream-chain';
134
+
135
+ chain([
136
+ $.from`ls -l .`,
137
+ $.io`grep LICENSE`,
138
+ $.io`wc`,
139
+ $.to({stdout: 'inherit'})`tee output.txt`
140
+ ]);
141
+
142
+ // Using $.from with web streams
143
+ $.from`ls -l .`
144
+ .pipeThrough($.io`grep LIC`)
145
+ .pipeTo($.to({stdout: 'inherit'})`wc`);
146
+ ```
147
+
148
+ When `$` is called with an options object, it returns a new `$` with updated defaults while preserving `.from`, `.to`, `.through`, `.io`:
149
+ ```js
150
+ const $custom = $({stdout: 'inherit', stderr: 'inherit'});
151
+ typeof $custom.from === 'function'; // true
152
+ typeof $custom.io === 'function'; // true
153
+ ```
154
+
155
+ ## shell / sh
156
+
157
+ Like `$$` but executes the command through a shell. Supports shell-specific features like pipes, aliases, and functions.
158
+
159
+ ```ts
160
+ interface ShellOptions extends SpawnOptions {
161
+ shellPath?: string; // path to shell executable
162
+ shellArgs?: string[]; // arguments passed to the shell
163
+ }
164
+
165
+ declare const shell: Dollar<Subprocess, ShellOptions>;
166
+ declare const sh = shell; // alias
167
+ ```
168
+
169
+ Shell defaults:
170
+ - Unix: shell = `$SHELL` or `/bin/sh` (or `/system/bin/sh` on Android), args = `['-c']`
171
+ - Windows cmd.exe: shell = `%ComSpec%` or `cmd.exe`, args = `['/d', '/s', '/c']`
172
+ - Windows PowerShell: shell = `pwsh.exe`/`powershell.exe`, args = `['-c']`
173
+
174
+ Example:
175
+ ```js
176
+ import {shell, sh} from 'dollar-shell';
177
+
178
+ const sp = sh`sleep 5`;
179
+ sp.kill();
180
+ await sp.exited;
181
+ ```
182
+
183
+ ## $sh (dollar shell)
184
+
185
+ Mirrors `$` but runs commands through a shell. Same relationship as `shell` to `$$`.
186
+
187
+ ```ts
188
+ interface ShellImpl<R = any> extends Dollar<Promise<DollarResult>, ShellOptions> {
189
+ from: Dollar<ReadableStream<R>, ShellOptions>;
190
+ to: Dollar<WritableStream<R>, ShellOptions>;
191
+ through: Dollar<DuplexPair<R>, ShellOptions>;
192
+ io: Dollar<DuplexPair<R>, ShellOptions>;
193
+ }
194
+
195
+ declare const $sh: ShellImpl;
196
+ ```
197
+
198
+ Examples:
199
+ ```js
200
+ import {$sh} from 'dollar-shell';
201
+
202
+ const result = await $sh`ls .`;
203
+ console.log(result.code, result.signal, result.killed);
204
+
205
+ // Interactive shell for aliases/functions
206
+ const $p = $sh({shellArgs: ['-ic'], stdout: 'inherit'});
207
+ await $p`nvm ls`;
208
+
209
+ // Shell pipes
210
+ await $sh({stdout: 'inherit'})`ls -l . | grep LICENSE | wc`;
211
+
212
+ // Stream pipeline
213
+ $sh.from`ls -l .`
214
+ .pipeThrough($sh.io`grep LIC`)
215
+ .pipeTo($sh.to({stdout: 'inherit'})`wc`);
216
+ ```
217
+
218
+ ## Utilities
219
+
220
+ ```js
221
+ import {
222
+ isWindows, raw, winCmdEscape,
223
+ cwd, currentExecPath, runFileArgs,
224
+ shellEscape, currentShellPath, buildShellCommand
225
+ } from 'dollar-shell';
226
+ ```
227
+
228
+ ### isWindows
229
+
230
+ `const isWindows: boolean` — `true` if the current platform is Windows.
231
+
232
+ ### raw(value)
233
+
234
+ Marks a value to bypass shell escaping or spawn argument splitting. The value is passed as-is.
235
+
236
+ ```js
237
+ import {$sh, raw} from 'dollar-shell';
238
+ await $sh({stdout: 'inherit'})`echo ${raw('"hello"')}`;
239
+ ```
240
+
241
+ For spawn (`$`/`$$`), `raw()` values are split by whitespace like template string literals.
242
+
243
+ ### winCmdEscape(value)
244
+
245
+ Escapes a value for Windows `cmd.exe` using caret (`^`) escaping. On non-Windows, returns the value as a string. Returns a `raw()`-wrapped object on Windows.
246
+
247
+ ```js
248
+ import {$sh, winCmdEscape} from 'dollar-shell';
249
+ await $sh`echo ${winCmdEscape('hello "world"')}`;
250
+ ```
251
+
252
+ ### cwd()
253
+
254
+ Returns the current working directory (platform-agnostic).
255
+
256
+ ### currentExecPath()
257
+
258
+ Returns the path of the current JS/TS runtime executable (Node, Deno, or Bun).
259
+
260
+ ### runFileArgs
261
+
262
+ Array of default arguments for the current runtime:
263
+ - Node: `[]`
264
+ - Deno: `['run']`
265
+ - Bun: `['run']`
266
+
267
+ Note: Deno may require additional permission flags (e.g., `--allow-read`).
268
+
269
+ ### shellEscape(value, options?)
270
+
271
+ Escapes a value for the current shell. Takes an optional `{shellPath?: string}` options object.
272
+ - Unix: wraps in single quotes
273
+ - Windows cmd.exe: quotes and escapes per cmd.exe rules
274
+ - Windows PowerShell: escapes control characters and non-alphanumeric characters
275
+
276
+ ### currentShellPath()
277
+
278
+ Returns the current shell path:
279
+ - Unix: `$SHELL` or `/bin/sh` (or `/system/bin/sh` on Android)
280
+ - Windows: `%ComSpec%` or `cmd.exe`
281
+
282
+ ### buildShellCommand(shell, args, command)
283
+
284
+ Builds a command array for `spawn()` from shell path, args, and command string.
285
+ - `shell`: string or undefined (defaults to `currentShellPath()`)
286
+ - `args`: string[] or undefined (defaults to shell-specific args)
287
+ - `command`: the shell command string
288
+
289
+ Returns `string[]` suitable for `spawn()`.
290
+
291
+ ## Template String Behavior
292
+
293
+ ### Spawn functions ($, $$)
294
+
295
+ Template strings are parsed into arrays of arguments by splitting on whitespace. Interpolated values:
296
+ - If surrounded by whitespace: added as a separate argument (preserving spaces within the value)
297
+ - If adjacent to text: concatenated with surrounding text
298
+ - Empty strings are skipped
299
+ - `raw()` values are split by whitespace like literal template text
300
+
301
+ ```js
302
+ $`ls -l ${'.'}`; // command: ['ls', '-l', '.']
303
+ $`${'l'}s -l a${'.'}b`; // command: ['ls', '-l', 'a.b']
304
+ $`ls ${'x y'}`; // command: ['ls', 'x y'] (preserved as one arg)
305
+ ```
306
+
307
+ ### Shell functions ($sh, shell)
308
+
309
+ Template strings are concatenated into a single command string. Interpolated values are escaped using `shellEscape()` unless wrapped with `raw()`.
310
+
311
+ ## TypeScript
312
+
313
+ Full TypeScript declarations are provided in `src/index.d.ts`. The package uses `"types": "./src/index.d.ts"` in package.json.
314
+
315
+ ## Platform Support
316
+
317
+ Works on Node.js, Deno, and Bun. The appropriate spawn implementation is selected automatically at import time. All streams use the web streams API (ReadableStream/WritableStream).
package/llms.txt ADDED
@@ -0,0 +1,79 @@
1
+ # dollar-shell
2
+
3
+ > A micro-library for running OS and shell commands from JavaScript/TypeScript using template tag functions. Works in Node, Deno, and Bun with the same API. Web streams, TypeScript typings, zero dependencies.
4
+
5
+ ## Install
6
+
7
+ npm i --save dollar-shell
8
+
9
+ ## Quick start
10
+
11
+ ```js
12
+ import $ from 'dollar-shell';
13
+
14
+ const result = await $`echo hello`;
15
+ // result: {code: 0, signal: null, killed: false}
16
+ ```
17
+
18
+ Run: `node my-script.js`
19
+
20
+ ## API
21
+
22
+ - `$` — run a command, returns `Promise<{code, signal, killed}>`. Default export.
23
+ - `$$` — run a command, returns a `Subprocess` object (full control: kill, streams, exit code).
24
+ - `$sh` — run a shell command, returns `Promise<{code, signal, killed}>`.
25
+ - `shell` / `sh` — run a shell command, returns a `Subprocess` object.
26
+ - `spawn()` — low-level process spawning with full configuration.
27
+
28
+ Stream pipeline helpers (available on `$` and `$sh`):
29
+ - `.from` — stdout as a ReadableStream (source).
30
+ - `.to` — stdin as a WritableStream (sink).
31
+ - `.io` / `.through` — {readable, writable} duplex pair (transform step).
32
+
33
+ All tag functions accept an options object to return a new tag function with updated defaults.
34
+
35
+ Utilities: `raw()`, `winCmdEscape()`, `isWindows`, `cwd()`, `currentExecPath()`, `runFileArgs`, `shellEscape()`, `currentShellPath()`, `buildShellCommand()`.
36
+
37
+ ## Common patterns
38
+
39
+ ```js
40
+ import $, {$$, $sh, raw} from 'dollar-shell';
41
+
42
+ // Simple command
43
+ const result = await $`echo hello`;
44
+
45
+ // Shell command (pipes, aliases, globbing)
46
+ await $sh({stdout: 'inherit'})`ls -l . | grep LICENSE | wc`;
47
+
48
+ // Full Subprocess control
49
+ const sp = $$`sleep 5`;
50
+ sp.kill();
51
+ await sp.exited;
52
+
53
+ // Custom options (returns new tag function)
54
+ const $verbose = $({stdout: 'inherit', stderr: 'inherit'});
55
+ await $verbose`ls -l .`;
56
+
57
+ // Web stream pipeline
58
+ $.from`ls -l .`
59
+ .pipeThrough($.io`grep LIC`)
60
+ .pipeTo($.to({stdout: 'inherit'})`wc`);
61
+
62
+ // Bypass escaping with raw()
63
+ await $sh({stdout: 'inherit'})`echo ${raw('"hello"')}`;
64
+ ```
65
+
66
+ ## Docs
67
+
68
+ - [$ (dollar)](https://github.com/uhop/dollar-shell/wiki/$): Spawn processes with simple return values, includes $.from, $.to, $.io/$.through
69
+ - [$$ (double dollar)](https://github.com/uhop/dollar-shell/wiki/$$): Spawn processes returning full Subprocess objects
70
+ - [$sh (dollar shell)](https://github.com/uhop/dollar-shell/wiki/$sh): Run shell commands with simple return values, includes $sh.from, $sh.to, $sh.io/$sh.through
71
+ - [shell / sh](https://github.com/uhop/dollar-shell/wiki/shell): Run shell commands returning full Subprocess objects
72
+ - [spawn()](https://github.com/uhop/dollar-shell/wiki/spawn): Low-level process spawning with full configuration
73
+ - [Utilities](https://github.com/uhop/dollar-shell/wiki/Utilities): Helper functions — raw(), winCmdEscape(), isWindows, cwd(), currentExecPath(), runFileArgs, shellEscape(), currentShellPath(), buildShellCommand()
74
+
75
+ ## Links
76
+
77
+ - Docs: https://github.com/uhop/dollar-shell/wiki
78
+ - npm: https://www.npmjs.com/package/dollar-shell
79
+ - Full LLM reference: https://github.com/uhop/dollar-shell/blob/main/llms-full.txt
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dollar-shell",
3
- "description": "Run shell commands and use them in streams with ease in Node, Deno, Bun. Tiny, simple, no dependency package.",
4
- "version": "1.1.8",
3
+ "description": "Run shell commands and use them in stream pipelines with ease in Node, Deno, Bun. Template tag API, web streams, TypeScript typings, zero dependencies.",
4
+ "version": "1.1.10",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./src/index.d.ts",
@@ -26,7 +26,12 @@
26
26
  "files": [
27
27
  "/src",
28
28
  "LICENSE",
29
- "README.md"
29
+ "README.md",
30
+ "AGENTS.md",
31
+ "CLAUDE.md",
32
+ "CONTRIBUTING.md",
33
+ "llms.txt",
34
+ "llms-full.txt"
30
35
  ],
31
36
  "repository": {
32
37
  "type": "git",
@@ -36,12 +41,28 @@
36
41
  "url": "https://github.com/uhop/dollar-shell/issues"
37
42
  },
38
43
  "homepage": "https://github.com/uhop/dollar-shell#readme",
44
+ "llms": "https://raw.githubusercontent.com/uhop/dollar-shell/main/llms.txt",
45
+ "llmsFull": "https://raw.githubusercontent.com/uhop/dollar-shell/main/llms-full.txt",
39
46
  "keywords": [
40
47
  "shell",
41
48
  "spawn",
42
49
  "$",
43
50
  "dollar",
44
- "stream"
51
+ "stream",
52
+ "process",
53
+ "exec",
54
+ "command",
55
+ "web-streams",
56
+ "template-tag",
57
+ "pipeline",
58
+ "typescript",
59
+ "esm",
60
+ "es-modules",
61
+ "cross-runtime",
62
+ "zero-dependency",
63
+ "nodejs",
64
+ "deno",
65
+ "bun"
45
66
  ],
46
67
  "author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (http://www.lazutkin.com/)",
47
68
  "funding": {
@@ -50,7 +71,8 @@
50
71
  },
51
72
  "license": "BSD-3-Clause",
52
73
  "devDependencies": {
53
- "tape-six": "^1.7.0",
74
+ "prettier": "^3.8.1",
75
+ "tape-six": "^1.7.4",
54
76
  "typescript": "^5.9.3"
55
77
  },
56
78
  "tape6": {
package/src/bq-shell.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  import {isRawValue, getRawValue, verifyStrings} from './utils.js';
4
2
 
5
3
  const impl =
@@ -28,7 +26,11 @@ const impl =
28
26
  const bqShell = (shellEscape, shell, options = {}) => {
29
27
  const bq = (strings, ...args) => {
30
28
  if (verifyStrings(strings)) return impl(shellEscape, shell, options)(strings, ...args);
31
- return Object.assign(bqShell(shellEscape, shell, {...options, ...strings}), bq);
29
+ const derived = bqShell(shellEscape, shell, {...options, ...strings});
30
+ for (const [key, value] of Object.entries(bq)) {
31
+ derived[key] = typeof value === 'function' ? value(strings) : value;
32
+ }
33
+ return derived;
32
34
  };
33
35
  return bq;
34
36
  };
package/src/bq-spawn.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  import {verifyStrings, isRawValue, getRawValue} from './utils.js';
4
2
 
5
3
  const appendString = (s, previousSpace, result) => {
@@ -69,7 +67,11 @@ const impl =
69
67
  const bqSpawn = (spawn, options = {}) => {
70
68
  const bq = (strings, ...args) => {
71
69
  if (verifyStrings(strings)) return impl(spawn, options)(strings, ...args);
72
- return Object.assign(bqSpawn(spawn, {...options, ...strings}), bq);
70
+ const derived = bqSpawn(spawn, {...options, ...strings});
71
+ for (const [key, value] of Object.entries(bq)) {
72
+ derived[key] = typeof value === 'function' ? value(strings) : value;
73
+ }
74
+ return derived;
73
75
  };
74
76
  return bq;
75
77
  };
package/src/index.d.ts CHANGED
@@ -123,6 +123,11 @@ export declare function currentExecPath(): string;
123
123
  */
124
124
  export declare const runFileArgs: string[];
125
125
 
126
+ /**
127
+ * Whether the current platform is Windows.
128
+ */
129
+ export declare const isWindows: boolean;
130
+
126
131
  /**
127
132
  * The function marks a value as raw. The value will be passed as-is to the shell.
128
133
  * It is used to bypass the default shell escaping rules.
package/src/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  // @ts-self-types="./index.d.ts"
2
2
 
3
- 'use strict';
4
-
5
3
  // load dependencies
6
4
 
7
5
  import {isWindows, raw, winCmdEscape} from './utils.js';
@@ -39,17 +37,17 @@ export const $ = bqSpawn((command, options) => {
39
37
  });
40
38
 
41
39
  const fromProcess = bqSpawn((command, options) => {
42
- const sp = spawn(command, Object.assign({}, options, {stdout: 'pipe'}));
40
+ const sp = spawn(command, {...options, stdout: 'pipe'});
43
41
  return sp.stdout;
44
42
  });
45
43
 
46
44
  const toProcess = bqSpawn((command, options) => {
47
- const sp = spawn(command, Object.assign({}, options, {stdin: 'pipe'}));
45
+ const sp = spawn(command, {...options, stdin: 'pipe'});
48
46
  return sp.stdin;
49
47
  });
50
48
 
51
49
  const throughProcess = bqSpawn((command, options) => {
52
- const sp = spawn(command, Object.assign({}, options, {stdin: 'pipe', stdout: 'pipe'}));
50
+ const sp = spawn(command, {...options, stdin: 'pipe', stdout: 'pipe'});
53
51
  return sp.asDuplex;
54
52
  });
55
53
 
@@ -60,36 +58,46 @@ $.through = $.io = throughProcess;
60
58
  // define shell functions
61
59
 
62
60
  export const shell = bqShell(shellEscape, (command, options) =>
63
- spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), options)
61
+ spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), {
62
+ ...options,
63
+ windowsVerbatimArguments: true
64
+ })
64
65
  );
65
66
  export {shell as sh};
66
67
 
67
68
  export const $sh = bqShell(shellEscape, (command, options) => {
68
- const sp = spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), options);
69
+ const sp = spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), {
70
+ ...options,
71
+ windowsVerbatimArguments: true
72
+ });
69
73
  return sp.exited.then(() => ({code: sp.exitCode, signal: sp.signalCode, killed: sp.killed}));
70
74
  });
71
75
 
72
76
  const fromShell = bqShell(shellEscape, (command, options) => {
73
- const sp = spawn(
74
- buildShellCommand(options?.shellPath, options?.shellArgs, command),
75
- Object.assign({}, options, {stdout: 'pipe'})
76
- );
77
+ const sp = spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), {
78
+ ...options,
79
+ stdout: 'pipe',
80
+ windowsVerbatimArguments: true
81
+ });
77
82
  return sp.stdout;
78
83
  });
79
84
 
80
85
  const toShell = bqShell(shellEscape, (command, options) => {
81
- const sp = spawn(
82
- buildShellCommand(options?.shellPath, options?.shellArgs, command),
83
- Object.assign({}, options, {stdin: 'pipe'})
84
- );
86
+ const sp = spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), {
87
+ ...options,
88
+ stdin: 'pipe',
89
+ windowsVerbatimArguments: true
90
+ });
85
91
  return sp.stdin;
86
92
  });
87
93
 
88
94
  const throughShell = bqShell(shellEscape, (command, options) => {
89
- const sp = spawn(
90
- buildShellCommand(options?.shellPath, options?.shellArgs, command),
91
- Object.assign({}, options, {stdin: 'pipe', stdout: 'pipe'})
92
- );
95
+ const sp = spawn(buildShellCommand(options?.shellPath, options?.shellArgs, command), {
96
+ ...options,
97
+ stdin: 'pipe',
98
+ stdout: 'pipe',
99
+ windowsVerbatimArguments: true
100
+ });
93
101
  return sp.asDuplex;
94
102
  });
95
103
 
package/src/shell/unix.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  import {getEnv, isAndroid} from '../utils.js';
4
2
 
5
3
  export const shellEscape = s => "'" + String(s).replace(/'/g, "'\\''") + "'";
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  import {getEnv, toBase64} from '../utils.js';
4
2
 
5
3
  export const currentShellPath = () => getEnv('ComSpec') || 'cmd.exe';
package/src/spawn/bun.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  const sanitize = (value, defaultValue = 'ignore') => {
4
2
  switch (value) {
5
3
  case 'pipe':
@@ -20,8 +18,10 @@ class Subprocess {
20
18
  this.options = options;
21
19
 
22
20
  this.killed = false;
21
+ this.finished = false;
23
22
 
24
- const spawnOptions = {windowsVerbatimArguments: true};
23
+ const spawnOptions = {};
24
+ if (options.windowsVerbatimArguments) spawnOptions.windowsVerbatimArguments = true;
25
25
  options.cwd && (spawnOptions.cwd = options.cwd);
26
26
  options.env && (spawnOptions.env = options.env);
27
27
 
@@ -31,27 +31,33 @@ class Subprocess {
31
31
 
32
32
  this.spawnOptions = spawnOptions;
33
33
 
34
- this.childProcess = Bun.spawn(command, spawnOptions);
34
+ try {
35
+ this.childProcess = Bun.spawn(command, spawnOptions);
36
+ } catch (error) {
37
+ this.childProcess = null;
38
+ this.finished = true;
39
+ this.exited = Promise.reject(error);
40
+ this.stdin = null;
41
+ this.stdout = null;
42
+ this.stderr = null;
43
+ return;
44
+ }
45
+ this.exited = this.childProcess.exited.then(code => {
46
+ this.finished = true;
47
+ return code;
48
+ });
35
49
 
36
50
  this.stdin = this.childProcess.stdin ? new WritableStream(this.childProcess.stdin) : null;
37
51
  this.stdout = this.childProcess.stdout || null;
38
52
  this.stderr = this.childProcess.stderr || null;
39
53
  }
40
54
 
41
- get exited() {
42
- return this.childProcess.exited;
43
- }
44
-
45
- get finished() {
46
- return this.childProcess.killed;
47
- }
48
-
49
55
  get exitCode() {
50
- return this.childProcess.exitCode;
56
+ return this.childProcess ? this.childProcess.exitCode : null;
51
57
  }
52
58
 
53
59
  get signalCode() {
54
- return this.childProcess.signalCode;
60
+ return this.childProcess ? this.childProcess.signalCode : null;
55
61
  }
56
62
 
57
63
  get asDuplex() {
package/src/spawn/deno.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  const sanitize = (value, defaultValue = 'null') => {
4
2
  switch (value) {
5
3
  case 'pipe':
@@ -28,9 +26,9 @@ class Subprocess {
28
26
 
29
27
  const spawnOptions = {
30
28
  signal: this.controller.signal,
31
- args: command.slice(1),
32
- windowsRawArguments: true
29
+ args: command.slice(1)
33
30
  };
31
+ if (options.windowsVerbatimArguments) spawnOptions.windowsRawArguments = true;
34
32
  options.cwd && (spawnOptions.cwd = options.cwd);
35
33
  options.env && (spawnOptions.env = options.env);
36
34
 
@@ -40,7 +38,14 @@ class Subprocess {
40
38
 
41
39
  this.spawnOptions = spawnOptions;
42
40
 
43
- this.childProcess = new Deno.Command(command[0], spawnOptions).spawn();
41
+ try {
42
+ this.childProcess = new Deno.Command(command[0], spawnOptions).spawn();
43
+ } catch (error) {
44
+ this.childProcess = null;
45
+ this.finished = true;
46
+ this.exited = Promise.reject(error);
47
+ return;
48
+ }
44
49
 
45
50
  this.exited = this.childProcess.status
46
51
  .then(status => {
@@ -56,15 +61,21 @@ class Subprocess {
56
61
  }
57
62
 
58
63
  get stdin() {
59
- return this.spawnOptions.stdin === 'piped' ? this.childProcess.stdin : null;
64
+ return this.childProcess && this.spawnOptions.stdin === 'piped'
65
+ ? this.childProcess.stdin
66
+ : null;
60
67
  }
61
68
 
62
69
  get stdout() {
63
- return this.spawnOptions.stdout === 'piped' ? this.childProcess.stdout : null;
70
+ return this.childProcess && this.spawnOptions.stdout === 'piped'
71
+ ? this.childProcess.stdout
72
+ : null;
64
73
  }
65
74
 
66
75
  get stderr() {
67
- return this.spawnOptions.stderr === 'piped' ? this.childProcess.stderr : null;
76
+ return this.childProcess && this.spawnOptions.stderr === 'piped'
77
+ ? this.childProcess.stderr
78
+ : null;
68
79
  }
69
80
 
70
81
  get asDuplex() {
package/src/spawn/node.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  import {spawn} from 'node:child_process';
4
2
  import {Readable, Writable} from 'node:stream';
5
3
 
@@ -29,7 +27,8 @@ class Subprocess {
29
27
  this.killed = false;
30
28
  this.finished = false;
31
29
 
32
- const spawnOptions = {stdio: ['ignore', 'ignore', 'ignore'], windowsVerbatimArguments: true};
30
+ const spawnOptions = {stdio: ['ignore', 'ignore', 'ignore']};
31
+ if (options.windowsVerbatimArguments) spawnOptions.windowsVerbatimArguments = true;
33
32
  options.cwd && (spawnOptions.cwd = options.cwd);
34
33
  options.env && (spawnOptions.env = options.env);
35
34
 
@@ -39,6 +38,22 @@ class Subprocess {
39
38
 
40
39
  this.spawnOptions = spawnOptions;
41
40
 
41
+ let settled = false;
42
+ this.exited = new Promise((resolve, reject) => {
43
+ this.resolve = value => {
44
+ if (!settled) {
45
+ settled = true;
46
+ resolve(value);
47
+ }
48
+ };
49
+ this.reject = value => {
50
+ if (!settled) {
51
+ settled = true;
52
+ reject(value);
53
+ }
54
+ };
55
+ });
56
+
42
57
  this.childProcess = spawn(command[0], command.slice(1), spawnOptions);
43
58
  this.childProcess.on('exit', (code, signal) => {
44
59
  this.finished = true;
@@ -51,11 +66,6 @@ class Subprocess {
51
66
  this.reject(error);
52
67
  });
53
68
 
54
- this.exited = new Promise((resolve, reject) => {
55
- this.resolve = resolve;
56
- this.reject = reject;
57
- });
58
-
59
69
  this.stdin = this.childProcess.stdin && Writable.toWeb(this.childProcess.stdin);
60
70
  this.stdout = this.childProcess.stdout && Readable.toWeb(this.childProcess.stdout);
61
71
  this.stderr = this.childProcess.stderr && Readable.toWeb(this.childProcess.stderr);
package/src/utils.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  let getEnv, isWindows, isAndroid;
4
2
  if (typeof Deno !== 'undefined') {
5
3
  getEnv = name => Deno.env.get(name);
@@ -16,8 +14,7 @@ if (typeof Deno !== 'undefined') {
16
14
  }
17
15
  export {getEnv, isWindows, isAndroid};
18
16
 
19
- export const verifyStrings = strings =>
20
- Array.isArray(strings) && strings.every(s => typeof s === 'string');
17
+ export const verifyStrings = strings => Array.isArray(strings) && Array.isArray(strings.raw);
21
18
 
22
19
  export const toBase64 = s => {
23
20
  // const buf = new TextEncoder().encode(s),