New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@molt/command

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@molt/command

Type-safe CLI command definition and execution.

  • 0.2.2
  • npm
  • Socket score

Version published
Weekly downloads
52K
increased by4.25%
Maintainers
1
Weekly downloads
 
Created
Source

@molt/command

🌱 Type-safe CLI command definition and execution.

Installation

npm add @molt/command

Example

import { Command } from '@molt/command'
import { z } from 'zod'

const args = Command.create({
  '--file-path': z.string().describe(`Path to the file to convert.`),
  '--to': z.enum(['json', ' yaml', 'toml']).describe(`Format to convert to.`),
  '--from': z
    .enum(['json', 'yaml', 'toml'])
    .optional()
    .describe(`Format to convert from. By default inferred from the file extension.`),
  '--verbose -v': z.boolean().default(false).describe(`Log detailed progress as conversion executes.`),
  '--move -m': z.boolean().default(false).describe(`Delete the original file after it has been converted.`),
}).parseOrThrow()

// Normalized, validated, statically typed, and ready to go!
args.filePath
args.to
args.from
args.verbose
args.move
my-binary --file ./music.yaml --to json

Autogenerated help:

ts-node convert -h

PARAMETERS

  Name           Type / Summary                          Default                 Via Environment

  to             json | yaml | toml                      REQUIRED                ✕
                 Format to convert to.

  file-path      string                                  REQUIRED                FILE_PATH
                 Path to the file to convert.

  from           json | yaml | toml                      Inferred from the file  ✕
                 Format to convert from.                 extension.

  verbose        boolean                                 false                   ✕
  v              Log detailed progress.

  move           boolean                                 false                   ✕
  v              Delete the original file after
                 it has been converted.

  h              boolean                                 false                   ✕
                 Output succinct info on how to
                 use this CLI.

  help           boolean                                 false                   ✕
                 Output detailed info on how to
                 use this CLI.

Features

  • Automatic parameter parsing based on specified Zod types.

  • Normalization between camel and kebab case:

    • Parameter specs are normalized:

      const args = Command.create({
        '--do-it-a': z.boolean()
        '--doItB': z.boolean()
        doItC: z.boolean()
        }).parseOrThrow()
      
      args1.doItA
      args2.doItB
      args3.doItC
      
    • User flags are normalized:

      $ binary --do-it
      $ binary --doIt
      
  • Specify one or multiple (aka. aliases) short and long flags:

    Command.create({ '-f --force --forcefully': z.boolean() }).parseOrThrow()
    
  • Use Zod .default(...) method for setting default values.

    const args = Command.create({ '--path': z.string().default('./a/b/c') }).parseOrThrow()
    args.path === './a/b/c/' //   $ binary
    args.path === '/over/ride' // $ binary --path /over/ride
    
  • Pass arguments via environment variables (customizable)

    const args = Command.create({ '--path': z.string() }).parseOrThrow()
    args.path === './a/b/c/' // $ CLI_PARAM_PATH='./a/b/c' binary
    
  • Use Zod .describe(...) for automatic docs.

  • Future feature: automatic help generation.

  • Parameter stacking e.g. binary -abc instead of binary -a -b -c

  • Separator of = or space, e.g. binary -a=foo -b=bar or binary -a foo -b bar

Guide

Parameter Naming

Property Name Syntax

You can define parameters as a zod object schema using regular property names. These are flags for your CLI but arguments can also be passed by environment variables so in a way this is a neutral form that doesn't privilege either argument passing mechanism.

const args = Command.create({
  foo: z.string(),
  bar: z.number(),
  qux: z.boolean(),
}).parseOrThrow()

args.foo
args.bar
args.qux
Flag Syntax

You can also define them using flag syntax if you prefer. Thanks to @molt/types this style doesn't sacrifice any type safety.

const args = Command.create({
  '--foo': z.string(),
  '--bar': z.number(),
  '--qux': z.boolean(),
}).parseOrThrow()

args.foo
args.bar
args.qux
Short, Long, & Aliasing

You can give your parameters short and long names, as well as aliases.

A set of parameter names gets normalized into its canonical name internally (again thanks to @molt/types this is all represented statically as well). The canonical name choice is as follows:

  1. The first long flag
  2. Otherwise the first short flag
const args = Command.create({
  '--foobar --foo -f ': z.string(),
  '--bar -b -x': z.number(),
  '-q --qux': z.boolean(),
  '-m -n': z.boolean(),
}).parseOrThrow()

// $ binary --foobar moo --bar 2 --qux -m
// $ binary --foo    moo  -x   2 --qux -m
// $ binary  -f      moo  -b   1  -q   -n
args.foobar === 'moo'
args.bar === 1
args.qux === true
args.m === true

If you prefer you can use a dash-prefix free syntax:

