UNPKG

@j-o-r/sh

Version:

Execute shell commands on Linux-based systems from javascript

169 lines (154 loc) 4.47 kB
import SHExecute from './SHExecute.js'; /** * @typedef {Object} SpawnSyncResponse * @property {number|null} status - Exit code (null if signal). * @property {string|null} signal - Terminating signal. * @property {(string|Buffer|null)[]} output - [stdin, stdout, stderr]. * @property {number} pid - Process ID. * @property {string|Buffer|null} stdout - Captured stdout. * @property {string|Buffer|null} stderr - Captured stderr. */ /** * @typedef {('pipe' | 'ignore' | 'inherit' | number)} StdioOption * @description Stdio config for a stream: * - `'pipe'`: Pipe to parent. * - `'ignore'`: Discard. * - `'inherit'`: From/to parent. * - `number`: FD. */ /** * @typedef {Array<StdioOption> | StdioOption} StdioOptions * @description Stdio array [stdin, stdout, stderr] or single value (applied to all). * @example ['inherit', 'pipe', 'pipe'] // Default: inherit stdin, pipe out/err */ /** * Core options for SH/SHDispatch. * * Defaults (merged from global SH): * - `cwd`: `process.cwd()` * - `env`: `process.env` * - `shell`: `'bash'` (string/true → shell; false → no-shell `/usr/bin/env -S`) * - `stdio`: `['inherit', 'pipe', 'pipe']` * - `timeout`: `0` (no timeout; rolling on data) * - `maxBuffer`: `512000` (500 kb (per stream in SHExecute)) * * Prefix (`.options(undefined, prefix)`) only for shell mode. * * @example { timeout: '5s', stdio: 'inherit', shell: false, maxBuffer: 10 * 1024} */ const SHOptions = { cwd: process.cwd(), env: process.env, shell: 'bash', stdio: ['inherit', 'pipe', 'pipe'], timeout: 0, }; /** * Merges user options into predefined defaults (non-destructive). * * Only copies defined keys. * * @param {typeof SHOptions} predefined - Base options. * @param {Partial<typeof SHOptions>} options - Overrides. * @returns {typeof SHOptions} Merged options. */ const mergeOptions = (predefined, options) => { const mergedObj = { ...predefined }; for (const key in options) { if (options[key] !== undefined) { mergedObj[key] = options[key]; } } return mergedObj; }; /** * High-level command dispatcher. * * Created by {@link SH`cmd`}; chain `.options()` then `.run()`. * * Delegates to {@link SHExecute} for exec/timeout/buffer/kill. * * @example * const dispatch = SH`ls -la`.options({ timeout: '2s' }); * const out = await dispatch.run(); */ class SHDispatch { #prefix = ''; #cmd = ''; #options = SHOptions; /** @type {SHExecute | null} */ #proc = null; /** * @param {string} cmd - Command string. * @param {Partial<typeof SHOptions>} [options] - Initial options. * @param {string} [prefix] - Shell prefix (e.g., 'set -euo pipefail'). * @throws {Error} Invalid/empty cmd. */ constructor(cmd, options = {}, prefix) { if (typeof cmd !== 'string' || cmd === '') { throw new Error('Undefined command'); } this.#cmd = cmd; this.options(options, prefix); } /** * Updates options/prefix; resets to defaults + user overrides (non-cumulative). * * Strings for `stdio` → array fill. * * @param {Partial<typeof SHOptions>} [options] - New options. * @param {string} [prefix] - New prefix. * @returns {SHDispatch} Self for chaining. */ options(options, prefix) { if (typeof prefix === 'string') { this.#prefix = prefix; } if (!options) { return this; } if (options.stdio && typeof options.stdio === 'string') { // convert stdio to array const io = options.stdio; options.stdio = Array(3).fill(io); } this.#options = mergeOptions(SHOptions, options); return this; } /** * Async run: Captures stdout; rejects on error/timeout. * * @param {string} [payload] - Stdin payload. * @returns {Promise<string>} Stdout. */ run(payload) { this.#proc = new SHExecute(this.#cmd, this.#prefix, this.#options); return this.#proc.run(payload); } /** * Sync run: Full Node SpawnSyncReturns. * * Good for TTY takeovers (e.g., vim). * * @param {string} [payload] - Stdin payload. * @returns {import('child_process').SpawnSyncReturns<Buffer>} */ runSync(payload) { return new SHExecute(this.#cmd, this.#prefix, this.#options).runSync(payload); } /** * Kills running process + children. * * @param {number | string} [signal='SIGTERM'] - Signal. * @returns {Promise<number[]>} Killed PIDs. */ async kill(signal = 'SIGTERM') { let res = []; if (this.#proc) { res = await this.#proc.kill(signal); this.#proc = null; } return res; } } export default SHDispatch;