tish
TypeScript icon, indicating that this package has built-in type declarations

0.1.1 • Public • Published

tish

An easy, performant, portable and safe replacement of shell script with TypeScript, aiming to emulate shell in TypeScript instead of calling child_process in fragments.

For those who love TypeScript and are tired of writing shell script.

Examples

import { $ } from 'tish'

try {
    await $('git add .')
} catch (err) {
    console.error(err)
}
import * as stream from 'stream'
import * as util from 'util'
import { dir, stderr } from 'tish'

const pipeline = util.promisify(stream.pipeline)

await dir('~/github.com/shqld/tish', async ($) => {
    console.log(await stdout($('git log --oneline').pipe($('head 5'))))

    try {
        await $('git checkout', { '-b': branch })
    } catch ({ status, command }) {
        console.error(err)
        console.error(await stderr(command))
    }

    await pipeline($("echo console.log('tish')"), fs.createWriteStream('./index.js'))

    await $('git add .')
        .then(() => $('git commit', { '-m': message }))
        .catch(
            (err) =>
                console.error(err) ||
                $('echo Rollbacking...').then(() => $('git reset --hard HEAD'))
        )
})
An advanced example
if (await isFileChanged()) {
    await addAndCommit({
        path: '.',
        message: 'Fix a bug',
        opts: { fixup: 'HEAD~1' },
    })

    console.log(await getCommitLogs(3))
}

async function isFileChanged(): Promise<boolean> {
    const isLocalClean = isSuccessfully($('git diff --exit-code'))
    return !isLocalClean
}

interface Log {
    hash: string
    message: string
}

async function getCommitLogs({ lines = 5 }): Promise<Array<Log>> {
    const rawLogs = await stdout($(`git log --oneline ..HEAD~${lines}`))

    return rawLogs.map((log) => ({
        hash: log.slice(0, 7),
        message: log.slice(8),
    }))
}

async function addAndCommit({
    path,
    message,
    opts,
}: {
    path: string
    message: string
    opts: Partial<{ squash: string; fixup: string }>
}): Command {
    return $('git add', [path]).then(
        $('git commit'),
        args({
            '--message': message,
            '--squash': opts.squash,
            '--fixup': opts.fixup,
        })
    )
}

Why tish

  • Efficient

    Highly optimized with such as Promise, stream, etc. for e.g. piping large stream data.

  • JavaScript-way

    Every command is a pure promise and a What's not. You can await it or catch it, and pipe it for optimized operations.

  • Multi-platform

  • Strongly-typed

Usage

import { $, stdout, stderr, stdouterr, isSuccessful, shell } from 'tish'

// call simply
// -----------
const result = await $('echo hello') // { status = 0, command: Command }

// or
$('echo hello').then((result) => {
    /*...*/
})

// run sequencially
// ----------------
$('git add .')
    .then(() => $('git commit -m "my commit"'))
    .then(() => {
        /*...*/
    })

// run parallel
// ------------
await Promise.allSettled($('git add file_a'), $('git add file_b')).then((results) => {
    /*...*/
})

// read lines async
// ----------------
for await (const log of $('git log --oneline')) {
    console.log(log)
}

// pipe to/from stream
// -------------------
fs.createReadStream('file_a').pipe($('gzip')).pipe(fs.createWriteStream('file_b'))

// pipe to/from command
// --------------------
$('echo hello, world.').pipe($('grep -o world.')).pipe($('xargs echo hello,')) // hello, world.

// get outputs
// -----------
const out = await stdout($('echo hello'))
const err = await stderr($('git non-existent-command'))
const outerr = await stdouterr($('echo hello'))

// error catch
// -----------
try {
    await $('non-existent-command')
} catch ({ status, command }) {
    console.error(status)
    console.error(await stderr(command))
}

// run conditionally
// -----------------
$('git diff --exit-code') // if no diff
    .then(() => console.log('no file changes'))
    .catch(() => $('git commit .'))

// or
if (await isSuccessful($('git diff --exit-code'))) {
    console.log('no file changes')
} else {
    await $('git commit .')
}

// extend shell
// ------------
const { $ } = shell({
    cwd: path.resolve('projects'),
    env: {
        NODE_ENV: 'development',
    },
})

Install

$ npm install -D tish@^0.1.0
$ yarn add -D tish@^0.1.0

What's not

  • NOT a replacement/enhancement of child_process.*

Motivation

Writing shell script is simply hard. Sometimes we’d like to write some operations in JavaScript(TypeScript).

However it's also tough to write a script in JavaScript with child_process of Node.js and since since a single Node.js process takes ~30ms at least for the startup and consumes a lot of memory, it's not suitable for iterations such as inside of xargs or something.

Also, when it comes to write everything in JavaScript then, still there would be a problem: performance. Even using great libraries that wraps child_process such as https://github.com/sindresorhus/execa, still it’s hard to write a performant script for multiple related operations.

We need a library that entirely replace shell script with JavaScript keeping performance.

Readme

Keywords

none

Package Sidebar

Install

npm i tish

Weekly Downloads

4

Version

0.1.1

License

MIT

Unpacked Size

63.8 kB

Total Files

20

Last publish

Collaborators

  • shqld