• Stars
    star
    206
  • Rank 190,504 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created almost 5 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

💻 A type-driven command line argument parser

cmd-ts

💻 A type-driven command line argument parser, with awesome error reporting 🤤

Not all command line arguments are strings, but for some reason, our CLI parsers force us to use strings everywhere. 🤔 cmd-ts is a fully-fledged command line argument parser, influenced by Rust's clap and structopt:

🤩 Awesome autocomplete, awesome safeness

🎭 Decode your own custom types from strings with logic and context-aware error handling

🌲 Nested subcommands, composable API

Basic usage

import { command, run, string, number, positional, option } from 'cmd-ts';

const cmd = command({
  name: 'my-command',
  description: 'print something to the screen',
  version: '1.0.0',
  args: {
    number: positional({ type: number, displayName: 'num' }),
    message: option({
      long: 'greeting',
      type: string,
    }),
  },
  handler: (args) => {
    args.message; // string
    args.number; // number
    console.log(args);
  },
});

run(cmd, process.argv.slice(2));

command(arguments)

Creates a CLI command.

Decoding custom types from strings

Not all command line arguments are strings. You sometimes want integers, UUIDs, file paths, directories, globs...

Note: this section describes the ReadStream type, implemented in ./src/example/test-types.ts

Let's say we're about to write a cat clone. We want to accept a file to read into stdout. A simple example would be something like:

// my-app.ts

import { command, run, positional, string } from 'cmd-ts';

const app = command({
  /// name: ...,
  args: {
    file: positional({ type: string, displayName: 'file' }),
  },
  handler: ({ file }) => {
    // read the file to the screen
    fs.createReadStream(file).pipe(stdout);
  },
});

// parse arguments
run(app, process.argv.slice(2));

That works okay. But we can do better. In which ways?

  • Error handling is out of the command line argument parser context, and in userland, making things less consistent and pretty.
  • It shows we lack composability and encapsulation — and we miss a way to distribute shared "command line" behavior.

What if we had a way to get a Stream out of the parser, instead of a plain string? This is where cmd-ts gets its power from, custom type decoding:

// ReadStream.ts

import { Type } from 'cmd-ts';
import fs from 'fs';

// Type<string, Stream> reads as "A type from `string` to `Stream`"
const ReadStream: Type<string, Stream> = {
  async from(str) {
    if (!fs.existsSync(str)) {
      // Here is our error handling!
      throw new Error('File not found');
    }

    return fs.createReadStream(str);
  },
};

Now we can use (and share) this type and always get a Stream, instead of carrying the implementation detail around:

// my-app.ts

import { command, run, positional } from 'cmd-ts';

const app = command({
  // name: ...,
  args: {
    stream: positional({ type: ReadStream, displayName: 'file' }),
  },
  handler: ({ stream }) => stream.pipe(process.stdout),
});

// parse arguments
run(app, process.argv.slice(2));

Encapsulating runtime behaviour and safe type conversions can help us with awesome user experience:

  • We can throw an error when the file is not found
  • We can try to parse the string as a URI and check if the protocol is HTTP, if so - make an HTTP request and return the body stream
  • We can see if the string is -, and when it happens, return process.stdin like many Unix applications

And the best thing about it — everything is encapsulated to an easily tested type definition, which can be easily shared and reused. Take a look at io-ts-types, for instance, which has types like DateFromISOString, NumberFromString and more, which is something we can totally do.

Inspiration

This project was previously called clio-ts, because it was based on io-ts. This is no longer the case, because I want to reduce the dependency count and mental overhead. I might have a function to migrate types between the two.

More Repositories

1

fnm

🚀 Fast and simple Node.js version manager, built in Rust
Rust
15,628
star
2

cuery

A composable SQL query builder using template literals ✨
TypeScript
217
star
3

gpkg

🌎 A global Node binary manager written in Rust
Rust
63
star
4

react-gooey-nav

The React Gooey Navigation Menu™
TypeScript
55
star
5

remastered

A full-stack approach to React development
TypeScript
50
star
6

svgify

service to threshold-svg your images
JavaScript
28
star
7

pointguard

An MVP-worthy background job server for PostgreSQL, written in Rust
Rust
27
star
8

next-static-paths

Statically prevent 404s in your Next.js applications using TypeScript
TypeScript
23
star
9

factoree

💥🔒 Fail early, fail fast: type-safe and runtime-safe partial factories for TypeScript
TypeScript
23
star
10

fnm.rs

