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 🙏

© 2026 – Pkg Stats / Ryan Hefner

yargs-extended

v1.0.0

Published

Typed helpers for building yargs-based CLI programs.

Readme

yargs-extended

Typed helpers for building yargs-based CLI programs.

yargs-extended is a small TypeScript-first wrapper around yargs. It keeps the familiar command, option, positional, help, strict parsing, aliases, and nested-command behavior of yargs, while adding a compact declaration syntax that TypeScript can read.

The goal is simple: write the CLI shape once, as strings you would already show to users, and get useful handler types back.

Why This Exists

Most CLI libraries make one side of the tradeoff feel good:

  • Runtime CLI behavior is nice, but handler types are broad.
  • Type safety is possible, but it asks for extra schemas, duplicated interfaces, or a separate typings package.
  • The command declaration is readable to humans, but not rich enough for TypeScript.

This project exists because I wanted the CLI declaration itself to be the type source:

program
  .command("deploy")
  .option("--env <dev|prod>")
  .option("--port <number>")
  .option("--dry-run")
  .action(async (args) => {
    args.env;
    //   ^? "dev" | "prod"

    args.port;
    //   ^? number

    args.dryRun;
    //   ^? boolean
  });

No duplicated DeployOptions interface. No parser schema next to the parser string. No "trust me" cast in every handler.

Install

yarn add yargs-extended
npm install yargs-extended

Requires Node.js 24 or newer.

Quick Start

import { Program } from "yargs-extended";

const program = Program.createProgram()
  .name("ship")
  .description("A release helper")
  .version("1.0.0");

program
  .command("deploy", "Deploy an environment")
  .option("--env <dev|prod>", { required: true })
  .option("--port <number>", { default: 3000 })
  .option("--dry-run")
  .action(async (args) => {
    console.log({
      env: args.env,
      port: args.port,
      dryRun: args.dryRun
    });
  });

await program.parseAsync();

Run it:

ship deploy --env prod --dry-run

API Overview

Program

const program = Program.createProgram()
  .name("my-cli")
  .description("A useful CLI")
  .version("1.0.0");

Program.createProgram() wraps yargs(hideBin(process.argv)) by default. In tests, you can inject args:

const program = Program.createProgram().overrideExit();

await program.parseAsync(["deploy", "--env", "prod"]);

Useful methods:

  • name(name)
  • description(text)
  • version(version)
  • option(spec, config?)
  • command(name, description?)
  • parse()
  • parseAsync(args?)
  • overrideExit(exitProcess = false)

Commands

program
  .command("workspace", "Manage workspaces")
  .command("add")
  .positional("<name>")
  .option("--private")
  .action(async (args) => {
    args.name;
    //   ^? string

    args.private;
    //   ^? boolean
  });

Command methods:

  • command(name)
  • addAlias(alias)
  • removeAlias(alias)
  • description(text)
  • option(spec, config?)
  • positional(spec, config?)
  • prefetch(callback)
  • validate(callback)
  • action(callback)

Option Syntax

Options must include at least one long flag:

program.option("--verbose");
program.option("--output <path>");
program.option("--env <dev|prod>");
program.option("--include <string...>");
program.option("--dry-run, -d");

Supported value tokens:

| Syntax | Inferred Type | Runtime Behavior | | --- | --- | --- | | --flag | boolean | Boolean flag | | --name <string> | string | String value | | --host [string] | string \| undefined | Optional string value | | --port <number> | number | Number value | | --count <count> | number | Number value | | --file <path> | string | String value | | --env <dev\|prod> | "dev" \| "prod" | Choice validation | | --include <string...> | string[] | Variadic array |

Use the config object for runtime requirements and metadata:

program.option("--env <dev|prod>", {
  description: "Target environment",
  required: true,
  default: "dev"
});

Important distinction: <string> makes the TypeScript value non-optional, but runtime requiredness is controlled by { required: true }. That keeps type syntax and CLI policy explicit.

Positional Syntax

