@silvery/commander 0.6.0 → 0.7.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 +34 -24
- package/package.json +2 -5
- package/src/colorize.ts +44 -103
package/README.md
CHANGED
|
@@ -12,44 +12,54 @@ npm install @silvery/commander
|
|
|
12
12
|
import { Command, z } from "@silvery/commander"
|
|
13
13
|
|
|
14
14
|
const program = new Command("deploy")
|
|
15
|
-
.description("Deploy to an environment")
|
|
15
|
+
.description("Deploy services to an environment")
|
|
16
16
|
.version("1.0.0")
|
|
17
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")
|
|
18
24
|
.option("-p, --port <n>", "Port number", z.port)
|
|
19
25
|
.option("-r, --retries <n>", "Retry count", z.int)
|
|
20
|
-
.
|
|
21
|
-
|
|
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
|
+
})
|
|
22
44
|
|
|
23
45
|
program.parse()
|
|
24
|
-
const { env,
|
|
25
|
-
// │
|
|
26
|
-
// │
|
|
27
|
-
//
|
|
28
|
-
// │ └──────────────────────── number (1–65535)
|
|
29
|
-
// └─────────────────────────────── "dev" | "staging" | "prod"
|
|
46
|
+
const { env, force, verbose } = program.opts()
|
|
47
|
+
// │ │ └─ boolean | undefined
|
|
48
|
+
// │ └────────── boolean | undefined
|
|
49
|
+
// └──────────────── "dev" | "staging" | "prod"
|
|
30
50
|
```
|
|
31
51
|
|
|
32
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.
|
|
33
53
|
|
|
34
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.
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<b>Usage:</b> <span style="color:#56b6c2">deploy</span> <span style="color:#98c379">[options]</span>
|
|
39
|
-
|
|
40
|
-
Deploy to an environment
|
|
56
|
+
Help is auto-colorized — bold headings, green flags, cyan commands, dim descriptions:
|
|
41
57
|
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
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>
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="help-output.svg" alt="Colorized help output" width="80%" />
|
|
60
|
+
</p>
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
Options with [Zod](https://github.com/colinhacks/zod) schemas or built-in types are validated at parse time with clear error messages.
|
|
53
63
|
|
|
54
64
|
## What's included
|
|
55
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silvery/commander",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Colorized Commander.js help output using ANSI escape codes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ansi",
|
|
@@ -29,16 +29,13 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@silvery/ansi": ">=1.0.0",
|
|
32
33
|
"commander": ">=12.0.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"@silvery/ansi": ">=0.1.0",
|
|
36
36
|
"zod": ">=3.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
|
-
"@silvery/ansi": {
|
|
40
|
-
"optional": true
|
|
41
|
-
},
|
|
42
39
|
"zod": {
|
|
43
40
|
"optional": true
|
|
44
41
|
}
|
package/src/colorize.ts
CHANGED
|
@@ -1,71 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Commander.js help colorization using
|
|
2
|
+
* Commander.js help colorization using @silvery/ansi.
|
|
3
3
|
*
|
|
4
4
|
* Uses Commander's built-in style hooks (styleTitle, styleOptionText, etc.)
|
|
5
|
-
* rather than regex post-processing.
|
|
6
|
-
* or plain commander — accepts a minimal CommandLike interface so Commander
|
|
7
|
-
* is a peer dependency, not a hard one.
|
|
8
|
-
*
|
|
9
|
-
* Zero dependencies — only raw ANSI escape codes.
|
|
5
|
+
* rather than regex post-processing.
|
|
10
6
|
*
|
|
11
7
|
* @example
|
|
12
8
|
* ```ts
|
|
13
9
|
* import { Command } from "@silvery/commander"
|
|
10
|
+
* // Command auto-colorizes in its constructor — no manual call needed.
|
|
11
|
+
* // For plain Commander:
|
|
14
12
|
* import { colorizeHelp } from "@silvery/commander"
|
|
15
|
-
*
|
|
16
|
-
* const program = new Command("myapp").description("My CLI tool")
|
|
17
13
|
* colorizeHelp(program)
|
|
18
14
|
* ```
|
|
19
15
|
*/
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const YELLOW = "\x1b[33m"
|
|
17
|
+
import { createStyle } from "@silvery/ansi"
|
|
18
|
+
|
|
19
|
+
// Auto-detect terminal color level. The Style instance handles the full
|
|
20
|
+
// degradation chain: truecolor → 256 → basic (ANSI 16) → null (no color).
|
|
21
|
+
// When level is null (NO_COLOR), style methods return plain text.
|
|
22
|
+
const s = createStyle()
|
|
28
23
|
|
|
29
24
|
/**
|
|
30
25
|
* Check if color output should be enabled.
|
|
31
|
-
*
|
|
32
|
-
* NO_COLOR/FORCE_COLOR/isTTY checks.
|
|
26
|
+
* Delegates to @silvery/ansi's auto-detection (NO_COLOR, FORCE_COLOR, TERM).
|
|
33
27
|
*/
|
|
34
|
-
let _shouldColorize: boolean | undefined
|
|
35
|
-
|
|
36
28
|
export function shouldColorize(): boolean {
|
|
37
|
-
|
|
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
|
-
|
|
54
|
-
return _shouldColorize
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Wrap a string with ANSI codes, handling nested resets. */
|
|
58
|
-
function ansi(text: string, code: string): string {
|
|
59
|
-
return `${code}${text}${RESET}`
|
|
29
|
+
return s.level > 0
|
|
60
30
|
}
|
|
61
31
|
|
|
62
32
|
/**
|
|
63
33
|
* Minimal interface for Commander's Command — avoids requiring Commander
|
|
64
34
|
* as a direct dependency. Works with both `commander` and
|
|
65
35
|
* `@silvery/commander`.
|
|
66
|
-
*
|
|
67
|
-
* Uses permissive types to ensure structural compatibility with all
|
|
68
|
-
* Commander versions, overloads, and generic instantiations.
|
|
69
36
|
*/
|
|
70
37
|
export interface CommandLike {
|
|
71
38
|
// biome-ignore lint: permissive to match Commander's overloaded signatures
|
|
@@ -76,82 +43,56 @@ export interface CommandLike {
|
|
|
76
43
|
readonly commands: readonly any[]
|
|
77
44
|
}
|
|
78
45
|
|
|
79
|
-
/** Color scheme for help output.
|
|
46
|
+
/** Color scheme for help output. Each value is a styling function (text → styled text). */
|
|
80
47
|
export interface ColorizeHelpOptions {
|
|
81
|
-
/**
|
|
82
|
-
commands?: string
|
|
83
|
-
/**
|
|
84
|
-
flags?: string
|
|
85
|
-
/**
|
|
86
|
-
description?: string
|
|
87
|
-
/**
|
|
88
|
-
heading?: string
|
|
89
|
-
/**
|
|
90
|
-
brackets?: string
|
|
48
|
+
/** Style for command/subcommand names. Default: cyan */
|
|
49
|
+
commands?: (text: string) => string
|
|
50
|
+
/** Style for --flags and -short options. Default: green */
|
|
51
|
+
flags?: (text: string) => string
|
|
52
|
+
/** Style for description text. Default: dim */
|
|
53
|
+
description?: (text: string) => string
|
|
54
|
+
/** Style for section headings (Usage:, Options:, etc.). Default: bold */
|
|
55
|
+
heading?: (text: string) => string
|
|
56
|
+
/** Style for <required> and [optional] argument brackets. Default: yellow */
|
|
57
|
+
brackets?: (text: string) => string
|
|
91
58
|
}
|
|
92
59
|
|
|
93
60
|
/**
|
|
94
61
|
* Apply colorized help output to a Commander.js program and all its subcommands.
|
|
95
62
|
*
|
|
96
63
|
* Uses Commander's built-in `configureHelp()` style hooks rather than
|
|
97
|
-
* post-processing the formatted string.
|
|
98
|
-
* formatting changes in Commander and handles wrapping correctly.
|
|
64
|
+
* post-processing the formatted string.
|
|
99
65
|
*
|
|
100
66
|
* @param program - A Commander Command instance (or compatible object)
|
|
101
|
-
* @param options - Override default
|
|
67
|
+
* @param options - Override default style functions for each element
|
|
102
68
|
*/
|
|
103
69
|
export function colorizeHelp(program: CommandLike, options?: ColorizeHelpOptions): void {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
70
|
+
// Ensure style generates codes — at minimum basic (ANSI 16).
|
|
71
|
+
// Auto-detected level may be higher (256/truecolor) for richer output.
|
|
72
|
+
// May be 0 if NO_COLOR is set; in that case, force basic since Commander
|
|
73
|
+
// handles the final strip via configureOutput.
|
|
74
|
+
if (s.level === 0) s.level = 1
|
|
75
|
+
|
|
76
|
+
const cmds = options?.commands ?? ((t: string) => s.cyan(t))
|
|
77
|
+
const flags = options?.flags ?? ((t: string) => s.green(t))
|
|
78
|
+
const desc = options?.description ?? ((t: string) => s.dim(t))
|
|
79
|
+
const heading = options?.heading ?? ((t: string) => s.bold(t))
|
|
80
|
+
const brackets = options?.brackets ?? ((t: string) => s.yellow(t))
|
|
109
81
|
|
|
110
82
|
const helpConfig: Record<string, unknown> = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return ansi(str, cmds)
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
// Option terms: "-v, --verbose", "--repo <path>", "[options]"
|
|
122
|
-
styleOptionText(str: string): string {
|
|
123
|
-
return ansi(str, flags)
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
// Subcommand names in the commands list
|
|
127
|
-
styleSubcommandText(str: string): string {
|
|
128
|
-
return ansi(str, cmds)
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
// Argument terms: "<file>", "[dir]"
|
|
132
|
-
styleArgumentText(str: string): string {
|
|
133
|
-
return ansi(str, brackets)
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
// Description text for options, subcommands, arguments
|
|
137
|
-
styleDescriptionText(str: string): string {
|
|
138
|
-
return ansi(str, desc)
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
// Command description (the main program description line) — keep normal
|
|
142
|
-
styleCommandDescription(str: string): string {
|
|
143
|
-
return str
|
|
144
|
-
},
|
|
83
|
+
styleTitle: (str: string) => heading(str),
|
|
84
|
+
styleCommandText: (str: string) => cmds(str),
|
|
85
|
+
styleOptionText: (str: string) => flags(str),
|
|
86
|
+
styleSubcommandText: (str: string) => cmds(str),
|
|
87
|
+
styleArgumentText: (str: string) => brackets(str),
|
|
88
|
+
styleDescriptionText: (str: string) => desc(str),
|
|
89
|
+
styleCommandDescription: (str: string) => str,
|
|
145
90
|
}
|
|
146
91
|
|
|
147
92
|
program.configureHelp(helpConfig)
|
|
148
93
|
|
|
149
94
|
// Tell Commander that color output is supported, even when stdout is not
|
|
150
|
-
// a TTY
|
|
151
|
-
// all ANSI codes from helpInformation() output.
|
|
152
|
-
//
|
|
153
|
-
// Callers who want to respect NO_COLOR/FORCE_COLOR should check
|
|
154
|
-
// shouldColorize() before calling colorizeHelp().
|
|
95
|
+
// a TTY. Without this, Commander strips ANSI codes from helpInformation().
|
|
155
96
|
program.configureOutput({
|
|
156
97
|
getOutHasColors: () => true,
|
|
157
98
|
getErrHasColors: () => true,
|