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
package/src/cli/doctor.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import * as z from 'zod/v4';
|
|
3
|
+
import { getJsonSchema } from '../core/args.ts';
|
|
4
|
+
import { getCommand, isPadroneProgram } from '../core/commands.ts';
|
|
5
|
+
import type { AnyPadroneCommand, PadroneActionContext } from '../types/index.ts';
|
|
6
|
+
import { detectEntry } from './link.ts';
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
entry: string
|
|
7
|
-
}
|
|
8
|
+
export const doctorSchema = z.object({
|
|
9
|
+
entry: z.string().optional().describe('Entry file that exports a Padrone program (auto-detected from package.json if omitted)'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type DoctorArgs = z.infer<typeof doctorSchema>;
|
|
8
13
|
|
|
9
14
|
type Severity = 'error' | 'warning';
|
|
10
15
|
|
|
@@ -15,7 +20,18 @@ interface Diagnostic {
|
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
export async function runDoctor(args: DoctorArgs, _ctx: PadroneActionContext) {
|
|
18
|
-
|
|
23
|
+
let entryPath: string;
|
|
24
|
+
|
|
25
|
+
if (args.entry) {
|
|
26
|
+
entryPath = resolve(args.entry);
|
|
27
|
+
} else {
|
|
28
|
+
const detected = detectEntry(process.cwd());
|
|
29
|
+
if (!detected) {
|
|
30
|
+
console.error('Could not detect entry point. Provide an entry file or add a "bin" field to package.json.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
entryPath = detected.entry;
|
|
34
|
+
}
|
|
19
35
|
|
|
20
36
|
let mod: Record<string, unknown>;
|
|
21
37
|
try {
|
|
@@ -33,7 +49,7 @@ export async function runDoctor(args: DoctorArgs, _ctx: PadroneActionContext) {
|
|
|
33
49
|
process.exit(1);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
|
-
const cmd = (program as
|
|
52
|
+
const cmd = getCommand(program as object);
|
|
37
53
|
const diagnostics: Diagnostic[] = [];
|
|
38
54
|
|
|
39
55
|
collectDiagnostics(cmd, diagnostics);
|
|
@@ -63,6 +79,8 @@ export async function runDoctor(args: DoctorArgs, _ctx: PadroneActionContext) {
|
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
function collectDiagnostics(cmd: AnyPadroneCommand, diagnostics: Diagnostic[]) {
|
|
82
|
+
checkCircularParentRefs(cmd, diagnostics);
|
|
83
|
+
|
|
66
84
|
const allCommands = flattenCommands(cmd);
|
|
67
85
|
|
|
68
86
|
checkDuplicateAliases(allCommands, diagnostics);
|
|
@@ -70,7 +88,12 @@ function collectDiagnostics(cmd: AnyPadroneCommand, diagnostics: Diagnostic[]) {
|
|
|
70
88
|
checkCommandsWithoutActions(allCommands, diagnostics);
|
|
71
89
|
checkSchemasWithoutDescriptions(allCommands, diagnostics);
|
|
72
90
|
checkConflictingPositionals(allCommands, diagnostics);
|
|
73
|
-
|
|
91
|
+
checkUnusedInterceptors(allCommands, diagnostics);
|
|
92
|
+
checkDuplicateOptionFlagsAndAliases(allCommands, diagnostics);
|
|
93
|
+
checkUnreachableCommands(allCommands, diagnostics);
|
|
94
|
+
checkMissingCommandDescriptions(allCommands, diagnostics);
|
|
95
|
+
checkEmptyCommandGroups(allCommands, diagnostics);
|
|
96
|
+
checkDeprecatedWithoutHint(allCommands, diagnostics);
|
|
74
97
|
}
|
|
75
98
|
|
|
76
99
|
function flattenCommands(cmd: AnyPadroneCommand): AnyPadroneCommand[] {
|
|
@@ -87,10 +110,10 @@ function commandDisplayName(cmd: AnyPadroneCommand): string {
|
|
|
87
110
|
return cmd.path || cmd.name || '<root>';
|
|
88
111
|
}
|
|
89
112
|
|
|
90
|
-
function
|
|
113
|
+
function getCommandJsonSchema(cmd: AnyPadroneCommand): Record<string, any> | null {
|
|
91
114
|
try {
|
|
92
115
|
if (!cmd.argsSchema) return null;
|
|
93
|
-
return cmd.argsSchema
|
|
116
|
+
return getJsonSchema(cmd.argsSchema) as Record<string, any>;
|
|
94
117
|
} catch {
|
|
95
118
|
return null;
|
|
96
119
|
}
|
|
@@ -147,7 +170,7 @@ function checkShadowedOptionNames(commands: AnyPadroneCommand[], diagnostics: Di
|
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
// Check option names in schema
|
|
150
|
-
const jsonSchema =
|
|
173
|
+
const jsonSchema = getCommandJsonSchema(cmd);
|
|
151
174
|
if (!jsonSchema?.properties) continue;
|
|
152
175
|
|
|
153
176
|
for (const propName of Object.keys(jsonSchema.properties)) {
|
|
@@ -220,7 +243,7 @@ function checkCommandsWithoutActions(commands: AnyPadroneCommand[], diagnostics:
|
|
|
220
243
|
*/
|
|
221
244
|
function checkSchemasWithoutDescriptions(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
222
245
|
for (const cmd of commands) {
|
|
223
|
-
const jsonSchema =
|
|
246
|
+
const jsonSchema = getCommandJsonSchema(cmd);
|
|
224
247
|
if (!jsonSchema?.properties) continue;
|
|
225
248
|
|
|
226
249
|
for (const [propName, propSchema] of Object.entries(jsonSchema.properties as Record<string, any>)) {
|
|
@@ -275,7 +298,7 @@ function checkConflictingPositionals(commands: AnyPadroneCommand[], diagnostics:
|
|
|
275
298
|
}
|
|
276
299
|
|
|
277
300
|
// Check for positional names that don't exist in schema
|
|
278
|
-
const jsonSchema =
|
|
301
|
+
const jsonSchema = getCommandJsonSchema(cmd);
|
|
279
302
|
if (jsonSchema?.properties) {
|
|
280
303
|
for (const p of positional) {
|
|
281
304
|
const name = (p as string).replace(/^\.\.\./, '');
|
|
@@ -292,27 +315,198 @@ function checkConflictingPositionals(commands: AnyPadroneCommand[], diagnostics:
|
|
|
292
315
|
}
|
|
293
316
|
|
|
294
317
|
/**
|
|
295
|
-
* Check for
|
|
318
|
+
* Check for interceptors that don't define any phase handlers.
|
|
296
319
|
*/
|
|
297
|
-
function
|
|
320
|
+
function checkUnusedInterceptors(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
298
321
|
const phases = ['start', 'parse', 'validate', 'execute', 'error', 'shutdown'] as const;
|
|
299
322
|
|
|
300
323
|
for (const cmd of commands) {
|
|
301
|
-
if (!cmd.
|
|
324
|
+
if (!cmd.interceptors) continue;
|
|
302
325
|
|
|
303
|
-
for (const
|
|
304
|
-
const
|
|
326
|
+
for (const interceptor of cmd.interceptors) {
|
|
327
|
+
const phases_obj = interceptor.factory();
|
|
328
|
+
const hasHandler = phases.some((phase) => typeof (phases_obj as any)[phase] === 'function');
|
|
305
329
|
if (!hasHandler) {
|
|
306
330
|
diagnostics.push({
|
|
307
331
|
severity: 'warning',
|
|
308
332
|
command: commandDisplayName(cmd),
|
|
309
|
-
message: `
|
|
333
|
+
message: `Interceptor "${interceptor.meta.name}" has no phase handlers.`,
|
|
310
334
|
});
|
|
311
335
|
}
|
|
312
336
|
}
|
|
313
337
|
}
|
|
314
338
|
}
|
|
315
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Check for circular parent references in the command tree.
|
|
342
|
+
* Uses a visited set to detect cycles before flattening.
|
|
343
|
+
*/
|
|
344
|
+
function checkCircularParentRefs(cmd: AnyPadroneCommand, diagnostics: Diagnostic[]) {
|
|
345
|
+
const visited = new Set<AnyPadroneCommand>();
|
|
346
|
+
|
|
347
|
+
function walk(node: AnyPadroneCommand) {
|
|
348
|
+
if (visited.has(node)) {
|
|
349
|
+
diagnostics.push({
|
|
350
|
+
severity: 'error',
|
|
351
|
+
command: commandDisplayName(node),
|
|
352
|
+
message: 'Circular reference detected in command tree.',
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
visited.add(node);
|
|
357
|
+
if (node.commands) {
|
|
358
|
+
for (const sub of node.commands) {
|
|
359
|
+
walk(sub);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
walk(cmd);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check for duplicate flags or aliases within a single command's meta fields.
|
|
369
|
+
* e.g., two different options both using `-v` or both aliased to `--output`.
|
|
370
|
+
*/
|
|
371
|
+
function checkDuplicateOptionFlagsAndAliases(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
372
|
+
for (const cmd of commands) {
|
|
373
|
+
if (!cmd.meta?.fields) continue;
|
|
374
|
+
|
|
375
|
+
const seenFlags = new Map<string, string>();
|
|
376
|
+
const seenAliases = new Map<string, string>();
|
|
377
|
+
|
|
378
|
+
for (const [fieldName, fieldMeta] of Object.entries(cmd.meta.fields)) {
|
|
379
|
+
if (!fieldMeta) continue;
|
|
380
|
+
|
|
381
|
+
if (fieldMeta.flags) {
|
|
382
|
+
const flagList = typeof fieldMeta.flags === 'string' ? [fieldMeta.flags] : fieldMeta.flags;
|
|
383
|
+
for (const flag of flagList) {
|
|
384
|
+
const existing = seenFlags.get(flag);
|
|
385
|
+
if (existing) {
|
|
386
|
+
diagnostics.push({
|
|
387
|
+
severity: 'error',
|
|
388
|
+
command: commandDisplayName(cmd),
|
|
389
|
+
message: `Flag "-${flag}" is used by both "${existing}" and "${fieldName}".`,
|
|
390
|
+
});
|
|
391
|
+
} else {
|
|
392
|
+
seenFlags.set(flag, fieldName);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (fieldMeta.alias) {
|
|
398
|
+
const aliasList = typeof fieldMeta.alias === 'string' ? [fieldMeta.alias] : fieldMeta.alias;
|
|
399
|
+
for (const alias of aliasList) {
|
|
400
|
+
const existing = seenAliases.get(alias);
|
|
401
|
+
if (existing) {
|
|
402
|
+
diagnostics.push({
|
|
403
|
+
severity: 'error',
|
|
404
|
+
command: commandDisplayName(cmd),
|
|
405
|
+
message: `Alias "--${alias}" is used by both "${existing}" and "${fieldName}".`,
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
seenAliases.set(alias, fieldName);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Check for commands that are unreachable because a parent is hidden.
|
|
418
|
+
* A hidden parent makes all its children effectively invisible to users.
|
|
419
|
+
*/
|
|
420
|
+
function checkUnreachableCommands(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
421
|
+
for (const cmd of commands) {
|
|
422
|
+
if (!cmd.parent || cmd.hidden) continue;
|
|
423
|
+
|
|
424
|
+
let ancestor: AnyPadroneCommand | undefined = cmd.parent;
|
|
425
|
+
while (ancestor) {
|
|
426
|
+
if (ancestor.hidden && ancestor.parent) {
|
|
427
|
+
diagnostics.push({
|
|
428
|
+
severity: 'warning',
|
|
429
|
+
command: commandDisplayName(cmd),
|
|
430
|
+
message: `Command is unreachable because parent "${commandDisplayName(ancestor)}" is hidden.`,
|
|
431
|
+
});
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
ancestor = ancestor.parent;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Check for non-root commands without a title or description.
|
|
441
|
+
*/
|
|
442
|
+
function checkMissingCommandDescriptions(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
443
|
+
for (const cmd of commands) {
|
|
444
|
+
if (!cmd.parent) continue;
|
|
445
|
+
if (cmd.hidden) continue;
|
|
446
|
+
|
|
447
|
+
if (!cmd.title && !cmd.description) {
|
|
448
|
+
diagnostics.push({
|
|
449
|
+
severity: 'warning',
|
|
450
|
+
command: commandDisplayName(cmd),
|
|
451
|
+
message: 'Command has no title or description.',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check for branch commands (have subcommands) that have no leaf descendants.
|
|
459
|
+
* This catches empty command groups where all subcommands are also empty branches.
|
|
460
|
+
*/
|
|
461
|
+
function checkEmptyCommandGroups(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
462
|
+
function hasLeafDescendant(cmd: AnyPadroneCommand): boolean {
|
|
463
|
+
if (!cmd.commands || cmd.commands.length === 0) return true;
|
|
464
|
+
return cmd.commands.some(hasLeafDescendant);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
for (const cmd of commands) {
|
|
468
|
+
if (!cmd.commands || cmd.commands.length === 0) continue;
|
|
469
|
+
|
|
470
|
+
const allSubsEmpty = cmd.commands.every((sub) => sub.commands && sub.commands.length > 0 && !hasLeafDescendant(sub));
|
|
471
|
+
|
|
472
|
+
if (allSubsEmpty) {
|
|
473
|
+
diagnostics.push({
|
|
474
|
+
severity: 'warning',
|
|
475
|
+
command: commandDisplayName(cmd),
|
|
476
|
+
message: 'Command group has no reachable leaf commands.',
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Check for deprecated commands that use `deprecated: true` without a hint message.
|
|
484
|
+
*/
|
|
485
|
+
function checkDeprecatedWithoutHint(commands: AnyPadroneCommand[], diagnostics: Diagnostic[]) {
|
|
486
|
+
for (const cmd of commands) {
|
|
487
|
+
if (cmd.deprecated === true) {
|
|
488
|
+
diagnostics.push({
|
|
489
|
+
severity: 'warning',
|
|
490
|
+
command: commandDisplayName(cmd),
|
|
491
|
+
message: 'Command is deprecated without a replacement hint. Use a string to provide guidance (e.g., "Use \'new-cmd\' instead").',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Also check deprecated option fields
|
|
496
|
+
if (cmd.meta?.fields) {
|
|
497
|
+
for (const [fieldName, fieldMeta] of Object.entries(cmd.meta.fields)) {
|
|
498
|
+
if (fieldMeta?.deprecated === true) {
|
|
499
|
+
diagnostics.push({
|
|
500
|
+
severity: 'warning',
|
|
501
|
+
command: commandDisplayName(cmd),
|
|
502
|
+
message: `Option "${fieldName}" is deprecated without a replacement hint.`,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
316
510
|
function findProgram(mod: Record<string, unknown>): unknown | null {
|
|
317
511
|
const defaultExport = mod.default;
|
|
318
512
|
if (isPadroneProgram(defaultExport)) return defaultExport;
|
|
@@ -323,8 +517,3 @@ function findProgram(mod: Record<string, unknown>): unknown | null {
|
|
|
323
517
|
|
|
324
518
|
return null;
|
|
325
519
|
}
|
|
326
|
-
|
|
327
|
-
function isPadroneProgram(value: unknown): boolean {
|
|
328
|
-
if (!value || typeof value !== 'object') return false;
|
|
329
|
-
return commandSymbol in value;
|
|
330
|
-
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createPadrone } from 'padrone';
|
|
2
2
|
import pkg from 'padrone/package.json' with { type: 'json' };
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { runWrap } from './wrap.ts';
|
|
3
|
+
import { completionsSchema, runCompletions } from './completions.ts';
|
|
4
|
+
import { docsSchema, runDocs } from './docs.ts';
|
|
5
|
+
import { doctorSchema, runDoctor } from './doctor.ts';
|
|
6
|
+
import { initSchema, runInit } from './init.ts';
|
|
7
|
+
import { linkSchema, runLink, runUnlink, unlinkSchema } from './link.ts';
|
|
8
|
+
import { runWrap, wrapSchema } from './wrap.ts';
|
|
10
9
|
|
|
11
10
|
const PadroneCLI = createPadrone('padrone')
|
|
12
11
|
.configure({
|
|
@@ -19,17 +18,9 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
19
18
|
.configure({
|
|
20
19
|
description: 'Scaffold a new Padrone CLI project',
|
|
21
20
|
})
|
|
22
|
-
.arguments(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
description: z.string().optional().describe('Project description'),
|
|
26
|
-
version: z.string().optional().default('0.1.0').describe('Initial version'),
|
|
27
|
-
dir: z.string().optional().describe('Target directory (defaults to current directory)'),
|
|
28
|
-
}),
|
|
29
|
-
{
|
|
30
|
-
positional: ['dir'],
|
|
31
|
-
},
|
|
32
|
-
)
|
|
21
|
+
.arguments(initSchema, {
|
|
22
|
+
positional: ['dir'],
|
|
23
|
+
})
|
|
33
24
|
.async()
|
|
34
25
|
.action(runInit),
|
|
35
26
|
)
|
|
@@ -38,18 +29,9 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
38
29
|
.configure({
|
|
39
30
|
description: 'Generate documentation for a Padrone CLI program',
|
|
40
31
|
})
|
|
41
|
-
.arguments(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
output: z.string().optional().default('./docs/cli').describe('Output directory'),
|
|
45
|
-
format: z.enum(['markdown', 'html', 'man', 'json']).optional().default('markdown').describe('Output format'),
|
|
46
|
-
includeHidden: z.boolean().optional().default(false).describe('Include hidden commands and options'),
|
|
47
|
-
dryRun: z.boolean().optional().default(false).describe('Print what would be generated without writing'),
|
|
48
|
-
}),
|
|
49
|
-
{
|
|
50
|
-
positional: ['entry'],
|
|
51
|
-
},
|
|
52
|
-
)
|
|
32
|
+
.arguments(docsSchema, {
|
|
33
|
+
positional: ['entry'],
|
|
34
|
+
})
|
|
53
35
|
.async()
|
|
54
36
|
.action(runDocs),
|
|
55
37
|
)
|
|
@@ -58,14 +40,9 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
58
40
|
.configure({
|
|
59
41
|
description: 'Lint and validate a Padrone CLI program definition',
|
|
60
42
|
})
|
|
61
|
-
.arguments(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}),
|
|
65
|
-
{
|
|
66
|
-
positional: ['entry'],
|
|
67
|
-
},
|
|
68
|
-
)
|
|
43
|
+
.arguments(doctorSchema, {
|
|
44
|
+
positional: ['entry'],
|
|
45
|
+
})
|
|
69
46
|
.async()
|
|
70
47
|
.action(runDoctor),
|
|
71
48
|
)
|
|
@@ -74,16 +51,9 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
74
51
|
.configure({
|
|
75
52
|
description: 'Show shell completion install instructions for a Padrone CLI program',
|
|
76
53
|
})
|
|
77
|
-
.arguments(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for: z.enum(['bash', 'zsh', 'fish', 'powershell']).optional().describe('Target shell (auto-detected if omitted)'),
|
|
81
|
-
setup: z.boolean().optional().default(false).describe('Write completions to shell config file'),
|
|
82
|
-
}),
|
|
83
|
-
{
|
|
84
|
-
positional: ['appPath'],
|
|
85
|
-
},
|
|
86
|
-
)
|
|
54
|
+
.arguments(completionsSchema, {
|
|
55
|
+
positional: ['appPath'],
|
|
56
|
+
})
|
|
87
57
|
.action(runCompletions),
|
|
88
58
|
)
|
|
89
59
|
.command('link', (cmd) =>
|
|
@@ -91,17 +61,9 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
91
61
|
.configure({
|
|
92
62
|
description: 'Link a Padrone CLI program for global use during development',
|
|
93
63
|
})
|
|
94
|
-
.arguments(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
name: z.string().optional().describe('Command name (auto-detected from package.json)'),
|
|
98
|
-
list: z.boolean().optional().default(false).describe('List all linked programs'),
|
|
99
|
-
setup: z.boolean().optional().default(false).describe('Add ~/.padrone/bin to PATH in shell config'),
|
|
100
|
-
}),
|
|
101
|
-
{
|
|
102
|
-
positional: ['entry'],
|
|
103
|
-
},
|
|
104
|
-
)
|
|
64
|
+
.arguments(linkSchema, {
|
|
65
|
+
positional: ['entry'],
|
|
66
|
+
})
|
|
105
67
|
.async()
|
|
106
68
|
.action(runLink),
|
|
107
69
|
)
|
|
@@ -110,14 +72,9 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
110
72
|
.configure({
|
|
111
73
|
description: 'Remove a previously linked Padrone CLI program',
|
|
112
74
|
})
|
|
113
|
-
.arguments(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}),
|
|
117
|
-
{
|
|
118
|
-
positional: ['name'],
|
|
119
|
-
},
|
|
120
|
-
)
|
|
75
|
+
.arguments(unlinkSchema, {
|
|
76
|
+
positional: ['name'],
|
|
77
|
+
})
|
|
121
78
|
.async()
|
|
122
79
|
.action(runUnlink),
|
|
123
80
|
)
|
|
@@ -126,21 +83,10 @@ const PadroneCLI = createPadrone('padrone')
|
|
|
126
83
|
.configure({
|
|
127
84
|
description: 'Generate a Padrone wrapper for an existing CLI tool',
|
|
128
85
|
})
|
|
129
|
-
.arguments(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
output: z.string().optional().describe('Output directory (default: ./src/<command>)'),
|
|
134
|
-
depth: z.number().default(4).optional().describe('Max subcommand depth'),
|
|
135
|
-
dryRun: z.boolean().optional().default(false).describe('Print what would be generated without writing'),
|
|
136
|
-
overwrite: z.boolean().optional().default(false).describe('Overwrite existing files'),
|
|
137
|
-
yes: z.boolean().optional().default(false).describe('Skip confirmation prompt'),
|
|
138
|
-
}),
|
|
139
|
-
{
|
|
140
|
-
positional: ['command'],
|
|
141
|
-
fields: { yes: { alias: 'y' } },
|
|
142
|
-
},
|
|
143
|
-
)
|
|
86
|
+
.arguments(wrapSchema, {
|
|
87
|
+
positional: ['command'],
|
|
88
|
+
fields: { yes: { alias: 'y' } },
|
|
89
|
+
})
|
|
144
90
|
.async()
|
|
145
91
|
.action(runWrap),
|
|
146
92
|
);
|
package/src/cli/init.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { basename, resolve } from 'node:path';
|
|
3
3
|
import { createFileEmitter, template } from 'padrone/codegen';
|
|
4
|
-
import
|
|
4
|
+
import * as z from 'zod/v4';
|
|
5
|
+
import type { PadroneActionContext } from '../types/index.ts';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
name
|
|
8
|
-
description
|
|
9
|
-
version
|
|
10
|
-
dir
|
|
11
|
-
}
|
|
7
|
+
export const initSchema = z.object({
|
|
8
|
+
name: z.string().optional().describe('Project name (defaults to directory name)'),
|
|
9
|
+
description: z.string().optional().describe('Project description'),
|
|
10
|
+
version: z.string().optional().default('0.1.0').describe('Initial version'),
|
|
11
|
+
dir: z.string().optional().describe('Target directory (defaults to current directory)'),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
type InitArgs = z.infer<typeof initSchema>;
|
|
12
15
|
|
|
13
16
|
const packageJsonTemplate = template(`{
|
|
14
17
|
"name": "{{name}}",
|
|
@@ -16,9 +19,7 @@ const packageJsonTemplate = template(`{
|
|
|
16
19
|
"private": true,
|
|
17
20
|
"type": "module",
|
|
18
21
|
"module": "src/index.ts",
|
|
19
|
-
"bin":
|
|
20
|
-
"{{name}}": "src/index.ts"
|
|
21
|
-
},
|
|
22
|
+
"bin": "src/index.ts",
|
|
22
23
|
"scripts": {
|
|
23
24
|
"start": "bun src/index.ts",
|
|
24
25
|
"dev": "bun --watch src/index.ts",
|
|
@@ -131,5 +132,6 @@ export async function runInit(args: InitArgs, ctx: PadroneActionContext) {
|
|
|
131
132
|
output(` cd ${dir}`);
|
|
132
133
|
}
|
|
133
134
|
output(' bun install');
|
|
135
|
+
output(' bun update');
|
|
134
136
|
output(' bun run dev');
|
|
135
137
|
}
|
package/src/cli/link.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { basename, dirname, resolve } from 'node:path';
|
|
4
|
-
import
|
|
5
|
-
import type { PadroneActionContext } from '../types.ts';
|
|
6
|
-
|
|
7
|
-
interface LinkArgs {
|
|
8
|
-
entry?: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
list?: boolean;
|
|
11
|
-
setup?: boolean;
|
|
12
|
-
}
|
|
4
|
+
import * as z from 'zod/v4';
|
|
5
|
+
import type { PadroneActionContext } from '../types/index.ts';
|
|
6
|
+
import { detectShell, getRcFile, type ShellType, writeToRcFile } from '../util/shell-utils.ts';
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
export const linkSchema = z.object({
|
|
9
|
+
entry: z.string().optional().describe('Entry file (auto-detected from package.json bin field)'),
|
|
10
|
+
name: z.string().optional().describe('Command name (auto-detected from package.json)'),
|
|
11
|
+
list: z.boolean().optional().default(false).describe('List all linked programs'),
|
|
12
|
+
setup: z.boolean().optional().default(false).describe('Add ~/.padrone/bin to PATH in shell config'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const unlinkSchema = z.object({
|
|
16
|
+
name: z.string().optional().describe('Program name to unlink (auto-detected from current directory)'),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
type LinkArgs = z.infer<typeof linkSchema>;
|
|
20
|
+
type UnlinkArgs = z.infer<typeof unlinkSchema>;
|
|
17
21
|
|
|
18
22
|
interface LinkEntry {
|
|
19
23
|
name: string;
|
|
@@ -49,7 +53,7 @@ function writeLinks(links: LinksData) {
|
|
|
49
53
|
writeFileSync(LINKS_FILE, `${JSON.stringify(links, null, 2)}\n`);
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
interface DetectedEntry {
|
|
56
|
+
export interface DetectedEntry {
|
|
53
57
|
entry: string;
|
|
54
58
|
name: string;
|
|
55
59
|
/** Full run command prefix parsed from scripts (e.g. "bun --conditions=padrone@dev") */
|
|
@@ -70,7 +74,7 @@ function parseRunPrefix(script: string, entryRelative: string, dir: string): str
|
|
|
70
74
|
return undefined;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
function detectEntry(dir: string): DetectedEntry | undefined {
|
|
77
|
+
export function detectEntry(dir: string): DetectedEntry | undefined {
|
|
74
78
|
const pkgPath = resolve(dir, 'package.json');
|
|
75
79
|
if (!existsSync(pkgPath)) return undefined;
|
|
76
80
|
|
|
@@ -143,8 +147,8 @@ function buildPathSnippet(shell: ShellType, binDir: string): string {
|
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
function setupPath(shell: ShellType): { file: string; updated: boolean } {
|
|
147
|
-
const rcFile = getRcFile(shell);
|
|
150
|
+
async function setupPath(shell: ShellType): Promise<{ file: string; updated: boolean }> {
|
|
151
|
+
const rcFile = await getRcFile(shell);
|
|
148
152
|
if (!rcFile) {
|
|
149
153
|
throw new Error(`Could not determine config file for ${shell}.`);
|
|
150
154
|
}
|
|
@@ -268,13 +272,13 @@ export async function runLink(args: LinkArgs, ctx: PadroneActionContext) {
|
|
|
268
272
|
|
|
269
273
|
if (!isInPath(BIN_DIR)) {
|
|
270
274
|
if (args.setup) {
|
|
271
|
-
const shell = detectShell();
|
|
275
|
+
const shell = await detectShell();
|
|
272
276
|
if (!shell) {
|
|
273
277
|
error('Could not detect shell. Add the PATH manually:');
|
|
274
278
|
error(` export PATH="${BIN_DIR}:$PATH"`);
|
|
275
279
|
return;
|
|
276
280
|
}
|
|
277
|
-
const result = setupPath(shell);
|
|
281
|
+
const result = await setupPath(shell);
|
|
278
282
|
const verb = result.updated ? 'Updated' : 'Added';
|
|
279
283
|
output(`${verb} PATH in ${result.file}`);
|
|
280
284
|
output('Restart your shell or run:');
|
package/src/cli/wrap.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { createCodeBuilder, createFileEmitter, generateCommandTree } from 'padrone/codegen';
|
|
3
|
+
import * as z from 'zod/v4';
|
|
3
4
|
import type { DiscoverySource } from '../codegen/discovery.ts';
|
|
4
5
|
import { discoverCli } from '../codegen/discovery.ts';
|
|
5
6
|
import { template } from '../codegen/template.ts';
|
|
6
7
|
import type { GeneratorContext } from '../codegen/types.ts';
|
|
7
|
-
import type { PadroneActionContext } from '../types.ts';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
command: string
|
|
11
|
-
source
|
|
12
|
-
output
|
|
13
|
-
depth
|
|
14
|
-
dryRun
|
|
15
|
-
overwrite
|
|
16
|
-
yes
|
|
17
|
-
}
|
|
8
|
+
import type { PadroneActionContext } from '../types/index.ts';
|
|
9
|
+
|
|
10
|
+
export const wrapSchema = z.object({
|
|
11
|
+
command: z.string().describe('CLI command to wrap (e.g. gh, docker, kubectl)'),
|
|
12
|
+
source: z.enum(['help', 'fish', 'zsh']).optional().default('help').describe('Parsing source (default: help)'),
|
|
13
|
+
output: z.string().optional().describe('Output directory (default: ./src/<command>)'),
|
|
14
|
+
depth: z.number().default(4).optional().describe('Max subcommand depth'),
|
|
15
|
+
dryRun: z.boolean().optional().default(false).describe('Print what would be generated without writing'),
|
|
16
|
+
overwrite: z.boolean().optional().default(false).describe('Overwrite existing files'),
|
|
17
|
+
yes: z.boolean().optional().default(false).describe('Skip confirmation prompt'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
type WrapArgs = z.infer<typeof wrapSchema>;
|
|
18
21
|
|
|
19
22
|
export async function runWrap(args: WrapArgs, ctx: PadroneActionContext) {
|
|
20
23
|
const { output, error } = ctx.runtime;
|