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.
- package/CHANGELOG.md +115 -0
- package/README.md +108 -283
- package/dist/args-Cnq0nwSM.mjs +272 -0
- package/dist/args-Cnq0nwSM.mjs.map +1 -0
- package/dist/codegen/index.d.mts +28 -3
- package/dist/codegen/index.d.mts.map +1 -1
- package/dist/codegen/index.mjs +169 -19
- 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} +86 -108
- package/dist/completion-BEuflbDO.mjs.map +1 -0
- package/dist/docs/index.d.mts +22 -2
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +92 -7
- package/dist/docs/index.mjs.map +1 -1
- package/dist/errors-CL63UOzt.mjs +137 -0
- package/dist/errors-CL63UOzt.mjs.map +1 -0
- package/dist/{formatter-ClUK5hcQ.d.mts → formatter-DrvhDMrq.d.mts} +35 -6
- package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
- package/dist/help-B5Kk83of.mjs +849 -0
- package/dist/help-B5Kk83of.mjs.map +1 -0
- package/dist/index-BaU3X6dY.d.mts +1178 -0
- package/dist/index-BaU3X6dY.d.mts.map +1 -0
- package/dist/index.d.mts +763 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3608 -1534
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-BM-d0nZi.mjs +377 -0
- package/dist/mcp-BM-d0nZi.mjs.map +1 -0
- package/dist/serve-Bk0JUlCj.mjs +402 -0
- package/dist/serve-Bk0JUlCj.mjs.map +1 -0
- package/dist/stream-DC4H8YTx.mjs +77 -0
- 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 +5 -27
- package/dist/test.mjs.map +1 -1
- package/dist/{update-check-EbNDkzyV.mjs → update-check-CZ2VqjnV.mjs} +16 -17
- package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
- package/dist/zod.d.mts +32 -0
- package/dist/zod.d.mts.map +1 -0
- package/dist/zod.mjs +50 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +20 -9
- package/src/cli/completions.ts +14 -11
- package/src/cli/docs.ts +13 -16
- package/src/cli/doctor.ts +213 -24
- package/src/cli/index.ts +28 -82
- package/src/cli/init.ts +12 -10
- package/src/cli/link.ts +22 -18
- package/src/cli/wrap.ts +14 -11
- package/src/codegen/discovery.ts +80 -28
- package/src/codegen/index.ts +2 -1
- package/src/codegen/parsers/bash.ts +179 -0
- package/src/codegen/schema-to-code.ts +2 -1
- package/src/core/args.ts +296 -0
- package/src/core/commands.ts +373 -0
- package/src/core/create.ts +268 -0
- package/src/{runtime.ts → core/default-runtime.ts} +70 -135
- 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 +124 -11
- package/src/extension/auto-output.ts +95 -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 +43 -0
- package/src/extension/ink.ts +93 -0
- package/src/extension/interactive.ts +106 -0
- package/src/extension/logger.ts +214 -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} +130 -57
- package/src/{interactive.ts → feature/interactive.ts} +47 -6
- package/src/feature/mcp.ts +387 -0
- package/src/{repl-loop.ts → feature/repl-loop.ts} +26 -16
- package/src/feature/serve.ts +438 -0
- 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} +27 -27
- package/src/index.ts +120 -11
- package/src/output/colorizer.ts +154 -0
- package/src/{formatter.ts → output/formatter.ts} +281 -135
- package/src/{help.ts → output/help.ts} +62 -15
- package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
- package/src/schema/zod.ts +50 -0
- package/src/test.ts +2 -285
- package/src/types/args-meta.ts +151 -0
- package/src/types/builder.ts +697 -0
- package/src/types/command.ts +157 -0
- package/src/types/index.ts +59 -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/util/stream.ts +101 -0
- package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
- package/src/{type-utils.ts → util/type-utils.ts} +99 -37
- package/src/util/utils.ts +51 -0
- package/src/zod.ts +1 -0
- package/dist/args-CVDbyyzG.mjs +0 -199
- package/dist/args-CVDbyyzG.mjs.map +0 -1
- package/dist/chunk-y_GBKt04.mjs +0 -5
- package/dist/completion.d.mts +0 -64
- package/dist/completion.d.mts.map +0 -1
- package/dist/completion.mjs.map +0 -1
- package/dist/formatter-ClUK5hcQ.d.mts.map +0 -1
- package/dist/help-CcBe91bV.mjs +0 -1254
- package/dist/help-CcBe91bV.mjs.map +0 -1
- package/dist/types-DjIdJN5G.d.mts +0 -1059
- package/dist/types-DjIdJN5G.d.mts.map +0 -1
- package/dist/update-check-EbNDkzyV.mjs.map +0 -1
- package/src/args.ts +0 -461
- package/src/colorizer.ts +0 -41
- package/src/command-utils.ts +0 -532
- package/src/create.ts +0 -1477
- package/src/types.ts +0 -1109
- package/src/utils.ts +0 -140
|
@@ -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 {
|
|
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
|
|
@@ -220,6 +220,18 @@ function generateMarkdownPage(info: HelpInfo, depth: number, frontmatterFn?: Doc
|
|
|
220
220
|
lines.push('```');
|
|
221
221
|
lines.push('');
|
|
222
222
|
|
|
223
|
+
// Examples
|
|
224
|
+
if (info.examples?.length) {
|
|
225
|
+
lines.push('## Examples');
|
|
226
|
+
lines.push('');
|
|
227
|
+
lines.push('```');
|
|
228
|
+
for (const ex of info.examples) {
|
|
229
|
+
lines.push(`$ ${ex}`);
|
|
230
|
+
}
|
|
231
|
+
lines.push('```');
|
|
232
|
+
lines.push('');
|
|
233
|
+
}
|
|
234
|
+
|
|
223
235
|
// Subcommands
|
|
224
236
|
if (info.subcommands?.length) {
|
|
225
237
|
const visibleSubs = info.subcommands.filter((s) => !s.hidden);
|
|
@@ -307,6 +319,12 @@ function generateHtmlPage(info: HelpInfo, depth: number): string {
|
|
|
307
319
|
sections.push(' <h2>Usage</h2>');
|
|
308
320
|
sections.push(` <pre><code>${escapeHtml(usageParts.join(' '))}</code></pre>`);
|
|
309
321
|
|
|
322
|
+
// Examples
|
|
323
|
+
if (info.examples?.length) {
|
|
324
|
+
sections.push(' <h2>Examples</h2>');
|
|
325
|
+
sections.push(` <pre><code>${info.examples.map((ex) => `$ ${escapeHtml(ex)}`).join('\n')}</code></pre>`);
|
|
326
|
+
}
|
|
327
|
+
|
|
310
328
|
// Subcommands
|
|
311
329
|
if (info.subcommands?.length) {
|
|
312
330
|
const visibleSubs = info.subcommands.filter((s) => !s.hidden);
|
|
@@ -381,7 +399,7 @@ function generateHtmlPage(info: HelpInfo, depth: number): string {
|
|
|
381
399
|
}
|
|
382
400
|
|
|
383
401
|
// ============================================================================
|
|
384
|
-
// Man Page Generator
|
|
402
|
+
// Man Page Generator (experimental)
|
|
385
403
|
// ============================================================================
|
|
386
404
|
|
|
387
405
|
function escapeMan(text: string): string {
|
|
@@ -418,6 +436,15 @@ function generateManPage(info: HelpInfo, _depth: number, programName: string): s
|
|
|
418
436
|
lines.push(escapeMan(info.description));
|
|
419
437
|
}
|
|
420
438
|
|
|
439
|
+
// EXAMPLES
|
|
440
|
+
if (info.examples?.length) {
|
|
441
|
+
lines.push('.SH EXAMPLES');
|
|
442
|
+
for (const ex of info.examples) {
|
|
443
|
+
lines.push('.PP');
|
|
444
|
+
lines.push(`.nf\n$ ${escapeMan(ex)}\n.fi`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
421
448
|
// COMMANDS
|
|
422
449
|
if (info.subcommands?.length) {
|
|
423
450
|
const visibleSubs = info.subcommands.filter((s) => !s.hidden);
|
|
@@ -517,11 +544,6 @@ function generateMarkdownIndex(rootInfo: HelpInfo, allInfos: HelpInfo[]): string
|
|
|
517
544
|
// Main Entry Point
|
|
518
545
|
// ============================================================================
|
|
519
546
|
|
|
520
|
-
function resolveCommand(programOrCommand: object): AnyPadroneCommand {
|
|
521
|
-
if (commandSymbol in programOrCommand) return (programOrCommand as any)[commandSymbol] as AnyPadroneCommand;
|
|
522
|
-
return programOrCommand as AnyPadroneCommand;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
547
|
/**
|
|
526
548
|
* Generate documentation for a Padrone CLI program or command tree.
|
|
527
549
|
* Accepts either a PadroneProgram (from createPadrone()) or a raw AnyPadroneCommand.
|
|
@@ -529,7 +551,7 @@ function resolveCommand(programOrCommand: object): AnyPadroneCommand {
|
|
|
529
551
|
export function generateDocs(program: object, options: DocsOptions = {}): DocsResult {
|
|
530
552
|
const { format = 'markdown', output, includeHidden = false, frontmatter, overwrite = true, dryRun = false } = options;
|
|
531
553
|
|
|
532
|
-
const cmd =
|
|
554
|
+
const cmd = getCommand(program);
|
|
533
555
|
const allInfos = collectAllHelpInfo(cmd, includeHidden);
|
|
534
556
|
const rootInfo = allInfos[0]!;
|
|
535
557
|
const programName = cmd.name || 'program';
|
|
@@ -605,3 +627,94 @@ export function generateDocs(program: object, options: DocsOptions = {}): DocsRe
|
|
|
605
627
|
|
|
606
628
|
return result;
|
|
607
629
|
}
|
|
630
|
+
|
|
631
|
+
// ============================================================================
|
|
632
|
+
// Man Page Installation
|
|
633
|
+
// ============================================================================
|
|
634
|
+
|
|
635
|
+
export type SetupManPagesResult = {
|
|
636
|
+
/** Directory where man pages were written. */
|
|
637
|
+
dir: string;
|
|
638
|
+
/** Man page files that were written. */
|
|
639
|
+
written: string[];
|
|
640
|
+
/** Whether existing pages were overwritten (true) or newly created (false). */
|
|
641
|
+
updated: boolean;
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Returns the local man page directory for the given section.
|
|
646
|
+
* Uses `~/.local/share/man/man<section>` (XDG convention).
|
|
647
|
+
*/
|
|
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}`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Converts a command name to a man page filename.
|
|
655
|
+
* "myapp" → "myapp.1", "myapp deploy" → "myapp-deploy.1"
|
|
656
|
+
*/
|
|
657
|
+
function manPageFilename(commandName: string, section = 1): string {
|
|
658
|
+
return `${commandName.replace(/\s+/g, '-')}.${section}`;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Installs man pages for a Padrone CLI program into the local man directory.
|
|
663
|
+
* Generates man pages for all commands and writes them to `~/.local/share/man/man1/`.
|
|
664
|
+
*
|
|
665
|
+
* After installation, `man <program>` and `man <program>-<subcommand>` should work
|
|
666
|
+
* (assuming `~/.local/share/man` is in `MANPATH` or `manpath` picks it up).
|
|
667
|
+
*/
|
|
668
|
+
export async function setupManPages(program: object): Promise<SetupManPagesResult> {
|
|
669
|
+
const cmd = getCommand(program);
|
|
670
|
+
const allInfos = collectAllHelpInfo(cmd, false);
|
|
671
|
+
const programName = cmd.name || 'program';
|
|
672
|
+
const manDir = await getManPageDir(1);
|
|
673
|
+
|
|
674
|
+
mkdirSync(manDir, { recursive: true });
|
|
675
|
+
|
|
676
|
+
const written: string[] = [];
|
|
677
|
+
let updated = false;
|
|
678
|
+
|
|
679
|
+
for (let i = 0; i < allInfos.length; i++) {
|
|
680
|
+
const info = allInfos[i]!;
|
|
681
|
+
const depth = i === 0 ? 0 : info.name.split(/\s+/).length;
|
|
682
|
+
const commandName = info.name === '<root>' || !info.name ? programName : info.name;
|
|
683
|
+
const filename = manPageFilename(commandName);
|
|
684
|
+
const fullPath = join(manDir, filename);
|
|
685
|
+
|
|
686
|
+
if (existsSync(fullPath)) updated = true;
|
|
687
|
+
|
|
688
|
+
const content = generateManPage(info, depth, programName);
|
|
689
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
690
|
+
written.push(filename);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return { dir: manDir, written, updated };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Removes installed man pages for a Padrone CLI program.
|
|
698
|
+
*/
|
|
699
|
+
export async function removeManPages(program: object): Promise<{ dir: string; removed: string[] }> {
|
|
700
|
+
const { unlinkSync } = await import('node:fs');
|
|
701
|
+
const cmd = getCommand(program);
|
|
702
|
+
const allInfos = collectAllHelpInfo(cmd, false);
|
|
703
|
+
const programName = cmd.name || 'program';
|
|
704
|
+
const manDir = await getManPageDir(1);
|
|
705
|
+
const removed: string[] = [];
|
|
706
|
+
|
|
707
|
+
for (let i = 0; i < allInfos.length; i++) {
|
|
708
|
+
const info = allInfos[i]!;
|
|
709
|
+
const commandName = info.name === '<root>' || !info.name ? programName : info.name;
|
|
710
|
+
const filename = manPageFilename(commandName);
|
|
711
|
+
const fullPath = join(manDir, filename);
|
|
712
|
+
|
|
713
|
+
if (existsSync(fullPath)) {
|
|
714
|
+
unlinkSync(fullPath);
|
|
715
|
+
removed.push(filename);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return { dir: manDir, removed };
|
|
720
|
+
}
|