@j-o-r/sh 1.1.28 → 1.1.29

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/README.md CHANGED
@@ -1,195 +1,160 @@
1
- # @j-o-r/sh
1
+ # @j-o-r/sh   [![npm](https://img.shields.io/npm/v/@j-o-r/sh?logo=npm)](https://www.npmjs.com/package/@j-o-r/sh) [![License](https://img.shields.io/npm/license/@j-o-r/sh)](https://codeberg.org/duin/sh/src/branch/main/LICENSE) [![Codeberg](https://img.shields.io/badge/Codeberg-main-blue?logo=codeberg)](https://codeberg.org/duin/sh)
2
2
 
3
- Execute shell commands from JavaScript.
3
+ Execute shell commands from JavaScript on Linux.
4
4
 
5
5
  ## Introduction
6
6
 
7
- `@j-o-r/sh` is a Node.js module that simplifies the execution of shell commands within JavaScript applications. It provides a range of utilities to handle shell scripts and manage their output efficiently.
7
+ `@j-o-r/sh` is a lightweight Node.js module for running shell commands with a clean, zx-inspired API. It fixes namespace pollution, adds **rolling timeouts** (reset on stdout/stderr), **no-shell mode** (`/usr/bin/env -S`), process tree kills, buffer limits (1MB default), and a built-in **Test** framework + **AsyncTracker** for leaks.
8
8
 
