0.0.2 β€’ Published 12 months ago

zyro v0.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

cover npm version npm downloads bundle JSDocs License

πŸš€ Unleash the power of command-line with our intuitive CLI development tool - your gateway to automation! πŸ› οΈπŸŽ‰

Features

  • Minimal API surface
  • Powerful flag (luminar) parsing
  • Strongly typed parameters and flags (luminars)
  • Command support
  • Help documentation generation (customizable too!)

➑️ Try it out online

Support this project by starring and sharing it. Follow me to see what other cool projects I'm working on.

πŸ“₯ Install:

# nyxi
nyxi zyro

# pnpm
pnpm add zyro

# npm
npm i zyro

# yarn
yarn add zyro

ℹ️ About

zyro makes it very easy to develop command-line scripts in Node.js. It handles argv parsing to give you strongly typed parameters + flags (luminars) and generates --help documentation based on the provided information.

Here's an example script that simply logs: Good morning/evening <name>!:

greet.js:

import { cli } from 'zyro'

// Parse argv
const argv = cli({
   name: 'greet.js',

   // Define parameters
   parameters: [
      '<first name>', // First name is required
      '[last name]' // Last name is optional
   ],

   // Define luminars/options
   luminars: {

      // Parses `--time` as a string
      time: {
         type: String,
         description: 'Time of day to greet (morning or evening)',
         default: 'morning'
      }
   }
})

const name = [argv._.firstName, argv._.lastName].filter(Boolean).join(' ')

if (argv.luminars.time === 'morning')
   console.log(`Good morning ${name}!`)

else
   console.log(`Good evening ${name}!`)

πŸ›  In development, type hints are provided on parsed flags (luminars) and parameters:

πŸ“– Generated help documentation can be viewed with the --help flag (luminar):

$ node greet.js --help

greet.js

Usage:
  greet.js [luminars...] <first name> [last name]

Luminars:
  -h, --help                 Show help
      --time <string>        Time of day to greet (morning or evening) (default: "morning")

πŸƒ Run the script to see it in action:

$ node greet.js John Doe --time evening

Good evening John Doe!

πŸ”₯ Examples

Want to dive right into some code? Check out some of these examples:

  • ✨ greet.js: Working example from above
  • πŸ“¦ npm install: Reimplementation of npm install's CLI
  • πŸš€ tsc: Reimplementation of TypeScript tsc's CLI
  • 🐦 snap-tweet: Reimplementation of snap-tweet's CLI
  • πŸ“ pkg-size: Reimplementation of pkg-size's CLI

πŸ“š Usage

πŸ› οΈ Arguments

Arguments are values passed into the script that are not associated with any luminars/options.

For example, in the following command, the first argument is file-a.txt and the second is file-b.txt:

$ my-script file-a.txt file-b.txt

Arguments can be accessed from the _ array-property of the returned object.

Example:

const argv = cli({ /* ... */ })

// $ my-script file-a.txt file-b.txt

argv._ // => ["file-a.txt", "file-b.txt"] (string[])

🧩 Parameters

Parameters (aka positional arguments) are the names that map against argument values. Think of parameters as variable names and arguments as values associated with the variables.

Parameters can be defined in the parameters array-property to make specific arguments accessible by name. This is useful for writing more readable code, enforcing validation, and generating help documentation.

Parameters are defined in the following formats:

  • 🌟 Required parameters are indicated by angle brackets (e.g., <parameter name>).
  • 🌈 Optional parameters are indicated by square brackets (e.g., [parameter name]).
  • πŸš€ Spread parameters are indicated by the ... suffix (e.g., <parameter name...> or [parameter name...]).

Note, required parameters cannot come after optional parameters, and spread parameters must be last.

Parameters can be accessed in camelCase on the _ property of the returned object.

🌟 Example:

const argv = cli({
   parameters: [
      '<required parameter>',
      '[optional parameter]',
      '[optional spread...]'
   ]
})

// $ my-script a b c d

argv._.requiredParameter // => "a" (string)
argv._.optionalParameter // => "b" (string | undefined)
argv._.optionalSpread // => ["c", "d"] (string[])

🏁 End-of-flags (luminars)

End-of-flags (luminars) (--) (aka end-of-options) allows users to pass in a subset of arguments. This is useful for passing in arguments that should be parsed separately from the rest of the arguments or passing in arguments that look like flags (luminars).

An example of this is npm run:

$ npm run <script> -- <script arguments>

The -- indicates that all arguments afterwards should be passed into the script rather than npm.

All end-of-luminar arguments will be accessible from argv._['--'].

Additionally, you can specify -- in the parameters array to parse end-of-luminars arguments.

Example:

const argv = cli({
   name: 'npm-run',
   parameters: [
      '<script>',
      '--',
      '[arguments...]'
   ]
})

// $ npm-run echo -- hello world

argv._.script // => "echo" (string)
argv._.arguments // => ["hello", "world] (string[])

