import { resolve } from 'node:path'; import { createCodeBuilder, createFileEmitter, generateCommandTree } from 'padrone/codegen'; import * as z from 'zod/v4'; import type { DiscoverySource } from '../codegen/discovery.ts'; import { discoverCli } from '../codegen/discovery.ts'; import { template } from '../codegen/template.ts'; import type { GeneratorContext } from '../codegen/types.ts'; import type { PadroneActionContext } from '../types/index.ts'; export const wrapSchema = z.object({ command: z.string().describe('CLI command to wrap (e.g. gh, docker, kubectl)'), source: z.enum(['help', 'fish', 'zsh']).optional().default('help').describe('Parsing source (default: help)'), output: z.string().optional().describe('Output directory (default: ./src/)'), depth: z.number().default(4).optional().describe('Max subcommand depth'), dryRun: z.boolean().optional().default(false).describe('Print what would be generated without writing'), overwrite: z.boolean().optional().default(false).describe('Overwrite existing files'), yes: z.boolean().optional().default(false).describe('Skip confirmation prompt'), }); type WrapArgs = z.infer; export async function runWrap(args: WrapArgs, ctx: PadroneActionContext) { const { output, error } = ctx.runtime; const command = args.command; const sources: DiscoverySource[] = args.source ? [args.source] : ['help']; const outDir = resolve(args.output || `./src/${command}`); // Experimental warning — skip with -y if (!args.yes) { output('⚠ The `wrap` command is experimental. Generated code may require manual adjustments.'); output(''); if (ctx.runtime.prompt) { const proceed = await ctx.runtime.prompt({ name: 'confirm', message: 'Do you want to continue?', type: 'confirm', default: true, }); if (!proceed) { output('Aborted.'); return; } output(''); } } output(`Discovering ${command} CLI structure...`); const result = await discoverCli({ command, sources, depth: args.depth, log: { info: (msg) => output(msg), warn: (msg) => output(` warn: ${msg}`), error: (msg) => error(msg), success: (msg) => output(msg), }, }); // Error out if the root command returned nothing useful const hasSubcommands = result.command.subcommands && result.command.subcommands.length > 0; const hasArguments = result.command.arguments && result.command.arguments.length > 0; if (!hasSubcommands && !hasArguments && !result.command.description) { error(`Could not discover CLI structure for "${command}". Make sure the command exists and supports --help.`); return; } if (result.warnings.length > 0) { output(''); output('Warnings:'); for (const warn of result.warnings) { output(` - ${warn}`); } } const subcommandCount = countSubcommands(result.command); const optionCount = countOptions(result.command); output(''); output(`Discovered: ${subcommandCount} commands, ${optionCount} options (${result.invocations} invocations)`); output(''); // Generate the wrapper project const emitter = createFileEmitter({ outDir, header: `// Generated by \`padrone wrap ${command}\` — do not edit manually`, overwrite: args.overwrite, dryRun: args.dryRun, }); const genCtx: GeneratorContext = { outDir, createCodeBuilder, emitter, template, log: { info: (msg) => output(msg), warn: (msg) => output(` warn: ${msg}`), error: (msg) => error(msg), success: (msg) => output(msg), }, }; generateCommandTree(result.command, genCtx, { wrap: { command } }); const emitResult = await emitter.emit(); if (emitResult.errors.length > 0) { for (const err of emitResult.errors) { error(`Failed to write ${err.file}: ${err.error.message}`); } return; } if (args.dryRun) { output('Dry run — files that would be written:'); } else { output('Files written:'); } for (const file of emitResult.written) { output(` ${file}`); } if (emitResult.skipped.length > 0) { output(''); output('Skipped (already exist, use --overwrite to replace):'); for (const file of emitResult.skipped) { output(` ${file}`); } } } function countSubcommands(cmd: { subcommands?: { subcommands?: any[] }[] }): number { let count = 0; if (cmd.subcommands) { count += cmd.subcommands.length; for (const sub of cmd.subcommands) { count += countSubcommands(sub); } } return count; } function countOptions(cmd: { arguments?: unknown[]; subcommands?: any[] }): number { let count = cmd.arguments?.length || 0; if (cmd.subcommands) { for (const sub of cmd.subcommands) { count += countOptions(sub); } } return count; }