npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@benev/argv

v0.3.8

Published

command line argument parser

Downloads

409

Readme

🎛️ @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
  • your custom types can throw errors and it works as validation
    const integer = asType({
      name: "integer",
      coerce: string => {
        const n = Number(string)
    
        if (isNaN(n))
          throw new Error("not a number")
    
        if (!Number.isSafeInteger(n))
          throw new Error("not a safe integer")
    
        return n
      },
    })

🦚 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