https://github.com/maartennnn/cli-builder
A cli builder tool to create fast and easy executable CLIs
https://github.com/maartennnn/cli-builder
cli javascript nodejs repl
Last synced: about 1 year ago
JSON representation
A cli builder tool to create fast and easy executable CLIs
- Host: GitHub
- URL: https://github.com/maartennnn/cli-builder
- Owner: maarteNNNN
- License: mit
- Created: 2021-02-13T17:11:25.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2023-02-10T01:58:41.000Z (over 3 years ago)
- Last Synced: 2025-04-11T05:12:38.378Z (about 1 year ago)
- Topics: cli, javascript, nodejs, repl
- Language: JavaScript
- Homepage:
- Size: 541 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# CLI-Builder
[API Docs](API.md)
## Trying out an example
This example can be found in `bin/example-cli.js`
```sh
git clone https://github.com/maarteNNNN/cli-builder.git
npm install
npm link
cli-builder help
npm unlink # to delete the cli-builder bin
```
## Installing
```
npm install cli-builder
```
Be sure to take a look at `bin/example-cli.js`
Instantiate a new CLI with `new REPLClient({ ...options })` `REPL` stands for `Read Eval Print Loop`
defining an `commands` Object as shown below in [below](#commands-example)
```js
cli.run(commands);
```
## Commands example
```js
// DO NOT ADD HELP TO THE ROOT OBJECT. THIS IS DYNAMICALLY MOUNTED
const commands = {
test: {
execute: () => console.log('this is the test run'),
help: 'help of test',
testing: {
execute: ({ argument, options }) =>
console.log('ARGUMENT: ', argument, '\nOPTIONS: ', options),
help: 'testing help',
input: '', // argument and options are passed as an object passed to execute function
// cmd test testing IAMPASSEDTOFUNCTION -f
options: [{ option: 'f', help: 'Follow the logs' }],
},
testing2: {
execute: () => console.log('executing testing2'),
help: 'testing2 help',
},
},
test2: {
execute: () => console.log('this is the test2 run'),
help: 'help of test2',
},
deep: {
nesting: {
works: {
as: {
// Without help
command: () =>
console.log('to run this type `deep nesting works as command`'),
// Or with help
// command: {
// // this get executed as `deep nesting works as command`
// execute: () => console.log('to run this type `deep nesting works as command`'),
// // this get executed as `deep nesting works as command help`
// help: 'help of command'
// }
},
},
},
},
runSomeFunction: async () => {
// DO SOME INSANE LOGIC HERE
},
// A more comprehensive example below titled: Defining help with only one execute function
exampleWithOneExecute: {
knownCommand: {
help: 'Help for this known command',
},
// Show the user there are other commands available
'any-yet-unknown-property': {
help: 'Help for ANY UNKOWN PROPERTY',
},
async execute({ argument, options }) {
console.log(this); // Mounts the REPLClient dynamically
console.log(param); // Passes the last given argument/param dynamically
console.log(options); // Passes the last given options/flags dynamically
},
},
};
```
---
**WARNING**
Help is dynamically mounted to the `commands` object. It generates a function which will mount all child `help` properties.
---
---
**NOTE**
Running is going through the object and should be written as arguments the following way: `deep nesting works as command` for the [example above](#commands-example) (It executes the function or the `execute` child function in case help wants to be added).
`deep nesting works as command --help` (executes `--help or -h` child function in case it's available)
---
---
**WARNING**
functions are added as camelCase but are transformed to kebab-case:
`run-some-function` will call result in `runSomeFunction`.
---
---
**ADVANCED EXAMPLE**
`actions` can be used to integrate imported files. You can check out the example of a full-fledged cli implementation in [ldpos-commander](https://github.com/Leasehold/ldpos-commander/) or [SocketCluster](https://github.com/SocketCluster/socketcluster).
The CLI (`REPLCient`) object is passed to the actions functions by [`Function.prototype.bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
The `bindActionArgs` are passed via [`Function.prototype.bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) as well. Except if an argument is passed in the commands object function Eg.
---
```js
someFunctionCmd: {
// CLI as this will be available but bindActionArgs won't be as argument replaces them
execute: (argument) => cli.actions.someFunction(argument),
}
```
---
## A more apprehensive example
```js
const actions = {
getUserData = async (id, someFunction, aString, aNumber) => {
try {
const data = await axios.get(`user/${id}`)
console.log(this.argv) // logs all passed arguments with - and --
// this references to the cli object as it's bound
this.successLog(data)
someFunction()
console.log(aString, aNumber)
} catch (e) {
throw new Error(e)
}
}
}
// Binding below array to `options.bindActionArgs` in `new REPLClient({ ...options })`
const options = {
bindActionArgs = [123, () => console.log('function executed'), 'a string', 23123]
}
const cli = new REPLClient(options)
const commands = {
// No arguments are passed here, they are mounted dynamically
anActionTest: async () => await cli.actions.getUserData()
}
cli.run(commands)
```
using `cli-builder an-action-test` will execute the `getUserData` function with the `bindActionArgs` parameters bound to it.
## Defining `help` with only one `execute` function
When you want to dynamically output to the console Eg. get a JSON Object from an API with undefined structure but you want to be able to log any of those properties to the console it can be done via:
```js
const commands = {
entrypoint: {
any: {
help: 'Help for any',
},
extra: {
help: 'Help for extra',
},
helping: {
help: 'Help for helping',
},
commands: {
help: 'Help for commands',
},
'any-unknown-property': {
help: 'Help for ANY UNKOWN PROPERTY',
},
async execute(param) {
param = cli.kebabCaseToCamel(param);
// We don't want the paging info in this case
const {
data: { data },
} = axios.get('https://reqres.in/api/users?page=2');
// If null it could be accessible, if undefined we know for sure it isn't
if (!data[param] === undefined)
throw new Error('Custom property not found.');
// The `REPLCient` is object is dynamically mounted
this.successLog(data[param], `${param}:`);
},
},
};
```