@silvery/commander 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -150
- package/package.json +1 -1
- package/src/command.ts +5 -5
package/README.md
CHANGED
|
@@ -1,178 +1,76 @@
|
|
|
1
1
|
# @silvery/commander
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe [Commander.js](https://github.com/tj/commander.js) with validated options, colorized help, and [Standard Schema](https://github.com/standard-schema/standard-schema) support. Drop-in replacement — `Command` extends Commander's `Command`. Install once, Commander is included.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install @silvery/commander
|
|
7
|
+
```
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## Example
|
|
8
10
|
|
|
9
11
|
```typescript
|
|
10
|
-
import { Command,
|
|
12
|
+
import { Command, z } from "@silvery/commander"
|
|
11
13
|
|
|
12
|
-
new Command("deploy")
|
|
13
|
-
.description("Deploy
|
|
14
|
+
const program = new Command("deploy")
|
|
15
|
+
.description("Deploy to an environment")
|
|
14
16
|
.version("1.0.0")
|
|
15
|
-
.option("-
|
|
16
|
-
.option("--
|
|
17
|
-
.option("-
|
|
17
|
+
.option("-e, --env <env>", "Target environment", z.enum(["dev", "staging", "prod"]))
|
|
18
|
+
.option("-p, --port <n>", "Port number", z.port)
|
|
19
|
+
.option("-r, --retries <n>", "Retry count", z.int)
|
|
20
|
+
.option("--tags <t>", "Labels", z.csv)
|
|
21
|
+
.option("-f, --force", "Skip confirmation")
|
|
18
22
|
|
|
19
23
|
program.parse()
|
|
24
|
+
const { env, port, retries, tags, force } = program.opts()
|
|
25
|
+
// │ │ │ │ └─ boolean | undefined
|
|
26
|
+
// │ │ │ └──────── string[]
|
|
27
|
+
// │ │ └────────────────── number
|
|
28
|
+
// │ └──────────────────────── number (1–65535)
|
|
29
|
+
// └─────────────────────────────── "dev" | "staging" | "prod"
|
|
20
30
|
```
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Colorization works out of the box with raw ANSI codes. Install [`@silvery/ansi`](https://github.com/beorn/silvery/tree/main/packages/ansi) for full terminal capability detection (respects `NO_COLOR`, `FORCE_COLOR`, and `isTTY`).
|
|
25
|
-
|
|
26
|
-
## Validated options with built-in types
|
|
27
|
-
|
|
28
|
-
Commander's `.option()` accepts a string and gives you a string back. Our built-in types parse and validate in one step:
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import { Command, port, csv, int } from "@silvery/commander"
|
|
32
|
-
|
|
33
|
-
new Command("deploy")
|
|
34
|
-
.option("-p, --port <n>", "Port", port) // number (1-65535, validated)
|
|
35
|
-
.option("--tags <t>", "Tags", csv) // string[]
|
|
36
|
-
.option("-r, --retries <n>", "Retries", int) // number (integer)
|
|
37
|
-
.option("-e, --env <e>", "Env", ["dev", "staging", "prod"]) // choices
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
These types are **not part of Commander** -- they're provided by `@silvery/commander`. Each implements [Standard Schema v1](https://github.com/standard-schema/standard-schema), so they work with any schema-aware tooling. They have zero dependencies.
|
|
41
|
-
|
|
42
|
-
### Available types
|
|
43
|
-
|
|
44
|
-
| Type | Output | Validation |
|
|
45
|
-
| ------- | ---------- | ---------------------------------------- |
|
|
46
|
-
| `int` | `number` | Integer (coerced from string) |
|
|
47
|
-
| `uint` | `number` | Unsigned integer (>= 0) |
|
|
48
|
-
| `float` | `number` | Any finite number (rejects NaN) |
|
|
49
|
-
| `port` | `number` | Integer 1-65535 |
|
|
50
|
-
| `url` | `string` | Valid URL (via `URL` constructor) |
|
|
51
|
-
| `path` | `string` | Non-empty string |
|
|
52
|
-
| `csv` | `string[]` | Comma-separated, trimmed, empty filtered |
|
|
53
|
-
| `json` | `unknown` | Parsed JSON |
|
|
54
|
-
| `bool` | `boolean` | true/false/yes/no/1/0 (case-insensitive) |
|
|
55
|
-
| `date` | `Date` | Valid date string |
|
|
56
|
-
| `email` | `string` | Basic email validation |
|
|
57
|
-
| `regex` | `RegExp` | Valid regex pattern |
|
|
58
|
-
|
|
59
|
-
### Factory type
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
import { intRange } from "@silvery/commander"
|
|
63
|
-
|
|
64
|
-
intRange(1, 100) // CLIType<number> -- integer within bounds
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Array choices
|
|
32
|
+
With plain Commander, `opts()` returns `Record<string, any>` — every value is untyped. With `@silvery/commander`, each option's type is inferred from its schema: `z.port` produces `number`, `z.enum(...)` produces a union, `z.csv` produces `string[]`. Invalid values are rejected at parse time with clear error messages — not silently passed through as strings.
|
|
68
33
|
|
|
69
|
-
|
|
34
|
+
[Zod](https://github.com/colinhacks/zod) is entirely optional — `z` is tree-shaken from your bundle if you don't import it. Without Zod, use the built-in types (`port`, `int`, `csv`) or plain Commander.
|
|
70
35
|
|
|
71
|
-
|
|
72
|
-
.option("-e, --env <e>", "Env", ["dev", "staging", "prod"])
|
|
73
|
-
```
|
|
36
|
+
<pre><code>$ deploy --help
|
|
74
37
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### Standalone usage
|
|
78
|
-
|
|
79
|
-
Types work outside Commander too -- for validating env vars, config files, etc.:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import { port, csv } from "@silvery/commander/parse"
|
|
83
|
-
|
|
84
|
-
port.parse("3000") // 3000
|
|
85
|
-
port.parse("abc") // throws: 'Expected port (1-65535), got "abc"'
|
|
86
|
-
port.safeParse("3000") // { success: true, value: 3000 }
|
|
87
|
-
port.safeParse("abc") // { success: false, issues: [{ message: "..." }] }
|
|
88
|
-
```
|
|
38
|
+
<b>Usage:</b> <span style="color:#56b6c2">deploy</span> <span style="color:#98c379">[options]</span>
|
|
89
39
|
|
|
90
|
-
|
|
40
|
+
Deploy to an environment
|
|
91
41
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
.option("-p, --port <n>", "Port", z.coerce.number().min(1).max(65535))
|
|
102
|
-
.option("-e, --env <env>", "Env", z.enum(["dev", "staging", "prod"]))
|
|
103
|
-
.option(
|
|
104
|
-
"--tags <t>",
|
|
105
|
-
"Tags",
|
|
106
|
-
z.string().transform((v) => v.split(",")),
|
|
107
|
-
)
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Schema libraries are optional peer dependencies -- detected at runtime, never imported at the top level.
|
|
111
|
-
|
|
112
|
-
## Zod CLI types
|
|
113
|
-
|
|
114
|
-
Import `z` from `@silvery/commander` for [Zod](https://github.com/colinhacks/zod) extended with CLI-specific schemas:
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
import { Command, z } from "@silvery/commander"
|
|
118
|
-
|
|
119
|
-
new Command("deploy")
|
|
120
|
-
.option("-p, --port <n>", "Port", z.port)
|
|
121
|
-
.option("--tags <t>", "Tags", z.csv)
|
|
122
|
-
.option("-r, --retries <n>", "Retries", z.int)
|
|
123
|
-
.option("-e, --env <e>", "Env", ["dev", "staging", "prod"])
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
The `z` export is tree-shakeable -- if you don't import it, [Zod](https://github.com/colinhacks/zod) won't be in your bundle. Requires `zod` as a peer dependency.
|
|
127
|
-
|
|
128
|
-
Available: `z.port`, `z.int`, `z.uint`, `z.float`, `z.csv`, `z.url`, `z.path`, `z.email`, `z.date`, `z.json`, `z.bool`, `z.intRange(min, max)`.
|
|
129
|
-
|
|
130
|
-
## Function parsers
|
|
131
|
-
|
|
132
|
-
[Commander's](https://github.com/tj/commander.js) standard parser function pattern also works:
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
new Command("app")
|
|
136
|
-
.option("-p, --port <n>", "Port", parseInt) // number
|
|
137
|
-
.option("--tags <items>", "Tags", (v) => v.split(",")) // string[]
|
|
138
|
-
.option("-p, --port <n>", "Port", parseInt, 8080) // number (with default)
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## colorizeHelp()
|
|
142
|
-
|
|
143
|
-
Use standalone with a plain [Commander](https://github.com/tj/commander.js) `Command` (without subclassing):
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
import { Command } from "commander"
|
|
147
|
-
import { colorizeHelp } from "@silvery/commander"
|
|
148
|
-
|
|
149
|
-
const program = new Command("myapp")
|
|
150
|
-
colorizeHelp(program) // applies recursively to all subcommands
|
|
151
|
-
```
|
|
42
|
+
<b>Options:</b>
|
|
43
|
+
<span style="color:#98c379">-V, --version</span> <span style="color:#888">output the version number</span>
|
|
44
|
+
<span style="color:#98c379">-e, --env <env></span> <span style="color:#888">Target environment</span>
|
|
45
|
+
<span style="color:#98c379">-p, --port <n></span> <span style="color:#888">Port number</span>
|
|
46
|
+
<span style="color:#98c379">-r, --retries <n></span> <span style="color:#888">Retry count</span>
|
|
47
|
+
<span style="color:#98c379">--tags <t></span> <span style="color:#888">Labels</span>
|
|
48
|
+
<span style="color:#98c379">-f, --force</span> <span style="color:#888">Skip confirmation</span>
|
|
49
|
+
<span style="color:#98c379">-h, --help</span> <span style="color:#888">display help for command</span>
|
|
50
|
+
</code></pre>
|
|
152
51
|
|
|
153
|
-
|
|
52
|
+
Help is auto-colorized — bold headings, green flags, cyan commands, dim descriptions. Options with [Zod](https://github.com/colinhacks/zod) schemas or built-in types are validated at parse time with clear error messages.
|
|
154
53
|
|
|
155
|
-
|
|
156
|
-
| -------------------------- | ------------------------------- | ----------------------------------------------- |
|
|
157
|
-
| `@silvery/commander` | Command, colorizeHelp, types, z | [commander](https://github.com/tj/commander.js) |
|
|
158
|
-
| `@silvery/commander/parse` | Types only (.parse/.safeParse) | none |
|
|
54
|
+
## What's included
|
|
159
55
|
|
|
160
|
-
|
|
56
|
+
- **Colorized help** — automatic, with color level detection and [`NO_COLOR`](https://no-color.org)/`FORCE_COLOR` support via [`@silvery/ansi`](https://github.com/beorn/silvery/tree/main/packages/ansi) (optional)
|
|
57
|
+
- **Typed `.option()` parsing** — pass a type as the third argument:
|
|
58
|
+
- 14 built-in types — `port`, `int`, `csv`, `url`, `email`, `date`, [more](https://silvery.dev/reference/commander)
|
|
59
|
+
- Array choices — `["dev", "staging", "prod"]`
|
|
60
|
+
- [Zod](https://github.com/colinhacks/zod) schemas — `z.port`, `z.int`, `z.csv`, or any custom `z.string()`, `z.number()`, etc.
|
|
61
|
+
- Any [Standard Schema](https://github.com/standard-schema/standard-schema) library — [Valibot](https://github.com/fabian-hiller/valibot), [ArkType](https://github.com/arktypeio/arktype)
|
|
62
|
+
- All types usable standalone via `.parse()`/`.safeParse()`
|
|
161
63
|
|
|
162
|
-
|
|
64
|
+
## Docs
|
|
163
65
|
|
|
164
|
-
|
|
165
|
-
- **Built-in validation** via [Standard Schema](https://github.com/standard-schema/standard-schema) -- works with [Zod](https://github.com/colinhacks/zod), [Valibot](https://github.com/fabian-hiller/valibot), [ArkType](https://github.com/arktypeio/arktype)
|
|
166
|
-
- **14 CLI types** -- `port`, `csv`, `int`, `url`, `email` and more, usable standalone via `.parse()`/`.safeParse()`
|
|
167
|
-
- **NO_COLOR support** via [`@silvery/ansi`](https://github.com/beorn/silvery/tree/main/packages/ansi) (optional)
|
|
168
|
-
- **Commander included** -- one install, no peer dep setup
|
|
66
|
+
Full reference, type table, and API details at **[silvery.dev/reference/commander](https://silvery.dev/reference/commander)**.
|
|
169
67
|
|
|
170
68
|
## Credits
|
|
171
69
|
|
|
172
|
-
- **[Commander.js](https://github.com/tj/commander.js)** by TJ Holowaychuk and contributors
|
|
173
|
-
- **[@commander-js/extra-typings](https://github.com/commander-js/extra-typings)**
|
|
174
|
-
- **[Standard Schema](https://github.com/standard-schema/standard-schema)**
|
|
175
|
-
- **[@silvery/ansi](https://github.com/beorn/silvery/tree/main/packages/ansi)**
|
|
70
|
+
- **[Commander.js](https://github.com/tj/commander.js)** by TJ Holowaychuk and contributors
|
|
71
|
+
- **[@commander-js/extra-typings](https://github.com/commander-js/extra-typings)** — inspired the type inference approach
|
|
72
|
+
- **[Standard Schema](https://github.com/standard-schema/standard-schema)** — universal schema interop protocol
|
|
73
|
+
- **[@silvery/ansi](https://github.com/beorn/silvery/tree/main/packages/ansi)** — terminal capability detection
|
|
176
74
|
|
|
177
75
|
## License
|
|
178
76
|
|
package/package.json
CHANGED
package/src/command.ts
CHANGED
|
@@ -96,17 +96,17 @@ export class Command extends BaseCommand {
|
|
|
96
96
|
* 4. **Function** -- passed through as Commander's parser function
|
|
97
97
|
* 5. **Anything else** -- passed through as a default value
|
|
98
98
|
*/
|
|
99
|
-
option(flags: string, description?: string, parseArgOrDefault?: any, defaultValue?: any): this {
|
|
99
|
+
override option(flags: string, description?: string, parseArgOrDefault?: any, defaultValue?: any): this {
|
|
100
100
|
if (Array.isArray(parseArgOrDefault)) {
|
|
101
|
-
const opt = new Option(flags, description ?? "").choices(parseArgOrDefault)
|
|
101
|
+
const opt = new Option(flags, description ?? "").choices(parseArgOrDefault as string[])
|
|
102
102
|
this.addOption(opt)
|
|
103
103
|
return this
|
|
104
104
|
}
|
|
105
105
|
if (isStandardSchema(parseArgOrDefault)) {
|
|
106
|
-
return super.option(flags, description ?? "", standardSchemaParser(parseArgOrDefault))
|
|
106
|
+
return super.option(flags, description ?? "", standardSchemaParser(parseArgOrDefault), defaultValue)
|
|
107
107
|
}
|
|
108
108
|
if (isLegacyZodSchema(parseArgOrDefault)) {
|
|
109
|
-
return super.option(flags, description ?? "", legacyZodParser(parseArgOrDefault))
|
|
109
|
+
return super.option(flags, description ?? "", legacyZodParser(parseArgOrDefault), defaultValue)
|
|
110
110
|
}
|
|
111
111
|
if (typeof parseArgOrDefault === "function") {
|
|
112
112
|
return super.option(flags, description ?? "", parseArgOrDefault, defaultValue)
|
|
@@ -115,7 +115,7 @@ export class Command extends BaseCommand {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// Subcommands also get colorized help, Standard Schema, and array choices
|
|
118
|
-
createCommand(name?: string): Command {
|
|
118
|
+
override createCommand(name?: string): Command {
|
|
119
119
|
return new Command(name)
|
|
120
120
|
}
|
|
121
121
|
}
|