@silvery/commander 0.5.0 → 0.6.1
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 +60 -152
- package/package.json +3 -5
- package/src/colorize.ts +12 -26
- package/src/command.ts +5 -5
package/README.md
CHANGED
|
@@ -1,178 +1,86 @@
|
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
## Usage
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { Command, port, csv } from "@silvery/commander"
|
|
11
|
-
|
|
12
|
-
new Command("deploy")
|
|
13
|
-
.description("Deploy the application")
|
|
14
|
-
.version("1.0.0")
|
|
15
|
-
.option("-p, --port <n>", "Port", port)
|
|
16
|
-
.option("--tags <t>", "Tags", csv)
|
|
17
|
-
.option("-e, --env <e>", "Env", ["dev", "staging", "prod"])
|
|
18
|
-
|
|
19
|
-
program.parse()
|
|
5
|
+
```bash
|
|
6
|
+
npm install @silvery/commander
|
|
20
7
|
```
|
|
21
8
|
|
|
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
|
|
68
|
-
|
|
69
|
-
Pass an array as the third argument to restrict an option to a fixed set of values:
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
.option("-e, --env <e>", "Env", ["dev", "staging", "prod"])
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Commander validates the choice at parse time and rejects invalid values.
|
|
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
|
-
```
|
|
89
|
-
|
|
90
|
-
The `/parse` subpath has zero dependencies -- no Commander, no [Zod](https://github.com/colinhacks/zod).
|
|
91
|
-
|
|
92
|
-
## Standard Schema validation
|
|
93
|
-
|
|
94
|
-
Pass any [Standard Schema v1](https://github.com/standard-schema/standard-schema) schema as the third argument to `.option()`. This works with [Zod](https://github.com/colinhacks/zod) (>=3.24), [Valibot](https://github.com/fabian-hiller/valibot) (>=1.0), [ArkType](https://github.com/arktypeio/arktype) (>=2.0), and any library implementing the protocol:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
import { Command } from "@silvery/commander"
|
|
98
|
-
import { z } from "zod"
|
|
99
|
-
|
|
100
|
-
new Command("deploy")
|
|
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:
|
|
9
|
+
## Example
|
|
115
10
|
|
|
116
11
|
```typescript
|
|
117
12
|
import { Command, z } from "@silvery/commander"
|
|
118
13
|
|
|
119
|
-
new Command("deploy")
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
.option("-
|
|
123
|
-
.option("-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
14
|
+
const program = new Command("deploy")
|
|
15
|
+
.description("Deploy services to an environment")
|
|
16
|
+
.version("1.0.0")
|
|
17
|
+
.option("-e, --env <env>", "Target environment", z.enum(["dev", "staging", "prod"]))
|
|
18
|
+
.option("-f, --force", "Skip confirmation")
|
|
19
|
+
.option("-v, --verbose", "Verbose output")
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command("start <service>")
|
|
23
|
+
.description("Start a service")
|
|
24
|
+
.option("-p, --port <n>", "Port number", z.port)
|
|
25
|
+
.option("-r, --retries <n>", "Retry count", z.int)
|
|
26
|
+
.action((service, opts) => {
|
|
27
|
+
/* ... */
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command("stop <service>")
|
|
32
|
+
.description("Stop a running service")
|
|
33
|
+
.action((service) => {
|
|
34
|
+
/* ... */
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command("status")
|
|
39
|
+
.description("Show service status")
|
|
40
|
+
.option("--json", "Output as JSON")
|
|
41
|
+
.action((opts) => {
|
|
42
|
+
/* ... */
|
|
43
|
+
})
|
|
133
44
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
45
|
+
program.parse()
|
|
46
|
+
const { env, force, verbose } = program.opts()
|
|
47
|
+
// │ │ └─ boolean | undefined
|
|
48
|
+
// │ └────────── boolean | undefined
|
|
49
|
+
// └──────────────── "dev" | "staging" | "prod"
|
|
139
50
|
```
|
|
140
51
|
|
|
141
|
-
|
|
52
|
+
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.
|
|
142
53
|
|
|
143
|
-
|
|
54
|
+
[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.
|
|
144
55
|
|
|
145
|
-
|
|
146
|
-
import { Command } from "commander"
|
|
147
|
-
import { colorizeHelp } from "@silvery/commander"
|
|
56
|
+
Help is auto-colorized — bold headings, green flags, cyan commands, dim descriptions:
|
|
148
57
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="help-output.svg" alt="Colorized help output" width="80%" />
|
|
60
|
+
</p>
|
|
152
61
|
|
|
153
|
-
|
|
62
|
+
Options with [Zod](https://github.com/colinhacks/zod) schemas or built-in types are validated at parse time with clear error messages.
|
|
154
63
|
|
|
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 |
|
|
64
|
+
## What's included
|
|
159
65
|
|
|
160
|
-
|
|
66
|
+
- **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)
|
|
67
|
+
- **Typed `.option()` parsing** — pass a type as the third argument:
|
|
68
|
+
- 14 built-in types — `port`, `int`, `csv`, `url`, `email`, `date`, [more](https://silvery.dev/reference/commander)
|
|
69
|
+
- Array choices — `["dev", "staging", "prod"]`
|
|
70
|
+
- [Zod](https://github.com/colinhacks/zod) schemas — `z.port`, `z.int`, `z.csv`, or any custom `z.string()`, `z.number()`, etc.
|
|
71
|
+
- Any [Standard Schema](https://github.com/standard-schema/standard-schema) library — [Valibot](https://github.com/fabian-hiller/valibot), [ArkType](https://github.com/arktypeio/arktype)
|
|
72
|
+
- All types usable standalone via `.parse()`/`.safeParse()`
|
|
161
73
|
|
|
162
|
-
|
|
74
|
+
## Docs
|
|
163
75
|
|
|
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
|
|
76
|
+
Full reference, type table, and API details at **[silvery.dev/reference/commander](https://silvery.dev/reference/commander)**.
|
|
169
77
|
|
|
170
78
|
## Credits
|
|
171
79
|
|
|
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)**
|
|
80
|
+
- **[Commander.js](https://github.com/tj/commander.js)** by TJ Holowaychuk and contributors
|
|
81
|
+
- **[@commander-js/extra-typings](https://github.com/commander-js/extra-typings)** — inspired the type inference approach
|
|
82
|
+
- **[Standard Schema](https://github.com/standard-schema/standard-schema)** — universal schema interop protocol
|
|
83
|
+
- **[@silvery/ansi](https://github.com/beorn/silvery/tree/main/packages/ansi)** — terminal capability detection
|
|
176
84
|
|
|
177
85
|
## License
|
|
178
86
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silvery/commander",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Colorized Commander.js help output using ANSI escape codes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ansi",
|
|
@@ -29,16 +29,14 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@silvery/ansi": ">=0.1.0",
|
|
33
|
+
"@silvery/style": ">=0.1.0",
|
|
32
34
|
"commander": ">=12.0.0"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
|
-
"@silvery/ansi": ">=0.1.0",
|
|
36
37
|
"zod": ">=3.0.0"
|
|
37
38
|
},
|
|
38
39
|
"peerDependenciesMeta": {
|
|
39
|
-
"@silvery/ansi": {
|
|
40
|
-
"optional": true
|
|
41
|
-
},
|
|
42
40
|
"zod": {
|
|
43
41
|
"optional": true
|
|
44
42
|
}
|
package/src/colorize.ts
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
* or plain commander — accepts a minimal CommandLike interface so Commander
|
|
7
7
|
* is a peer dependency, not a hard one.
|
|
8
8
|
*
|
|
9
|
-
* Zero dependencies — only raw ANSI escape codes.
|
|
10
|
-
*
|
|
11
9
|
* @example
|
|
12
10
|
* ```ts
|
|
13
11
|
* import { Command } from "@silvery/commander"
|
|
@@ -18,39 +16,27 @@
|
|
|
18
16
|
* ```
|
|
19
17
|
*/
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
import { MODIFIERS, FG_COLORS } from "@silvery/style"
|
|
20
|
+
import { detectColor } from "@silvery/ansi"
|
|
21
|
+
|
|
22
|
+
// Derive ANSI escape sequences from @silvery/style constants.
|
|
22
23
|
const RESET = "\x1b[0m"
|
|
23
|
-
const BOLD =
|
|
24
|
-
const DIM =
|
|
25
|
-
const CYAN =
|
|
26
|
-
const GREEN =
|
|
27
|
-
const YELLOW =
|
|
24
|
+
const BOLD = `\x1b[${MODIFIERS.bold![0]}m`
|
|
25
|
+
const DIM = `\x1b[${MODIFIERS.dim![0]}m`
|
|
26
|
+
const CYAN = `\x1b[${FG_COLORS.cyan}m`
|
|
27
|
+
const GREEN = `\x1b[${FG_COLORS.green}m`
|
|
28
|
+
const YELLOW = `\x1b[${FG_COLORS.yellow}m`
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Check if color output should be enabled.
|
|
31
|
-
* Uses @silvery/ansi detectColor()
|
|
32
|
-
*
|
|
32
|
+
* Uses @silvery/ansi detectColor() for full detection (respects NO_COLOR,
|
|
33
|
+
* FORCE_COLOR, TERM, etc.).
|
|
33
34
|
*/
|
|
34
35
|
let _shouldColorize: boolean | undefined
|
|
35
36
|
|
|
36
37
|
export function shouldColorize(): boolean {
|
|
37
38
|
if (_shouldColorize !== undefined) return _shouldColorize
|
|
38
|
-
|
|
39
|
-
// Try @silvery/ansi for full detection (respects NO_COLOR, FORCE_COLOR, TERM, etc.)
|
|
40
|
-
try {
|
|
41
|
-
const { detectColor } = require("@silvery/ansi") as { detectColor: (stdout: NodeJS.WriteStream) => string | null }
|
|
42
|
-
_shouldColorize = detectColor(process.stdout) !== null
|
|
43
|
-
} catch {
|
|
44
|
-
// Fallback: basic NO_COLOR / FORCE_COLOR / isTTY checks
|
|
45
|
-
if (process.env.NO_COLOR !== undefined) {
|
|
46
|
-
_shouldColorize = false
|
|
47
|
-
} else if (process.env.FORCE_COLOR !== undefined) {
|
|
48
|
-
_shouldColorize = true
|
|
49
|
-
} else {
|
|
50
|
-
_shouldColorize = process.stdout?.isTTY ?? true
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
39
|
+
_shouldColorize = detectColor(process.stdout) !== null
|
|
54
40
|
return _shouldColorize
|
|
55
41
|
}
|
|
56
42
|
|
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
|
}
|