9
- This project draws inspiration from the exceptional [zx library](https://github.com/google/zx). The core functionality of zx, particularly the shell execution method, has been extracted and forms the foundation of this project.
9
+ **Key Features**:
10
+ - Template tag `SH`cmd`` → `SHDispatch` for `.options().run()`.
11
+ - Global options: `SH.timeout = '5s'; SH.cwd = '/tmp';`.
12
+ - Sync/async `run()`/`runSync()`; stdin payload; detached mode.
13
+ - Utils: `sleep`, `retry`, `expBackoff`, `cd`, `parseArgs`, `userIn`, `readIn`.
14
+ - Testing: `new Test().add('name', () => assert(...)).run()`.
15
+ - Async leak detection: `AsyncTracker` via `async_hooks`.
16
+ - Safe interpolation; `bashEscape`; full JSDoc.
10
17
 
11
- ## Installation
18
+ No runtime deps. ESM-only (ES2020+).
12
19
 
13
- Install the module using npm:
20
+ ## Quick Install
14
21
 
15
- ```sh
16
- npm install @j-o-r/sh
22
+ ```bash
23
+ npm i @j-o-r/sh
17
24
  ```
18
25
 
19
26
  ## Usage
20
27
 
21
- ### Basic Usage
28
+ ### Basics
22
29
 
23
- To execute a shell command, use the `SH` function:
30
+ ```js
31
+ import { SH, cd, sleep } from '@j-o-r/sh';
24
32
 
25
- ```javascript
26
- import { SH, cd, within, sleep, retry, expBackoff } from '@j-o-r/sh';
27
-
28
- SH`your_shell_command`.run()
29
- .then(output => {
30
- console.log('Output:', output);
31
- })
32
- .catch(error => {
33
- console.error('Error:', error);
34
- });
33
+ cd('/tmp');
34
+ const out = await SH`ls -la`.run();
35
+ console.log(out); // Captured stdout (trimmed)
35
36
  ```
36
37
 
37
- ### Advanced Usage
38
-
39
- ```javascript
40
- const res = await SH`ls -FLa | grep package.json | wc -l`.run();
41
- console.log(res);
38
+ ### Chaining & Options
42
39
 
43
- const ar = within(async () => {
44
- const res = await Promise.all([
45
- SH`sleep 1; echo 1`.run(),
46
- SH`sleep 2; echo 2`.run(),
47
- sleep(2),
48
- SH`sleep 3; echo 3`.run()
49
- ]);
50
- });
40
+ ```js
41
+ SH`curl -s ip.js.org`
42
+ .options({ timeout: '2s', shell: false }) // No-shell: /usr/bin/env -S
43
+ .run()
44
+ .catch(e => console.error(e.message)); // "Command failed with code 1: ..."
51
45
  ```
52
46
 
53
- ```javascript
54
- const p = await retry(3, expBackoff(), () => SH`curl -s https://unreachable`.run());
55
- ```
47
+ ### Parallel & Context
56
48
 
57
- The `SH` method accepts a template literal string enclosed in backticks as its argument. It returns an `SHDispatch` object.
49
+ ```js
50
+ import { within } from '@j-o-r/sh';
58
51
 
59
- ### Additional Utilities
60
-
61
- The module also provides additional utilities for common tasks:
62
- - `parseArgs(process.args)`: Transform an array of strings into an object
63
- - `cd(dir)`: Change the working directory.
64
- - `sleep(duration)`: Pause execution for a specified duration.
65
- - `retry(count, interval, callback)`: Retry a command a specified number of times with an optional interval.
66
- - `readIn()`: Read from standard input.
67
- - `userIn(prompt)`: Prompt the user for input and return an object with `input` (Promise resolving to the user input) and `abort()` method to cancel.
68
- - `within(callback)`: Create an async context in a sync block.
69
- - `expBackoff(max, rand)`: Generate intervals for exponential backoff.
70
- - `jsType(any)`: Get the 'real' javascript variable type
71
- - `hasProp(object, property)`: Safely check if an object has its own property (handles null/undefined objects).
72
- - `assert.`: Node assert library
73
- - `new Test()`: A small sync/async minimal test framework
74
- - `AsyncTracker`: A class for tracking asynchronous operations using Node.js async hooks.
52
+ const results = await within(async () => Promise.all([
53
+ SH`sleep 1; echo ok`.run(),
54
+ sleep('500ms'),
55
+ SH`uname`.run()
56
+ ]));
57
+ ```
75
58
 
76
- ## SHDispatch
59
+ ### Retry & Backoff
77
60
 
78
- This class is returned by the `SH` function. Here's a summary of its methods and properties:
61
+ ```js
62
+ import { retry, expBackoff } from '@j-o-r/sh';
79
63
 
80
- ### Methods
64
+ try {
65
+ const res = await retry(3, expBackoff('10s'), () =>
66
+ SH`curl -s unreachable`.run()
67
+ );
68
+ } catch (e) {
69
+ // Last error
70
+ }
71
+ ```
81
72
 
82
- - **options(options)**: Sets options for the command execution.
83
- - **run(payload?)**: Executes the command and returns a promise that resolves with the command's output.
84
- - **runSync(payload?)**: Executes the command synchronously and returns a `SpawnSyncResponse`.
85
- - **kill()**: Sends a kill signal to the child process.
73
+ ### Interactive / Stdin
86
74
 
87
- ### Examples
75
+ ```js
76
+ import { userIn, readIn } from '@j-o-r/sh';
88
77
 
89
- - Elementary usages, piped:
90
- ```javascript
91
- const res = await SH`ls -FLa | grep package.json | wc -l`.run();
92
- console.log(res);
93
- ```
78
+ // User prompt (abortable)
79
+ const { input, abort } = userIn('Password: ');
80
+ const pw = await input;
94
81
 
95
- - Feed command with content:
96
- ```javascript
97
- const res = await SH`wc -l`.run(`one\ntwo\n`);
98
- console.log(res);
99
- ```
82
+ // Piped stdin
83
+ const out = await SH`grep secret`.run(await readIn());
84
+ ```
100
85
 
101
- - Create a command from a string
102
- ```javascript
103
- const command = "uname -r";
104
- const content = await SH`${command}`.run();
105
- console.log(content);
106
- ```
86
+ ### Vim / TTY (Sync)
107
87
 
108
- - Async context with multiple commands and sleep:
109
- ```javascript
110
- within(async () => {
111
- const res = await Promise.all([
112
- SH`sleep 1; echo 1`.run(),
113
- SH`sleep 2; echo 2`.run(),
114
- sleep(2),
115
- SH`sleep 3; echo 3`.run()
116
- ]);
117
- console.log(res);
118
- });
119
- ```
88
+ ```js
89
+ SH`vim`.options({ stdio: 'inherit' }).runSync();
90
+ ```
120
91
 
121
- - Retry with exponential backoff:
122
- ```javascript
123
- try {
124
- const p = await retry(3, expBackoff(), () => SH`curl -s https://unreachable`.run());
125
- } catch (e) {
126
- console.error('Retry failed:', e);
127
- }
128
- ```
92
+ ## Testing
129
93
 
130
- - Prompt user for input:
131
- ```javascript
132
- const user = userIn('Enter your name: ');
133
- const name = await user.input;
134
- console.log('Hello,', name);
135
- ```
94
+ Full-featured tester with async support, error reporting, unresolved Promise detection.
136
95
 
137
- - Check if object has property:
138
- ```javascript
139
- const obj = { a: 1 };
140
- console.log(hasProp(obj, 'a')); // true
141
- console.log(hasProp(obj, 'b')); // false
142
- console.log(hasProp(null, 'a')); // false
143
- ```
96
+ ```js
97
+ import { Test, assert, jsType } from '@j-o-r/sh';
144
98
 
145
- - Method for copying data to the clipboard:
146
- ```javascript
147
- /**
148
- * Copy text to the clipboard
149
- * @param {string} text
150
- * @returns {Promise<string>}
151
- */
152
- const copyToClipboard = async (text) => {
153
- const prams = [
154
- '-selection',
155
- 'clipboard'
156
- ]
157
- return SH`xclip ${prams}`.options({stdio: 'inherit'}).run(text);
158
- }
159
- ```
99
+ const t = new Test(true); // quiet: no console
100
+ t.add('sync assert', () => assert.strictEqual(1 + 1, 2));
101
+ t.add('async', async () => {
102
+ await sleep('100ms');
103
+ assert.strictEqual(jsType([]), 'Array');
104
+ });
105
+ const report = await t.run();
106
+ console.log(report); // { tests: 2, executed: 2, duration: 150, errors: 0 }
160
107
 
161
- - Open the 'vim' editor
162
- ```javascript
163
- SH`vim`.options({stdio: 'inherit'}).runSync();
164
- ```
108
+ t.unresolved(); // Logs leaks if any
109
+ ```
165
110
 
166
- - Create and run a test
167
- ```javascript
168
- import { assert, jsType, Test} from '@j-o-r/sh';
111
+ ## Advanced
169
112
 
170
- const test = new Test();
171
- test.add('Test is test in sync', () => {
172
- assert.strictEqual(jsType(test), 'Test');
173
- });
174
- test.add('Test an Array in sync', () => {
175
- assert.strictEqual(jsType([]), 'Array');
176
- });
177
- test.add('Test is test in async', async () => {
178
- assert.strictEqual(jsType(test), 'Test');
179
- });
180
- test.add('Test is test in async, returning a promise', async () => {
181
- return new Promise((resolve, _reject) => {
182
- assert.strictEqual(jsType(test), 'Test');
183
- resolve();
184
- });
185
- });
186
- const report = await test.run();
187
- if (report.errors > 0) {
188
- process.exit(1);
189
- }
190
- // await test.run([0,3]); // only run test 0 and 3
113
+ - **SSH Example** (interactive; use keys/sshpass for automation):
114
+ ```js
115
+ // TTY/inherit for prompts
116
+ SH`ssh user@host`.options({ stdio: 'inherit' }).runSync();
117
+ // Or sshpass: SH`sshpass -p pw ssh user@host`.run()
118
+ ```
119
+ See `scenarios/` for full demos.
120
+
121
+ - **CLI Args**: `parseArgs()` → `{ port: '8080', _: ['file'] }`.
122
+ - **Global Defaults**: Set global options on the `SH` object to apply to all subsequent commands. These can be overridden per-command via `.options()`. Examples:
123
+ - `SH.timeout = '5s';` Default timeout for all runs.
124
+ - `SH.cwd = '/tmp';` Default working directory.
125
+ - `SH.shell = false;` – Disable shell mode (uses `/usr/bin/env -S`).
126
+ - `SH.maxBuffer = 1 * 1024 * 1024;` – Override default buffer limit (500kb) to 1MB per stream (stdout/stderr). Buffering captures output up to this limit; excess is truncated with markers.
127
+ - **Kill Tree**: `dispatch.kill('SIGKILL')` → children via `pgrep -P`.
128
+
129
+ ## API
130
+
131
+ Full JSDoc in `lib/*.js`. Key exports:
132
+
133
+ | Utility | Description |
134
+ |---------|-------------|
135
+ | `SH`cmd`` | Template → {@link SHDispatch} |
136
+ | `cd(dir)` | `process.chdir()` |
137
+ | `sleep('1s')` | Promise delay |
138
+ | `retry(3, '1s', fn)` | Retry w/ delay/gen |
139
+ | `userIn(prompt)` | `{ input: Promise, abort() }` |
140
+ | `Test` | Test runner |
141
+ | `AsyncTracker` | Async leak detector |
142
+ | `parseArgs(argv)` | CLI parser |
143
+ | `bashEscape(str)` | Shell-safe string |
144
+
145
+ See [types/index.d.ts](types/index.d.ts) for TS defs.
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ npm run types # Generate types/
151
+ npm test # Run scenarios/sh.js
152
+ npm run release # Pack for publish
153
+ npm run publish # npm publish
191
154
  ```
192
155
 
156
+ Repo: [Codeberg](https://codeberg.org/duin/sh) | Issues: [Codeberg Issues](https://codeberg.org/duin/sh/issues)
157
+
193
158
  ## License
194
159
 
195
- This project is licensed under the Apache License, Version 2.0.
160
+ Apache-2.0 © [Jorrit Duin](mailto:jorrit.duin+sh@gmail.com)
package/TODO.md ADDED
@@ -0,0 +1,18 @@
1
+ # TODOs for @j-o-r/sh project
2
+
3
+ ## High Priority (pending)
4
+ - [ ] Review and commit recent changes to lib/ files and types/ (from git status: modified AsyncTracker.js, SH.js, etc.)
5
+ - [*] Generate or improve API documentation using types/*.d.ts files
6
+
7
+ ## In Progress
8
+ - [ ]
9
+
10
+ ## Later (future tasks)
11
+ - [ ] Create separate docs folder with full API reference
12
+
13
+ ## Done
14
+ - [x] Initial project setup from package.json and README.md inspection (2026-04-15)
15
+ - [x] Update README.md to include documentation for recent changes in AsyncTracker and Test features (2026-04-15)
16
+ - [x] Add more examples to README.md for SHDispatch methods and utilities like retry and expBackoff (2026-04-15)
17
+ - [x] Reference general SSH example in README.md (demo-interactive-ssh.js not found in scenarios/) (2026-04-15)
18
+ - [x] Reduce SHExecute maxBuffer default from 40MB to 1MB, update all docs/JSDoc mentioning it. Make sure global SH.maxBuffer works. (2026-04-15)