npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shellsync

v0.2.2

Published

Synchronous shell scripting for Node.js.

Downloads

999

Readme

shellsync

Synchronous shell scripting for Node.js.

Overview

Usage

Use sh to synchronously run shell commands:

const sh = require("shellsync");
const filename = "file name with spaces.txt";
sh`cd /tmp`;
sh`cat ${filename}`; // read filename\ with\ spaces.txt

Note how the above uses ES6 tagged template literals, calling the sh function without parentheses. This makes the invocations slightly shorter and allows shellsync to safely escape any values passed to it.

Use sh, sh.array, or sh.json to capture values:

let v1 = sh`echo hello`;                 // set v1 to "hello"
let v2 = sh.array`lsof -t -i :8080`;     // set v2 to all process ids listening on port 8080
let v3 = sh.json`echo '{"foo": "bar"}'`; // set v3 to {"foo": "bar"}

Use sh.test to determine command success (by default, failure throws):

if (!sh.test`which node`) {
    throw new Error("Node is not on the path!");
}

The commands above only output what is written to stderr. Use sh.out to also print stdout, or use shh completely mute stdout and stderr:

const {shh} = require("shellsync");
shh`git init`;             // git init (no output printed)
sh.out`echo "SHOUTING!"`;  // print "SHOUTING!" to stdout

Safe Variable Escaping

"The vast majority of [shell scripting] pitfalls are in some way related to unquoted expansions" – Bash Pitfalls wiki

shellsync safely quotes variables automatically:

let filename = "filename with spaces.txt";
sh`echo "hello" > cat ${filename}`; // write to filename\ with\ spaces.txt

Use unquoted() to disable automatic quoting:

import {unquoted} from "shellsync";
let command2 = "sudo apt-get install foo";
sh`ls; ${unquoted(command2)}`; // ls; sudo apt-get install foo

If you write your scripts using TypeScript with strictNullChecks, undefined variables in shellsync invocations are reported as an error.

Writing Tests

"I find that writing unit tests actually increases my programming speed." – Martin Fowler

Test your shellsync scripts using mocking and standard testing frameworks such as Mocha or Jest.

Shell scripts often have many side effects, so it's a good habit to mock out commands that touch the file system, interact with processes, and so on.

Use sh.mock(pattern, command) to mock shell command using glob patterns. For example, use the pattern git log to mock any calls to git log, or use git * to mock all calls to git accross the board. If you have multiple mocks, the longest (most specific) matching pattern wins:

// Script under test
function script() {
    return sh`git status`;
}

// Mocha tests
it("mocks git status", () => {
    let mock = sh.mock("git status", `echo mock for git status`); // instead of 'git status', run 'echo ...'
    assert.equal(script(), "mock for git status");
    assert(mock.called);
});

it("mocks arbitrary git command", () => {
    let mock = sh.mock("git *", `echo git command called: $1`);
    assert.equal(script(), "git command called: status");
    assert(mock.called);
});

It's a good habit to mock out all shell commands that have side effects. Use sh.mockAllCommmands() to ensure a mock exists all shell commands. You can then selectively add mocks or use sh.unmock(pattern) to unmock command:

// Script under test
function script() {
    return sh`git status`;
}

// Before each Mocha test, mock the world 
beforeEach(() => sh.mockAllCommands());

// Mocha tests
it("fails when no mocks are defined", () => {
    program(); // FAILS: no mock was defined for "git status"
});

it("runs with git status mocked", () => {
    sh.mock("git status");
    program(); // passes, returns ""
});

it("runs with all git commands mocked", () => {
    sh.unmock("git *");
    program(); // passes, returns response of git status
});

Finally, sh.unmockAllCommands() restores all mocked commands to the original shell command.

// After each Mocha test, restore all mocked commands
afterEach(() => sh.unmockAllCommands());

Under the hood, shellsync implements mocking by defining shell functions for mocked commands (e.g., git() { ... }) and using a DEBUG trap to intercept unmocked commands for sh.mockAllCommands().

Debugging

Use sh.options.debug to trace all commands executed by your scripts or your mocks:

sh.options.debug = true;
sh.mock("ls *", "echo ls was mocked");
sh`cd /`;
sh`ls -l`;

// Prints:
// + cd /
// + ls -l
// + : mock for ls :
// + echo ls was mocked
// ls was mocked

Uninterruptable Sections

"Please do not interrupt me while I'm ignoring you" – unknown author

Users can press Control-C in CLI programs, which means they can end scripts halfway any statement. That means they can leave a system in an undefined state. In Node.js, Control-C even ends a program ignoring any finally clauses that might be used for cleanup.

Use sh.handleSignals() for sections of code where these signals should be temporarily ignored:

sh.handleSignals(); // begin critical section

sh`command1`;
sh`command2`;
sh`command3`;
sh`command4`;

sh.handleSignalsEnd(); // end critical section

Note that sh.handleSignals() affects both shell and Node.js code. If you're concerned your program won't end until the heat death of the universe and need to offer Control-C as an early way out, you can also pass a timeout in milliseconds: sh.handleSignals({timeout: 3000}).

Examples

See /examples.

API

sh`command`: void

Execute a command, return stdout.

sh.test`command`: boolean

Execute a command, return true in case of success.

sh.array`command`: string[]

Execute a command, return stdout split by null characters (if found) or by newline characters. Use sh.options.fieldSeperator to pick a custom delimiter character.

sh.json`command`: any

Execute a command, parse the result as JSON.

sh.handleSignals({timeout = null}): void

Disable processing of SIGINT/TERM/QUIT signals. Optionally accepts a timeout in milliseconds, or null for no timeout.

When invoked, any signals pending since the last invocation get processed.

sh.handleSignalsEnd(): void

Re-enable processing of SIGINT/TERM/QUIT signals.

When invoked, any signals pending since the last invocation get processed.

sh.echooutput

Print output to stdout.

sh.mock(pattern, [command]): Mock

Define a mock: instead of pattern, run command. Patterns consist of one or more words and support globbing from the second word, e.g. git, git status, git s*. The longest (most specific) pattern is used in case multiple mocks are defined.

sh.mockAllCommands(): void

Force mocks for all shell commands, throwing an error when an unmocked command is used. Does not handle commands in subshells or shell functions.

sh.unmock(pattern: string): void

Remove a specific mock by pattern. Best used with mockAllCommands().

sh.unmockAllCommands(): void

Remove all mocks. When pattern is specified, remove a single mock.

sh.quote`command`: string

Similar to sh, but return the command that would be executed.

sh.unquoted(...args): UnquotedPart

Create an unquoted part of a command template.

sh.options: SpawnSyncOptions

See the options for child_process.

sh.options.debug: boolean

Run in debug mode, printing commands that are executed.

sh.options.fieldSeperator: string

The delimiter used for sh.array.

sh.options.preferLocal: boolean

Whether to prefer executables installed in node_modules (using npm-run-path). Default true.

sh(options): Shell

Return a shell with specific options assigned. See sh.options. Example use:

const input = "some text to write to a file";
sh({input})`cat > file.txt`;

shh`command`: string

Same as sh; doesn't print anything to stdout or stderr.

Mock

A mock object.

Mock.called: number

Indicates how often this mock was called.

License

MIT.

See Also