Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
CLI Simple, Stupid. Automatic discovery of parameters names. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand. Support to sync / async. Sup
CLI Simple, Stupid. Automatic discovery of parameters names and support to subcommands down to N levels. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand.
Side note: It is worth taking a look at MagiCLI, which is a module capable to create a CLI interface automatically for a module, instead of creating one by hand.
$ npm install cliss
Through this section we'll be going from the most minimal usage of the module, where options names are extracted from functions parameters:
const func = (param1, param2) => `${param1}_${param2}`;
cliss(func);
to a version using all the possible options it provides:
const cliSpec = {
name,
description,
version,
options: [{
name,
description,
required,
type
}],
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {},
before: (args, positionalArgs, argsAfterEndOfOptions) => {},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {}
},
action: () => {},
commands: [{}]
};
const clissOptions = {
command: {
subcommandsDelimiter
},
options: {
validateRequiredParameters
},
version: {
option
},
help: {
option,
stripAnsi
},
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {},
before: (args, positionalArgs, argsAfterEndOfOptions) => {},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {}
}
};
cliss(cliSpec, clissOptions);
cliss(functionReference)
Creating a CLI for a function by doing nothing more than passing it as a parameter to cliss. The options names will be the same as the parameters expected by the function.
'use strict';
const cliss = require('cliss');
const aFunctionWithWeirdParametersDefinition = (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;
return result;Run the program passing with the following options:
};
cliss(aFunctionWithWeirdParametersDefinition);
Calling it via CLI with --help
will give you:
Options:
--param1
--param2
--param3
--args
Passing in the options:
node cli.js --param2=PARAM2 --param1=PARAM1 --param3=PARAM3 --args=a --args=r --args=g --args=s
Or passing options + arguments (arguments for the "...args" parameter in this case):
node cli.js --param2=PARAM2 --param1=PARAM1 --param3=PARAM3 a r g s
Will result in:
param1: PARAM1
param2: PARAM2
param3: PARAM3
args: a,r,g,s
Note that the order of the options doesn't need to match the order of the parameters.
cliss(cliSpec)
Great, but probably one would like to improve a bit the --help
section of the module, by providing to the end user the name (the command's name for calling it via CLI), description and version of the module. In this case a Object Literal will be used instead of just a function reference.
'use strict';
const cliss = require('../');
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;
return result;
}
});
Now, when calling it with --help
, a better help section will be shown:
Description:
Just an example that will do nothing but concat all the parameters.
Usage:
$ some-command [options] [args...]
Options:
--param1
--param2
--param3
--args
cliss(cliSpec)
The options were effortlessly extracted from the parameters names, but cliss provides a way for one to provide more information about each of them. The Object Literal passed in the cliSpec parameter can have a property named options, which expects an Array of objects, containing the name of the option plus some of the following properties:
required To tell if the parameter is required.
description To give hints or explain what the option is about.
type To define how the parser should treat the option (Array, Object, String, Number, etc.). Check yargs-parser for instructions about type, as it is the engine being used to parse the options.
alias To define an alias for the option.
Following the last example, let's improve it to:
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
options: [{
name: 'param1',
description: 'This param is the base value to compute everything else.',
required: true,
type: 'String'
}, {
name: 'args',
required: true
}],
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;
return result;
}
});
Call --help
, and note that the Usage section will also be affected. Now [options] [args...] will be shown as <args...>, because both of them are required.
Description:
Just an example that will do nothing but logging all the parameters.
Usage:
$ some-command <options> <args...>
Options:
--param1 String Required - This param is the base value to compute
everything else.
--param2
--param3
--args Required
Run the program with the following options:
node cli.js --param1=001 --param2=002 --param3=PARAM3 a r g s
And check the result to see how param1 was indeed treated as a string, while param2 was parsed as a number:
param1: 001
param2: 2
param3: PARAM3
args: a,r,g,s
cliss(cliSpec)
A property named pipe can also be defined on cliSpec in order to handle stdin and also, some steps of the execution flow (before and after). To define a single handle for all the commands, the pipe option can be defined on Cliss options as will be shown later on the documentation. The pipeline execution of a command is:
stdin (command.pipe.stdin || clissOptions.pipe.stdin) =>
clissOptions.pipe.before =>
command.pipe.before =>
command.action =>
command.pipe.after =>
clissOptions.pipe.after =>
stdout
Where each of these steps can be handled if needed.
The properties expected by pipe are:
stdin
(stdinValue, args, positionalArgs, argsAfterEndOfOptions)
before
(args, positionalArgs, argsAfterEndOfOptions)
To transform the data being input, before it is passed in to the main command action.
after
(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)
To transform the output (for example, to JSON.stringify an Object Literal)
Note: stdin and before must always return args, and after must always return result, as these values will be passed in for the next function in the pipeline.
To better explain with an example, let's modify the previous one to:
Check the pipe property on the following code:
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
options: [{
name: 'param1',
description: 'This param is needed to compute everything else.',
required: true,
type: 'String'
}, {
name: 'args',
required: true
}],
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {
args.param3 = stdinValue;
return args;
},
before: (args, positionalArgs, argsAfterEndOfOptions) => {
positionalArgs.reverse();
return args;
},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {
return `======\n${result}\n======`;
}
},
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;
return result;
}
});
Calling it as:
echo "fromSTDIN" | node cli.js --param1=001 --param2=002 a r g s
Will result in:
=======
param1: 001
param2: 2
param3: fromSTDIN
args: s,g,r,a
=======
Subcommands can be defined in a very simple way. Thinking naturally, a subcommand should be just a command that comes nested into another one, and it is exactly how it's done.
Here one more property of the cliSpec is introduced: commands. It is an Array that can contains N commands, including the commands property (commands can be nested down to N levels).
As each subcommand is a command itself, they also counts with its own --help
section, and possibly its own --version
(if it is not defined for a subcommand, the one defined for the root will be shown).
The following example will introduce:
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
options: [{
name: 'param1',
description: 'This param is the base value to compute everything else.',
required: true,
type: 'String'
}, {
name: 'args',
required: true
}],
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {
args.param3 = stdinValue;
return args;
},
before: (args, positionalArgs, argsAfterEndOfOptions) => {
positionalArgs.reverse();
return args;
},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {
return `======\n${result}\n======`;
}
},
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;
return result;
},
commands: [{
name: 'subcommand1',
commands: [{
name: 'action1',
options: [{
name: 'param',
required: true
}],
action: param => `subcommand1 action1 param: ${param}`
}, {
name: 'action2',
action: () => 'subcommand1 action2'
}]
}, {
name: 'subcommand2',
action: () => console.log('subcommand2')
}]
});
Call --help
to see that a new section Commands: is presented:
Description:
Just an example that will do nothing but concat all the parameters.
Usage:
$ some-command <options> <args...>
$ some-command [command]
Options:
--param1 String Required - This param is needed to compute
everything else.
--param2
--param3
--args Required
Commands:
subcommand1
subcommand2
Each subcomand has its own help section, check:
node cli.js subcommand1 --help
Usage:
$ some-command subcommand1
node cli.js subcommand2 --help
:
Usage:
$ some-command subcommand2 <command>
Commands:
action1
action2
Just call the commands names separated by space:
node cli.js subcommand2 action1 --param=VALUE
Result:
subcommand2 action1 param: VALUE
cliss(cliSpec, clissOptions)
An Object Literal
with the following options can be passed in as the second parameter to cliss:
command
'-'
is passed in, the subcommands should be called as subcommand1-action1
instead of subcommand1 action1
.options
true
, the required parameters will be checked before the command action is called, and the help section will be shown in case a required parameter is missing.help
option
To define a different option name to show the help section. For example, if 'helpsection'
is passed in, --helpsection
must be used instead of --help
.
stripAnsi
Set to true
to strip all ansi escape codes (colors, underline, etc.) and output just a raw text.
version
'moduleversion'
is passed in, --moduleversion
must be used instead of --version
.pipe As it is defined on cliSpec for each command, pipe can also be defined in clissOptions to implement for all commands a unique way to handle stdin and also, some steps of the execution flow (before and after) in case it is needed. The pipeline execution of a command is: stdin (command.pipe.stdin || clissOptions.pipe.stdin) => => clissOptions.pipe.before => => command.pipe.before => => command.action => => command.pipe.after => => clissOptions.pipe.after => stdout The properties expected by pipe are:
stdin
(stdinValue, args, positionalArgs, argsAfterEndOfOptions)
before
(args, positionalArgs, argsAfterEndOfOptions)
To transform the data being input, before it is passed in to the main command action.
after
(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)
To transform the output (for example, to JSON.stringify an Object Literal)
Note: stdin and before must always return args, and after must always return result, as these values will be passed in for the next function in the pipeline.
FAQs
CLI Simple, Stupid. Automatic discovery of parameters names. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand. Support to sync / async. Sup
The npm package cliss receives a total of 42,445 weekly downloads. As such, cliss popularity was classified as popular.
We found that cliss demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
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.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.