padrone 1.4.0 → 1.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.
Files changed (141) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +108 -283
  3. package/dist/args-Cnq0nwSM.mjs +272 -0
  4. package/dist/args-Cnq0nwSM.mjs.map +1 -0
  5. package/dist/codegen/index.d.mts +28 -3
  6. package/dist/codegen/index.d.mts.map +1 -1
  7. package/dist/codegen/index.mjs +169 -19
  8. package/dist/codegen/index.mjs.map +1 -1
  9. package/dist/commands-B_gufyR9.mjs +514 -0
  10. package/dist/commands-B_gufyR9.mjs.map +1 -0
  11. package/dist/{completion.mjs → completion-BEuflbDO.mjs} +86 -108
  12. package/dist/completion-BEuflbDO.mjs.map +1 -0
  13. package/dist/docs/index.d.mts +22 -2
  14. package/dist/docs/index.d.mts.map +1 -1
  15. package/dist/docs/index.mjs +92 -7
  16. package/dist/docs/index.mjs.map +1 -1
  17. package/dist/errors-CL63UOzt.mjs +137 -0
  18. package/dist/errors-CL63UOzt.mjs.map +1 -0
  19. package/dist/{formatter-ClUK5hcQ.d.mts → formatter-DrvhDMrq.d.mts} +35 -6
  20. package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
  21. package/dist/help-B5Kk83of.mjs +849 -0
  22. package/dist/help-B5Kk83of.mjs.map +1 -0
  23. package/dist/index-BaU3X6dY.d.mts +1178 -0
  24. package/dist/index-BaU3X6dY.d.mts.map +1 -0
  25. package/dist/index.d.mts +763 -36
  26. package/dist/index.d.mts.map +1 -1
  27. package/dist/index.mjs +3608 -1534
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/mcp-BM-d0nZi.mjs +377 -0
  30. package/dist/mcp-BM-d0nZi.mjs.map +1 -0
  31. package/dist/serve-Bk0JUlCj.mjs +402 -0
  32. package/dist/serve-Bk0JUlCj.mjs.map +1 -0
  33. package/dist/stream-DC4H8YTx.mjs +77 -0
  34. package/dist/stream-DC4H8YTx.mjs.map +1 -0
  35. package/dist/test.d.mts +5 -8
  36. package/dist/test.d.mts.map +1 -1
  37. package/dist/test.mjs +5 -27
  38. package/dist/test.mjs.map +1 -1
  39. package/dist/{update-check-EbNDkzyV.mjs → update-check-CZ2VqjnV.mjs} +16 -17
  40. package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
  41. package/dist/zod.d.mts +32 -0
  42. package/dist/zod.d.mts.map +1 -0
  43. package/dist/zod.mjs +50 -0
  44. package/dist/zod.mjs.map +1 -0
  45. package/package.json +20 -9
  46. package/src/cli/completions.ts +14 -11
  47. package/src/cli/docs.ts +13 -16
  48. package/src/cli/doctor.ts +213 -24
  49. package/src/cli/index.ts +28 -82
  50. package/src/cli/init.ts +12 -10
  51. package/src/cli/link.ts +22 -18
  52. package/src/cli/wrap.ts +14 -11
  53. package/src/codegen/discovery.ts +80 -28
  54. package/src/codegen/index.ts +2 -1
  55. package/src/codegen/parsers/bash.ts +179 -0
  56. package/src/codegen/schema-to-code.ts +2 -1
  57. package/src/core/args.ts +296 -0
  58. package/src/core/commands.ts +373 -0
  59. package/src/core/create.ts +268 -0
  60. package/src/{runtime.ts → core/default-runtime.ts} +70 -135
  61. package/src/{errors.ts → core/errors.ts} +22 -0
  62. package/src/core/exec.ts +259 -0
  63. package/src/core/interceptors.ts +302 -0
  64. package/src/{parse.ts → core/parse.ts} +36 -89
  65. package/src/core/program-methods.ts +301 -0
  66. package/src/core/results.ts +229 -0
  67. package/src/core/runtime.ts +246 -0
  68. package/src/core/validate.ts +247 -0
  69. package/src/docs/index.ts +124 -11
  70. package/src/extension/auto-output.ts +95 -0
  71. package/src/extension/color.ts +38 -0
  72. package/src/extension/completion.ts +49 -0
  73. package/src/extension/config.ts +262 -0
  74. package/src/extension/env.ts +101 -0
  75. package/src/extension/help.ts +192 -0
  76. package/src/extension/index.ts +43 -0
  77. package/src/extension/ink.ts +93 -0
  78. package/src/extension/interactive.ts +106 -0
  79. package/src/extension/logger.ts +214 -0
  80. package/src/extension/man.ts +51 -0
  81. package/src/extension/mcp.ts +52 -0
  82. package/src/extension/progress-renderer.ts +338 -0
  83. package/src/extension/progress.ts +299 -0
  84. package/src/extension/repl.ts +94 -0
  85. package/src/extension/serve.ts +48 -0
  86. package/src/extension/signal.ts +87 -0
  87. package/src/extension/stdin.ts +62 -0
  88. package/src/extension/suggestions.ts +114 -0
  89. package/src/extension/timing.ts +81 -0
  90. package/src/extension/tracing.ts +175 -0
  91. package/src/extension/update-check.ts +77 -0
  92. package/src/extension/utils.ts +51 -0
  93. package/src/extension/version.ts +63 -0
  94. package/src/{completion.ts → feature/completion.ts} +130 -57
  95. package/src/{interactive.ts → feature/interactive.ts} +47 -6
  96. package/src/feature/mcp.ts +387 -0
  97. package/src/{repl-loop.ts → feature/repl-loop.ts} +26 -16
  98. package/src/feature/serve.ts +438 -0
  99. package/src/feature/test.ts +262 -0
  100. package/src/{update-check.ts → feature/update-check.ts} +16 -16
  101. package/src/{wrap.ts → feature/wrap.ts} +27 -27
  102. package/src/index.ts +120 -11
  103. package/src/output/colorizer.ts +154 -0
  104. package/src/{formatter.ts → output/formatter.ts} +281 -135
  105. package/src/{help.ts → output/help.ts} +62 -15
  106. package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
  107. package/src/schema/zod.ts +50 -0
  108. package/src/test.ts +2 -285
  109. package/src/types/args-meta.ts +151 -0
  110. package/src/types/builder.ts +697 -0
  111. package/src/types/command.ts +157 -0
  112. package/src/types/index.ts +59 -0
  113. package/src/types/interceptor.ts +296 -0
  114. package/src/types/preferences.ts +83 -0
  115. package/src/types/result.ts +71 -0
  116. package/src/types/schema.ts +19 -0
  117. package/src/util/dotenv.ts +244 -0
  118. package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
  119. package/src/util/stream.ts +101 -0
  120. package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
  121. package/src/{type-utils.ts → util/type-utils.ts} +99 -37
  122. package/src/util/utils.ts +51 -0
  123. package/src/zod.ts +1 -0
  124. package/dist/args-CVDbyyzG.mjs +0 -199
  125. package/dist/args-CVDbyyzG.mjs.map +0 -1
  126. package/dist/chunk-y_GBKt04.mjs +0 -5
  127. package/dist/completion.d.mts +0 -64
  128. package/dist/completion.d.mts.map +0 -1
  129. package/dist/completion.mjs.map +0 -1
  130. package/dist/formatter-ClUK5hcQ.d.mts.map +0 -1
  131. package/dist/help-CcBe91bV.mjs +0 -1254
  132. package/dist/help-CcBe91bV.mjs.map +0 -1
  133. package/dist/types-DjIdJN5G.d.mts +0 -1059
  134. package/dist/types-DjIdJN5G.d.mts.map +0 -1
  135. package/dist/update-check-EbNDkzyV.mjs.map +0 -1
  136. package/src/args.ts +0 -461
  137. package/src/colorizer.ts +0 -41
  138. package/src/command-utils.ts +0 -532
  139. package/src/create.ts +0 -1477
  140. package/src/types.ts +0 -1109
  141. package/src/utils.ts +0 -140