🚩 Flags (Luminars)

Flags (Luminars) (aka Options) are key-value pairs passed into the script in the format --luminar-name <value>.

For example, in the following command, --file-a has value data.json and --file-b has value file.txt:

$ my-script --file-a data.json --file-b=file.txt

πŸ” Parsing features

Zyro's flag (luminar) parsing is powered by luminar and comes with many features:

  • πŸ”’ Array & Custom types
  • 🚩 Flag (Luminar) delimiters: --luminar value, --luminar=value, --luminar:value, and --luminar.value
  • πŸ”„ Combined aliases: -abcd 2 β†’ -a -b -c -d 2
  • ⏹️ End of flags (luminars): Pass in -- to end flag (luminar) parsing
  • ❓ Unknown flags (luminars): Unexpected flags (luminars) stored in unknownLuminars

Read the luminar docs to learn more.

🚩 Defining flags (luminars)

Flags (Luminars) can be specified in the luminar object-property, where the key is the luminar name, and the value is a flag (luminar) type function or an object that describes the luminar.

The flag (luminar) name is recommended to be in camelCase as it will be interpreted to parse kebab-case equivalents.

The flag (luminar) type function can be any function that accepts a string and returns the parsed value. Default JavaScript constructors should cover most use-cases: String, Number, Boolean, etc.

The flag (luminar) description object can be used to store additional information about the flag (luminar), such as alias, default, and description. To accept multiple values for a flag (luminar), wrap the type function in an array.

All of the provided information will be used to generate better help documentation.

🌟 Example:

const argv = cli({
   luminars: {
      someBoolean: Boolean,

      someString: {
         type: String,
         description: 'Some string flag (luminar)',
         default: 'n/a'
      },

      someNumber: {
         // Wrap the type function in an array to allow multiple values
         type: [Number],
         alias: 'n',
         description: 'Array of numbers. (eg. -n 1 -n 2 -n 3)'
      }
   }
})

// $ my-script --some-boolean --some-string hello --some-number 1 -n 2

argv.luminars.someBoolean // => true (boolean | undefined)
argv.luminars.someString // => "hello" (string)
argv.luminars.someNumber // => [1, 2] (number[])

🚩 Custom flag (luminar) types & validation

Custom flag (luminar) types can be created to validate flags (luminars) and narrow types. Simply create a new function that accepts a string and returns the parsed value.

Here's an example with a custom Size type that narrows the flag (luminar) type to "small" | "medium" | "large":

const possibleSizes = ['small', 'medium', 'large'] as const

type Sizes = typeof possibleSizes[number] // => "small" | "medium" | "large"

// Custom type function
function Size(size: Sizes) {
   if (!possibleSizes.includes(size))
      throw new Error(`Invalid size: "${size}"`)

   return size
}

const argv = cli({
   luminars: {
      size: {
         type: Size,
         description: 'Size of the pizza (small, medium, large)'
      }
   }
})

// $ my-script --size large

argv.luminars.size // => "large" ("small" | "medium" | "large")

🏁 Default flags (luminars)

By default, Zyro will try to handle the --help, -h and --version flags (luminars).

ℹ️ Help flag (luminar)

Handling --help, -h is enabled by default.

To disable it, set help to false. The help documentation can still be manually displayed by calling .showHelp(helpOptions) on the returned object.

πŸ“‹ Version flag (luminar)

To enable handling --version, specify the version property.

cli({
   version: '1.2.3'
})
$ my-script --version
1.2.3

The version is also shown in the help documentation. To opt out of handling --version while still showing the version in --help, pass the version into help.version.

πŸ’‘ Commands

Commands allow organizing multiple "scripts" into a single script. An example of this is the npm install command, which is essentially an "install" script inside the "npm" script, adjacent to other commands like npm run.

πŸ’‘ Defining commands

A command can be created by importing the command function and initializing it with a name. The rest of the options are the same as the cli function.

Pass the created command into cli option's commands array-property to register it:

npm.js

import { cli, command } from 'zyro'

const argv = cli({
   name: 'npm',

   version: '1.2.3',

   commands: [
      command({
         // Command name
         name: 'install',

         parameters: ['<package name>'],

         luminars: {
            noSave: Boolean,
            saveDev: Boolean
         }
      })
   ]
})

// $ npm install lodash

argv.command // => "install" (string)
argv._.packageName // => "lodash" (string)

Depending on the command given, the resulting type can be narrowed:

πŸ”§ Command callback

When a CLI app has many commands, it's recommended to organize each command in its own file. With this structure, parsed output handling for each command is better placed where they are respectively defined rather than the single cli output point. This can be done by passing a callback function into the command function (callbacks are supported in the cli function too).

πŸ’‘ Example:

install-command.js (install command using callback)

import { command } from 'zyro'

