Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@benev/argv

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@benev/argv

command line argument parser

  • 0.3.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
147
decreased by-33.78%
Maintainers
1
Weekly downloads
 
Created
Source

🎛️ @benev/argv

the greatest command line parser for typescript, maybe

🤖 for making node cli programs
🕵️ incredible typescript type inference
🧼 zero dependencies
💖 made free and open source, just for you


💁 autogenerated --help pages

pizza --help

pizza large --pepperoni --slices 3

# or, any way you like it
pizza --slices=9 large
pizza medium -p --slices=5
pizza small --pepperoni="no" --slices="2"

📖 build your cli

  1. install @benev/argv via npm
    npm i @benev/argv
    
  2. import stuff
    import {cli, command, arg, param, string, number} from "@benev/argv"
    
  3. specify your cli, and perform the parsing
    const {args, params} = cli(process.argv, {
      name: "pizza",
      commands: command({
        args: [
          arg("size").required(string),
        ],
        params: {
          slices: param.default(number, "1"),
          pepperoni: param.flag("p"),
        },
      }),
    }).tree
    
  4. now you have your args and params
    args.size // "large"
    params.slices // 5
    params.pepperoni // true
    
    • this is the "flat strategy" for receiving your args and params.
      simple, easy!
  5. all your types automagically work!
    it took me a long time to make it elegant and cool like this.

🧑‍🔧 configuring your cli's args and params

  • let's start by making a command
    command({
      args: [],
      params: {},
    })
    
  • a command can optionally accept a help string
    command({
      help: "what a time to be alive!",
      args: [],
      params: {},
    })
    
  • let's add positional args
    command({
      args: [
        arg("active").required(boolean),
        arg("count").default(number, "101"),
        arg("name").optional(string),
      ],
      params: {},
    })
    
    • args are in an array, so each needs a name, eg "active" above
    • there are three modes, required, default, and optional
    • default requires a fallback value
    • there are three basic types, string, number, and boolean, but you can make your own types
  • now let's talk about params
    command({
      args: [],
      params: {
        active: param.required(boolean),
        count: param.default(number, "101"),
        name: param.optional(string),
        verbose: param.flag("-v"),
      },
    })
    
    • pretty similar. but see the way the names are different?
    • there's a new variety of param called flag, of course, it's automatically a boolean (how could it be otherwise?)

validation for args and params

  • you can set a validate function on any arg or param
    arg("quality").optional(number, {
      validate: n => {
        if (n > 100) throw new Error("to big")
        if (n < 0) throw new Error("to smol")
        return n
      },
    })
    
    • if you throw any error in a validate, it will be printed all nice-like to the user

help literally everywhere!

  • in fact, every arg and param can have its own help
    command({
      help: "it's the best command, nobody makes commands like me",
    
      args: [
        arg("active").required(boolean, {
          help: "all systems go?",
        }),
    
        arg("count").default(number, "101", {
          help: "number of dalmatians",
        }),
    
        arg("name").optional(string, {
          help: `
            see this multi-line string?
            it will be trimmed all nicely on the help page.
          `
        }),
      ],
    
      params: {
        active: param.required(boolean, {
          help: "toggle this carefully!",
        }),
    
        count: param.default(number, "101", {
          help: "classroom i'm late for",
        }),
    
        name: param.optional(string, {
          help: "pick your pseudonym",
        }),
    
        verbose: param.flag("-v", {
          help: "going loud",
        }),
      },
    })
    

choice helper

  • you can use the choice helper to set up a multiple choice string
    param.required(string, choice(["thick", "thin"]))
    
  • you can add a help to it as well
    param.required(string, choice(["thick", "thin"], {
      help: "made with organic whole-wheat flour",
    }))
    

