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

@dynamize/termax

v1.0.4

Published

A tool to create cli forms, execute shell cammands and call processes accompanied with spinners.

Downloads

3

Readme

Build Node CLI's in minutes

Termax is a wrapper library around exec, execFile, fork and spawn child processes which allows you to call them sequentially.

Features

  • OS Fallbacks - Credits to sindresorhus
  • Built in spinner - Credits to sindresorhus
  • Built in error handling
  • Themes and custom styling - Credits to chalk

Table of Contents

  1. About Termax
    1. What does termax do?
    2. Spinner limits
    3. About termax wrappers
  2. Documentation
    1. Installation
    2. Dependencies
    3. Importing
    4. tExec
    5. tExecFile
    6. tFork
    7. tSpawn
    8. Chain
    9. Error Handling;
    10. Configuration
  3. Styling
    1. Themes
    2. Custom Styling

About Termax

What does termax do?

If you ended up here, you might be wondering what is termax, and why would I ever use it? Well, simply put termax allows you to use async child processes in a sequential way. Now you might be wondering why would I ever do that, I can just call synchronous contraparts to those child processes and solve the problem? You would be completely right! You can do that, and quit franklly it would be easier to do so. You can exchange exac for exacSync, spawn for spawnSync etc... If the user experience is of no concern to you, that would be a more preferable approach. But if user experience is something you are looking to maximize then you're in a pickle. Spinners and such terminal animations won't work properly (maybe not even work) with a synchronous process.(More on that in spinner limits section) Additionally to that termax comes with built-in error handling, themes, styling and more to speed up your development time, so that you might focus on the meat and potatoes of your project.

Spinner limits

To put it as simple as possible, spinners need continues execution so they can be animated, they will continuously update what's printed on the terminal (till we stop them). Now JavaScript is single-threaded, so if we call exec or fork etc.. As they are non-blocking they spawn a shell then execute the command within that shell, but leave the rest of the code to be executed (this includes the spinners), execSync, forkSync etc... are blocking, which means that those method will not return until the child process has fully closed (effectively stopping the spinner execution till then).

That's why synchronous operations lead to spinner freezing, glitching etc..

Now if we execute asynchronous operations(such as exec, execFile, fork and spawn) in a sequential way, we can still preserve the order of operations, but keep the spinner going continuously for each operation. Which is essentially what termax does.(More on that in about termax wrappers section).

About termax wrappers

Asynchronous operations are awesome, the allow you to minimaze the execution time fo your code significantly, by both allowing multiple operation to run at the same time as well as allowing the execution flow to continue! But they are situations when this is not an ideal approach(All tools have, there prupose), as explaind in spinner limits section. All four teramx wrappers (tExec, tExecFile, tFork, tSpawn) work pretty much the same way, picture below will give a visual explanation:

All four wrappers take two arguments, a mondentary configs argument which is an array of config objects (More on configs in configuration section), and an optional callback argument. Additionally to callbacks, all wrappers have a executeState emitter wich emitts 'start' and 'done'. Example:

tExec(calls).executeState.on('done', () => {
  console.log('done');
});

tExecFile(calls).executeState.on('done', () => {
  console.log('done');
});

tFork(calls).executeState.on('done', () => {
  console.log('done');
});

tSpawn(calls).executeState.on('done', () => {
  console.log('done');
});

Wrapper limits and best practise

As termax wrappers are in fact asynchronous it would be most beneficial to continue the execution flow ether on executeState 'done' or in a callback. Calling any block of code after the wrapper will result in that code being executed parallel the wrapper finishes its own execution. Example case:

tExec(calls).executeState.on('done', () => {
  console.log('done');
});

function demo() {
  for(let i = 0; i < 3; i++) {
    console.log('Code after');
  }
}

demo();

Output from the code above:

Executing to wrappers back to back will also lead to overlap, because they are both asynchronous, the picture below will give more context.

Let's see it in example:

tExec(calls).executeState.on('done', () => {
  console.log('Sleep done');
});

tExec(calls2).executeState.on('done', () => {
  console.log('Ping done');
});

Output from the code above:

In case you would like to call multiple wrappers you best use a chain(more on chain in chain section). This will allow you to bypass this issue, plus chain will also provide you with callback functionality so you might be able to continue your execution flow properly.

Documentation

Installation

npm install --save @dynamize/termax

Dependencies

| Dependency | README | | ----------- | ---------------------------------------------------------- | | chalk | [https://github.com/chalk/chalk#readme][PlDb] | | inquirer | [https://github.com/SBoudrias/Inquirer.js#readme][PlGh] | | log-symbols | [https://github.com/sindresorhus/log-symbols#readme][PlGd] | | ora | [https://github.com/sindresorhus/ora#readme][PlOd] |

Importing

import {tExec, tExecFile, tFork, tSpawn} from '@dynamize/termax';

Teramx only supports ECMAScript imports, and has no support for CommonJS.

Which means you need to setup your project with the type of a module in package.json:

tExec

tExec is a wrapper around exec from child_process with the nuance that it can execute a sequence of exec calls. This is useful when one wants to execute multiple exec call in an order and have a spinner working on each call without pauses. Let's see a simple usage case:

import {tExec} from '@dynamize/termax';

tExec([{cmd: 'sleep 3'}]);

This will execute a simple sleep command for 3 seconds, the style will fall back to default as we didn't specify it (more on styling in styling section). tExec takes two arguments, a configs(array of config objects) argument and an optional callback. The only must is a cmd property in the config argument. (More on config in config section).

Let's take a look at a callback example:

import {tExec} from '@dynamize/termax';

const calls = [
  {
    cmd: 'sleep 3'
  },
  {
    cmd: 'sleep 3',
    spinner: {
      style: 'cold'
    }
  },
  {
    cmd: 'sleep 3',
    spinner: {
      style: 'sunrise'
    }
  }
];

tExec(calls, () => {
  console.log('All done!');
});

Output from the code above:

tExecFile

tExecFile is a wrapper around execFile from child_process, it takes two arguments: configs(array of config objects) and the optional callback function. What is always required is the cmd and args properties in configs argument, where cmd is an environment in which to execute the file (node, python etc..), and args will take the file name. Let's see an example:

import {tExecFile} from '@dynamize/termax';

const calls = [
  {
    cmd: 'node',
    args: ['./example.js']
  },
  {
    cmd: 'node',
    args: ['./example.js'],
    spinner: {
      style: 'cold'
    }
  },
  {
    cmd: 'node',
    args: ['./example.js'],
    spinner: {
      style: 'sunrise'
    }
  }
];

tExecFile(calls, () => {
  console.log('All Done');
});

example.js

import {exec} from 'child_process';

exec('ping 8.8.8.8 -c 4');

Output from the code above:

tFork

tFork is a wrapper around fork from child_process, it also takes two arguments configs(array of config objects) and an optional callback function, with the only required property in configs being cmd, which will take the name of the file to be executed. Example:

import {tFork} from '@dynamize/termax';

const calls = [
  {
    cmd: './test.js'
  },
  {
    cmd: './test.js',
    spinner: {
      style: 'cold'
    }
  },
  {
    cmd: './test.js',
    spinner: {
      style: 'sunrise'
    }
  }
];

tFork(calls, () => {
  console.log('All Done');
});

example.js

import {exec} from 'child_process';

exec('ping 8.8.8.8 -c 4');

Output from the code above:

tSpawn

tSpawn is a wrapper around spawn from child_process like all other termax wrappers. It also takes two arguments, configs(array of config objects) and an optional callback function, and it always requires the cmd and args properties.Example:

import {tSpawn} from '@dynamize/termax';

const calls = [
  {
    cmd: 'sleep',
    args: ['3']
  },
  {
    cmd: 'slep',
    args: ['3'],
    spinner: {
      style: 'sunrise'
    }
  },
  {
    cmd: 'sleep',
    args: ['3'],
    spinner: {
      style: 'vivid'
    }
  }
];

tSpawn(calls, () => {
  console.log('All Done');
});

Output from the code above:

Chain

Chain API

| Name | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | addToChain | Method which adds an execution sequence to chain takes three arguments: child process name (type string), configs(config array) and an optional callback function | | isExecuting | A getter which returns a boolean | | executeChain | Method which executes a chain. | | callback | A setter whict sets a callback to be executed at the end of the chain |

Chain Use Case

Chain is a class allowing you to chain together multiple different execution sequences. Let's say you want to execute three commands in a sequence and you choose tSpawn to do so, but then you would like to execute a file(maybe generated by one of the commands), now if you try to call tSpawn and right under it tFork, both these will start to execute almost simultaneously. The reason being that each execution in both tSpawn and tFork are async and as such, they will execute side by side there for both execution sequence of tSpawn and tFork will execute side by side.Chain fixes this by allowing to chain together these execution sequences. Let's see this in an example:

import {Chain} from '@dynamize/termax';

const calls1 = [
  {
    cmd: 'sleep',
    args: ['3']
  },
  {
    cmd: 'sleep',
    args: ['3'],
    spinner: {
      style: 'sunrise'
    }
  },
  {
    cmd: 'sleep',
    args: ['3'],
    spinner: {
      style: 'vivid'
    }
  }
];

const calls2 = [
  {
    cmd: './test.js'
  },
  {
    cmd: './test.js',
    spinner: {
      style: 'cold'
    }
  },
  {
    cmd: './test.js',
    spinner: {
      style: 'sunrise'
    }
  }
];

const chain = new Chain();
chain.addToChain('spawn', calls1);
chain.addToChain('fork', calls2);
chain.executeChain();

Output from the code above:

Setting callbacks on a chain

There a multiple ways to set a callback on a chain. We can use a setter:

import {Chain} from '@dynamize/termax';
// calls1 and calls2 declared here...
const chain = new Chain();
chain.addToChain('spawn', calls1);
chain.addToChain('fork', calls2);
chain.callback = () => {
  console.log('All Done');
};
chain.executeChain();

Or we can give it as an argument in addToChain:

import {Chain} from '@dynamize/termax';
// calls1 and calls2 declared here...
const chain = new Chain();
chain.addToChain('spawn', calls1);
chain.addToChain('fork', calls2, () => {
  console.log('All Done');
});
chain.executeChain();

Output from the code above:

What we need to keep in mind is that only one callback can be set on a chain.That will be the last callback given:

import { Chain } from '@dynamize/termax';
// calls1 and calls2 declared here...
const chain = new Chain();
chain.addToChain('spawn', calls1, () => {
  console.log('Done #1')
})
chain.addToChain('fork', calls2, () => {
  console.log('Done #2')
});
chain.callback = () => {
  console.log('Done #3');
}
chain.executeChain();
// At the end we only get Done #3 printed out

Output from the code above:

Chain saftey integration

Once executeChain is called a chain, it can't be called again. You need to set up your chain ahead of executing it, any later alterations to it will be ignored:

import {Chain} from '@dynamize/termax';
// calls1, calls2 and calls3 declared here...
const chain = new Chain();
chain.addToChain('spawn', calls1);
chain.addToChain('fork', calls2);
chain.executeChain();
chain.addToChain('exec', calls3);
chain.executeChain();
// Only spawn and fork execution sequence are executed

Error Handling

All termax wrappers come with built-in error handling. By default, it is turned off and you will simply be prompted that there was an error, but the execution sequence will continue:

import {tExec} from '@dynamize/termax';

const calls = [
  {
    cmd: 'sleep 3'
  },
  {
    cmd: 'slep 3',
    sinner: {
      style: 'cold'
    }
  },
  {
    cmd: 'sleep 3',
    spinner: {
      style: 'sunrise'
    }
  }
];

tExec(calls, () => {
  console.log('All done!');
});

Output from the code above:

But we can change this by adding a handleErrors: true to our config:

import { tExec } from '@dynamize/termax';

const calls = [
  {
    cmd: "sleep 3"
  },
  {
    cmd: "slep 3",
    handleErrors: true,
    spinner: {
      style: "cold"
    },
  },
    {
    cmd: "sleep 3",
    spinner: {
      style: "sunrise",
    },
  },
];

tExec(calls, () => {
  console.log('All done!')
});

Output from the code above:

Continue

Abort

This will stop the execution sequence and prompt the user to choose between: Continue, See Error or Abort, giving users more control.

Configuration

Configurations that termax wrappers take can be as simple or as complicated as you desire and can get quite a bit more expensive. Here is how a full config would look:

import {tExec} from '@dynamize/termax';

const call = [
  {
    cmd: 'sleep 3',
    args: [],
    handleErrors: true,
    spinner: {
      spinner: 'arrow3',
      style: 'system',
      spawnText: {
        prefix: 'Sleep Starts',
        text: 'Sleeping for 3 sec'
      },
      succeedText: {
        prefix: 'Sleep ended',
        text: 'Woken up'
      },
      errorText: {
        prefix: 'Something disturbed me',
        text: "I guess I'm aweak now"
      },
      color: 'red',
      indent: 4
    }
  }
];

tExec(call);

Output from the code above:

cmd

Type: string

In tExec, tExecFile and tSpawn it takes the command to be executed, and in tFork it takes a filename to be executed.

args

Type: string[]

In tExecFile it takes a name of a file to be executes in tSpawn it takes arguments passed to the command.

handleErrors

Type: boolean

Default: 'false'

If set to true, it implements error handling (default false).

spinner

Type: object

Takes configuration settings for the spinner.

spinner.spinner

Type: string

Default: 'dots'

Values: provided spinners

Sets the type of spinner.

spinner.style

Type: string

Default: 'default'

Values: 'default' | 'custom' | 'none' | 'pale' | 'vivid' | 'system' | 'modesty' | 'sunrise' | 'cold'

Sets one of the prebuilt styles

spinner.spawnText

Type: object

Sets prefix and text at spawn time.

spinner.succeedText

Type: object

Sets prefix and text to be shown once the child process ends.

spinner.errorText

Type: object

Sets prefix and text to be shown in case of an error.

spinner.color

Type: string

Default: 'green'

Values: 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray'

Sets the color of the spinner.

spinner.indent

Type: number

Default: '0'

Sets the indent of the spinner.

Styling

Themes

You can use a number of built-in themes to speed up your work simply by adding a style property in spinner section of the execution config:

import {tExec} from '@dynamize/termax';

const call = [
  {
    cmd: 'sleep 3',
    spinner: {
      style: 'vivid'
    }
  }
];

tExec(call);

Available themes are:

default

none

pale

vivid

system

modesty

sunrise

cold

custom

Custom is the odd one out.

Custom Styling

If, on the other hand, you would like to have full control of the styling, you could do that by choosing a custom style and passing a style config:

import {tExec} from '@dynamize/termax';

const call = 
{
  cmd: 'sleep 3',
  spinner: {
    style: 'custom',
    styleConfig: {
      errorColor: '#cceb34',
      warrningColor: '#eb3483',
      spawnColor: '#4287f5',
      succeedColor: '#34eb67',
      pausedcolor: '#4287f5',
      messageColor: '#4287f5',
      textColor: '#7134eb'
    }
  }
}

const calls = [];
for(let i = 0; i < 3; i++) {
  calls.push(call);
}

tExec(calls);

Output from the code above:

Keep in mind that the style config takes hash colors only.

Related

License

MIT