Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

kush-cli

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

kush-cli

Protect and beautify your shell scripting

  • 0.0.4
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
5
increased by25%
Maintainers
1
Weekly downloads
 
Created
Source

Shellac

A tool to make invoking a series of shell commands safer & better-looking.

npm GitHub last commit GitHub Workflow Status

Usage

import shellac from 'shellac'

test('morty', async () => await shellac`
  $ echo "End-to-end CLI testing made nice"
  $ node -p "5 * 9"
  stdout >> ${ answer => expect(Number(answer)).toBeGreaterThan(40) }
`)

Syntax

Basic commands

await shellac`
  // To execute a command, use $
  $ my command here  
  
  // If you want the output piped through to process.stdout/err, use $$
  $$ echo "This command will print to terminal"
  
  // Use stdout/err and >> to check the output of the last command
  stdout >> ${ last_cmd_stdout => {
    expect(last_cmd_stdout).toBe("This command will print to terminal")
  }}
`

Returning output

Shellac returns the stdout/err of the last command in a block as { stdout, stderr }

const { stdout, stderr } = await shellac`
  $ echo "This command will run but its output will be lost"
  $ echo "The last command executed returns its stdout/err"
`
expect(stdout).toBe("The last command executed returns its stdout/err")

You can also return named captures from a series of commands:

const { current_sha, current_branch } = await shellac`
  $ git rev-parse --short HEAD
  stdout >> current_sha

  $ git rev-parse --abbrev-ref HEAD
  stdout >> current_branch
`

Branching

You can use if ${ ... } { ... } else { ... } to run conditionally based on the value of an interpolation:

await shellac`
  if ${ process.env.CLEAN_RUN } {
    $ yarn create react-app
  } else {
    $ git reset --hard
    $ git clean -df
  }
  
  $$ npx fab init -y
  // ...
`

Changing directory

You can either use an in directive:

await shellac`
  // Change directory for the duration of the block:
  in ${ __dirname } {
    $ pwd
    stdout >> ${ cwd => expect(cwd).toBe(__dirname) }
  }
  
  // By default we run in process.cwd()
  $ pwd
  stdout >> ${ cwd => expect(cwd).toBe(process.cwd()) }
`

If the whole script needs to run in one place, use shellac.in(dir):

import tmp from 'tmp-promise'
const dir = await tmp.dir()

await shellac.in(dir.path)`
  $ pwd
  stdout >> ${ cwd => expect(cwd).toBe(dir.path) }
`

Async

Use the await declaration to invoke & wait for some JS inline with your script. It works great when Bash doesn't quite do what you need.

import fs from 'fs-extra'

await shellac.in(cwd)`
  await ${ async () => {
    await fs.writeFile(
      path.join(cwd, 'bigfile.dat'),
      huge_data
    )
  }}
  
  $ ls -l
  stdout >> ${(files) => expect(files).toMatch('bigfile.dat')}
`

Interpolated commands

Inside a $ command you can use string interpolation like normal:

await shellac.in(cwd)`
  $ echo "${JSON.stringify({ current_sha, current_branch })}" > git_info.json
`

These can even be promises or async functions:

const getAllPackageNames = async () => { /* ... */ }
await shellac.in(cwd)`
  // You can pass a promise and it will be awaited
  $ yarn link ${ getAllPackageNames() }
  
  // ...
  
  // Or pass an async function and shellac will call and await it
  $ yarn unlink ${ async () => getAllPackageNames() }
`

Persistence between commands

A shellac call invokes a single instance of bash for the duration, so changes you make are reflected later in the script:

await shellac`
  $ echo $LOL
  stdout >> ${lol => expect(lol).toBe('') }
  
  $ LOL=boats
  
  $ echo $LOL
  stdout >> ${lol => expect(lol).toBe('boats') }
`

Note: the current working directory is only configured by shellac.in() or the in ${} { ... } directive:

const cwd = __dirname
const parent_dir = path.resolve(cwd, '..')
await shellac.in(cwd)`
  // Normal behaviour
  $ pwd
  stdout >> ${pwd => expect(pwd).toBe(cwd) }
  
  // Has no effect on the remaining commands
  $ cd ..
  
  $ pwd
  stdout >> ${pwd => expect(pwd).toBe(cwd) }
  
  // If you want to change dir use in {}
  in ${ parent_dir } {
    $ pwd
    stdout >> ${pwd => expect(pwd).toBe(parent_dir) }
  }
  
  // Or do it on a single line
  $ cd .. && pwd
  stdout >> ${pwd => expect(pwd).toBe(parent_dir) }
  
  // Joining commands with ; also works
  $ cd ..; pwd
  stdout >> ${(pwd) => expect(pwd).toBe(parent_dir)}
`

Comments

All these examples are valid, since // single-line-comments are ignored as expected.

Example

Works great with ts-jest:

// ts-jest-example.test.js
import shellac from 'shellac'

describe('my CLI tool', () => {
  it('should do everything I need', async () => {
    await shellac`
      $ echo "Hello, world!"
      stdout >> ${(echo) => {
        expect(echo).toBe('Hello, world!')
      }}
      
      $ rm -rf working-dir
      $ mkdir -p working-dir/example
      $ cp -R fixtures/run-1/* working-dir/example
      
      await ${async () => {
        // generate some more test data
      }}
      
      in ${'working-dir/example'} {
        $ ls -l
        stdout >> ${(files) => {
          expect(files).toMatch('package.json')
        }}
        
        $ yarn
        $$ run-app
      }
    `
  })
})

Using CommonJS, import it like:

const test = require('ava')
const shellac = require('shellac').default

test('plugin should be installable', async (t) => {
  await shellac.default`
    $ echo "Hello, world!"
    stdout >> ${(echo) => {
      t.is(echo, 'Hello, world!')
    }}
  `
})

Snippets

Use double-$ $$ for logging while the test runs:

shellac.in(cwd)`
  $$ ls -al
`

is the same as:

shellac.in(cwd)`
  $ ls -al
  stdout >> ${console.log}
`

Confirm a file is present:

shellac`
  $ ls -l
  stdout >> ${files => expect(files).toMatch('fab.zip')}
`

Acknowledgements

@kitten for reghex which is genuinely incredible and the only reason this library is possible at all.

@superhighfives for coming up with the name!

exactly, bats, Expect, cram, aruba for prior art.

Keywords

FAQs

Package last updated on 20 Feb 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc