maarteNNNN / cli-builder

A cli builder tool to create fast and easy executable CLIs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CLI-Builder

API Docs

Trying out an example

This example can be found in bin/example-cli.js

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

cli.run(commands);

Commands example

// 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: '<arg-to-pass-to-execute-function>', // 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 (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 or SocketCluster.

The CLI (REPLCient) object is passed to the actions functions by Function.prototype.bind

The bindActionArgs are passed via Function.prototype.bind as well. Except if an argument is passed in the commands object function Eg.


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

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:

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}:`);
    },
  },
};

About

A cli builder tool to create fast and easy executable CLIs

License:MIT License


Languages

Language:JavaScript 100.0%