padrone 1.0.0 → 1.2.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 +51 -0
- package/LICENSE +1 -1
- package/README.md +92 -49
- package/dist/args-CKNh7Dm9.mjs +175 -0
- package/dist/args-CKNh7Dm9.mjs.map +1 -0
- package/dist/chunk-y_GBKt04.mjs +5 -0
- package/dist/codegen/index.d.mts +305 -0
- package/dist/codegen/index.d.mts.map +1 -0
- package/dist/codegen/index.mjs +1348 -0
- package/dist/codegen/index.mjs.map +1 -0
- package/dist/completion.d.mts +64 -0
- package/dist/completion.d.mts.map +1 -0
- package/dist/completion.mjs +417 -0
- package/dist/completion.mjs.map +1 -0
- package/dist/docs/index.d.mts +34 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +404 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/formatter-Dvx7jFXr.d.mts +82 -0
- package/dist/formatter-Dvx7jFXr.d.mts.map +1 -0
- package/dist/help-mUIX0T0V.mjs +1195 -0
- package/dist/help-mUIX0T0V.mjs.map +1 -0
- package/dist/index.d.mts +122 -438
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1240 -1161
- package/dist/index.mjs.map +1 -1
- package/dist/test.d.mts +112 -0
- package/dist/test.d.mts.map +1 -0
- package/dist/test.mjs +138 -0
- package/dist/test.mjs.map +1 -0
- package/dist/types-qrtt0135.d.mts +1037 -0
- package/dist/types-qrtt0135.d.mts.map +1 -0
- package/dist/update-check-EbNDkzyV.mjs +146 -0
- package/dist/update-check-EbNDkzyV.mjs.map +1 -0
- package/package.json +61 -20
- package/src/args.ts +365 -0
- package/src/cli/completions.ts +29 -0
- package/src/cli/docs.ts +86 -0
- package/src/cli/doctor.ts +312 -0
- package/src/cli/index.ts +159 -0
- package/src/cli/init.ts +135 -0
- package/src/cli/link.ts +320 -0
- package/src/cli/wrap.ts +152 -0
- package/src/codegen/README.md +118 -0
- package/src/codegen/code-builder.ts +226 -0
- package/src/codegen/discovery.ts +232 -0
- package/src/codegen/file-emitter.ts +73 -0
- package/src/codegen/generators/barrel-file.ts +16 -0
- package/src/codegen/generators/command-file.ts +184 -0
- package/src/codegen/generators/command-tree.ts +124 -0
- package/src/codegen/index.ts +33 -0
- package/src/codegen/parsers/fish.ts +163 -0
- package/src/codegen/parsers/help.ts +378 -0
- package/src/codegen/parsers/merge.ts +158 -0
- package/src/codegen/parsers/zsh.ts +221 -0
- package/src/codegen/schema-to-code.ts +199 -0
- package/src/codegen/template.ts +69 -0
- package/src/codegen/types.ts +143 -0
- package/src/colorizer.ts +2 -2
- package/src/command-utils.ts +501 -0
- package/src/completion.ts +110 -97
- package/src/create.ts +1044 -284
- package/src/docs/index.ts +607 -0
- package/src/errors.ts +131 -0
- package/src/formatter.ts +149 -63
- package/src/help.ts +151 -55
- package/src/index.ts +13 -15
- package/src/interactive.ts +169 -0
- package/src/parse.ts +31 -16
- package/src/repl-loop.ts +317 -0
- package/src/runtime.ts +304 -0
- package/src/shell-utils.ts +83 -0
- package/src/test.ts +285 -0
- package/src/type-helpers.ts +12 -12
- package/src/type-utils.ts +124 -14
- package/src/types.ts +803 -144
- package/src/update-check.ts +244 -0
- package/src/wrap.ts +185 -0
- package/src/zod.d.ts +2 -2
- package/src/options.ts +0 -180
package/src/args.ts
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import type { StandardJSONSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
3
|
+
export interface PadroneFieldMeta {
|
|
4
|
+
description?: string;
|
|
5
|
+
alias?: string[] | string;
|
|
6
|
+
deprecated?: boolean | string;
|
|
7
|
+
hidden?: boolean;
|
|
8
|
+
examples?: unknown[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type PositionalArgs<TObj> =
|
|
12
|
+
TObj extends Record<string, any>
|
|
13
|
+
? {
|
|
14
|
+
[K in keyof TObj]: TObj[K] extends Array<any> ? `...${K & string}` : K & string;
|
|
15
|
+
}[keyof TObj]
|
|
16
|
+
: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Meta configuration for arguments, including positional arguments.
|
|
20
|
+
* The `positional` array defines which arguments are positional and their order.
|
|
21
|
+
* Use '...name' prefix to indicate variadic (rest) arguments, matching JS/TS rest syntax.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* .arguments(schema, {
|
|
26
|
+
* positional: ['source', '...files', 'dest'], // '...files' is variadic
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Configuration for reading from stdin and mapping it to an argument field.
|
|
32
|
+
*/
|
|
33
|
+
export type StdinConfig<TObj = Record<string, any>> =
|
|
34
|
+
| (keyof TObj & string)
|
|
35
|
+
| {
|
|
36
|
+
/** The argument field to populate with stdin data. */
|
|
37
|
+
field: keyof TObj & string;
|
|
38
|
+
/**
|
|
39
|
+
* How to consume stdin:
|
|
40
|
+
* - `'text'` (default): read all stdin as a single string.
|
|
41
|
+
* - `'lines'`: read stdin as an array of lines (string[]).
|
|
42
|
+
*/
|
|
43
|
+
as?: 'text' | 'lines';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export interface PadroneArgsSchemaMeta<TObj = Record<string, any>> {
|
|
47
|
+
/**
|
|
48
|
+
* Array of argument names that should be treated as positional arguments.
|
|
49
|
+
* Order in array determines position. Use '...name' prefix for variadic args.
|
|
50
|
+
* @example ['source', '...files', 'dest'] - 'files' captures multiple values
|
|
51
|
+
*/
|
|
52
|
+
positional?: PositionalArgs<TObj>[];
|
|
53
|
+
/**
|
|
54
|
+
* Per-argument metadata.
|
|
55
|
+
*/
|
|
56
|
+
fields?: { [K in keyof TObj]?: PadroneFieldMeta };
|
|
57
|
+
/**
|
|
58
|
+
* Read from stdin and inject the data into the specified argument field.
|
|
59
|
+
* Only reads when stdin is piped (not a TTY) and the field wasn't already provided via CLI flags.
|
|
60
|
+
*
|
|
61
|
+
* - `string`: shorthand for `{ field: name, as: 'text' }` — read all stdin as a string.
|
|
62
|
+
* - `{ field, as }`: explicit form. `as: 'text'` reads all stdin as a string,
|
|
63
|
+
* `as: 'lines'` reads stdin as an array of line strings.
|
|
64
|
+
*
|
|
65
|
+
* Precedence: CLI flags > stdin > env vars > config file > schema defaults.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* // Shorthand: read all stdin as text into 'data' field
|
|
70
|
+
* .arguments(z.object({ data: z.string() }), { stdin: 'data' })
|
|
71
|
+
*
|
|
72
|
+
* // Explicit: read stdin lines into 'lines' field
|
|
73
|
+
* .arguments(z.object({ lines: z.string().array() }), {
|
|
74
|
+
* stdin: { field: 'lines', as: 'lines' },
|
|
75
|
+
* })
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
stdin?: StdinConfig<TObj>;
|
|
79
|
+
/**
|
|
80
|
+
* Fields to interactively prompt for when their values are missing after CLI/env/config resolution.
|
|
81
|
+
* - `true`: prompt for all required fields that are missing.
|
|
82
|
+
* - `string[]`: prompt for these specific fields if missing.
|
|
83
|
+
*
|
|
84
|
+
* Interactive prompting only occurs in `cli()` when the runtime has `interactive: true`.
|
|
85
|
+
* Setting this makes `parse()` and `cli()` return Promises.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* .arguments(schema, {
|
|
90
|
+
* interactive: true, // prompt all missing required fields
|
|
91
|
+
* interactive: ['name', 'template'], // prompt only these fields
|
|
92
|
+
* })
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
interactive?: true | (keyof TObj & string)[];
|
|
96
|
+
/**
|
|
97
|
+
* Optional fields offered after required interactive prompts.
|
|
98
|
+
* Users are shown a multi-select to choose which of these fields to configure.
|
|
99
|
+
* - `true`: offer all optional fields that are missing.
|
|
100
|
+
* - `string[]`: offer these specific fields.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* .arguments(schema, {
|
|
105
|
+
* interactive: ['name'],
|
|
106
|
+
* optionalInteractive: ['typescript', 'eslint', 'prettier'],
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
optionalInteractive?: true | (keyof TObj & string)[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Normalizes stdin config into its explicit form.
|
|
115
|
+
*/
|
|
116
|
+
export function parseStdinConfig(stdin: StdinConfig): { field: string; as: 'text' | 'lines' } {
|
|
117
|
+
if (typeof stdin === 'string') return { field: stdin, as: 'text' };
|
|
118
|
+
return { field: stdin.field as string, as: stdin.as ?? 'text' };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parse positional configuration to extract names and variadic info.
|
|
123
|
+
*/
|
|
124
|
+
export function parsePositionalConfig(positional: string[]): { name: string; variadic: boolean }[] {
|
|
125
|
+
return positional.map((p) => {
|
|
126
|
+
const isVariadic = p.startsWith('...');
|
|
127
|
+
const name = isVariadic ? p.slice(3) : p;
|
|
128
|
+
return { name, variadic: isVariadic };
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Result type for extractSchemaMetadata function.
|
|
134
|
+
*/
|
|
135
|
+
interface SchemaMetadataResult {
|
|
136
|
+
aliases: Record<string, string>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Extract all arg metadata from schema and meta in a single pass.
|
|
141
|
+
* This consolidates aliases, env bindings, and config keys extraction.
|
|
142
|
+
*/
|
|
143
|
+
export function extractSchemaMetadata(
|
|
144
|
+
schema: StandardJSONSchemaV1,
|
|
145
|
+
meta?: Record<string, PadroneFieldMeta | undefined>,
|
|
146
|
+
): SchemaMetadataResult {
|
|
147
|
+
const aliases: Record<string, string> = {};
|
|
148
|
+
|
|
149
|
+
// Extract from meta object
|
|
150
|
+
if (meta) {
|
|
151
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
152
|
+
if (!value) continue;
|
|
153
|
+
|
|
154
|
+
// Extract aliases
|
|
155
|
+
if (value.alias) {
|
|
156
|
+
const list = typeof value.alias === 'string' ? [value.alias] : value.alias;
|
|
157
|
+
for (const aliasKey of list) {
|
|
158
|
+
if (typeof aliasKey === 'string' && aliasKey && aliasKey !== key) {
|
|
159
|
+
aliases[aliasKey] = key;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Extract from JSON schema properties
|
|
167
|
+
try {
|
|
168
|
+
const jsonSchema = schema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
|
|
169
|
+
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
170
|
+
for (const [propertyName, propertySchema] of Object.entries(jsonSchema.properties as Record<string, any>)) {
|
|
171
|
+
if (!propertySchema) continue;
|
|
172
|
+
|
|
173
|
+
// Extract aliases from schema
|
|
174
|
+
const propAlias = propertySchema.alias;
|
|
175
|
+
if (propAlias) {
|
|
176
|
+
const list = typeof propAlias === 'string' ? [propAlias] : propAlias;
|
|
177
|
+
if (Array.isArray(list)) {
|
|
178
|
+
for (const aliasKey of list) {
|
|
179
|
+
if (typeof aliasKey === 'string' && aliasKey && aliasKey !== propertyName && !(aliasKey in aliases)) {
|
|
180
|
+
aliases[aliasKey] = propertyName;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// Ignore errors from JSON schema generation
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { aliases };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function preprocessAliases(data: Record<string, unknown>, aliases: Record<string, string>): Record<string, unknown> {
|
|
195
|
+
const result = { ...data };
|
|
196
|
+
|
|
197
|
+
for (const [aliasKey, fullArgName] of Object.entries(aliases)) {
|
|
198
|
+
if (aliasKey in data && aliasKey !== fullArgName) {
|
|
199
|
+
const aliasValue = data[aliasKey];
|
|
200
|
+
// Prefer full arg name if it exists
|
|
201
|
+
if (!(fullArgName in result)) result[fullArgName] = aliasValue;
|
|
202
|
+
delete result[aliasKey];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface ParseArgsContext {
|
|
210
|
+
aliases?: Record<string, string>;
|
|
211
|
+
stdinData?: Record<string, unknown>;
|
|
212
|
+
envData?: Record<string, unknown>;
|
|
213
|
+
configData?: Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Apply values directly to arguments.
|
|
218
|
+
* CLI values take precedence over the provided values.
|
|
219
|
+
*/
|
|
220
|
+
function applyValues(data: Record<string, unknown>, values: Record<string, unknown>): Record<string, unknown> {
|
|
221
|
+
const result = { ...data };
|
|
222
|
+
|
|
223
|
+
for (const [key, value] of Object.entries(values)) {
|
|
224
|
+
// Only apply value if arg wasn't already set
|
|
225
|
+
if (key in result && result[key] !== undefined) continue;
|
|
226
|
+
if (value !== undefined) {
|
|
227
|
+
result[key] = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Combined preprocessing of arguments with all features.
|
|
236
|
+
* Precedence order (highest to lowest): CLI args > stdin > env vars > config file
|
|
237
|
+
*/
|
|
238
|
+
export function preprocessArgs(data: Record<string, unknown>, ctx: ParseArgsContext): Record<string, unknown> {
|
|
239
|
+
let result = { ...data };
|
|
240
|
+
|
|
241
|
+
// 1. Apply aliases first
|
|
242
|
+
if (ctx.aliases && Object.keys(ctx.aliases).length > 0) {
|
|
243
|
+
result = preprocessAliases(result, ctx.aliases);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 2. Apply stdin data (higher precedence than env)
|
|
247
|
+
// Only applies if CLI didn't set the arg
|
|
248
|
+
if (ctx.stdinData) {
|
|
249
|
+
result = applyValues(result, ctx.stdinData);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 3. Apply environment variables (higher precedence than config)
|
|
253
|
+
// These only apply if CLI/stdin didn't set the arg
|
|
254
|
+
if (ctx.envData) {
|
|
255
|
+
result = applyValues(result, ctx.envData);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 4. Apply config file values (lowest precedence)
|
|
259
|
+
// These only apply if neither CLI, stdin, nor env set the arg
|
|
260
|
+
if (ctx.configData) {
|
|
261
|
+
result = applyValues(result, ctx.configData);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Auto-coerce CLI string values to match the expected schema types.
|
|
269
|
+
* Handles: string → number, string → boolean for primitive schema fields.
|
|
270
|
+
* Arrays of primitives are also coerced element-wise.
|
|
271
|
+
*/
|
|
272
|
+
export function coerceArgs(data: Record<string, unknown>, schema: StandardJSONSchemaV1): Record<string, unknown> {
|
|
273
|
+
let properties: Record<string, any>;
|
|
274
|
+
try {
|
|
275
|
+
const jsonSchema = schema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
|
|
276
|
+
if (jsonSchema.type !== 'object' || !jsonSchema.properties) return data;
|
|
277
|
+
properties = jsonSchema.properties;
|
|
278
|
+
} catch {
|
|
279
|
+
return data;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const result = { ...data };
|
|
283
|
+
|
|
284
|
+
for (const [key, value] of Object.entries(result)) {
|
|
285
|
+
const prop = properties[key];
|
|
286
|
+
if (!prop) continue;
|
|
287
|
+
|
|
288
|
+
const targetType = prop.type as string | undefined;
|
|
289
|
+
|
|
290
|
+
if (targetType === 'number' || targetType === 'integer') {
|
|
291
|
+
if (typeof value === 'string') {
|
|
292
|
+
const num = Number(value);
|
|
293
|
+
if (!Number.isNaN(num)) result[key] = num;
|
|
294
|
+
}
|
|
295
|
+
} else if (targetType === 'boolean') {
|
|
296
|
+
if (typeof value === 'string') {
|
|
297
|
+
if (value === 'true' || value === '1') result[key] = true;
|
|
298
|
+
else if (value === 'false' || value === '0') result[key] = false;
|
|
299
|
+
}
|
|
300
|
+
} else if (targetType === 'array' && Array.isArray(value)) {
|
|
301
|
+
const itemType = prop.items?.type as string | undefined;
|
|
302
|
+
if (itemType === 'number' || itemType === 'integer') {
|
|
303
|
+
result[key] = value.map((v) => {
|
|
304
|
+
if (typeof v === 'string') {
|
|
305
|
+
const num = Number(v);
|
|
306
|
+
return Number.isNaN(num) ? v : num;
|
|
307
|
+
}
|
|
308
|
+
return v;
|
|
309
|
+
});
|
|
310
|
+
} else if (itemType === 'boolean') {
|
|
311
|
+
result[key] = value.map((v) => {
|
|
312
|
+
if (typeof v === 'string') {
|
|
313
|
+
if (v === 'true' || v === '1') return true;
|
|
314
|
+
if (v === 'false' || v === '0') return false;
|
|
315
|
+
}
|
|
316
|
+
return v;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Keys consumed by the CLI framework that are not user-defined args. */
|
|
326
|
+
const frameworkReservedKeys = new Set(['config', 'c']);
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Detect unknown keys in the args that don't match any schema property.
|
|
330
|
+
* Returns an array of { key, suggestion? } for each unknown key.
|
|
331
|
+
* Framework-reserved keys (--config, -c) are always allowed.
|
|
332
|
+
*/
|
|
333
|
+
export function detectUnknownArgs(
|
|
334
|
+
data: Record<string, unknown>,
|
|
335
|
+
schema: StandardJSONSchemaV1,
|
|
336
|
+
aliases: Record<string, string>,
|
|
337
|
+
suggestFn: (input: string, candidates: string[]) => string,
|
|
338
|
+
): { key: string; suggestion: string }[] {
|
|
339
|
+
let properties: Record<string, any>;
|
|
340
|
+
let isLoose = false;
|
|
341
|
+
try {
|
|
342
|
+
const jsonSchema = schema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
|
|
343
|
+
if (jsonSchema.type !== 'object' || !jsonSchema.properties) return [];
|
|
344
|
+
properties = jsonSchema.properties;
|
|
345
|
+
// If additionalProperties is set (true, {}, or a schema), the schema allows extra keys
|
|
346
|
+
if (jsonSchema.additionalProperties !== undefined && jsonSchema.additionalProperties !== false) isLoose = true;
|
|
347
|
+
} catch {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (isLoose) return [];
|
|
352
|
+
|
|
353
|
+
const knownKeys = new Set<string>([...Object.keys(properties), ...Object.keys(aliases), ...Object.values(aliases)]);
|
|
354
|
+
const propertyNames = Object.keys(properties);
|
|
355
|
+
const unknowns: { key: string; suggestion: string }[] = [];
|
|
356
|
+
|
|
357
|
+
for (const key of Object.keys(data)) {
|
|
358
|
+
if (!knownKeys.has(key) && !frameworkReservedKeys.has(key)) {
|
|
359
|
+
const suggestion = suggestFn(key, propertyNames);
|
|
360
|
+
unknowns.push({ key, suggestion });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return unknowns;
|
|
365
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { detectShell, getCompletionInstallInstructions, type ShellType, setupCompletions } from '../completion.ts';
|
|
3
|
+
import type { PadroneActionContext } from '../types.ts';
|
|
4
|
+
|
|
5
|
+
interface CompletionsArgs {
|
|
6
|
+
appPath?: string;
|
|
7
|
+
for?: ShellType;
|
|
8
|
+
setup?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function runCompletions(args: CompletionsArgs, _ctx: PadroneActionContext) {
|
|
12
|
+
const programName = args.appPath ? basename(args.appPath).replace(/\.[cm]?[jt]sx?$/, '') : 'padrone';
|
|
13
|
+
const shell = args.for ?? detectShell();
|
|
14
|
+
|
|
15
|
+
if (!shell) {
|
|
16
|
+
console.error('Could not detect shell. Use --for to specify one: bash, zsh, fish, powershell');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (args.setup) {
|
|
21
|
+
const result = setupCompletions(programName, shell);
|
|
22
|
+
const verb = result.updated ? 'Updated' : 'Added';
|
|
23
|
+
console.log(`${verb} ${programName} completions in ${result.file}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const instructions = getCompletionInstallInstructions(programName, shell);
|
|
28
|
+
console.log(instructions);
|
|
29
|
+
}
|
package/src/cli/docs.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { commandSymbol } from '../command-utils.ts';
|
|
3
|
+
import { type DocsFormat, generateDocs } from '../docs/index.ts';
|
|
4
|
+
import type { PadroneActionContext } from '../types.ts';
|
|
5
|
+
|
|
6
|
+
interface DocsArgs {
|
|
7
|
+
entry: string;
|
|
8
|
+
output?: string;
|
|
9
|
+
format?: DocsFormat;
|
|
10
|
+
includeHidden?: boolean;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function runDocs(args: DocsArgs, _ctx: PadroneActionContext) {
|
|
15
|
+
const entryPath = resolve(args.entry);
|
|
16
|
+
|
|
17
|
+
let mod: Record<string, unknown>;
|
|
18
|
+
try {
|
|
19
|
+
mod = (await import(entryPath)) as Record<string, unknown>;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(`Failed to import entry file: ${entryPath}`);
|
|
22
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Look for a padrone program export (default export or named export with .cli method)
|
|
27
|
+
const program = findProgram(mod);
|
|
28
|
+
if (!program) {
|
|
29
|
+
console.error('No Padrone program found in the entry file.');
|
|
30
|
+
console.error('The entry file must export a Padrone program (default or named export).');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = generateDocs(program, {
|
|
35
|
+
format: args.format,
|
|
36
|
+
output: args.output,
|
|
37
|
+
includeHidden: args.includeHidden,
|
|
38
|
+
dryRun: args.dryRun,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (args.dryRun) {
|
|
42
|
+
console.log('Dry run — files that would be generated:');
|
|
43
|
+
for (const page of result.pages) {
|
|
44
|
+
console.log(` ${page.path}`);
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (result.written.length > 0) {
|
|
50
|
+
console.log(`Generated ${result.written.length} documentation file(s):`);
|
|
51
|
+
for (const file of result.written) {
|
|
52
|
+
console.log(` ${file}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (result.skipped.length > 0) {
|
|
57
|
+
console.log(`Skipped ${result.skipped.length} existing file(s).`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.errors.length > 0) {
|
|
61
|
+
console.error(`Failed to write ${result.errors.length} file(s):`);
|
|
62
|
+
for (const { file, error } of result.errors) {
|
|
63
|
+
console.error(` ${file}: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findProgram(mod: Record<string, unknown>): any {
|
|
70
|
+
// Check default export first
|
|
71
|
+
const defaultExport = mod.default;
|
|
72
|
+
if (isPadroneProgram(defaultExport)) return defaultExport;
|
|
73
|
+
|
|
74
|
+
// Then check named exports
|
|
75
|
+
for (const value of Object.values(mod)) {
|
|
76
|
+
if (isPadroneProgram(value)) return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isPadroneProgram(value: unknown): boolean {
|
|
83
|
+
if (!value || typeof value !== 'object') return false;
|
|
84
|
+
// A PadroneProgram has the internal command symbol (set by createPadrone)
|
|
85
|
+
return commandSymbol in value;
|
|
86
|
+
}
|