export const installCommand = command({
   // Command name
   name: 'install',

   parameters: ['<package name>'],

   luminars: {
      noSave: Boolean,
      saveDev: Boolean
   }
}, (argv) => {
   // $ npm install lodash

   argv._.packageName // => "lodash" (string)
})

npm.js (CLI entry file)

import { installCommand } from './install-command.js'

cli({
   name: 'npm',

   commands: [
      installCommand
   ]
})

πŸ“š Help documentation

Zyro uses all information provided to generate rich help documentation. The more information you give, the better the docs!

🎨 Help customization

The help document can be customized by passing a render(nodes, renderers) => string function to help.render.

The nodes parameter contains an array of nodes that will be used to render the document. The renderers parameter is an object of functions used to render the document. Each node has properties type and data, where type corresponds to a property in renderers and data is passed into the render function.

Default renderers can be found in /src/render-help/renderers.ts.

Here's an example that adds an extra sentence at the end and also updates the flags (luminars) table to use the = operator (--luminar <value> β†’ --luminar=<value>):

cli({
   // ...,

   help: {
      render(nodes, renderers) {
         /* Modify nodes... */

         // Add some text at end of document
         nodes.push('\nCheckout Zyro: https://github.com/nyxblabs/zyro')

         /* Extend renderers... */

         // Make all iluminar examples use `=` as the separator
         renderers.iluminarOperator = () => '='

         /* Render nodes and return help */
         return renderers.render(nodes)
      }
   }
})

πŸ“Š Responsive tables

Zyro's "Luminars" table in the help document is responsive and wraps cell text content based on the column & terminal width. It also has breakpoints to display more vertically-optimized tables for narrower viewports.

This feature is powered by tabletron and can be configured via the renderers.table renderer.

πŸ“š API

πŸš€ cli(options, callback?, argvs?)

Return type:

interface ParsedArgv {
   // Parsed arguments
   _: string[] & Parameters

   // Parsed luminars
   luminars: {
      [luminarName: string]: InferredType
   }

   // Unexpected luminars
   unknownLuminars: {
      [luminarName: string]: (string | boolean)[]
   }

   // Method to print version
   showVersion: () => void

   // Method to print help
   showHelp: (options: HelpOptions) => void
}

Function to parse argvs by declaring parameters and flags (luminars).

πŸ› οΈ options

Options object to configure cli.

πŸ“ name

Type: string

Name of the script used in the help documentation.

πŸ“ version

Type: string

Version of the script used in the help documentation.

Passing this in enables auto-handling --version. To provide a version for the documentation without auto-handling --version, pass it into help.version.

πŸ“ parameters

Type: string[]

Parameter names to map onto arguments. Also used for validation and help documentation.

Parameters must be defined in the following formats: | Format | Description | | - | - | | <parameter name> | Required parameter | | [parameter name] | Optional parameter | | <parameter name...> | Required spread parameter (1 or more) | | [parameter name...] | Optional spread parameter (0 or more) |

Required parameters must be defined before optional parameters, and spread parameters must be defined at the end.

🚩 flags (luminars)

Type: An object that maps the flag (luminar) name (in camelCase) to a flag (luminar) type function or an object describing the flag (luminar):

PropertyTypeDescription
typeFunctionLuminar value parsing function.
aliasstringSingle character alias for the luminar.
defaultanyDefault value for the luminar.
descriptionstringDescription of the luminar shown in --help.
placeholderstringPlaceholder for the luminar value shown in --help.
ℹ️ help

Type: false or an object with the following properties.

PropertyTypeDescription
versionstringVersion shown in --help.
descriptionstringDescription shown in --help.
usagestring \| string[]Usage code examples shown in --help.
examplesstring \| string[]Example code snippets shown in --help.
render(nodes, renderers) => stringFunction to customize the help document.

Handling --help, -h is enabled by default. To disable it, pass in false.

πŸ“‹ commands

Type: Command[]

Array of commands to register.

ignoreArgv

Type:

type IgnoreArgvCallback = (
   type: 'known-luminar' | 'unknown-luminar' | 'argument',
   luminarOrArgv: string,
   value: string | undefined,
) => boolean | void

A callback to ignore argv tokens from being parsed.

βš™οΈ callback(parsed)

Type:

Optional callback function that is called when the script is invoked without a command.

πŸ“‹ argvs

Type: string[]

Default: process.argv.slice(2)

The raw parameters array to parse.

βš™οΈ command(options, callback?)

βš™οΈ options

PropertyTypeDescription
namestringRequired name used to invoke the command.
aliasstring \| string[]Aliases used to invoke the command.
parametersstring[]Parameters for the command. Same as parameters.
luminarsLuminarsLuminars for the command. Same as luminars.
helpfalse \| HelpOptionsHelp options for the command. Same as help.

⚑️callback(parsed)

Type:

Optional callback function that is called when the command is invoked.

πŸ“œ License

MIT - Made with πŸ’ž