@@ -1,4 +1,4 @@
1
- import type { ResolvedPadroneRuntime } from './runtime.ts';
1
+ import type { ResolvedPadroneRuntime } from '../core/runtime.ts';
2
2
 
3
3
  /**
4
4
  * Configuration for the update check feature.
@@ -92,9 +92,9 @@ export function isNewerVersion(current: string, latest: string): boolean {
92
92
  /**
93
93
  * Reads the update check cache file.
94
94
  */
95
- function readCache(cachePath: string): CacheData | undefined {
95
+ async function readCache(cachePath: string): Promise<CacheData | undefined> {
96
96
  try {
97
- const { existsSync, readFileSync } = require('node:fs') as typeof import('node:fs');
97
+ const { existsSync, readFileSync } = await import('node:fs');
98
98
  if (!existsSync(cachePath)) return undefined;
99
99
  const data = JSON.parse(readFileSync(cachePath, 'utf-8'));
100
100
  if (typeof data.lastCheck === 'number' && typeof data.latestVersion === 'string') {
@@ -109,10 +109,10 @@ function readCache(cachePath: string): CacheData | undefined {
109
109
  /**
110
110
  * Writes the update check cache file.
111
111
  */
112
- function writeCache(cachePath: string, data: CacheData): void {
112
+ async function writeCache(cachePath: string, data: CacheData): Promise<void> {
113
113
  try {
114
- const { existsSync, mkdirSync, writeFileSync } = require('node:fs') as typeof import('node:fs');
115
- const { dirname } = require('node:path') as typeof import('node:path');
114
+ const { existsSync, mkdirSync, writeFileSync } = await import('node:fs');
115
+ const { dirname } = await import('node:path');
116
116
  const dir = dirname(cachePath);
117
117
  if (!existsSync(dir)) {
118
118
  mkdirSync(dir, { recursive: true });
@@ -126,9 +126,9 @@ function writeCache(cachePath: string, data: CacheData): void {
126
126
  /**
127
127
  * Resolves the cache path, expanding `~` to the home directory.
128
128
  */
129
- function resolveCachePath(cachePath: string): string {
130
- const { homedir } = require('node:os') as typeof import('node:os');
131
- const { resolve } = require('node:path') as typeof import('node:path');
129
+ async function resolveCachePath(cachePath: string): Promise<string> {
130
+ const { homedir } = await import('node:os');
131
+ const { resolve } = await import('node:path');
132
132
  if (cachePath.startsWith('~')) {
133
133
  return cachePath.replace('~', homedir());
134
134
  }
@@ -173,28 +173,28 @@ export function formatUpdateMessage(currentVersion: string, latestVersion: strin
173
173
  * This is designed to be non-blocking: the check starts immediately but the
174
174
  * result is only consumed after command execution completes.
175
175
  */
176
- export function createUpdateChecker(
176
+ export async function createUpdateChecker(
177
177
  programName: string,
178
178
  currentVersion: string,
179
179
  config: UpdateCheckConfig,
180
180
  runtime: ResolvedPadroneRuntime,
181
- ): () => void {
181
+ ): Promise<() => void> {
182
182
  const packageName = config.packageName ?? programName;
183
183
  const registry = config.registry ?? 'npm';
184
184
  const intervalMs = parseInterval(config.interval ?? '1d');
185
185
  const disableEnvVar = config.disableEnvVar ?? `${programName.toUpperCase().replace(/-/g, '_')}_NO_UPDATE_CHECK`;
186
186
 
187
187
  const defaultCachePath = `~/.config/${programName}-update-check.json`;
188
- const cachePath = resolveCachePath(config.cache ?? defaultCachePath);
188
+ const cachePath = await resolveCachePath(config.cache ?? defaultCachePath);
189
189
 
190
190
  // Check if disabled
191
191
  const env = runtime.env();
192
192
  if (env.CI || env.CONTINUOUS_INTEGRATION) return noop;
193
193
  if (env[disableEnvVar]) return noop;
194
- if (typeof process !== 'undefined' && !process.stdout?.isTTY) return noop;
194
+ if (runtime.terminal && !runtime.terminal.isTTY) return noop;
195
195
 
196
196
  // Check cache — if we checked recently, use cached result
197
- const cached = readCache(cachePath);
197
+ const cached = await readCache(cachePath);
198
198
  if (cached && Date.now() - cached.lastCheck < intervalMs) {
199
199
  // Use cached version for display
200
200
  if (isNewerVersion(currentVersion, cached.latestVersion)) {
@@ -206,9 +206,9 @@ export function createUpdateChecker(
206
206
  }
207
207
 
208
208
  // Start background fetch
209
- const fetchPromise = fetchLatestVersion(packageName, registry).then((latestVersion) => {
209
+ const fetchPromise = fetchLatestVersion(packageName, registry).then(async (latestVersion) => {
210
210
  if (latestVersion) {
211
- writeCache(cachePath, { lastCheck: Date.now(), latestVersion });
211
+ await writeCache(cachePath, { lastCheck: Date.now(), latestVersion });
212
212
  if (isNewerVersion(currentVersion, latestVersion)) {
213
213
  return latestVersion;
214
214
  }
@@ -1,6 +1,7 @@
1
1
  import type { StandardSchemaV1 } from '@standard-schema/spec';
2
- import { ValidationError } from './errors.ts';
3
- import type { PadroneSchema } from './types.ts';
2
+ import { ValidationError } from '../core/errors.ts';
3
+ import type { PadroneSchema } from '../types/index.ts';
4
+ import { concatBytes } from '../util/stream.ts';
4
5
 
5
6
  /**
6
7
  * Configuration for wrapping an external CLI tool.
@@ -58,7 +59,7 @@ export type WrapResult = {
58
59
  /**
59
60
  * Converts parsed arguments to CLI arguments for an external command.
60
61
  */
61
- function argsToCliArgs(input: Record<string, unknown> | undefined, positional: string[] = []): string[] {
62
+ function argsToCliArgs(input: Record<string, unknown> | undefined, positional: readonly string[] = []): string[] {
62
63
  const args: string[] = [];
63
64
 
64
65
  // Handle undefined or null input
@@ -122,7 +123,7 @@ function argsToCliArgs(input: Record<string, unknown> | undefined, positional: s
122
123
  export function createWrapHandler<TCommandArgs extends PadroneSchema, TWrapArgs extends PadroneSchema>(
123
124
  config: WrapConfig<TCommandArgs, TWrapArgs>,
124
125
  commandArguments: TCommandArgs,
125
- commandPositional?: string[],
126
+ commandPositional?: readonly string[],
126
127
  ): (args: StandardSchemaV1.InferOutput<TCommandArgs>) => Promise<WrapResult> {
127
128
  return async (args: StandardSchemaV1.InferOutput<TCommandArgs>): Promise<WrapResult> => {
128
129
  const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
@@ -153,33 +154,32 @@ export function createWrapHandler<TCommandArgs extends PadroneSchema, TWrapArgs
153
154
  const allArgs = [...fixedArgs, ...regularArgs];
154
155
 
155
156
  // Execute the external command
156
- const proc = Bun.spawn([command, ...allArgs], {
157
- stdout: inheritStdio ? 'inherit' : 'pipe',
158
- stderr: inheritStdio ? 'inherit' : 'pipe',
159
- stdin: inheritStdio ? 'inherit' : 'ignore',
160
- });
157
+ const { spawn } = await import('node:child_process');
161
158
 
162
- const exitCode = await proc.exited;
159
+ return new Promise<WrapResult>((resolve, reject) => {
160
+ const proc = spawn(command, allArgs, {
161
+ stdio: inheritStdio ? 'inherit' : ['ignore', 'pipe', 'pipe'],
162
+ });
163
163
 
164
- let stdout: string | undefined;
165
- let stderr: string | undefined;
164
+ const stdoutChunks: Uint8Array[] = [];
165
+ const stderrChunks: Uint8Array[] = [];
166
166
 
167
- if (!inheritStdio) {
168
- if (proc.stdout) {
169
- const stdoutBuffer = await new Response(proc.stdout).arrayBuffer();
170
- stdout = new TextDecoder().decode(stdoutBuffer);
171
- }
172
- if (proc.stderr) {
173
- const stderrBuffer = await new Response(proc.stderr).arrayBuffer();
174
- stderr = new TextDecoder().decode(stderrBuffer);
167
+ if (!inheritStdio) {
168
+ proc.stdout!.on('data', (chunk: Uint8Array) => stdoutChunks.push(chunk));
169
+ proc.stderr!.on('data', (chunk: Uint8Array) => stderrChunks.push(chunk));
175
170
  }
176
- }
177
171
 
178
- return {
179
- exitCode,
180
- stdout,
181
- stderr,
182
- success: exitCode === 0,
183
- };
172
+ const decoder = new TextDecoder();
173
+ proc.on('error', reject);
174
+ proc.on('close', (code) => {
175
+ const exitCode = code ?? 1;
176
+ resolve({
177
+ exitCode,
178
+ stdout: inheritStdio ? undefined : decoder.decode(concatBytes(stdoutChunks)),
179
+ stderr: inheritStdio ? undefined : decoder.decode(concatBytes(stderrChunks)),
180
+ success: exitCode === 0,
181
+ });
182
+ });
183
+ });
184
184
  };
185
185
  }
package/src/index.ts CHANGED
@@ -1,23 +1,132 @@
1
- export { asyncSchema, buildReplCompleter, createPadrone } from './create.ts';
2
- export type { PadroneErrorOptions } from './errors.ts';
3
- export { ActionError, ConfigError, PadroneError, RoutingError, ValidationError } from './errors.ts';
4
- export type { HelpInfo } from './formatter.ts';
5
- export type { InteractiveMode, InteractivePromptConfig, PadroneRuntime } from './runtime.ts';
6
- export { REPL_SIGINT } from './runtime.ts';
7
- export type { InferArgsInput, InferArgsOutput, InferCommand } from './type-helpers.ts';
1
+ export { buildReplCompleter } from './core/commands.ts';
2
+ export type { PadroneOptions } from './core/create.ts';
3
+ export { createPadrone } from './core/create.ts';
4
+ export type { PadroneErrorOptions } from './core/errors.ts';
5
+ export { ActionError, ConfigError, PadroneError, RoutingError, SignalError, ValidationError } from './core/errors.ts';
6
+ export { defineInterceptor } from './core/interceptors.ts';
7
+ export { asyncSchema } from './core/results.ts';
8
+ export type {
9
+ InteractiveMode,
10
+ InteractivePromptConfig,
11
+ PadroneBarAnimation,
12
+ PadroneBarChar,
13
+ PadroneBarConfig,
14
+ PadroneProgressIndicator,
15
+ PadroneProgressOptions,
16
+ PadroneProgressShow,
17
+ PadroneProgressUpdate,
18
+ PadroneRuntime,
19
+ PadroneSignal,
20
+ PadroneSpinnerConfig,
21
+ PadroneSpinnerPreset,
22
+ } from './core/runtime.ts';
23
+ export { REPL_SIGINT } from './core/runtime.ts';
24
+ export type {
25
+ HelpCommand,
26
+ InkOptions,
27
+ OtelSpan,
28
+ OtelTracer,
29
+ OtelTracerProvider,
30
+ PadroneLogger,
31
+ PadroneLoggerConfig,
32
+ PadroneLogLevel,
33
+ PadroneProgressConfig,
34
+ PadroneProgressDefaults,
35
+ PadroneProgressMessage,
36
+ PadroneProgressMessages,
37
+ PadroneProgressRenderer,
38
+ PadroneTracer,
39
+ PadroneTracingConfig,
40
+ VersionCommand,
41
+ WithCompletion,
42
+ WithHelp,
43
+ WithLogger,
44
+ WithMan,
45
+ WithMcp,
46
+ WithProgress,
47
+ WithRepl,
48
+ WithServe,
49
+ WithTracing,
50
+ WithVersion,
51
+ } from './extension/index.ts';
52
+ export {
53
+ createTerminalProgress,
54
+ isReactElement,
55
+ padroneAutoOutput,
56
+ padroneColor,
57
+ padroneCompletion,
58
+ padroneConfig,
59
+ padroneEnv,
60
+ padroneHelp,
61
+ padroneInk,
62
+ padroneInteractive,
63
+ padroneLogger,
64
+ padroneMan,
65
+ padroneMcp,
66
+ padroneProgress,
67
+ padroneRepl,
68
+ padroneServe,
69
+ padroneSignalHandling,
70
+ padroneStdin,
71
+ padroneSuggestions,
72
+ padroneTiming,
73
+ padroneTracing,
74
+ padroneUpdateCheck,
75
+ padroneVersion,
76
+ } from './extension/index.ts';
77
+ export type { PadroneMcpPreferences } from './feature/mcp.ts';
78
+ export type { UpdateCheckConfig } from './feature/update-check.ts';
79
+ export type { WrapConfig, WrapResult } from './feature/wrap.ts';
80
+ export type { AnsiStyle, ColorConfig, ColorTheme } from './output/colorizer.ts';
81
+ export { colorThemes } from './output/colorizer.ts';
82
+ export type { HelpInfo } from './output/formatter.ts';
8
83
  export type {
9
84
  AnyPadroneBuilder,
10
85
  AnyPadroneCommand,
11
86
  AnyPadroneProgram,
12
87
  AsyncPadroneSchema,
88
+ CommandTypesBase,
89
+ ExtractInterceptorContext,
90
+ ExtractInterceptorRequires,
91
+ GetArgsMeta,
92
+ InterceptorBaseContext,
93
+ InterceptorDefBuilder,
94
+ InterceptorErrorContext,
95
+ InterceptorErrorResult,
96
+ InterceptorExecuteContext,
97
+ InterceptorExecuteResult,
98
+ InterceptorFactory,
99
+ InterceptorMeta,
100
+ InterceptorParseContext,
101
+ InterceptorParseResult,
102
+ InterceptorPhases,
103
+ InterceptorShutdownContext,
104
+ InterceptorStartContext,
105
+ InterceptorValidateContext,
106
+ InterceptorValidateResult,
13
107
  PadroneActionContext,
14
108
  PadroneBuilder,
15
109
  PadroneCommand,
16
110
  PadroneCommandResult,
111
+ PadroneContextInterceptor,
112
+ PadroneDrainResult,
113
+ PadroneExtension,
114
+ PadroneInterceptor,
115
+ PadroneInterceptorFn,
17
116
  PadroneParseResult,
18
- PadronePlugin,
19
117
  PadroneProgram,
118
+ PadroneProgramMeta,
20
119
  PadroneSchema,
21
- } from './types.ts';
22
- export type { UpdateCheckConfig } from './update-check.ts';
23
- export type { WrapConfig, WrapResult } from './wrap.ts';
120
+ } from './types/index.ts';
121
+ export type { AsyncStreamMeta } from './util/stream.ts';
122
+ export { asyncStream } from './util/stream.ts';
123
+ export type {
124
+ InferArgsInput,
125
+ InferArgsOutput,
126
+ InferCommand,
127
+ InferContext,
128
+ InferContextProvided,
129
+ InferInterceptorContext,
130
+ InferInterceptorRequires,
131
+ } from './util/type-helpers.ts';
132
+ export type { Drained } from './util/type-utils.ts';
@@ -0,0 +1,154 @@
1
+ // ANSI color/style codes
2
+ const ansiCodes: Record<AnsiStyle, string> = {
3
+ reset: '\x1b[0m',
4
+ bold: '\x1b[1m',
5
+ dim: '\x1b[2m',
6
+ italic: '\x1b[3m',
7
+ underline: '\x1b[4m',
8
+ strikethrough: '\x1b[9m',
9
+ red: '\x1b[31m',
10
+ green: '\x1b[32m',
11
+ yellow: '\x1b[33m',
12
+ blue: '\x1b[34m',
13
+ magenta: '\x1b[35m',
14
+ cyan: '\x1b[36m',
15
+ white: '\x1b[37m',
16
+ gray: '\x1b[90m',
17
+ };
18
+
19
+ /**
20
+ * Available ANSI styles that can be combined for each color role.
21
+ */
22
+ export type AnsiStyle =
23
+ | 'reset'
24
+ | 'bold'
25
+ | 'dim'
26
+ | 'italic'
27
+ | 'underline'
28
+ | 'strikethrough'
29
+ | 'red'
30
+ | 'green'
31
+ | 'yellow'
32
+ | 'blue'
33
+ | 'magenta'
34
+ | 'cyan'
35
+ | 'white'
36
+ | 'gray';
37
+
38
+ /**
39
+ * Maps each semantic color role to an array of ANSI styles.
40
+ * Partial configs are merged with the default theme.
41
+ */
42
+ export type ColorConfig = {
43
+ command?: AnsiStyle[];
44
+ arg?: AnsiStyle[];
45
+ type?: AnsiStyle[];
46
+ description?: AnsiStyle[];
47
+ label?: AnsiStyle[];
48
+ meta?: AnsiStyle[];
49
+ example?: AnsiStyle[];
50
+ exampleValue?: AnsiStyle[];
51
+ deprecated?: AnsiStyle[];
52
+ };
53
+
54
+ export type Colorizer = {
55
+ command: (text: string) => string;
56
+ arg: (text: string) => string;
57
+ type: (text: string) => string;
58
+ description: (text: string) => string;
59
+ label: (text: string) => string;
60
+ meta: (text: string) => string;
61
+ example: (text: string) => string;
62
+ exampleValue: (text: string) => string;
63
+ deprecated: (text: string) => string;
64
+ };
65
+
66
+ // ============================================================================
67
+ // Predefined Themes
68
+ // ============================================================================
69
+
70
+ const defaultTheme: Required<ColorConfig> = {
71
+ command: ['cyan', 'bold'],
72
+ arg: ['green'],
73
+ type: ['yellow'],
74
+ description: ['dim'],
75
+ label: ['bold'],
76
+ meta: ['gray'],
77
+ example: ['underline'],
78
+ exampleValue: ['italic'],
79
+ deprecated: ['strikethrough', 'gray'],
80
+ };
81
+
82
+ const oceanTheme: Required<ColorConfig> = {
83
+ command: ['blue', 'bold'],
84
+ arg: ['cyan'],
85
+ type: ['green'],
86
+ description: ['dim'],
87
+ label: ['bold'],
88
+ meta: ['gray'],
89
+ example: ['underline', 'cyan'],
90
+ exampleValue: ['italic'],
91
+ deprecated: ['strikethrough', 'gray'],
92
+ };
93
+
94
+ const warmTheme: Required<ColorConfig> = {
95
+ command: ['yellow', 'bold'],
96
+ arg: ['red'],
97
+ type: ['magenta'],
98
+ description: ['dim'],
99
+ label: ['bold'],
100
+ meta: ['gray'],
101
+ example: ['underline', 'yellow'],
102
+ exampleValue: ['italic'],
103
+ deprecated: ['strikethrough', 'gray'],
104
+ };
105
+
106
+ const monochromeTheme: Required<ColorConfig> = {
107
+ command: ['bold'],
108
+ arg: ['underline'],
109
+ type: ['dim'],
110
+ description: ['dim'],
111
+ label: ['bold'],
112
+ meta: ['dim'],
113
+ example: ['underline'],
114
+ exampleValue: ['italic'],
115
+ deprecated: ['strikethrough', 'dim'],
116
+ };
117
+
118
+ /**
119
+ * Available predefined color themes.
120
+ */
121
+ export type ColorTheme = 'default' | 'ocean' | 'warm' | 'monochrome';
122
+
123
+ export const colorThemes: Record<ColorTheme, Required<ColorConfig>> = {
124
+ default: defaultTheme,
125
+ ocean: oceanTheme,
126
+ warm: warmTheme,
127
+ monochrome: monochromeTheme,
128
+ };
129
+
130
+ function makeStyleFn(styles: AnsiStyle[]): (text: string) => string {
131
+ const prefix = styles.map((s) => ansiCodes[s]).join('');
132
+ return (text: string) => `${prefix}${text}${ansiCodes.reset}`;
133
+ }
134
+
135
+ function resolveConfig(config?: ColorTheme | ColorConfig): Required<ColorConfig> {
136
+ if (!config) return defaultTheme;
137
+ if (typeof config === 'string') return colorThemes[config] ?? defaultTheme;
138
+ return { ...defaultTheme, ...config };
139
+ }
140
+
141
+ export function createColorizer(config?: ColorTheme | ColorConfig): Colorizer {
142
+ const resolved = resolveConfig(config);
143
+ return {
144
+ command: makeStyleFn(resolved.command),
145
+ arg: makeStyleFn(resolved.arg),
146
+ type: makeStyleFn(resolved.type),
147
+ description: makeStyleFn(resolved.description),
148
+ label: makeStyleFn(resolved.label),
149
+ meta: makeStyleFn(resolved.meta),
150
+ example: makeStyleFn(resolved.example),
151
+ exampleValue: makeStyleFn(resolved.exampleValue),
152
+ deprecated: makeStyleFn(resolved.deprecated),
153
+ };
154
+ }