program
  .command("copy")
  .positional("<source: path>")
  .positional("[target: path]")
  .positional("...files: string[]");

Supported positional forms:

| Syntax | Inferred Type | Meaning | | --- | --- | --- | | <name> | { name: string } | Required string | | [name] | { name?: string } | Optional string | | name? | { name?: string } | Optional string | | <count: number> | { count: number } | Required number | | <file: path> | { file: string } | Required path/string | | ...files: string[] | { files: string[] } | Variadic string array |

Prefetch and Validate

prefetch lets you load data before the handler. The result is typed into validate and action.

program
  .command("publish")
  .option("--tag <string>", { required: true })
  .prefetch(async (args) => {
    return {
      releaseId: `release:${args.tag}`
    };
  })
  .validate((args, release) => {
    if (!release.releaseId) {
      throw new Error(`Missing release for ${args.tag}`);
    }
  })
  .action(async (args, release) => {
    console.log(`Publishing ${release.releaseId} as ${args.tag}`);
  });

Execution order:

  1. Parse args
  2. Run prefetch
  3. Run validate
  4. Run action

Type Extraction

Use inferCommandType when you want to reuse a command's inferred argument type.

import { Command, type inferCommandType } from "yargs-extended";

const deploy = new Command("deploy")
  .option("--env <dev|prod>")
  .option("--dry-run");

type DeployArgs = inferCommandType<typeof deploy>;
// {
//   env: "dev" | "prod";
//   dryRun: boolean;
// }

Comparison With Commander

Commander is excellent. Its official README describes it as a library where you describe the command line interface and Commander handles parsing, usage errors, and help output. That is a great fit for many CLIs.

yargs-extended is narrower and more opinionated: it is for people who like the yargs runtime model and want inferred TypeScript types from compact option strings.

| Area | Commander | yargs-extended | | --- | --- | --- | | Runtime foundation | Commander runtime | yargs runtime | | CLI declaration style | Fluent command/options API | Fluent command/options API | | Help and errors | Built in | Delegated to yargs | | Strong inferred handler types | Available through @commander-js/extra-typings | Built into this wrapper | | Option string as type source | Partial, depending on typings layer | Core design goal | | Enum string inference | Not the main API focus | --env <dev\|prod> infers "dev" \| "prod" | | Prefetch result typing | Userland pattern | Built into command chain | | Best for | Mature, general-purpose CLIs | Typed yargs CLIs with compact declarations |

Why choose Commander:

  • You want the established Commander API and ecosystem.
  • You prefer Commander semantics over yargs.
  • You already use @commander-js/extra-typings successfully.
  • You need a feature Commander supports directly and this wrapper does not.

Why choose yargs-extended:

  • You already like or rely on yargs.
  • You want option strings like --env <dev|prod> to produce useful TypeScript types.
  • You want aliases, positionals, prefetch, validate, and action to stay in one chain.
  • You want fewer duplicated runtime/type declarations.

References:

Playground

The repo includes a Vite-powered playground workspace.

yarn workspace @yargs-extended/playground dev dev

Expected output:

Running

The playground maps yargs-extended to the root source through playground/tsconfig.json paths, and Vite consumes those paths with native resolve.tsconfigPaths support.

Development

Install dependencies:

yarn install

Run checks:

yarn lint
yarn format:check
yarn typecheck
yarn test
yarn test:coverage
yarn build

Apply safe formatting/lint fixes:

yarn fix

Publishing

This repo includes .github/workflows/publish.yml for npm publishing through GitHub Actions trusted publishing.

It does not require an npm token in GitHub secrets. Configure the npm package trusted publisher on npm, then publish by pushing a v* tag, publishing a GitHub release, or manually dispatching the workflow.

Before the first public publish, make sure package.json has the correct repository field for the GitHub repository you connect.

Status

Current test coverage:

  • 65 tests
  • 92%+ statement coverage
  • 85%+ branch coverage

This is still a small library. The API is intentionally compact, and behavior should be expanded with tests before broadening the surface area.