list helper

  • okay this is seriously crazy cool, check this out
    param.required(list(string))
    
  • you can just wrap any type in the list helper
    • user inputs comma-separated values mp3,wav,ogg
    • you get an array ["mp3", "wav", "ogg"]
  • is works with any type, like numbers and such
    param.required(list(number))
    
    • now you get a number[] array (not strings)
    • yes, list preserves the type's validation

🌳 tree of multiple commands

  • the commands object is a recursive tree with command leaves
    const {tree} = cli(process.argv, {
      name: "converter",
      commands: {
        image: command({
          args: [],
          params: {
            quality: param.required(number),
          },
        }),
        media: {
          audio: command({
            args: [],
            params: {
              mono: param.required(boolean),
            },
          }),
          video: command({
            args: [],
            params: {
              codec: param.required(string),
            },
          })
        },
      },
    })
    

flat strategy

  • you get this tree object that reflects its shape
    tree.image?.params.quality // 9
    tree.media.audio?.mono // false
    tree.media.video?.codec // "av1"
    
    • all the commands are undefined except for the "selected" command
    • and yes, all the typings work

command-execution strategy

  • you can choose to provide each command with an async execute function
    command({
      args: [],
      params: {
        active: param.required(boolean),
        count: param.default(number, "101"),
      },
      async execute({params}) {
        params.active // true
        params.count // 101
      },
    })
    
    • your execute function receives fully-typed args, params, and some more stuff
  • your execute function can opt-into pretty-printing errors (with colors) by throwing an ExecutionError
    import {ExecutionError, command} from "@benev/argv"
    
    async execute({params}) {
      throw new ExecutionError("scary error printed in red!")
    }
    
  • if you choose to use this command-execution strategy, then you need to call your cli's final execute function
    // 👇 awaiting cli execution
    await cli(process.argv, {
      name: "pizza",
      commands: {
        meatlovers: command({
          args: [],
          params: {
            meatiness: param.required(number),
          },
          async execute({params}) {
            console.log(params.meatiness) // 9
          },
        }),
        hawaiian: command({
          args: [],
          params: {
            pineappleyness: param.required(number),
          },
          async execute({params}) {
            console.log(params.pineappleyness) // 8
          },
        }),
      },
    }).execute()
      // ☝️ calling cli final execute
    

🛠️ custom types

  • i can't believe i got all the types working for everything with custom types
  • it's easy to make your own types
    const date = asType({
      name: "date",
      coerce: string => new Date(string),
    })
    
    • the name is shown in help pages
    • the coerce function takes a string input, and you turn it into anything you like
  • then you can use 'em in your args and params like normal
    param.required(date)
    
  • hey why not make a list of 'em while we're at it
    param.required(list(date))
    
  • feeling spiffy? make a whole group of custom types with this one weird tip
    const date = asTypes({
      date: string => new Date(string),
      integer: string => Math.floor(Number(string)),
    })
    
    • asTypes will use your object's property names as the type name

🦚 custom themes

  • you can set the theme for your --help pages
    import {themes} from
    
    await cli(process.argv, {
    
      // the default theme
      theme: themes.standard,
    
      ...otherStuff,
    }).execute()
    
    • maybe try themes.seaside for a more chill vibe
    • if you hate fun, use themes.noColor to disable ansi colors
  • make your own theme like this
    import {theme, color} from
    
    const seaside = theme({
      plain: [color.white],
      error: [color.brightRed, color.bold],
      program: [color.brightCyan, color.bold],
      command: [color.cyan, color.bold],
      property: [color.blue],
      link: [color.brightBlue, color.underline],
      arg: [color.brightBlue, color.bold],
      param: [color.brightBlue, color.bold],
      flag: [color.brightBlue],
      required: [color.cyan],
      mode: [color.blue],
      type: [color.brightBlue],
      value: [color.cyan],
    })
    

🌠 give me a github star!

  • i worked way too hard on this
  • please submit issues for any problems or questions
  • maybe make a cool help theme and submit a PR for it

Keywords

FAQs

Package last updated on 24 May 2024

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