👁‍🗨 cleye — The intuitive CLI development tool for Node.js

Overview

cleye

The intuitive command-line interface (CLI) development tool.

Features

  • Minimal API surface
  • Powerful flag parsing
  • Strongly typed parameters and flags
  • 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

npm i cleye

About

Cleye makes it very easy to develop command-line scripts in Node.js. It handles argv parsing to give you strongly typed parameters + flags 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 'cleye'

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

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

    // Define flags/options
    flags: {

        // 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.flags.time === 'morning') {
    console.log(`Good morning ${name}!`)
} else {
    console.log(`Good evening ${name}!`)
}

🛠 In development, type hints are provided on parsed flags and parameters:



Type hints for Cleye's output are very verbose and readable

📖 Generated help documentation can be viewed with the --help flag:

$ node greet.js --help

greet.js

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

Flags:
  -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:

Usage

Arguments

Arguments are values passed into the script that are not associated with any flags/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 (eg. <parameter name>).
  • Optional parameters are indicated by square brackets (eg. [parameter name]).
  • Spread parameters are indicated by ... suffix (eg. <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

End-of-flags (--) (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.

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-flag arguments will be accessible from argv._['--'].

Additionally, you can specify -- in the parameters array to parse end-of-flags 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

Flags (aka Options) are key-value pairs passed into the script in the format --flag-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

Cleye's flag parsing is powered by type-flag and comes with many features:

  • Array & Custom types
  • Flag delimiters: --flag value, --flag=value, --flag:value, and --flag.value
  • Combined aliases: -abcd 2-a -b -c -d 2
  • End of flags: Pass in -- to end flag parsing
  • Unknown flags: Unexpected flags stored in unknownFlags

Read the type-flag docs to learn more.

Defining flags

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

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

The flag 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 description object can be used to store additional information about the flag, such as alias, default, and description. To accept multiple values for a flag, wrap the type function in an array.

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

Example:

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

        someString: {
            type: String,
            description: 'Some string flag',
            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.flags.someBoolean // => true (boolean | undefined)
argv.flags.someString // => "hello" (string)
argv.flags.someNumber // => [1, 2] (number[])

Custom flag types & validation

Custom flag types can be created to validate flags 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 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({
    flags: {
        size: {
            type: Size,
            description: 'Size of the pizza (small, medium, large)'
        }
    }
})

// $ my-script --size large

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

Default flags

By default, Cleye will try to handle the --help, -h and --version flags.

Help flag

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

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 'cleye'

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

    version: '1.2.3',

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

            parameters: ['<package name>'],

            flags: {
                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 'cleye'

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

    parameters: ['<package name>'],

    flags: {
        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

Cleye 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 table to use the = operator (--flag <value>--flag=<value>):

cli({
    // ...,

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

            // Add some text at end of document
            nodes.push('\nCheckout Cleye: https://github.com/privatenumber/cleye')

            /* Extend renderers... */

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

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

Responsive tables

Cleye's "Flags" 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 terminal-columns and can be configured via the renderers.table renderer.

Normal width Narrow width

API

cli(options, callback?, argvs?)

Return type:

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

    // Parsed flags
    flags: {
        [flagName: string]: InferredType
    }

    // Unexpected flags
    unknownFlags: {
        [flagName: 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.

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

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

Property Type Description
type Function Flag value parsing function.
alias string Single character alias for the flag.
default any Default value for the flag.
description string Description of the flag shown in --help.
placeholder string Placeholder for the flag value shown in --help.
help

Type: false or an object with the following properties.

Property Type Description
version string Version shown in --help.
description string Description shown in --help.
usage string | string[] Usage code examples shown in --help.
examples string | string[] Example code snippets shown in --help.
render (nodes, renderers) => string Function 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.

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

Property Type Description
name string Required name used to invoke the command.
alias string | string[] Aliases used to invoke the command.
parameters string[] Parameters for the command. Same as parameters.
flags Flags Flags for the command. Same as flags.
help false | HelpOptions Help options for the command. Same as help.

callback(parsed)

Type:

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

Comments
  • Support for global custom options

    Support for global custom options

    Feature request

    Thanks for this type-safe library. Support global custom options like vite dev --debug, vite build --debug. I try replicating Vite CLI with this library. Currently, there is no an easy way to declare global options.

    Why?

    The existing libraries/frameworks already support this. Global options is a core part of a CLI app.

    Alternatives

    create a shared global options object and used in every command.

    Additional context

    No response

    opened by ydcjeff 4
  • fix(types): export Renderers and Command

    fix(types): export Renderers and Command

    Resolves https://github.com/privatenumber/cleye/issues/10

    Playing a bit more with the types, it seems like exposing both Renderers and Command solves the issue.

    This is a discussion on TypeScript's Discord with an expert:

    image released 
    opened by jgoux 2
  • Flag type inference fails for command() with 'description'

    Flag type inference fails for command() with 'description'

    Bug description

    Flags definitions passed to command() that include a description field are inferred as never. If you omit description, the type is inferred correctly.

    Reproduction

    import { command } from "cleye";
    
    const build = command({
      name: 'build',
      flags: {
        watch: {
          type: Boolean,
          alias: 'w',
          default: false,
          description: 'Watch for changes and rebuild', // when commented out, type is correct
        },
      },
    }, argv => {
      if (argv.flags.watch) {
        // expected "watch" to be boolean, but it is "never"
      }
    });
    

    Node.js package manager

    pnpm

    Environment

    System:
        OS: macOS 12.1
        CPU: (8) arm64 Apple M1
        Memory: 191.53 MB / 16.00 GB
        Shell: 3.3.1 - /opt/homebrew/bin/fish
      Binaries:
        Node: 16.13.0 - ~/.volta/tools/image/node/16.13.0/bin/node
        Yarn: 1.22.10 - ~/.volta/tools/image/yarn/1.22.10/bin/yarn
        npm: 8.1.0 - ~/.volta/tools/image/node/16.13.0/bin/npm
      npmPackages:
        cleye: ^1.0.1 => 1.0.1
    

    Can you contribute a fix?

    • [ ] I’m interested in opening a pull request for this issue.
    bug 
    opened by jgoz 1
  • Only parse flags before arguments

    Only parse flags before arguments

    Feature request

    Option to only parse flags before parameters

    Why?

    In tsx, we only want to parse the flags before the actual file being passed in. The flags after the file will be passed onto the file.

    Alternatives

    No response

    Additional context

    No response

    enhancement 
    opened by privatenumber 0
  • error TS4023 when emitting types declaration files

    error TS4023 when emitting types declaration files

    Bug description

    When running the command:

    tsc --emitDeclarationOnly --declaration --declarationMap
    

    to build the project and obtain my types declaration files, I got these errors:

    > tsc --emitDeclarationOnly --declaration --declarationMap
    
    index.ts:3:14 - error TS4023: Exported variable 'projectCreateCommand' has or is using name 'Renderers' from external module "/Users/jgoux/Documents/code/cleye-repro/node_modules/.pnpm/[email protected]/node_modules/cleye/dist/index" but cannot be named.
    
    3 export const projectCreateCommand = command(
                   ~~~~~~~~~~~~~~~~~~~~
    
    index.ts:3:14 - error TS4023: Exported variable 'projectCreateCommand' has or is using name 'parsedType' from external module "/Users/jgoux/Documents/code/cleye-repro/node_modules/.pnpm/[email protected]/node_modules/cleye/dist/index" but cannot be named.
    
    3 export const projectCreateCommand = command(
                   ~~~~~~~~~~~~~~~~~~~~
    
    
    Found 2 errors in the same file, starting at: index.ts:3
    

    I've found these corresponding issues on TypeScript repository:

    • https://github.com/microsoft/TypeScript/issues/40718
    • https://github.com/microsoft/TypeScript/issues/9944

    Seems like the internals of the library have to be exposed in order to TypeScript to see them somehow. 🤔

    I played a little with your generated index.d.ts and I was able to fix the Renderers error by exposing the type. But for the parsedType, exposing the variable didn't fix it unfortunatly.

    Reproduction

    I've set up a repository with a minimal reproduction: https://github.com/jgoux/cleye-repro

    Node.js package manager

    pnpm

    Environment

    System:
        OS: macOS 12.4
        CPU: (10) arm64 Apple M1 Pro
        Memory: 141.14 MB / 16.00 GB
        Shell: 5.8.1 - /bin/zsh
      Binaries:
        Node: 16.15.0 - /opt/homebrew/bin/node
        npm: 8.5.5 - /opt/homebrew/bin/npm
      npmPackages:
        cleye: 1.2.0 => 1.2.0
    

    Can you contribute a fix?

    • [X] I’m interested in opening a pull request for this issue.
    bug 
    opened by jgoux 0
Releases(v1.3.1)
Owner
hiroki osame
I'm on a mission to open source my solutions 🚀
hiroki osame
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
A CLI tool to create a NodeJS project with TypeScript CTSP is a CLI tool to make easier to start a new NodeJS project and configure Typescript on it.

CTSP- Create TS Project A CLI tool to create a NodeJS project with TypeScript CTSP is a CLI tool to make easier to start a new NodeJS project and conf

Jean Rodríguez 7 Sep 13, 2022
Node-cli-starter - Basic starter kit for building Node CLI applications with TypeScript.

node-cli-starter Minimal starter kit for building Node CLI applications with TypeScript. Getting Started To get started clone repo locally and run npm

Cory Rylan 7 May 17, 2022
Intuitive and dynamic Chrome Dev Tool Extension for Three.js debugging

sceneSniff An intuitive and dynamic Chrome Dev Tool Extension for Three.js debugging sceneSniff is an in browser developer tool for Three.js projects.

OSLabs Beta 59 Dec 8, 2022
Strcmp-node - A cli string comparison tool, because apparently one doesn't exist.

strcmp-node I couldn't find a string comparison command, so i made my own. its probably the ugliest thing since godzilla with makeup on, but it works.

Gingka/Ginger Pepper 1 Jan 1, 2022
Dynamic-web-development - Dynamic web development used CSS and HTML

Dynamic-web-development ASSISNMENT I just used CSS and HTML to make a mobile int

null 1 Feb 8, 2022
The project integrates workflow engine, report engine and organization authority management background, which can be applied to the development of OA, HR, CRM, PM and other systems. With tlv8 IDE, business system development, testing and deployment can be realized quickly.

介绍 项目集成了工作流引擎、报表引擎和组织机构权限管理后台,可以应用于OA、HR、CRM、PM等系统开发。配合使用tlv8 ide可以快速实现业务系统开发、测试、部署。 后台采用Spring MVC架构简单方便,前端使用流行的layui界面美观大方。 采用组件开发技术,提高系统的灵活性和可扩展性;采

Qian Chen 38 Dec 27, 2022
Tempo is an easy, intuitive JavaScript rendering engine that enables you to craft data templates in pure HTML.

Tempo 2.0 Tempo is an easy, intuitive JavaScript rendering engine that enables you to craft data templates in pure HTML. Why use Tempo? Clear separati

Twigkit 707 Jan 3, 2023
front-end framework for fast and powerful configuration of utilities and intuitive UI

front-end framework for fast and powerful configuration of utilities and intuitive UI Getting Started with Vector → Getting started A variety of optio

Skill Class 12 Jun 29, 2022
front-end framework for fast and powerful configuration of utilities and intuitive UI

front-end framework for fast and powerful configuration of utilities and intuitive UI Getting Started with Vector → Getting started A variety of optio

DE:MO 12 Jun 29, 2022
Team Empire offers an innovative and intuitive game based on puzzles solved by two players working in a team.

Team Empire offers an innovative and intuitive game based on puzzles solved by two players working in a team. The user can create an account to monitor their results and achievements. The whole idea of the product is to make the player think and develop his logical thinking.

Vocational school for computer programming and innovation 14 Aug 8, 2022
The jQuery plugin that brings select elements into the 21st century with intuitive multiselection, searching, and much more. Now with Bootstrap 5 support.

bootstrap-select The jQuery plugin that brings select elements into the 21st century with intuitive multiselection, searching, and much more. Now with

SnapAppointments 9.7k Dec 30, 2022
At Arctic Desert we set out to create an intuitive app that aids developer teams

At Arctic Desert we set out to create an intuitive app that aids developer teams. We aimed to create a service that integrates git workflow, task management and real-time communication. This is integrated into a single workspace, allowing you to seamlessly switch between teams, projects and branches.

Artic Desert 7 Jul 14, 2022
Simple and intuitive API Client made into a VSCode extension 😊

REST API Client Simple and intuitive API Client made into a VSCode extension. Visual Studio Marketplace • Repository • Releases Visual Studio Code ext

REST API Client 19 Dec 23, 2022
A visual, interactive outline map that combines the clarity of the outline with the intuitive overview of the minimap. Alternative Minimap.

Outline Map EN | 中文 A visual, interactive outline map that combines the clarity of the outline with the intuitive overview of the minimap. Alternative

null 97 Dec 21, 2022
The jQuery plugin that brings select elements into the 21st century with intuitive multiselection, searching, and much more. Now with Bootstrap 5 support

bootstrap-select The jQuery plugin that brings select elements into the 21st century with intuitive multiselection, searching, and much more. Now with

SnapAppointments 9.7k Dec 30, 2022
Invadium runs exploit playbooks against vulnerable target applications in an intuitive, reproducible, and well-defined manner.

Invadium Invadium runs exploits against one or more target applications in an intuitive, reproducable, and well-defined manner. It focuses on bridging

Dynatrace Open Source 10 Nov 6, 2022
All in one is a CLI to make your journey in web development less painful (it makes your life way easier).

All In One CLI (Aio) The Ultimate CLI for all your needs in web development. Description This is a CLI that has all the commands you need to do anythi

Я♡M...∞ 17 Sep 25, 2022