An experimental Rust implementation of fnm
Rust
20
star
11

sapapa

A soothing CouchDB client for Reason and OCaml
OCaml
17
star
12

bs-faker

Faker.js bindings for BuckleScript in Reason
Reason
17
star
13

migratype

🔒▶🔒 Safe runtime type migrations for TypeScript
TypeScript
13
star
14

benchy-action

a hassle-free GitHub Action to benchmark your code continuously.
TypeScript
12
star
15

reason-pr-labels

Look for PR labels in GitHub pull requests
OCaml
12
star
16

dotfiles

my dotfiles
Shell
11
star
17

httyped

⛓️ Type-safe HTTP client/server communications with awesome autocompletion
TypeScript
11
star
18

route-ts

TypeScript
9
star
19

simpleplan

Simple dependency injector for your precious node apps.
JavaScript
8
star
20

draft-js-create-inline-style-plugin

Handle Draft.js' inline styles as if they were controlled by strategies 💥
JavaScript
7
star
21

libre-converter

Converting LibreOffice documents to other formats. like PDF and stuff. yeah.
CoffeeScript
7
star
22

infer-types

Returns the exported inferred types from TypeScript.
TypeScript
6
star
23

need-this

One Store To Rule Them All
JavaScript
6
star
24

soundtype-commander

A type-safe wrapper around commander.js with excellent type inference.
TypeScript
6
star
25

lets-chat-reverseproxy

reverse proxy sso header add-on for Let's Chat
JavaScript
5
star
26

obsidian-prettier

TypeScript
4
star
27

matcher-ts

TypeScript
4
star
28

soundtype-eventemitter

A typesafe event emitter for TypeScript
TypeScript
4
star
29

draft-js-lister-plugin

automatic unordered and ordered lists in draftjs based on draft-js-plugins
JavaScript
4
star
30

tgrm

TypeScript
4
star
31

react-contexter

Use Context with Higher Order Components for better testing and reuse.
JavaScript
4
star
32

fnm-playground

I want to add Changesets so I'll do it in a different repo to test
Rust
3
star
33

circleci_redirection

Use Circle CI's artifact system to host binaries and documentation
Crystal
3
star
34

home-automation-cluster

My home automation cluster
TypeScript
3
star
35

functional-programming-smalltalks

small talks about fp in js for Keywee
3
star
36

magic-string-playground

TypeScript
2
star
37

reql-pointfree

Functional RethinkDB functions for point free programming
JavaScript
2
star
38

ical_http_server

another project to play with rust
Rust
2
star
39

neder

really simple and jewish javascript promises
JavaScript
2
star
40

homebrew-tap

A custom homebrew tap
2
star
41

telegram-to-facebook-bot

Post links tagged with `#fullstack` in your Telegram froup to your Facebook group
JavaScript
2
star
42

fb-group-poster

Posts to a facebook group using your credentials using Selenium WebDriver
JavaScript
2
star
43

serverless-graphql-demo

JavaScript
2
star
44

js-playgrounds

Experimental editor agnostic in-line evaluation: like Swift playgrounds, only for JS!
JavaScript
2
star
45

nextjs-example-fetching-gzipped

JavaScript
1
star
46

decompress-wasm-example

JavaScript
1
star
47

streaming-tester

TypeScript
1
star
48

windows-vc-dev-modules-repro

JavaScript
1
star
49

new-dotfiles

Shell
1
star
50

prs_look_for_labels

Look for PR labels in GitHub pull requests
Crystal
1
star
51

jt

small and easy jira ticket helper for bash
Shell
1
star
52

kiwi_json.cr

Save and load typed objects from Kiwi stores
Crystal
1
star
53

delegator.cr

Decorates an object and delegates missing methods to it
Crystal
1
star
54

cargo-mdbook

Distribute https://github.com/rust-lang/mdBook as a npm dependency
TypeScript
1
star
55

redis_mutex.cr

Distributed mutex in Ruby using Redis for Crystal
Crystal
1
star
56

ramda-immutable

Immutable.js helpers for Ramda
JavaScript
1
star
57

test-css-modules

JavaScript
1
star
58

docker-node-gyp

A node docker image with the headers already installed in
1
star
59

m1-node-versions

1
star
60

chrome-fulfill-request-issue

JavaScript
1
star
61

beg-to-differ

Simple string diff patching for node.js
JavaScript
1
star
62

merged_branches.rs

🚮 Delete merged branches from GitHub. Squashed too.
Rust
1
star