File size: 20,116 Bytes
b5ea024
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# sade [![Build Status](https://travis-ci.org/lukeed/sade.svg?branch=master)](https://travis-ci.org/lukeed/sade)

> Smooth (CLI) Operator 🎶

Sade is a small but powerful tool for building command-line interface (CLI) applications for Node.js that are fast, responsive, and helpful!

It enables default commands, git-like subcommands, option flags with aliases, default option values with type-casting, required-vs-optional argument handling, command validation, and automated help text generation!

Your app's UX will be as smooth as butter... just like [Sade's voice](https://www.youtube.com/watch?v=4TYv2PhG89A). 😉


## Install

```
$ npm install --save sade
```


## Usage

***Input:***

```js
#!/usr/bin/env node

const sade = require('sade');

const prog = sade('my-cli');

prog
  .version('1.0.5')
  .option('--global, -g', 'An example global flag')
  .option('-c, --config', 'Provide path to custom config', 'foo.config.js');

prog
  .command('build <src> <dest>')
  .describe('Build the source directory. Expects an `index.js` entry file.')
  .option('-o, --output', 'Change the name of the output file', 'bundle.js')
  .example('build src build --global --config my-conf.js')
  .example('build app public -o main.js')
  .action((src, dest, opts) => {
    console.log(`> building from ${src} to ${dest}`);
    console.log('> these are extra opts', opts);
  });

prog.parse(process.argv);
```

***Output:***

```a
$ my-cli --help

  Usage
    $ my-cli <command> [options]

  Available Commands
    build    Build the source directory.

  For more info, run any command with the `--help` flag
    $ my-cli build --help

  Options
    -v, --version    Displays current version
    -g, --global     An example global flag
    -c, --config     Provide path to custom config  (default foo.config.js)
    -h, --help       Displays this message


$ my-cli build --help

  Description
    Build the source directory.
    Expects an `index.js` entry file.

  Usage
    $ my-cli build <src> [options]

  Options
    -o, --output    Change the name of the output file  (default bundle.js)
    -g, --global    An example global flag
    -c, --config    Provide path to custom config  (default foo.config.js)
    -h, --help      Displays this message

  Examples
    $ my-cli build src build --global --config my-conf.js
    $ my-cli build app public -o main.js
```

## Tips

- **Define your global/program-wide version, options, description, and/or examples first.**<br>
  _Once you define a Command, you can't access the global-scope again._

- **Define all commands & options in the order that you want them to appear.**<br>
  _Sade will not mutate or sort your CLI for you. Global options print before local options._

- **Required arguments without values will error & exit**<br>
  _An `Insufficient arguments!` error will be displayed along with a help prompt._

- **Don't worry about manually displaying help~!**<br>
  _Your help text is displayed automatically... including command-specific help text!_

- **Automatic default/basic patterns**<br>
  _Usage text will always append `[options]` & `--help` and `--version` are done for you._

- **Only define what you want to display!**<br>
  _Help text sections (example, options, etc) will only display if you provide values._


## Subcommands

Subcommands are defined & parsed like any other command! When defining their [`usage`](#usage-1), everything up until the first argument (`[foo]` or `<foo>`) is interpreted as the command string.

They should be defined in the order that you want them to appear in your general `--help` output.

Lastly, it is _not_ necessary to define the subcommand's "base" as an additional command. However, if you choose to do so, it's recommended that you define it first for better visibility.

```js
const prog = sade('git');

// Not necessary for subcommands to work, but it's here anyway!
prog
  .command('remote')
  .describe('Manage set of tracked repositories')
  .action(opts => {
    console.log('~> Print current remotes...');
  });

prog
  .command('remote add <name> <url>', 'Demo...')
  .action((name, url, opts) => {
    console.log(`~> Adding a new remote (${name}) to ${url}`);
  });

prog
  .command('remote rename <old> <new>', 'Demo...')
  .action((old, nxt, opts) => {
    console.log(`~> Renaming from ${old} to ${nxt}~!`);
  });
```


## Single Command Mode

In certain circumstances, you may only need `sade` for a single-command CLI application.

> **Note:** Until `v1.6.0`, this made for an awkward pairing.

To enable this, you may make use of the [`isSingle`](#issingle) argument. Doing so allows you to pass the program's entire [`usage` text](#usage-1) into the `name` argument.

With "Single Command Mode" enabled, your entire binary operates as one command. This means that any [`prog.command`](#progcommandusage-desc-opts) calls are disallowed & will instead throw an Error. Of course, you may still define a program version, a description, an example or two, and declare options. You are customizing the program's attributes as a whole.<sup>*</sup>

> <sup>*</sup> This is true for multi-command applications, too, up until your first `prog.command()` call!

***Example***

Let's reconstruct [`sirv-cli`](https://github.com/lukeed/sirv), which is a single-command application that (optionally) accepts a directory from which to serve files. It also offers a slew of option flags:

```js
sade('sirv [dir]', true)
  .version('1.0.0')
  .describe('Run a static file server')
  .example('public -qeim 31536000')
  .example('--port 8080 --etag')
  .example('my-app --dev')
  .option('-D, --dev', 'Enable "dev" mode')
  .option('-e, --etag', 'Enable "Etag" header')
  // There are a lot...
  .option('-H, --host', 'Hostname to bind', 'localhost')
  .option('-p, --port', 'Port to bind', 5000)
  .action((dir, opts) => {
    // Program handler
  })
  .parse(process.argv);
```

When `sirv --help` is run, the generated help text is trimmed, fully aware that there's only one command in this program:

```
  Description
    Run a static file server

  Usage
    $ sirv [dir] [options]

  Options
    -D, --dev        Enable "dev" mode
    -e, --etag       Enable "Etag" header
    -H, --host       Hostname to bind  (default localhost)
    -p, --port       Port to bind  (default 5000)
    -v, --version    Displays current version
    -h, --help       Displays this message

  Examples
    $ sirv public -qeim 31536000
    $ sirv --port 8080 --etag
    $ sirv my-app --dev
```

## Command Aliases

Command aliases are alternative names (aliases) for a command. They are often used as shortcuts or as typo relief!

The aliased names do not appear in the general help text.<br>
Instead, they only appear within the Command-specific help text under an "Aliases" section.

***Limitations***

* You cannot assign aliases while in [Single Command Mode](#single-command-mode)
* You cannot call [`prog.alias()`](#progaliasnames) before defining any Commands (via `prog.commmand()`)
* You, the developer, must keep track of which aliases have already been used and/or exist as Command names

***Example***

Let's reconstruct the `npm install` command as a Sade program:

```js
sade('npm')
  // ...
  .command('install [package]', 'Install a package', {
    alias: ['i', 'add', 'isntall']
  })
  .option('-P, --save-prod', 'Package will appear in your dependencies.')
  .option('-D, --save-dev', 'Package will appear in your devDependencies.')
  .option('-O, --save-optional', 'Package will appear in your optionalDependencies')
  .option('-E, --save-exact', 'Save exact versions instead of using a semver range operator')
  // ...
```

When we run `npm --help` we'll see this general help text:

```
  Usage
    $ npm <command> [options]

  Available Commands
    install    Install a package

  For more info, run any command with the `--help` flag
    $ npm install --help

  Options
    -v, --version    Displays current version
    -h, --help       Displays this message
```

When we run `npm install --help` &mdash; ***or*** the help flag with any of `install`'s aliases &mdash; we'll see this command-specific help text:

```
  Description
    Install a package

  Usage
    $ npm install [package] [options]

  Aliases
    $ npm i
    $ npm add
    $ npm isntall

  Options
    -P, --save-prod        Package will appear in your dependencies.
    -D, --save-dev         Package will appear in your devDependencies.
    -O, --save-optional    Package will appear in your optionalDependencies
    -E, --save-exact       Save exact versions instead of using a semver range operator
    -h, --help             Displays this message
```



## API

### sade(name, isSingle)
Returns: `Program`

Returns your chainable Sade instance, aka your `Program`.

#### name
Type: `String`<br>
Required: `true`

The name of your `Program` / binary application.

#### isSingle
Type: `Boolean`<br>
Default: `name.includes(' ');`

If your `Program` is meant to have ***only one command***.<br>
When `true`, this simplifies your generated `--help` output such that:

* the "root-level help" is your _only_ help text
* the "root-level help" does not display an `Available Commands` section
* the "root-level help" does not inject `$ name <command>` into the `Usage` section
* the "root-level help" does not display `For more info, run any command with the `--help` flag` text

You may customize the `Usage` of your command by modifying the `name` argument directly.<br>
Please read [Single Command Mode](#single-command-mode) for an example and more information.

> **Important:** Whenever `name` includes a custom usage, then `isSingle` is automatically assumed and enforced!

### prog.command(usage, desc, opts)

Create a new Command for your Program. This changes the current state of your Program.

All configuration methods (`prog.describe`, `prog.action`, etc) will apply to this Command until another Command has been created!

#### usage

Type: `String`

The usage pattern for your current Command. This will be included in the general or command-specific `--help` output.

_Required_ arguments are wrapped with `<` and `>` characters; for example, `<foo>` and `<bar>`.

_Optional_ arguments are wrapped with `[` and `]` characters; for example, `[foo]` and `[bar]`.

All arguments are ***positionally important***, which means they are passed to your current Command's [`handler`](#handler) function in the order that they were defined.

When optional arguments are defined but don't receive a value, their positionally-equivalent function parameter will be `undefined`.

> **Important:** You **must** define & expect required arguments _before_ optional arguments!

```js
sade('foo')

  .command('greet <adjective> <noun>')
  .action((adjective, noun, opts) => {
    console.log(`Hello, ${adjective} ${noun}!`);
  })

  .command('drive <vehicle> [color] [speed]')
  .action((vehicle, color, speed, opts) => {
    let arr = ['Driving my'];
    arr.push(color ? `${color} ${vehicle}` : vehicle);
    speed && arr.push(`at ${speed}`);
    opts.yolo && arr.push('...YOLO!!');
    let str = arr.join(' ');
    console.log(str);
  });
```

```sh
$ foo greet beautiful person
# //=> Hello, beautiful person!

$ foo drive car
# //=> Driving my car

$ foo drive car red
# //=> Driving my red card

$ foo drive car blue 100mph --yolo
# //=> Driving my blue car at 100mph ...YOLO!!
```


#### desc

Type: `String`<br>
Default: `''`

The Command's description. The value is passed directly to [`prog.describe`](#progdescribetext).

#### opts

Type: `Object`<br>
Default: `{}`

##### opts.alias
Type: `String|Array`

Optionally define one or more aliases for the current Command.<br>
When declared, the `opts.alias` value is passed _directly_ to the [`prog.alias`](#progaliasnames) method.

```js
// Program A is equivalent to Program B
// ---

const A = sade('bin')
  .command('build', 'My build command', { alias: 'b' })
  .command('watch', 'My watch command', { alias: ['w', 'dev'] });

const B = sade('bin')
  .command('build', 'My build command').alias('b')
  .command('watch', 'My watch command').alias('w', 'dev');
```


##### opts.default

Type: `Boolean`

Manually set/force the current Command to be the Program's default command. This ensures that the current Command will run if no command was specified.

> **Important:** If you run your Program without a Command _and_ without specifying a default command, your Program will exit with a `No command specified` error.

```js
const prog = sade('greet');

prog.command('hello');
//=> only runs if :: `$ greet hello`

// $ greet
//=> error: No command specified.

prog.command('howdy', '', { default:true });
//=> runs as `$ greet` OR `$ greet howdy`

// $ greet
//=> runs 'howdy' handler

// $ greet foobar
//=> error: Invalid command
```


### prog.describe(text)

Add a description to the current Command.

#### text

Type: `String|Array`

The description text for the current Command. This will be included in the general or command-specific `--help` output.

Internally, your description will be separated into an `Array` of sentences.

For general `--help` output, ***only*** the first sentence will be displayed. However, **all sentences** will be printed for command-specific `--help` text.

> **Note:** Pass an `Array` if you don't want internal assumptions. However, the first item is _always_ displayed in general help, so it's recommended to keep it short.


### prog.alias(...names)

Define one or more aliases for the current Command.

> **Important:** An error will be thrown if:<br>1) the program is in [Single Command Mode](#single-command-mode); or<br>2) `prog.alias` is called before any `prog.command`.

#### names

Type: `String`

The list of alternative names (aliases) for the current Command.<br>
For example, you may want to define shortcuts and/or common typos for the Command's full name.

> **Important:** Sade _does not_ check if the incoming `names` are already in use by other Commands or their aliases.<br>During conflicts, the Command with the same `name` is given priority, otherwise the first Command (according to Program order) with `name` as an alias is chosen.

The `prog.alias()` is append-only, so calling it multiple times within a Command context will _keep_ all aliases, including those initially passed via [`opts.alias`](#optsdefault).

```js
sade('bin')
  .command('hello <name>', 'Greet someone by their name', {
    alias: ['hey', 'yo']
  })
  .alias('hi', 'howdy')
  .alias('hola', 'oi');
//=> hello aliases: hey, yo, hi, howdy, hola, oi
```


### prog.action(handler)

Attach a callback to the current Command.

#### handler

Type: `Function`

The function to run when the current Command is executed.

Its parameters are based (positionally) on your Command's [`usage`](#usage-1) definition.

All options, flags, and extra/unknown values are included as the last parameter.

> **Note:** Optional arguments are also passed as parameters & may be `undefined`!

```js
sade('foo')
  .command('cp <src> <dest>')
  .option('-f, --force', 'Overwrite without confirmation')
  .option('-c, --clone-dir', 'Copy files to additional directory')
  .option('-v, --verbose', 'Enable verbose output')
  .action((src, dest, opts) => {
    console.log(`Copying files from ${src} --> ${dest}`);
    opts.c && console.log(`ALSO copying files from ${src} --> ${opts['clone-dir']}`);
    console.log('My options:', opts);
  })

// $ foo cp original my-copy -v
//=> Copying files from original --> my-copy
//=> My options: { _:[], v:true, verbose:true }

// $ foo cp original my-copy --clone-dir my-backup
//=> Copying files from original --> my-copy
//=> ALSO copying files from original --> my-backup
//=> My options: { _:[], c:'my-backup', 'clone-dir':'my-backup' }
```


### prog.example(str)

Add an example for the current Command.

#### str

Type: `String`

The example string to add. This will be included in the general or command-specific `--help` output.

> **Note:** Your example's `str` will be prefixed with your Program's [`name`](#sadename).


### prog.option(flags, desc, value)

Add an Option to the current Command.

#### flags

Type: `String`

The Option's flags, which may optionally include an alias.

You may use a comma (`,`) or a space (` `) to separate the flags.

> **Note:** The short & long flags can be declared in any order. However, the alias will always be displayed first.

> **Important:** If using hyphenated flag names, they will be accessible **as declared** within your [`action()`](#progactionhandler) handler!

```js
prog.option('--global'); // no alias
prog.option('-g, --global'); // alias first, comma
prog.option('--global -g'); // alias last, space
// etc...
```

#### desc

Type: `String`

The description for the Option.

#### value

Type: `String`

The **default** value for the Option.

Flags and aliases, if parsed, are `true` by default. See [`mri`](https://github.com/lukeed/mri#minimist) for more info.

> **Note:** You probably only want to define a default `value` if you're expecting a `String` or `Number` value type.

If you _do_ pass a `String` or `Number` value type, your flag value will be casted to the same type. See [`mri#options.default`](https://github.com/lukeed/mri#optionsdefault) for info~!


### prog.version(str)

The `--version` and `-v` flags will automatically output the Program version.

#### str

Type: `String`<br>
Default: `0.0.0`

The new version number for your Program.

> **Note:** Your Program `version` is `0.0.0` until you change it.

### prog.parse(arr, opts)

Parse a set of CLI arguments.

#### arr

Type: `Array`

Your Program's `process.argv` input.

> **Important:** Do not `.slice(2)`! Doing so will break parsing~!

#### opts

Type: `Object`<br>
Default: `{}`

Additional `process.argv` parsing config. See [`mri`'s options](https://github.com/lukeed/mri#mriargs-options) for details.

> **Important:** These values _override_ any internal values!

```js
prog
  .command('hello')
  .option('-f, --force', 'My flag');
//=> currently has alias pair: f <--> force

prog.parse(process.argv, {
  alias: {
    f: ['foo', 'fizz']
  },
  default: {
    abc: 123
  }
});
//=> ADDS alias pair: f <--> foo
//=> REMOVES alias pair: f <--> force
//=> ADDS alias pair: f <--> fizz
//=> ADDS default: abc -> 123 (number)
```

#### opts.unknown

Type: `Function`<br>
Default: `undefined`

Callback to run when an unspecified option flag has been found. This is [passed directly to `mri`](https://github.com/lukeed/mri#optionsunknown).

Your handler will receive the unknown flag (string) as its only argument.<br>
You may return a string, which will be used as a custom error message. Otherwise, a default message is displayed.

```js
sade('sirv')
  .command('start [dir]')
  .parse(process.argv, {
    unknown: arg => `Custom error message: ${arg}`
  });

/*
$ sirv start --foobar

  ERROR
    Custom error message: --foobar

  Run `$ sirv --help` for more info.
*/
```

#### opts.lazy

Type: `Boolean`<br>
Default: `false`

If true, Sade will not immediately execute the `action` handler. Instead, `parse()` will return an object of `{ name, args, handler }` shape, wherein the `name` is the command name, `args` is all arguments that _would be_ passed to the action handler, and `handler` is the function itself.

From this, you may choose when to run the `handler` function. You also have the option to further modify the `args` for any reason, if needed.

```js
let { name, args, handler } = prog.parse(process.argv, { lazy:true });
console.log('> Received command: ', name);

// later on...
handler.apply(null, args);
```

### prog.help(cmd)

Manually display the help text for a given command. If no command name is provided, the general/global help is printed.

Your general and command-specific help text is automatically attached to the `--help` and `-h` flags.

> **Note:** You don't have to call this directly! It's automatically run when you `bin --help`

#### cmd
Type: `String`<br>
Default: `null`

The name of the command for which to display help. Otherwise displays the general help.


## License

MIT © [Luke Edwards](https://lukeed.com)