padrone 1.5.0 → 1.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/CHANGELOG.md +44 -0
- package/README.md +15 -11
- package/dist/{args-D5PNDyNu.mjs → args-Cnq0nwSM.mjs} +91 -41
- package/dist/args-Cnq0nwSM.mjs.map +1 -0
- package/dist/codegen/index.mjs +4 -4
- package/dist/codegen/index.mjs.map +1 -1
- package/dist/commands-B_gufyR9.mjs +514 -0
- package/dist/commands-B_gufyR9.mjs.map +1 -0
- package/dist/{completion.mjs → completion-BEuflbDO.mjs} +12 -82
- package/dist/completion-BEuflbDO.mjs.map +1 -0
- package/dist/docs/index.d.mts +4 -4
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +10 -12
- package/dist/docs/index.mjs.map +1 -1
- package/dist/{errors-BiVrBgi6.mjs → errors-DA4KzK1M.mjs} +26 -3
- package/dist/errors-DA4KzK1M.mjs.map +1 -0
- package/dist/{formatter-DtHzbP22.d.mts → formatter-DrvhDMrq.d.mts} +3 -3
- package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
- package/dist/{help-bbmu9-qd.mjs → help-BtxLgrF_.mjs} +190 -43
- package/dist/help-BtxLgrF_.mjs.map +1 -0
- package/dist/{types-Ch8Mk6Qb.d.mts → index-D6-7dz0l.d.mts} +634 -745
- package/dist/index-D6-7dz0l.d.mts.map +1 -0
- package/dist/index.d.mts +869 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3884 -1699
- package/dist/index.mjs.map +1 -1
- package/dist/{mcp-mLWIdUIu.mjs → mcp-6-Jw4Bpq.mjs} +13 -15
- package/dist/mcp-6-Jw4Bpq.mjs.map +1 -0
- package/dist/{serve-B0u43DK7.mjs → serve-YVTPzBCl.mjs} +12 -14
- package/dist/serve-YVTPzBCl.mjs.map +1 -0
- package/dist/{stream-BcC146Ud.mjs → stream-DC4H8YTx.mjs} +24 -3
- package/dist/stream-DC4H8YTx.mjs.map +1 -0
- package/dist/test.d.mts +5 -8
- package/dist/test.d.mts.map +1 -1
- package/dist/test.mjs +2 -13
- package/dist/test.mjs.map +1 -1
- package/dist/{update-check-CFX1FV3v.mjs → update-check-CZ2VqjnV.mjs} +16 -17
- package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
- package/dist/zod.d.mts +2 -2
- package/dist/zod.d.mts.map +1 -1
- package/dist/zod.mjs +2 -2
- package/dist/zod.mjs.map +1 -1
- package/package.json +15 -12
- package/src/cli/completions.ts +14 -11
- package/src/cli/docs.ts +13 -10
- package/src/cli/doctor.ts +22 -18
- package/src/cli/index.ts +28 -82
- package/src/cli/init.ts +10 -7
- package/src/cli/link.ts +20 -16
- package/src/cli/wrap.ts +14 -11
- package/src/codegen/schema-to-code.ts +2 -2
- package/src/{args.ts → core/args.ts} +32 -225
- package/src/core/commands.ts +373 -0
- package/src/core/create.ts +301 -0
- package/src/core/default-runtime.ts +239 -0
- package/src/{errors.ts → core/errors.ts} +22 -0
- package/src/core/exec.ts +259 -0
- package/src/core/interceptors.ts +302 -0
- package/src/{parse.ts → core/parse.ts} +36 -89
- package/src/core/program-methods.ts +301 -0
- package/src/core/results.ts +229 -0
- package/src/core/runtime.ts +246 -0
- package/src/core/validate.ts +247 -0
- package/src/docs/index.ts +12 -13
- package/src/extension/auto-output.ts +146 -0
- package/src/extension/color.ts +38 -0
- package/src/extension/completion.ts +49 -0
- package/src/extension/config.ts +262 -0
- package/src/extension/env.ts +101 -0
- package/src/extension/help.ts +192 -0
- package/src/extension/index.ts +44 -0
- package/src/extension/ink.ts +93 -0
- package/src/extension/interactive.ts +106 -0
- package/src/extension/logger.ts +262 -0
- package/src/extension/man.ts +51 -0
- package/src/extension/mcp.ts +52 -0
- package/src/extension/progress-renderer.ts +338 -0
- package/src/extension/progress.ts +299 -0
- package/src/extension/repl.ts +94 -0
- package/src/extension/serve.ts +48 -0
- package/src/extension/signal.ts +87 -0
- package/src/extension/stdin.ts +62 -0
- package/src/extension/suggestions.ts +114 -0
- package/src/extension/timing.ts +81 -0
- package/src/extension/tracing.ts +175 -0
- package/src/extension/update-check.ts +77 -0
- package/src/extension/utils.ts +51 -0
- package/src/extension/version.ts +63 -0
- package/src/{completion.ts → feature/completion.ts} +12 -12
- package/src/{interactive.ts → feature/interactive.ts} +4 -4
- package/src/{mcp.ts → feature/mcp.ts} +12 -15
- package/src/{repl-loop.ts → feature/repl-loop.ts} +10 -13
- package/src/{serve.ts → feature/serve.ts} +11 -15
- package/src/feature/test.ts +262 -0
- package/src/{update-check.ts → feature/update-check.ts} +16 -16
- package/src/{wrap.ts → feature/wrap.ts} +10 -8
- package/src/index.ts +115 -30
- package/src/{formatter.ts → output/formatter.ts} +124 -176
- package/src/{help.ts → output/help.ts} +22 -8
- package/src/output/output-indicator.ts +87 -0
- package/src/output/primitives.ts +335 -0
- package/src/output/styling.ts +221 -0
- package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
- package/src/schema/zod.ts +50 -0
- package/src/test.ts +2 -276
- package/src/types/args-meta.ts +151 -0
- package/src/types/builder.ts +718 -0
- package/src/types/command.ts +157 -0
- package/src/types/index.ts +60 -0
- package/src/types/interceptor.ts +296 -0
- package/src/types/preferences.ts +83 -0
- package/src/types/result.ts +71 -0
- package/src/types/schema.ts +19 -0
- package/src/util/dotenv.ts +244 -0
- package/src/{shell-utils.ts → util/shell-utils.ts} +26 -9
- package/src/{stream.ts → util/stream.ts} +27 -1
- package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
- package/src/{type-utils.ts → util/type-utils.ts} +71 -33
- package/src/util/utils.ts +51 -0
- package/src/zod.ts +1 -50
- package/dist/args-D5PNDyNu.mjs.map +0 -1
- package/dist/chunk-CjcI7cDX.mjs +0 -15
- package/dist/command-utils-B1D-HqCd.mjs +0 -1117
- package/dist/command-utils-B1D-HqCd.mjs.map +0 -1
- package/dist/completion.d.mts +0 -64
- package/dist/completion.d.mts.map +0 -1
- package/dist/completion.mjs.map +0 -1
- package/dist/errors-BiVrBgi6.mjs.map +0 -1
- package/dist/formatter-DtHzbP22.d.mts.map +0 -1
- package/dist/help-bbmu9-qd.mjs.map +0 -1
- package/dist/mcp-mLWIdUIu.mjs.map +0 -1
- package/dist/serve-B0u43DK7.mjs.map +0 -1
- package/dist/stream-BcC146Ud.mjs.map +0 -1
- package/dist/types-Ch8Mk6Qb.d.mts.map +0 -1
- package/dist/update-check-CFX1FV3v.mjs.map +0 -1
- package/src/command-utils.ts +0 -882
- package/src/create.ts +0 -1829
- package/src/runtime.ts +0 -497
- package/src/types.ts +0 -1291
- package/src/utils.ts +0 -140
- /package/src/{colorizer.ts → output/colorizer.ts} +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { ColorConfig, ColorTheme } from '../output/colorizer.ts';
|
|
2
|
+
import type { HelpFormat } from '../output/formatter.ts';
|
|
3
|
+
|
|
4
|
+
/** Process signals that Padrone can handle for graceful shutdown. */
|
|
5
|
+
export type PadroneSignal = 'SIGINT' | 'SIGTERM' | 'SIGHUP';
|
|
6
|
+
|
|
7
|
+
/** Value accepted by `PadroneProgressIndicator.update()`. */
|
|
8
|
+
export type PadroneProgressUpdate = string | number | { message?: string; progress?: number; indeterminate?: boolean; time?: boolean };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A progress indicator instance (spinner, progress bar, etc).
|
|
12
|
+
* Created by the runtime's `progress` factory and used to show loading state during command execution.
|
|
13
|
+
*/
|
|
14
|
+
export type PadroneProgressIndicator = {
|
|
15
|
+
/**
|
|
16
|
+
* Update the indicator.
|
|
17
|
+
* - `string` — update the displayed message.
|
|
18
|
+
* - `number` — set progress ratio (0–1). Values outside this range are clamped.
|
|
19
|
+
* - `{ message?, progress?, indeterminate? }` — update message, progress, or both.
|
|
20
|
+
*
|
|
21
|
+
* Set `indeterminate: true` to force the bar into indeterminate mode (shows animation, no percentage).
|
|
22
|
+
* This makes the bar visible even in `show: 'auto'` mode without providing a number.
|
|
23
|
+
* Omitting `progress` (or passing a string) leaves the bar in its current state.
|
|
24
|
+
* Setting `progress` when bar mode is not enabled is a no-op for the bar portion.
|
|
25
|
+
*
|
|
26
|
+
* Set `time: true` to start the elapsed timer on demand (useful when `time` was not set in options).
|
|
27
|
+
* Set `time: false` to hide the elapsed timer.
|
|
28
|
+
*/
|
|
29
|
+
update: (value: PadroneProgressUpdate) => void;
|
|
30
|
+
/** Mark as succeeded and stop. Pass `null` to stop without rendering a final message. */
|
|
31
|
+
succeed: (message?: string | null, options?: { indicator?: string }) => void;
|
|
32
|
+
/** Mark as failed and stop. Pass `null` to stop without rendering a final message. */
|
|
33
|
+
fail: (message?: string | null, options?: { indicator?: string }) => void;
|
|
34
|
+
/** Stop without success/fail status. */
|
|
35
|
+
stop: () => void;
|
|
36
|
+
/** Temporarily hide the indicator so other output can be written cleanly. */
|
|
37
|
+
pause: () => void;
|
|
38
|
+
/** Redraw the indicator after a `pause()`. */
|
|
39
|
+
resume: () => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Controls when a progress element (spinner or bar) is visible. */
|
|
43
|
+
export type PadroneProgressShow = 'auto' | 'always' | 'never';
|
|
44
|
+
|
|
45
|
+
/** Built-in spinner presets. */
|
|
46
|
+
export type PadroneSpinnerPreset = 'dots' | 'line' | 'arc' | 'bounce';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Spinner configuration for progress indicators.
|
|
50
|
+
* - A preset name (e.g., `'dots'`) to use built-in frames.
|
|
51
|
+
* - `true` — default spinner with `show: 'always'` (visible even alongside a bar).
|
|
52
|
+
* - An object with custom `frames`, `interval`, and/or `show`.
|
|
53
|
+
* - `false` to disable the spinner (`show: 'never'`).
|
|
54
|
+
*
|
|
55
|
+
* Default `show` is `'auto'`: visible when the bar is not shown.
|
|
56
|
+
*/
|
|
57
|
+
export type PadroneSpinnerConfig = PadroneSpinnerPreset | boolean | { frames?: string[]; interval?: number; show?: PadroneProgressShow };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Options passed to the runtime's `progress` factory.
|
|
61
|
+
*/
|
|
62
|
+
/** Common fill/empty character pairs for progress bars. */
|
|
63
|
+
export type PadroneBarChar = '█' | '░' | '▓' | '▒' | '─' | '━' | '■' | '□' | '#' | '-' | '=' | '·' | '▰' | '▱' | (string & {});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Built-in indeterminate bar animation presets.
|
|
67
|
+
* - `'bounce'` — a filled segment slides back and forth (default).
|
|
68
|
+
* - `'slide'` — a filled segment slides left-to-right and wraps around.
|
|
69
|
+
* - `'pulse'` — the entire bar fades in and out using gradient characters (`░▒▓█▓▒░`).
|
|
70
|
+
*/
|
|
71
|
+
export type PadroneBarAnimation = 'bounce' | 'slide' | 'pulse';
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Progress bar configuration.
|
|
75
|
+
*/
|
|
76
|
+
export type PadroneBarConfig = {
|
|
77
|
+
/** Total width of the bar in characters. Defaults to `20`. */
|
|
78
|
+
width?: number;
|
|
79
|
+
/** Character used for the filled portion of the bar. Defaults to `'█'`. */
|
|
80
|
+
filled?: PadroneBarChar;
|
|
81
|
+
/** Character used for the empty portion of the bar. Defaults to `'░'`. */
|
|
82
|
+
empty?: PadroneBarChar;
|
|
83
|
+
/** Indeterminate animation style. Defaults to `'bounce'`. */
|
|
84
|
+
animation?: PadroneBarAnimation;
|
|
85
|
+
/**
|
|
86
|
+
* When the bar is visible. Defaults to `'always'` when bar is enabled, `'auto'` when bar is not explicitly configured.
|
|
87
|
+
* - `'always'` — bar is always shown (indeterminate until a number is provided).
|
|
88
|
+
* - `'auto'` — bar is shown only after `update()` is called with a number.
|
|
89
|
+
* - `'never'` — bar is never shown.
|
|
90
|
+
*/
|
|
91
|
+
show?: PadroneProgressShow;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type PadroneProgressOptions = {
|
|
95
|
+
spinner?: PadroneSpinnerConfig;
|
|
96
|
+
/** Enable a progress bar. `true` for defaults (`show: 'always'`), or a `PadroneBarConfig` object. `false` to disable entirely. When omitted, bar defaults to `show: 'auto'` (appears when a number is provided). */
|
|
97
|
+
bar?: boolean | PadroneBarConfig;
|
|
98
|
+
/** Show elapsed time since the indicator started. Defaults to `false`. */
|
|
99
|
+
time?: boolean;
|
|
100
|
+
/** Show estimated time remaining based on progress rate. Requires numeric `update()` calls. Defaults to `false`. */
|
|
101
|
+
eta?: boolean;
|
|
102
|
+
/** Character/string shown before the success message. Defaults to `'✔'`. */
|
|
103
|
+
successIndicator?: string;
|
|
104
|
+
/** Character/string shown before the error message. Defaults to `'✖'`. */
|
|
105
|
+
errorIndicator?: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Controls interactive prompting capability and default behavior at the runtime level.
|
|
110
|
+
* - `'supported'` — capable; caller decides.
|
|
111
|
+
* - `'unsupported'` — hard veto; nothing can override.
|
|
112
|
+
* - `'forced'` — capable and forces prompts by default.
|
|
113
|
+
* - `'disabled'` — capable but suppresses prompts by default.
|
|
114
|
+
*/
|
|
115
|
+
export type InteractiveMode = 'supported' | 'unsupported' | 'forced' | 'disabled';
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Configuration passed to the runtime's `prompt` function for interactive field prompting.
|
|
119
|
+
* The prompt type and choices are auto-detected from the field's JSON schema.
|
|
120
|
+
*/
|
|
121
|
+
export type InteractivePromptConfig = {
|
|
122
|
+
/** The field name being prompted. */
|
|
123
|
+
name: string;
|
|
124
|
+
/** Human-readable message/label for the prompt, derived from the field's description or name. */
|
|
125
|
+
message: string;
|
|
126
|
+
/** The prompt type, auto-detected from the JSON schema. */
|
|
127
|
+
type: 'input' | 'confirm' | 'select' | 'multiselect' | 'password';
|
|
128
|
+
/** Available choices for select/multiselect prompts. */
|
|
129
|
+
choices?: { label: string; value: unknown }[];
|
|
130
|
+
/** Default value from the schema. */
|
|
131
|
+
default?: unknown;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Defines the execution context for a Padrone program.
|
|
136
|
+
* Abstracts all environment-dependent I/O so the CLI framework
|
|
137
|
+
* can run outside of a terminal (e.g., web UIs, chat interfaces, testing).
|
|
138
|
+
*
|
|
139
|
+
* All fields are optional — unspecified fields fall back to the Node.js/Bun defaults.
|
|
140
|
+
*/
|
|
141
|
+
export type PadroneRuntime = {
|
|
142
|
+
/** Write normal output (replaces console.log). Receives the raw value — runtime handles formatting. */
|
|
143
|
+
output?: (...args: unknown[]) => void;
|
|
144
|
+
/** Write error output (replaces console.error). */
|
|
145
|
+
error?: (text: string) => void;
|
|
146
|
+
/** Return the raw CLI arguments (replaces process.argv.slice(2)). */
|
|
147
|
+
argv?: () => string[];
|
|
148
|
+
/** Return environment variables (replaces process.env). */
|
|
149
|
+
env?: () => Record<string, string | undefined>;
|
|
150
|
+
/** Default help output format. */
|
|
151
|
+
format?: HelpFormat | 'auto';
|
|
152
|
+
/** Color theme for ANSI/console help output. A theme name or partial color config. */
|
|
153
|
+
theme?: ColorTheme | ColorConfig;
|
|
154
|
+
/**
|
|
155
|
+
* Standard input abstraction. Provides methods to read piped data from stdin.
|
|
156
|
+
* When not provided, defaults to reading from `process.stdin`.
|
|
157
|
+
*
|
|
158
|
+
* Used by commands that declare a `stdin` field in their arguments meta.
|
|
159
|
+
* The framework reads stdin automatically during the validate phase and
|
|
160
|
+
* injects the data into the specified argument field.
|
|
161
|
+
*/
|
|
162
|
+
stdin?: {
|
|
163
|
+
/** Whether stdin is a TTY (interactive terminal) vs a pipe/file. */
|
|
164
|
+
isTTY?: boolean;
|
|
165
|
+
/** Read all of stdin as a string. */
|
|
166
|
+
text: () => Promise<string>;
|
|
167
|
+
/** Async iterable of lines for streaming. */
|
|
168
|
+
lines: () => AsyncIterable<string>;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Controls interactive prompting capability and default behavior.
|
|
172
|
+
* - `'supported'` — runtime can handle prompts; caller (flag/pref) decides whether to prompt. This is the default when `prompt` is provided.
|
|
173
|
+
* - `'unsupported'` — runtime cannot handle prompts; hard veto that nothing can override.
|
|
174
|
+
* - `'forced'` — runtime supports prompts and forces them by default (prompts even for provided values).
|
|
175
|
+
* - `'disabled'` — runtime supports prompts but suppresses them by default.
|
|
176
|
+
*
|
|
177
|
+
* `'unsupported'` is the only immutable state. For the others, the `--interactive`/`-i` flag
|
|
178
|
+
* and `cli()` preferences can override the default behavior.
|
|
179
|
+
*/
|
|
180
|
+
interactive?: InteractiveMode;
|
|
181
|
+
/**
|
|
182
|
+
* Prompt the user for input. Called during `cli()` for fields marked as interactive.
|
|
183
|
+
* When `interactive` is `true` and this is not provided, defaults to an Enquirer-based terminal prompt.
|
|
184
|
+
*/
|
|
185
|
+
prompt?: (config: InteractivePromptConfig) => Promise<unknown>;
|
|
186
|
+
/**
|
|
187
|
+
* Read a line of input from the user. Used by `repl()` for custom runtimes
|
|
188
|
+
* (web UIs, chat interfaces, testing).
|
|
189
|
+
* Returns the input string, `null` on EOF (e.g. Ctrl+D, closed connection),
|
|
190
|
+
* or `REPL_SIGINT` when the user presses Ctrl+C.
|
|
191
|
+
*
|
|
192
|
+
* When not provided, `repl()` uses a built-in Node.js readline session
|
|
193
|
+
* with command history (up/down arrows) and tab completion.
|
|
194
|
+
*/
|
|
195
|
+
readLine?: (prompt: string) => Promise<string | typeof REPL_SIGINT | null>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Register a callback for process signals. Returns an unsubscribe function.
|
|
199
|
+
* The default runtime wires this to `process.on('SIGINT' | 'SIGTERM' | 'SIGHUP')`.
|
|
200
|
+
* Non-Node runtimes (web UIs, tests) can map their own cancellation semantics.
|
|
201
|
+
*
|
|
202
|
+
* When not provided, signal handling is disabled for this runtime.
|
|
203
|
+
*/
|
|
204
|
+
onSignal?: (callback: (signal: PadroneSignal) => void) => () => void;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Terminal/output capabilities. Used for ANSI detection, text wrapping, and TTY checks.
|
|
208
|
+
* The default runtime auto-detects from `process.stdout`. Non-terminal runtimes
|
|
209
|
+
* (web UIs, tests) should provide explicit values.
|
|
210
|
+
*/
|
|
211
|
+
terminal?: {
|
|
212
|
+
/** Number of columns in the terminal. Used for text wrapping. */
|
|
213
|
+
columns?: number;
|
|
214
|
+
/** Whether stdout is a TTY. Affects ANSI color output and interactive features. */
|
|
215
|
+
isTTY?: boolean;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Force-exit the process. The default runtime wires this to `process.exit()`.
|
|
220
|
+
* Non-Node runtimes can throw an error or no-op.
|
|
221
|
+
*/
|
|
222
|
+
exit?: (code: number) => never;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Internal resolved runtime where all fields are guaranteed to be present.
|
|
227
|
+
* The `prompt`, `interactive`, and `readLine` fields remain optional since not all runtimes provide them.
|
|
228
|
+
*/
|
|
229
|
+
export type ResolvedPadroneRuntime = Required<
|
|
230
|
+
Omit<PadroneRuntime, 'prompt' | 'interactive' | 'readLine' | 'stdin' | 'theme' | 'onSignal' | 'terminal' | 'exit'>
|
|
231
|
+
> &
|
|
232
|
+
Pick<PadroneRuntime, 'prompt' | 'interactive' | 'readLine' | 'stdin' | 'theme' | 'onSignal' | 'terminal' | 'exit'>;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Sentinel value returned by the terminal REPL session when Ctrl+C is pressed.
|
|
236
|
+
* Distinguished from empty string (user pressed enter) and null (EOF/Ctrl+D).
|
|
237
|
+
*/
|
|
238
|
+
export const REPL_SIGINT = Symbol('REPL_SIGINT');
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Internal session config for the REPL's persistent readline interface.
|
|
242
|
+
*/
|
|
243
|
+
export type ReplSessionConfig = {
|
|
244
|
+
completer?: (line: string) => [string[], string];
|
|
245
|
+
history?: string[];
|
|
246
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { AnyPadroneCommand, InterceptorValidateResult } from '../types/index.ts';
|
|
3
|
+
import { coerceArgs, detectUnknownArgs, extractSchemaMetadata, getJsonSchema, parsePositionalConfig, preprocessArgs } from './args.ts';
|
|
4
|
+
import { getCommandRuntime } from './commands.ts';
|
|
5
|
+
import { getNestedValue, parseCliInputToParts, setNestedValue } from './parse.ts';
|
|
6
|
+
import { thenMaybe } from './results.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parses CLI input to find the command and extract raw arguments without validation.
|
|
10
|
+
*/
|
|
11
|
+
export function parseCommand(input: string | undefined, rootCommand: AnyPadroneCommand, findCommandByName: FindCommandFn) {
|
|
12
|
+
input ??= getCommandRuntime(rootCommand).argv().join(' ') || undefined;
|
|
13
|
+
if (!input) {
|
|
14
|
+
const defaultCommand = findCommandByName('', rootCommand.commands);
|
|
15
|
+
if (defaultCommand) {
|
|
16
|
+
return { command: defaultCommand, rawArgs: {} as Record<string, unknown>, args: [] as string[], unmatchedTerms: [] as string[] };
|
|
17
|
+
}
|
|
18
|
+
return { command: rootCommand, rawArgs: {} as Record<string, unknown>, args: [] as string[], unmatchedTerms: [] as string[] };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parts = parseCliInputToParts(input);
|
|
22
|
+
|
|
23
|
+
const terms = parts.filter((p) => p.type === 'term').map((p) => p.value);
|
|
24
|
+
const argTokens = parts.filter((p) => p.type === 'arg').map((p) => p.value);
|
|
25
|
+
|
|
26
|
+
let curCommand: AnyPadroneCommand | undefined = rootCommand;
|
|
27
|
+
let unmatchedTerms: string[] = [];
|
|
28
|
+
|
|
29
|
+
if (terms[0] === rootCommand.name) terms.shift();
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < terms.length; i++) {
|
|
32
|
+
const term = terms[i] || '';
|
|
33
|
+
const found = findCommandByName(term, curCommand.commands);
|
|
34
|
+
|
|
35
|
+
if (found) {
|
|
36
|
+
curCommand = found;
|
|
37
|
+
} else {
|
|
38
|
+
unmatchedTerms = terms.slice(i);
|
|
39
|
+
argTokens.unshift(...unmatchedTerms);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (unmatchedTerms.length === 0 && curCommand.commands?.length) {
|
|
45
|
+
const defaultCommand = findCommandByName('', curCommand.commands);
|
|
46
|
+
if (defaultCommand) curCommand = defaultCommand;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!curCommand) return { command: rootCommand, rawArgs: {} as Record<string, unknown>, args: argTokens, unmatchedTerms };
|
|
50
|
+
|
|
51
|
+
const argsMeta = curCommand.meta?.fields;
|
|
52
|
+
const schemaMetadata = curCommand.argsSchema
|
|
53
|
+
? extractSchemaMetadata(curCommand.argsSchema, argsMeta, curCommand.meta?.autoAlias)
|
|
54
|
+
: { flags: {}, aliases: {} };
|
|
55
|
+
const { flags, aliases } = schemaMetadata;
|
|
56
|
+
|
|
57
|
+
const arrayArguments = new Set<string>();
|
|
58
|
+
if (curCommand.argsSchema) {
|
|
59
|
+
try {
|
|
60
|
+
const jsonSchema = getJsonSchema(curCommand.argsSchema) as Record<string, any>;
|
|
61
|
+
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
62
|
+
for (const [key, prop] of Object.entries(jsonSchema.properties as Record<string, any>)) {
|
|
63
|
+
if (prop?.type === 'array') arrayArguments.add(key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Ignore schema parsing errors
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const argParts = parts.filter((p) => p.type === 'named' || p.type === 'alias');
|
|
72
|
+
const rawArgs: Record<string, unknown> = {};
|
|
73
|
+
|
|
74
|
+
for (const arg of argParts) {
|
|
75
|
+
let key: string[];
|
|
76
|
+
if (arg.type === 'alias' && arg.key.length === 1 && flags[arg.key[0]!]) {
|
|
77
|
+
key = [flags[arg.key[0]!]!];
|
|
78
|
+
} else if (arg.type === 'named' && arg.key.length === 1 && aliases[arg.key[0]!]) {
|
|
79
|
+
key = [aliases[arg.key[0]!]!];
|
|
80
|
+
} else {
|
|
81
|
+
key = arg.key;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const rootKey = key[0]!;
|
|
85
|
+
|
|
86
|
+
if (arg.type === 'named' && arg.negated) {
|
|
87
|
+
setNestedValue(rawArgs, key, false);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const value = arg.value ?? true;
|
|
92
|
+
|
|
93
|
+
if (arrayArguments.has(rootKey)) {
|
|
94
|
+
const existing = getNestedValue(rawArgs, key);
|
|
95
|
+
if (existing !== undefined) {
|
|
96
|
+
if (Array.isArray(existing)) {
|
|
97
|
+
if (Array.isArray(value)) existing.push(...value);
|
|
98
|
+
else existing.push(value);
|
|
99
|
+
} else {
|
|
100
|
+
if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
|
|
101
|
+
else setNestedValue(rawArgs, key, [existing, value]);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
setNestedValue(rawArgs, key, Array.isArray(value) ? value : [value]);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const existing = getNestedValue(rawArgs, key);
|
|
108
|
+
if (existing !== undefined) {
|
|
109
|
+
if (Array.isArray(existing)) {
|
|
110
|
+
if (Array.isArray(value)) existing.push(...value);
|
|
111
|
+
else existing.push(value);
|
|
112
|
+
} else {
|
|
113
|
+
if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
|
|
114
|
+
else setNestedValue(rawArgs, key, [existing, value]);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
setNestedValue(rawArgs, key, value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { command: curCommand, rawArgs, args: argTokens, unmatchedTerms };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type FindCommandFn = (name: string, commands?: AnyPadroneCommand[]) => AnyPadroneCommand | undefined;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Preprocesses raw arguments: maps positional arguments and performs auto-coercion.
|
|
129
|
+
* External data sources (stdin, env, config) are handled by extensions before this runs.
|
|
130
|
+
*/
|
|
131
|
+
export function buildCommandArgs(
|
|
132
|
+
command: AnyPadroneCommand,
|
|
133
|
+
rawArgs: Record<string, unknown>,
|
|
134
|
+
positionalArgs: string[],
|
|
135
|
+
): Record<string, unknown> {
|
|
136
|
+
let preprocessedArgs = preprocessArgs(rawArgs, { flags: {}, aliases: {} });
|
|
137
|
+
|
|
138
|
+
const positionalConfig = command.meta?.positional ? parsePositionalConfig(command.meta.positional) : [];
|
|
139
|
+
|
|
140
|
+
if (positionalConfig.length > 0) {
|
|
141
|
+
let argIndex = 0;
|
|
142
|
+
for (let i = 0; i < positionalConfig.length; i++) {
|
|
143
|
+
const { name, variadic } = positionalConfig[i]!;
|
|
144
|
+
if (argIndex >= positionalArgs.length) break;
|
|
145
|
+
|
|
146
|
+
if (variadic) {
|
|
147
|
+
const remainingPositionals = positionalConfig.slice(i + 1);
|
|
148
|
+
const nonVariadicAfter = remainingPositionals.filter((p) => !p.variadic).length;
|
|
149
|
+
const variadicEnd = positionalArgs.length - nonVariadicAfter;
|
|
150
|
+
preprocessedArgs[name] = positionalArgs.slice(argIndex, variadicEnd);
|
|
151
|
+
argIndex = variadicEnd;
|
|
152
|
+
} else if (i === positionalConfig.length - 1 && positionalArgs.length > argIndex + 1) {
|
|
153
|
+
preprocessedArgs[name] = positionalArgs.slice(argIndex).join(' ');
|
|
154
|
+
argIndex = positionalArgs.length;
|
|
155
|
+
} else {
|
|
156
|
+
preprocessedArgs[name] = positionalArgs[argIndex];
|
|
157
|
+
argIndex++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (command.argsSchema) {
|
|
163
|
+
preprocessedArgs = coerceArgs(preprocessedArgs, command.argsSchema);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return preprocessedArgs;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Detects unknown options in args that aren't defined in the schema.
|
|
171
|
+
* Returns unknown key info with suggestions, or empty array if schema is loose.
|
|
172
|
+
*/
|
|
173
|
+
export function checkUnknownArgs(command: AnyPadroneCommand, preprocessedArgs: Record<string, unknown>): { key: string }[] {
|
|
174
|
+
if (!command.argsSchema) {
|
|
175
|
+
const unknowns: { key: string }[] = [];
|
|
176
|
+
for (const key of Object.keys(preprocessedArgs)) {
|
|
177
|
+
unknowns.push({ key });
|
|
178
|
+
}
|
|
179
|
+
return unknowns;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const argsMeta = command.meta?.fields;
|
|
183
|
+
const { flags, aliases } = extractSchemaMetadata(command.argsSchema, argsMeta, command.meta?.autoAlias);
|
|
184
|
+
|
|
185
|
+
return detectUnknownArgs(preprocessedArgs, command.argsSchema, flags, aliases);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Validates preprocessed arguments against the command's schema.
|
|
190
|
+
* First checks for unknown args (strict by default), then runs schema validation.
|
|
191
|
+
* Returns sync or async result depending on the schema's validate method.
|
|
192
|
+
*/
|
|
193
|
+
export function validateCommandArgs(command: AnyPadroneCommand, preprocessedArgs: Record<string, unknown>) {
|
|
194
|
+
const unknownArgs = checkUnknownArgs(command, preprocessedArgs);
|
|
195
|
+
if (unknownArgs.length > 0) {
|
|
196
|
+
const issues: StandardSchemaV1.Issue[] = unknownArgs.map(({ key }) => ({
|
|
197
|
+
path: [key],
|
|
198
|
+
message: `Unknown option: "${key}"`,
|
|
199
|
+
}));
|
|
200
|
+
return { args: undefined, argsResult: { issues } as any };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const argsParsed = command.argsSchema ? command.argsSchema['~standard'].validate(preprocessedArgs) : { value: {} };
|
|
204
|
+
|
|
205
|
+
const buildResult = (parsed: StandardSchemaV1.Result<unknown>) => ({
|
|
206
|
+
args: parsed.issues ? undefined : (parsed.value as any),
|
|
207
|
+
argsResult: parsed as any,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return thenMaybe(argsParsed, buildResult);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Returns the list of known option names from a command's schema (for fuzzy suggestion).
|
|
215
|
+
*/
|
|
216
|
+
export function getKnownOptionNames(command: AnyPadroneCommand): string[] {
|
|
217
|
+
if (!command.argsSchema) return [];
|
|
218
|
+
try {
|
|
219
|
+
const js = getJsonSchema(command.argsSchema) as Record<string, any>;
|
|
220
|
+
if (js.type === 'object' && js.properties) return Object.keys(js.properties);
|
|
221
|
+
} catch {
|
|
222
|
+
/* ignore */
|
|
223
|
+
}
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Formats validation issue messages for display.
|
|
229
|
+
*/
|
|
230
|
+
export function formatIssueMessages(issues: readonly StandardSchemaV1.Issue[]): string {
|
|
231
|
+
return issues.map((i) => ` - ${i.path?.join('.') || 'root'}: ${i.message}`).join('\n');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Core validate function for parse() — preprocesses and validates CLI args.
|
|
236
|
+
* Used by the parse program method (lighter weight than the full exec pipeline).
|
|
237
|
+
* External data sources (stdin, env, config) are not resolved here — use eval() for that.
|
|
238
|
+
*/
|
|
239
|
+
export function coreValidateForParse(
|
|
240
|
+
command: AnyPadroneCommand,
|
|
241
|
+
rawArgs: Record<string, unknown>,
|
|
242
|
+
positionalArgs: string[],
|
|
243
|
+
): InterceptorValidateResult | Promise<InterceptorValidateResult> {
|
|
244
|
+
const preprocessedArgs = buildCommandArgs(command, rawArgs, positionalArgs);
|
|
245
|
+
const validated = validateCommandArgs(command, preprocessedArgs);
|
|
246
|
+
return thenMaybe(validated, (v) => v as InterceptorValidateResult);
|
|
247
|
+
}
|
package/src/docs/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
|
-
import { getCommand } from '../
|
|
4
|
-
import type { HelpArgumentInfo, HelpInfo, HelpPositionalInfo, HelpSubcommandInfo } from '../formatter.ts';
|
|
5
|
-
import { getHelpInfo } from '../help.ts';
|
|
6
|
-
import type { AnyPadroneCommand } from '../types.ts';
|
|
3
|
+
import { getCommand } from '../core/commands.ts';
|
|
4
|
+
import type { HelpArgumentInfo, HelpInfo, HelpPositionalInfo, HelpSubcommandInfo } from '../output/formatter.ts';
|
|
5
|
+
import { getHelpInfo } from '../output/help.ts';
|
|
6
|
+
import type { AnyPadroneCommand } from '../types/index.ts';
|
|
7
7
|
|
|
8
8
|
// ============================================================================
|
|
9
9
|
// Types
|
|
@@ -645,10 +645,9 @@ export type SetupManPagesResult = {
|
|
|
645
645
|
* Returns the local man page directory for the given section.
|
|
646
646
|
* Uses `~/.local/share/man/man<section>` (XDG convention).
|
|
647
647
|
*/
|
|
648
|
-
function getManPageDir(section = 1): string {
|
|
649
|
-
const { homedir } =
|
|
650
|
-
|
|
651
|
-
return joinPath(process.env.XDG_DATA_HOME || joinPath(homedir(), '.local', 'share'), 'man', `man${section}`);
|
|
648
|
+
async function getManPageDir(section = 1): Promise<string> {
|
|
649
|
+
const { homedir } = await import('node:os');
|
|
650
|
+
return join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'man', `man${section}`);
|
|
652
651
|
}
|
|
653
652
|
|
|
654
653
|
/**
|
|
@@ -666,11 +665,11 @@ function manPageFilename(commandName: string, section = 1): string {
|
|
|
666
665
|
* After installation, `man <program>` and `man <program>-<subcommand>` should work
|
|
667
666
|
* (assuming `~/.local/share/man` is in `MANPATH` or `manpath` picks it up).
|
|
668
667
|
*/
|
|
669
|
-
export function setupManPages(program: object): SetupManPagesResult {
|
|
668
|
+
export async function setupManPages(program: object): Promise<SetupManPagesResult> {
|
|
670
669
|
const cmd = getCommand(program);
|
|
671
670
|
const allInfos = collectAllHelpInfo(cmd, false);
|
|
672
671
|
const programName = cmd.name || 'program';
|
|
673
|
-
const manDir = getManPageDir(1);
|
|
672
|
+
const manDir = await getManPageDir(1);
|
|
674
673
|
|
|
675
674
|
mkdirSync(manDir, { recursive: true });
|
|
676
675
|
|
|
@@ -697,12 +696,12 @@ export function setupManPages(program: object): SetupManPagesResult {
|
|
|
697
696
|
/**
|
|
698
697
|
* Removes installed man pages for a Padrone CLI program.
|
|
699
698
|
*/
|
|
700
|
-
export function removeManPages(program: object): { dir: string; removed: string[] } {
|
|
701
|
-
const { unlinkSync } =
|
|
699
|
+
export async function removeManPages(program: object): Promise<{ dir: string; removed: string[] }> {
|
|
700
|
+
const { unlinkSync } = await import('node:fs');
|
|
702
701
|
const cmd = getCommand(program);
|
|
703
702
|
const allInfos = collectAllHelpInfo(cmd, false);
|
|
704
703
|
const programName = cmd.name || 'program';
|
|
705
|
-
const manDir = getManPageDir(1);
|
|
704
|
+
const manDir = await getManPageDir(1);
|
|
706
705
|
const removed: string[] = [];
|
|
707
706
|
|
|
708
707
|
for (let i = 0; i < allInfos.length; i++) {
|