const args = Command.create({
  'foobar foo f ': z.string(),
  'bar b x': z.number(),
  'q qux': z.boolean(),
  'm n': z.boolean(),
}).parseOrThrow()
Kebab / Camel Case

You can use kebab or camel case (and likewise your users can pass flags in either style). Canonical form internally uses camel case.

const args = Command.create({
  'foo-bar': z.string(),
  quxLot: z.string(),
}).parseOrThrow()

// $ binary --foo-bar moo --qux-lot zoo
// $ binary --fooBar moo --quxLot zoo
args.fooBar === 'moo'
args.quxLot === 'zoo'

Parameter Typing

Parameter types via Zod schemas affect parsing in the following ways.

Boolean
  • Flag does not accept any arguments.
  • Environment variable accepts "true" or "1" for true and "false" or "0" for false.
  • Negated form of parameters automatically accepted.

Examples:

const args = Command.create({ 'f force forcefully': z.boolean() }).parseOrThrow()
// $ CLI_PARAM_NO_F='true' binary
// $ CLI_PARAM_NO_FORCE='true' binary
// $ CLI_PARAM_NO_FORCEFULLY='true' binary
// $ CLI_PARAM_F='false' binary
// $ CLI_PARAM_FORCE='false' binary
// $ CLI_PARAM_FORCEFULLY='false' binary
// $ binary --no-f
// $ binary --noF
// $ binary --no-force
// $ binary --noForce
// $ binary --no-forcefully
// $ binary --noForcefully
args.force === false
// $ CLI_PARAM_NO_F='false' binary
// $ CLI_PARAM_NO_FORCE='false' binary
// $ CLI_PARAM_NO_FORCEFULLY='false' binary
// $ CLI_PARAM_F='true' binary
// $ CLI_PARAM_FORCE='true' binary
// $ CLI_PARAM_FORCEFULLY='true' binary
// $ binary -f
// $ binary --force
// $ binary --forcefully
args.force === true
Number
  • Flag expects an argument.
  • Argument is cast via the Number() function.
Enum
  • Flag expects an argument.

Argument Passing

This section is about users passing arguments to the parameters you've defined for your CLI.

Parameter Argument Separator

Arguments can be separated from parameters using the following characters:

  • whitespace
  • equals sign

Examples:

binary --foo=moo
binary --foo= moo
binary --foo = moo
binary --foo moo

Note that when = is attached to the value side then it is considered part of the value:

binary --foo =moo
Stacked Short Flags

Boolean short flags can be stacked. Imagine you have defined three parameters a, b, c. They could be passed like so:

binary -abc

The last short flag does not have to be boolean flag. For example if there were a d parameter taking a string, this could work:

binary -abcd foobar

Environment Arguments

Parameter arguments can be passed by environment variables instead of flags.

Environment arguments have lower precedence than Flags, so if an argument is available from both places, the environment argument is ignored while the flag argument is used.

Default Name Pattern

By default environment arguments can be set using one of the following naming conventions (note: Molt reads environment variables with case-insensitivity):

CLI_PARAMETER_{parameter_name}
CLI_PARAM_{parameter_name}
const args = Command.create({ '--path': z.string() }).parseOrThrow()
args.path === './a/b/c/' // $ CLI_PARAMETER_PATH='./a/b/c' binary
Toggling

You can toggle environment arguments on/off. It is on by default.

const command = Command.create({ '--path': z.string() }).settings({
  environment: false,
})
// $ CLI_PARAMETER_PATH='./a/b/c' binary
// Throws error because no argument given for "path"
command.parseOrThrow()

You can also toggle with the environment variable CLI_SETTINGS_READ_ARGUMENTS_FROM_ENVIRONMENT (case insensitive):

const command = Command.create({ '--path': z.string() })
// $ CLI_SETTINGS_READ_ARGUMENTS_FROM_ENVIRONMENT='false' CLI_PARAMETER_PATH='./a/b/c' binary
// Throws error because no argument given for "path"
command.parseOrThrow()
Selective Toggling

You can toggle environment on for just one or some parameters.

const args = Command.create({
  '--foo': z.string(),
  '--bar': z.string().default('not_from_env'),
})
  .settings({ environment: { foo: true } })
  .parseOrThrow()

// $ CLI_PARAMETER_FOO='foo' CLI_PARAMETER_BAR='bar' binary
args.foo === 'foo'
args.bar === 'not_from_env'

You can toggle environment on except for just one or some parameters.

const args = Command.create({
  '--foo': z.string().default('not_from_env'),
  '--bar': z.string().default('not_from_env'),
  '--qux': z.string().default('not_from_env'),
})
  .settings({ environment: { $default: true, bar: false } })
  .parseOrThrow()

