# @j-o-r/sh

Execute shell commands from JavaScript.

## Introduction

`@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.

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.

## Installation

Install the module using npm:

```sh
npm install @j-o-r/sh
```

## Usage

### Basic Usage

To execute a shell command, use the `SH` function:

```javascript
import { SH, cd, within, sleep, retry, expBackoff } from '@j-o-r/sh';

SH`your_shell_command`.run()
  .then(output => {
    console.log('Output:', output);
  })
  .catch(error => {
    console.error('Error:', error);
  });
```

### Advanced Usage

```javascript
const res = await SH`ls -FLa | grep package.json | wc -l`.run();
console.log(res);

const ar = within(async () => {
  const res = await Promise.all([
    SH`sleep 1; echo 1`.run(),
    SH`sleep 2; echo 2`.run(),
    sleep(2),
    SH`sleep 3; echo 3`.run()
  ]);
});
```

```javascript
const p = await retry(3, expBackoff(), () => SH`curl -s https://unreachable`.run());
```

The `SH` method accepts a template literal string enclosed in backticks as its argument. It returns an `SHDispatch` object.

### Additional Utilities

The module also provides additional utilities for common tasks:
- `parseArgs(process.args)`: Transform an array of strings into an object 
- `cd(dir)`: Change the working directory.
- `sleep(duration)`: Pause execution for a specified duration.
- `retry(count, interval, callback)`: Retry a command a specified number of times with an optional interval.
- `readIn()`: Read from standard input.
- `within(callback)`: Create an async context in a sync block.
- `expBackoff(max, rand)`: Generate intervals for exponential backoff.
- `jsType(any)`: Get the 'real' javascript variable type 
- `assert.`: Node assert library 
- `new Test()`: A small sync/async minimal test framework 

## SHDispatch

This class is returned by the `SH` function. Here's a summary of its methods and properties:

### Methods

- **options(options)**: Sets options for the command execution.
- **run(payload?)**: Executes the command and returns a promise that resolves with the command's output.
- **runSync(payload?)**: Executes the command synchronously and returns a `SpawnSyncResponse`.
- **kill()**: Sends a kill signal to the child process.

### Examples

- Elementary usages, piped:
  ```javascript
  const res = await SH`ls -FLa | grep package.json | wc -l`.run();
  console.log(res);
  ```

- Feed command with content:
  ```javascript
  const res = await SH`wc -l`.run(`one\ntwo\n`);
  console.log(res);
  ```

- Create a command from a string
  ```javascript
	const command = "uname -r";
	const content = await SH`${command}`.run();
	console.log(content);
  ```

- Async context with multiple commands and sleep:
  ```javascript
  within(async () => {
    const res = await Promise.all([
      SH`sleep 1; echo 1`.run(),
      SH`sleep 2; echo 2`.run(),
      sleep(2),
      SH`sleep 3; echo 3`.run()
    ]);
    console.log(res);
  });
  ```

- Retry with exponential backoff:
  ```javascript
  try {
    const p = await retry(3, expBackoff(), () => SH`curl -s https://flipwrsi`.run());
  } catch (e) {
    console.error('Retry failed:', e);
  }
  ```
- Method for copying data to the clipboard:
  ```javascript
  /**
  * Copy text to the clipboard
  * @param {string} text
  * @returns {Promise<string>}
  */
  const copyToClipboard = async (text) => {
  	const prams = [
  		'-selection',
  		'clipboard'
  	]
  	return SH`xclip ${prams}`.options({stdio: 'inherit'}).run(text);
  }
  ```

- Open the 'vim' editor  
  ```javascript
  SH`vim`.options({stdio: 'inherit'}).runSync();
  ```

- Create and run a test
```javascript
import { assert, jsType, Test} from '@j-o-r/sh';

const test = new Test();
test.add('Test is test in sync', () => {
	assert.strictEqual(jsType(test), 'Test');
});
test.add('Test an Array in sync', () => {
	assert.strictEqual(jsType([]), 'Array');
});
test.add('Test is test in async', async () => {
	assert.strictEqual(jsType(test), 'Test');
});
test.add('Test is test in async, returning a promise', async () => {
	return new Promise((resolve, _reject) => {
		assert.strictEqual(jsType(test), 'Test');
		resolve();
	});
});
const report = await test.run();
if (report.errors > 0) {
  process.exit(1);
}
// await test.run([0,3]); // only run test 0 and 3
```

## License

This project is licensed under the Apache License, Version 2.0.
