@silvery/commander 0.6.1 → 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.
Files changed (2) hide show
  1. package/package.json +2 -3
  2. package/src/colorize.ts +43 -88
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silvery/commander",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Colorized Commander.js help output using ANSI escape codes",
5
5
  "keywords": [
6
6
  "ansi",
@@ -29,8 +29,7 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@silvery/ansi": ">=0.1.0",
33
- "@silvery/style": ">=0.1.0",
32
+ "@silvery/ansi": ">=1.0.0",
34
33
  "commander": ">=12.0.0"
35
34
  },
36
35
  "peerDependencies": {
package/src/colorize.ts CHANGED
@@ -1,57 +1,38 @@
1
1
  /**
2
- * Commander.js help colorization using ANSI escape codes.
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. Works with @silvery/commander
6
- * or plain commander — accepts a minimal CommandLike interface so Commander
7
- * is a peer dependency, not a hard one.
5
+ * rather than regex post-processing.
8
6
  *
9
7
  * @example
10
8
  * ```ts
11
9
  * import { Command } from "@silvery/commander"
10
+ * // Command auto-colorizes in its constructor — no manual call needed.
11
+ * // For plain Commander:
12
12
  * import { colorizeHelp } from "@silvery/commander"
13
- *
14
- * const program = new Command("myapp").description("My CLI tool")
15
13
  * colorizeHelp(program)
16
14
  * ```
17
15
  */
18
16
 
19
- import { MODIFIERS, FG_COLORS } from "@silvery/style"
20
- import { detectColor } from "@silvery/ansi"
17
+ import { createStyle } from "@silvery/ansi"
21
18
 
22
- // Derive ANSI escape sequences from @silvery/style constants.
23
- const RESET = "\x1b[0m"
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`
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()
29
23
 
30
24
  /**
31
25
  * Check if color output should be enabled.
32
- * Uses @silvery/ansi detectColor() for full detection (respects NO_COLOR,
33
- * FORCE_COLOR, TERM, etc.).
26
+ * Delegates to @silvery/ansi's auto-detection (NO_COLOR, FORCE_COLOR, TERM).
34
27
  */
35
- let _shouldColorize: boolean | undefined
36
-
37
28
  export function shouldColorize(): boolean {
38
- if (_shouldColorize !== undefined) return _shouldColorize
39
- _shouldColorize = detectColor(process.stdout) !== null
40
- return _shouldColorize
41
- }
42
-
43
- /** Wrap a string with ANSI codes, handling nested resets. */
44
- function ansi(text: string, code: string): string {
45
- return `${code}${text}${RESET}`
29
+ return s.level > 0
46
30
  }
47
31
 
48
32
  /**
49
33
  * Minimal interface for Commander's Command — avoids requiring Commander
50
34
  * as a direct dependency. Works with both `commander` and
51
35
  * `@silvery/commander`.
52
- *
53
- * Uses permissive types to ensure structural compatibility with all
54
- * Commander versions, overloads, and generic instantiations.
55
36
  */
56
37
  export interface CommandLike {
57
38
  // biome-ignore lint: permissive to match Commander's overloaded signatures
@@ -62,82 +43,56 @@ export interface CommandLike {
62
43
  readonly commands: readonly any[]
63
44
  }
64
45
 
65
- /** Color scheme for help output. Values are raw ANSI escape sequences. */
46
+ /** Color scheme for help output. Each value is a styling function (text → styled text). */
66
47
  export interface ColorizeHelpOptions {
67
- /** ANSI code for command/subcommand names. Default: cyan */
68
- commands?: string
69
- /** ANSI code for --flags and -short options. Default: green */
70
- flags?: string
71
- /** ANSI code for description text. Default: dim */
72
- description?: string
73
- /** ANSI code for section headings (Usage:, Options:, etc.). Default: bold */
74
- heading?: string
75
- /** ANSI code for <required> and [optional] argument brackets. Default: yellow */
76
- 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
77
58
  }
78
59
 
79
60
  /**
80
61
  * Apply colorized help output to a Commander.js program and all its subcommands.
81
62
  *
82
63
  * Uses Commander's built-in `configureHelp()` style hooks rather than
83
- * post-processing the formatted string. This approach is robust against
84
- * formatting changes in Commander and handles wrapping correctly.
64
+ * post-processing the formatted string.
85
65
  *
86
66
  * @param program - A Commander Command instance (or compatible object)
87
- * @param options - Override default ANSI color codes for each element
67
+ * @param options - Override default style functions for each element
88
68
  */
89
69
  export function colorizeHelp(program: CommandLike, options?: ColorizeHelpOptions): void {
90
- const cmds = options?.commands ?? CYAN
91
- const flags = options?.flags ?? GREEN
92
- const desc = options?.description ?? DIM
93
- const heading = options?.heading ?? BOLD
94
- const brackets = options?.brackets ?? YELLOW
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))
95
81
 
96
82
  const helpConfig: Record<string, unknown> = {
97
- // Section headings: "Usage:", "Options:", "Commands:", "Arguments:"
98
- styleTitle(str: string): string {
99
- return ansi(str, heading)
100
- },
101
-
102
- // Command name in usage line and subcommand terms
103
- styleCommandText(str: string): string {
104
- return ansi(str, cmds)
105
- },
106
-
107
- // Option terms: "-v, --verbose", "--repo <path>", "[options]"
108
- styleOptionText(str: string): string {
109
- return ansi(str, flags)
110
- },
111
-
112
- // Subcommand names in the commands list
113
- styleSubcommandText(str: string): string {
114
- return ansi(str, cmds)
115
- },
116
-
117
- // Argument terms: "<file>", "[dir]"
118
- styleArgumentText(str: string): string {
119
- return ansi(str, brackets)
120
- },
121
-
122
- // Description text for options, subcommands, arguments
123
- styleDescriptionText(str: string): string {
124
- return ansi(str, desc)
125
- },
126
-
127
- // Command description (the main program description line) — keep normal
128
- styleCommandDescription(str: string): string {
129
- return str
130
- },
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,
131
90
  }
132
91
 
133
92
  program.configureHelp(helpConfig)
134
93
 
135
94
  // Tell Commander that color output is supported, even when stdout is not
136
- // a TTY (e.g., piped output, CI, tests). Without this, Commander strips
137
- // all ANSI codes from helpInformation() output.
138
- //
139
- // Callers who want to respect NO_COLOR/FORCE_COLOR should check
140
- // shouldColorize() before calling colorizeHelp().
95
+ // a TTY. Without this, Commander strips ANSI codes from helpInformation().
141
96
  program.configureOutput({
142
97
  getOutHasColors: () => true,
143
98
  getErrHasColors: () => true,