// $ CLI_PARAMETER_FOO='foo' CLI_PARAMETER_BAR='bar' CLI_PARAMETER_QUX='qux' binary
args.foo === 'foo'
args.bar === 'not_from_env'
args.qux === 'qux'
Custom Prefix

You can customize the environment variable name prefix:

const args = Command.create({ '--path': z.string() })
  //                                              o-- case insensitive
  .settings({ environment: { $default: { prefix: 'foo' } } })
  .parseOrThrow()

args.path === './a/b/c/' // $ FOO_PATH='./a/b/c' binary

You can pass a list of accepted prefixes instead of just one. Earlier ones take precedence over later ones:

const args = Command.create({ '--path': z.string() })
  //                                               o---------o--- case insensitive
  .settings({ environment: { $default: { prefix: ['foobar', 'foo'] } } })
  .parseOrThrow()

args.path === './a/b/c/' // $ FOOBAR_PATH='./a/b/c' binary
args.path === './a/b/c/' // $ FOO_PATH='./a/b/c' binary
args.path === './a/b/c/' // $ FOO_PATH='./x/y/z' FOOBAR_PATH='./a/b/c' binary
Selective Custom Prefix

You can customize the environment variable name prefix for just one or some parameters.

const args = Command.create({
  '--foo': z.string().default('not_from_env'),
  '--bar': z.string().default('not_from_env'),
  '--qux': z.string().default('not_from_env'),
})
  .settings({ environment: { bar: { prefix: 'MOO' } } })
  .parseOrThrow()

// $ CLI_PARAMETER_FOO='foo' MOO_BAR='bar' CLI_PARAMETER_QUX='qux' binary
args.foo === 'foo'
args.bar === 'bar'
args.qux === 'qux'

You can customize the environment variable name prefix except for just one or some parameters.

const args = Command.create({
  '--foo': z.string().default('not_from_env'),
  '--bar': z.string().default('not_from_env'),
  '--qux': z.string().default('not_from_env'),
})
  .settings({ environment: { $default: { enabled: true, prefix: 'MOO' }, bar: { prefix: true } } })
  .parseOrThrow()

// $ MOO_FOO='foo' CLI_PARAM_BAR='bar' MOO_QUX='qux' binary
args.foo === 'foo'
args.bar === 'bar'
args.qux === 'qux'
Prefix Disabling

You can remove the prefix altogether. Pretty and convenient, but be careful for unexpected use of variables in host environment that would affect your CLI execution!

const args = Command.create({ '--path': z.string() })
  .settings({ environment: { $default: { prefix: false } } })
  .parseOrThrow()

args.path === './a/b/c/' // $ PATH='./a/b/c' binary
Selective Prefix Disabling

You can disable environment variable name prefixes for just one or some parameters.

const args = Command.create({
  '--foo': z.string().default('not_from_env'),
  '--bar': z.string().default('not_from_env'),
  '--qux': z.string().default('not_from_env'),
})
  .settings({ environment: { bar: { prefix: false } } })
  .parseOrThrow()

// $ CLI_PARAMETER_FOO='foo' BAR='bar' CLI_PARAMETER_QUX='qux' binary
args.foo === 'foo'
args.bar === 'bar'
args.qux === 'qux'

You can disable environment variable name prefixes except for just one or some parameters.

const args = Command.create({
  '--foo': z.string().default('not_from_env'),
  '--bar': z.string().default('not_from_env'),
  '--qux': z.string().default('not_from_env'),
})
  .settings({ environment: { $default: { enabled: true, prefix: false }, bar: { prefix: true } } })
  .parseOrThrow()

// $ FOO='foo' CLI_PARAM_BAR='bar' QUX='qux' binary
args.foo === 'foo'
args.bar === 'bar'
args.qux === 'qux'
Case Insensitive

Environment variables are considered in a case insensitive way so all of these work:

const args = Command.create({ '--path': z.string() }).parseOrThrow()
// $ CLI_PARAM_PATH='./a/b/c' binary
// $ cli_param_path='./a/b/c' binary
// $ cLi_pAraM_paTh='./a/b/c' binary
args.path === './a/b/c/'
Validation

By default, when a prefix is defined, a typo will raise an error:

const command = Command.create({ '--path': z.string() })

// $ CLI_PARAM_PAH='./a/b/c' binary
// Throws error because there is no parameter named "pah" defined.
command.parseOrThrow()

If you pass arguments for a parameter multiple times under different environment variable name aliases an error will be raised.

const command = Command.create({ '--path': z.string() })

// $ CLI_PARAMETER_PAH='./1/2/3' CLI_PARAM_PAH='./a/b/c' binary
/ole/ Throws error because user intent is ambiguous.
command.parseOrThrow()

FAQs

Package last updated on 20 Oct 2022

Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc