README
Solid Node CLI utilities
@solid-js/cli
is a very simple node library to show small running tasks and messages in any terminal.
Dependencies are Chalk for text transforms and mri for argument parsing.
This lib uses Inquirer to manage user inputs.
We use strip-ansi to count chars even when stylised for CLI (removes bold or color markers from string).
This gif is recorded from demo.js.
Installation
To install Nanostache in your Node project :
npm install @solid-js/cli
or
yarn add @solid-js/cli
Import
const { createSpaces, print, offset, ... } = require('@solid-js/cli');
Banner
Show a big old ASCII banner
banner(title, width = 78, margin = 1, padding = 2);
Print some content to the stdout
, with bold and same line options.
Set newLine
to false to disable trailing \n\r
. Useful if you need to append some texts on the same line after.
print(content, bold = false, newLine = true);
Offset
Returns an offset text with leading spaces.
const offsetText = offset(3, 'My offset');
// ' My offset'
New line
Write a new line to stdout
newLine();
Halt
Show a message in red and bold to stderr
. Exit current process. Exit
code can be overridden, default exit code is 1.
halt('This is a pretty big error')
Exec
Run any shell command synchronously.
// Sync version
const result = execSync('ls -la');
Wrap in try catch to get errors.
let result;
try
{
result = execSync('./custom-command.sh');
}
catch ( e )
{
halt( e );
}
print( result );
// Async version
try
{
const stdout = await exec('ls -la');
}
catch ( e )
{
// e === stderr
}
By default, stdout
and stderr
are hidden. To change this behavior,
Set stdLevel to :
- 0 (default) : No
stdout
andstderr
are shown. Use return and try / catch to get them. - 1 : Only
stdout
is shown - 2 : Only
stderr
is shown - 3 : Both
stdout
andstderr
are shown
Know more about options :
- see NodeJS exec options doc)
- see NodeJS stdio doc
execSync('command');
// No stdout and stderr shown, use return and try / catch to get them
const result = execSync('command', 2);
// Only stderr are shown. Stdin is in result
// Setting custom options
execSync('command', 2, {
cwd: 'otherDirectory/'
});
// Options argument can be collapsed onto stdlevel argument.
execSync('command', {
stdio: 'my super specific stdio'
});
Task
// Create and show a new task on CLI
const spriteTask = task('Building sprites');
// ➤ Building sprites ...
// Set to success
spriteTask.success();
// ✔ Building sprites
// Set to success with updated text
spriteTask.success(`Built ${ total } sprites`);
// ✔ Built 12 sprites
Task error
task.error( errorObject, code = 0 );
First argument errorObject
can be :
- null (will print nothing)
- a string
- an object containing stdout and stderr properties
- an error object
Second argument code
will exit if code > 0
. Note : process.exit
can be hooked with hookStandards()
.
// Set to error
spriteTask.error();
// ✘ Building sprites
// Set to error, show message to stderr and exit process
spriteTask.error( myError, 1 );
// ✘ Building sprites
// Error message
Task progress
Show a progress bar next to the current task. Width can be set, default width is 30px.
spriteTask.progress( 3, 12 ) // 3 per 12 is 3 / 12
// ✔ Building ██░░░░░░░░░░░░
spriteTask.progress( 3, 3 ) // 3 per 3 is 1
// ✔ Building ██████████████
spriteTask.progress( 1, 100, 50 ) // 1 per 100
// ✔ Building █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Asynchronous task chaining
await task('My task').run( async t => {
// t is task instance so you can t.success or t.error if needed
await stuff1();
await stuff2();
// If any error occurs, task will stop and show error
// If everything is good, task will show success and promise will be resolved
});
Table
Will show a nice table to the CLI
const data = [
['File', 'Size', 'Is a module'],
['test.js', 534, true],
['other-file.js', 1535, false],
['data.js', 42, false],
['test.js', 534, true]
];
table( data, true ); // first line are labels
// With some display options
table( data, firstLineAreLabels = false, minColumnWidths = [40, 20, 20], lineStart = ' ', lineEnd = '', separator = chalk.grey(' │ ');
// Get column position to align some info bellow table.
// columnPositions has one item more than total number of columns (container table start and table end)
const columnPositions = table( data );
Tests
const { test } = require('@solid-js/cli');
test("My lib's feature", it =>
{
// "It" is a function to declare list of assertions
// Every "it" will add a progress bar to the current task
// Every "it" can be sync or async it does not matter
it('sould return 42', assert =>
{
// Here we call our tested method / lib
const result = myLib.doStuff();
// The assertion will fail if the result is not exactly 42
assert( result, 42 );
});
it('sould throw error', assert =>
{
// We try error throw for example here
// Assertion will fail if our lib does not throw expected error
try
{
myLib.doStuff('invalid parameter');
}
catch (e)
{
assert( e instanceof Error );
return;
}
assert( false );
});
// Every test will stop and process will quit with an error code
// If any assertion have failed.
});
// Will wait previous test to start ...
test("Another test", it =>
{
// ...
})
Commands from argv
const { commands } = require('./cli');
// Use commands.add to register a command for CLI
// $ script dev -> { port: 4000, noCheck: false }
// $ script dev --port 2000 -> { port: 2000, noCheck: false }
// $ script dev --noCheck -> { port: 4000, noCheck: true }
commands.add('dev', { port: 4000, noCheck: false }, (options) =>
{
// Do dev stuff ...
});
// $ script production
commands.add('production', () =>
{
// Do production stuff ...
});
// $ script help
commands.add('help', () =>
{
// Show help to user ...
});
// Will execute command with loose check
// Argument need to start like command to execute it
// $ script big
commands.add('bigCommandName', () => {});
// Start parsing and run matching command
commands.start( command =>
{
// $ script
// command === '' if no command is given
// $ script badcommandname
// command === 'badcommandname' if command has been given but not found
// Show help to lost users
commands.run('help');
});