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,301 @@
|
|
|
1
|
+
import type { Schema } from 'ai';
|
|
2
|
+
import type { ShellType } from '../feature/completion.ts';
|
|
3
|
+
import { createReplIterator } from '../feature/repl-loop.ts';
|
|
4
|
+
import { generateHelp } from '../output/help.ts';
|
|
5
|
+
import type {
|
|
6
|
+
AnyPadroneCommand,
|
|
7
|
+
AnyPadroneProgram,
|
|
8
|
+
InterceptorExecuteContext,
|
|
9
|
+
InterceptorExecuteResult,
|
|
10
|
+
PadroneActionContext,
|
|
11
|
+
PadroneAPI,
|
|
12
|
+
PadroneReplPreferences,
|
|
13
|
+
} from '../types/index.ts';
|
|
14
|
+
import { parsePositionalConfig } from './args.ts';
|
|
15
|
+
import { findCommandByName, getCommandRuntime, resolveAllCommands } from './commands.ts';
|
|
16
|
+
import { RoutingError } from './errors.ts';
|
|
17
|
+
import type { ExecContext } from './exec.ts';
|
|
18
|
+
import { collectInterceptors, errorResultWithSignal, execCommand } from './exec.ts';
|
|
19
|
+
import { resolveRegisteredInterceptors, runInterceptorChain } from './interceptors.ts';
|
|
20
|
+
import { errorResult, makeThenable, thenMaybe, warnIfUnexpectedAsync, withDrain, withPromiseDrain } from './results.ts';
|
|
21
|
+
import { coreValidateForParse } from './validate.ts';
|
|
22
|
+
|
|
23
|
+
export function createProgramMethods(ctx: ExecContext, evalCommand: AnyPadroneProgram['eval']) {
|
|
24
|
+
const { rootCommand } = ctx;
|
|
25
|
+
|
|
26
|
+
// A never-aborted signal for contexts that don't need signal handling (parse, run).
|
|
27
|
+
const inertSignal = new AbortController().signal;
|
|
28
|
+
|
|
29
|
+
const stringify: AnyPadroneProgram['stringify'] = (command = '' as any, args) => {
|
|
30
|
+
const commandObj = typeof command === 'string' ? findCommandByName(command, rootCommand.commands) : (command as AnyPadroneCommand);
|
|
31
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ''}" not found`);
|
|
32
|
+
|
|
33
|
+
const parts: string[] = [];
|
|
34
|
+
|
|
35
|
+
if (commandObj.path) parts.push(commandObj.path);
|
|
36
|
+
|
|
37
|
+
const positionalConfig = commandObj.meta?.positional ? parsePositionalConfig(commandObj.meta.positional) : [];
|
|
38
|
+
const positionalNames = new Set(positionalConfig.map((p) => p.name));
|
|
39
|
+
|
|
40
|
+
if (args && typeof args === 'object') {
|
|
41
|
+
for (const { name, variadic } of positionalConfig) {
|
|
42
|
+
const value = (args as Record<string, unknown>)[name];
|
|
43
|
+
if (value === undefined) continue;
|
|
44
|
+
|
|
45
|
+
if (variadic && Array.isArray(value)) {
|
|
46
|
+
for (const v of value) {
|
|
47
|
+
const vStr = String(v);
|
|
48
|
+
if (vStr.includes(' ')) parts.push(`"${vStr}"`);
|
|
49
|
+
else parts.push(vStr);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
const argStr = String(value);
|
|
53
|
+
if (argStr.includes(' ')) parts.push(`"${argStr}"`);
|
|
54
|
+
else parts.push(argStr);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stringifyValue = (key: string, value: unknown) => {
|
|
59
|
+
if (value === undefined) return;
|
|
60
|
+
|
|
61
|
+
if (typeof value === 'boolean') {
|
|
62
|
+
if (value) parts.push(`--${key}`);
|
|
63
|
+
else parts.push(`--no-${key}`);
|
|
64
|
+
} else if (Array.isArray(value)) {
|
|
65
|
+
for (const v of value) {
|
|
66
|
+
const vStr = String(v);
|
|
67
|
+
if (vStr.includes(' ')) parts.push(`--${key}="${vStr}"`);
|
|
68
|
+
else parts.push(`--${key}=${vStr}`);
|
|
69
|
+
}
|
|
70
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
71
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
72
|
+
stringifyValue(`${key}.${nestedKey}`, nestedValue);
|
|
73
|
+
}
|
|
74
|
+
} else if (typeof value === 'string') {
|
|
75
|
+
if (value.includes(' ')) parts.push(`--${key}="${value}"`);
|
|
76
|
+
else parts.push(`--${key}=${value}`);
|
|
77
|
+
} else {
|
|
78
|
+
parts.push(`--${key}=${value}`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
for (const [key, value] of Object.entries(args)) {
|
|
83
|
+
if (value === undefined || positionalNames.has(key)) continue;
|
|
84
|
+
stringifyValue(key, value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return parts.join(' ');
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const resolveContext = (command: AnyPadroneCommand, initialContext: unknown): unknown => {
|
|
92
|
+
const chain: AnyPadroneCommand[] = [];
|
|
93
|
+
let current: AnyPadroneCommand | undefined = command;
|
|
94
|
+
while (current) {
|
|
95
|
+
chain.unshift(current);
|
|
96
|
+
current = current.parent;
|
|
97
|
+
}
|
|
98
|
+
let resolved = initialContext;
|
|
99
|
+
for (const cmd of chain) {
|
|
100
|
+
if (cmd.contextTransform) resolved = cmd.contextTransform(resolved);
|
|
101
|
+
}
|
|
102
|
+
return resolved;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const run: AnyPadroneProgram['run'] = (command, args, prefs?: { context?: unknown }) => {
|
|
106
|
+
try {
|
|
107
|
+
const commandObj = typeof command === 'string' ? findCommandByName(command, rootCommand.commands) : (command as AnyPadroneCommand);
|
|
108
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ''}" not found`);
|
|
109
|
+
if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
|
|
110
|
+
|
|
111
|
+
const resolvedCtx = resolveContext(commandObj, prefs?.context);
|
|
112
|
+
const commandRuntime = getCommandRuntime(commandObj);
|
|
113
|
+
const executeCtx: InterceptorExecuteContext = {
|
|
114
|
+
command: commandObj,
|
|
115
|
+
input: undefined,
|
|
116
|
+
rawArgs: {},
|
|
117
|
+
positionalArgs: [],
|
|
118
|
+
args,
|
|
119
|
+
signal: inertSignal,
|
|
120
|
+
context: resolvedCtx,
|
|
121
|
+
runtime: commandRuntime,
|
|
122
|
+
program: ctx.builder as any,
|
|
123
|
+
caller: 'run',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const coreExecute = (executeCtx: InterceptorExecuteContext): InterceptorExecuteResult => {
|
|
127
|
+
const actionCtx: PadroneActionContext = {
|
|
128
|
+
runtime: executeCtx.runtime,
|
|
129
|
+
command: executeCtx.command,
|
|
130
|
+
program: ctx.builder as any,
|
|
131
|
+
signal: inertSignal,
|
|
132
|
+
context: executeCtx.context,
|
|
133
|
+
caller: 'run',
|
|
134
|
+
};
|
|
135
|
+
const result = commandObj.action!(executeCtx.args as any, actionCtx);
|
|
136
|
+
return { result };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const commandInterceptors = resolveRegisteredInterceptors(collectInterceptors(commandObj, rootCommand), new Map());
|
|
140
|
+
const executedOrPromise = runInterceptorChain('execute', commandInterceptors, executeCtx, coreExecute);
|
|
141
|
+
|
|
142
|
+
const toResult = (e: InterceptorExecuteResult) => withDrain({ command: commandObj as any, args: args as any, result: e.result });
|
|
143
|
+
|
|
144
|
+
if (executedOrPromise instanceof Promise) {
|
|
145
|
+
return executedOrPromise.then(toResult).catch((err: unknown) => errorResult(err, { command: commandObj, args })) as any;
|
|
146
|
+
}
|
|
147
|
+
return toResult(executedOrPromise);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return errorResult(err) as any;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const tool: AnyPadroneProgram['tool'] = () => {
|
|
154
|
+
resolveAllCommands(rootCommand);
|
|
155
|
+
const helpText = generateHelp(rootCommand, undefined, { format: 'text' });
|
|
156
|
+
|
|
157
|
+
const description = `Run a command. Pass the full command string including arguments. Use "help <command>" for detailed usage.\n\n${helpText}`;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
type: 'function',
|
|
161
|
+
name: rootCommand.name,
|
|
162
|
+
strict: true,
|
|
163
|
+
title: rootCommand.description,
|
|
164
|
+
description,
|
|
165
|
+
inputExamples: [{ input: { command: '<command> [positionals...] [arguments...]' } }],
|
|
166
|
+
inputSchema: {
|
|
167
|
+
[Symbol.for('vercel.ai.schema') as keyof Schema & symbol]: true,
|
|
168
|
+
jsonSchema: {
|
|
169
|
+
type: 'object',
|
|
170
|
+
properties: { command: { type: 'string' } },
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
},
|
|
173
|
+
_type: undefined as unknown as { command: string },
|
|
174
|
+
validate: (value) => {
|
|
175
|
+
const command = (value as any)?.command;
|
|
176
|
+
if (typeof command === 'string') return { success: true, value: { command } };
|
|
177
|
+
return { success: false, error: new Error('Expected an object with command property as string.') };
|
|
178
|
+
},
|
|
179
|
+
} satisfies Schema<{ command: string }> as Schema<{ command: string }>,
|
|
180
|
+
needsApproval: async (input) => {
|
|
181
|
+
const parsed = await parse(input.command);
|
|
182
|
+
if (typeof parsed.command.needsApproval === 'function') return parsed.command.needsApproval(parsed.args);
|
|
183
|
+
if (parsed.command.needsApproval != null) return !!parsed.command.needsApproval;
|
|
184
|
+
return !!parsed.command.mutation;
|
|
185
|
+
},
|
|
186
|
+
execute: async (input) => {
|
|
187
|
+
const output: string[] = [];
|
|
188
|
+
const errors: string[] = [];
|
|
189
|
+
const result = await evalCommand(input.command, {
|
|
190
|
+
caller: 'tool',
|
|
191
|
+
runtime: {
|
|
192
|
+
output: (...args) => output.push(args.map(String).join(' ')),
|
|
193
|
+
error: (text) => errors.push(text),
|
|
194
|
+
interactive: 'unsupported',
|
|
195
|
+
format: 'text',
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
return { result: result.result, logs: output.join('\n'), error: errors.join('\n') };
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const replActiveRef = { value: false };
|
|
204
|
+
const replFn = (options?: PadroneReplPreferences) =>
|
|
205
|
+
createReplIterator({ existingCommand: rootCommand, evalCommand, replActiveRef }, options);
|
|
206
|
+
|
|
207
|
+
const cli: AnyPadroneProgram['cli'] = (cliOptions) => {
|
|
208
|
+
try {
|
|
209
|
+
const runtime = getCommandRuntime(rootCommand);
|
|
210
|
+
const resolvedInput = (runtime.argv().join(' ') || undefined) as string | undefined;
|
|
211
|
+
|
|
212
|
+
const result = execCommand(resolvedInput, ctx, cliOptions, 'hard', 'cli');
|
|
213
|
+
|
|
214
|
+
if (result instanceof Promise) return withPromiseDrain(result.catch((err: unknown) => errorResultWithSignal(err))) as any;
|
|
215
|
+
return makeThenable(result);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return makeThenable(errorResultWithSignal(err)) as any;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const find: AnyPadroneProgram['find'] = (command) => {
|
|
222
|
+
if (typeof command !== 'string') return findCommandByName(command.path, rootCommand.commands) as any;
|
|
223
|
+
return findCommandByName(command, rootCommand.commands) as any;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const parse: AnyPadroneProgram['parse'] = (input) => {
|
|
227
|
+
const { command, rawArgs, args } = ctx.parseCommandFn(input as string | undefined);
|
|
228
|
+
|
|
229
|
+
const validatedOrPromise = coreValidateForParse(command, rawArgs, args);
|
|
230
|
+
|
|
231
|
+
return makeThenable(
|
|
232
|
+
warnIfUnexpectedAsync(
|
|
233
|
+
thenMaybe(validatedOrPromise, (v: any) => ({ command: command as any, args: v.args, argsResult: v.argsResult })),
|
|
234
|
+
command,
|
|
235
|
+
),
|
|
236
|
+
) as any;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const help: AnyPadroneProgram['help'] = (command, prefs) => {
|
|
240
|
+
resolveAllCommands(rootCommand);
|
|
241
|
+
const commandObj = !command
|
|
242
|
+
? rootCommand
|
|
243
|
+
: typeof command === 'string'
|
|
244
|
+
? findCommandByName(command, rootCommand.commands)
|
|
245
|
+
: (command as AnyPadroneCommand);
|
|
246
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ''}" not found`);
|
|
247
|
+
const runtime = getCommandRuntime(rootCommand);
|
|
248
|
+
return generateHelp(rootCommand, commandObj, {
|
|
249
|
+
...prefs,
|
|
250
|
+
format: prefs?.format ?? runtime.format,
|
|
251
|
+
theme: prefs?.theme ?? runtime.theme,
|
|
252
|
+
terminal: prefs?.terminal ?? runtime.terminal,
|
|
253
|
+
env: prefs?.env ?? runtime.env(),
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const api: AnyPadroneProgram['api'] = () => {
|
|
258
|
+
resolveAllCommands(rootCommand);
|
|
259
|
+
function buildApi(command: AnyPadroneCommand) {
|
|
260
|
+
const runCommand = ((args) => run(command, args).result) as PadroneAPI<AnyPadroneCommand>;
|
|
261
|
+
if (!command.commands) return runCommand;
|
|
262
|
+
for (const cmd of command.commands) runCommand[cmd.name] = buildApi(cmd);
|
|
263
|
+
return runCommand;
|
|
264
|
+
}
|
|
265
|
+
return buildApi(rootCommand);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const completion: AnyPadroneProgram['completion'] = async (shell) => {
|
|
269
|
+
resolveAllCommands(rootCommand);
|
|
270
|
+
const { generateCompletionOutput } = await import('../feature/completion.ts');
|
|
271
|
+
return generateCompletionOutput(rootCommand, shell as ShellType | undefined);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const mcp: AnyPadroneProgram['mcp'] = async (prefs) => {
|
|
275
|
+
resolveAllCommands(rootCommand);
|
|
276
|
+
const { startMcpServer } = await import('../feature/mcp.ts');
|
|
277
|
+
return startMcpServer(ctx.builder as any, rootCommand, evalCommand, prefs);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const serve: AnyPadroneProgram['serve'] = async (prefs) => {
|
|
281
|
+
resolveAllCommands(rootCommand);
|
|
282
|
+
const { startServeServer } = await import('../feature/serve.ts');
|
|
283
|
+
return startServeServer(ctx.builder as any, rootCommand, evalCommand, prefs);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
find,
|
|
288
|
+
parse,
|
|
289
|
+
stringify,
|
|
290
|
+
run,
|
|
291
|
+
eval: evalCommand,
|
|
292
|
+
cli,
|
|
293
|
+
tool,
|
|
294
|
+
repl: replFn,
|
|
295
|
+
api,
|
|
296
|
+
help,
|
|
297
|
+
completion,
|
|
298
|
+
mcp,
|
|
299
|
+
serve,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { AnyPadroneCommand, PadroneSchema } from '../types/index.ts';
|
|
2
|
+
import type { Thenable } from '../util/type-utils.ts';
|
|
3
|
+
import { getCommandRuntime } from './commands.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Brands a schema as async, signaling that its `validate()` may return a Promise.
|
|
7
|
+
* When an async-branded schema is passed to `.arguments()`, `.configFile()`, or `.env()`,
|
|
8
|
+
* the command's `parse()` and `cli()` will return Promises.
|
|
9
|
+
*/
|
|
10
|
+
export function asyncSchema<T extends PadroneSchema>(schema: T): T & { '~async': true } {
|
|
11
|
+
return Object.assign(schema, { '~async': true as const });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const noop = <TRes>() => undefined as TRes;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Maps over a value that may or may not be a Promise.
|
|
18
|
+
* If the value is a Promise, chains with `.then()`. Otherwise, calls the function synchronously.
|
|
19
|
+
* This preserves sync behavior for sync schemas and async behavior for async schemas.
|
|
20
|
+
*/
|
|
21
|
+
export function thenMaybe<T, U>(value: T | Promise<T>, fn: (v: T) => U | Promise<U>): U | Promise<U> {
|
|
22
|
+
if (value instanceof Promise) return value.then(fn);
|
|
23
|
+
return fn(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Makes a sync result object thenable by adding `.then()`, `.catch()`, and `.finally()` methods.
|
|
28
|
+
* If the value is already a Promise, returns it as-is.
|
|
29
|
+
* This allows users to write `await program.cli()` or `program.cli().then(...)` regardless of sync/async.
|
|
30
|
+
*
|
|
31
|
+
* The `.then()` resolves with a plain copy (without thenable methods) to avoid infinite
|
|
32
|
+
* recursive unwrapping by the Promise resolution algorithm.
|
|
33
|
+
*/
|
|
34
|
+
export function makeThenable<T>(value: T | Promise<T>): Thenable<T> {
|
|
35
|
+
if (value instanceof Promise) return value as any;
|
|
36
|
+
if (value !== null && typeof value === 'object' && !('then' in value)) {
|
|
37
|
+
const toPlain = () => {
|
|
38
|
+
const plain = { ...value } as any;
|
|
39
|
+
delete plain.then;
|
|
40
|
+
delete plain.catch;
|
|
41
|
+
delete plain.finally;
|
|
42
|
+
return plain as T;
|
|
43
|
+
};
|
|
44
|
+
// biome-ignore lint/suspicious/noThenProperty: intentional thenable shim for sync results
|
|
45
|
+
(value as any).then = (onfulfilled?: (v: T) => any, onrejected?: (reason: any) => any) => {
|
|
46
|
+
try {
|
|
47
|
+
const result = onfulfilled ? onfulfilled(toPlain()) : toPlain();
|
|
48
|
+
return Promise.resolve(result);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (onrejected) return Promise.resolve(onrejected(err));
|
|
51
|
+
return Promise.reject(err);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
(value as any).catch = (onrejected?: (reason: any) => any) => (value as any).then(undefined, onrejected);
|
|
55
|
+
(value as any).finally = (onfinally?: () => void) =>
|
|
56
|
+
(value as any).then(
|
|
57
|
+
(v: any) => {
|
|
58
|
+
onfinally?.();
|
|
59
|
+
return v;
|
|
60
|
+
},
|
|
61
|
+
(err: any) => {
|
|
62
|
+
onfinally?.();
|
|
63
|
+
throw err;
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return value as any;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wraps a Promise to include a `drain()` method at the top level.
|
|
72
|
+
* This allows `await promise.drain()` without first awaiting the promise.
|
|
73
|
+
* Since cli/eval never reject, this just delegates to the resolved result's `drain()`.
|
|
74
|
+
*/
|
|
75
|
+
export function withPromiseDrain<T extends Promise<any>>(promise: T): T & { drain: () => Promise<any> } {
|
|
76
|
+
(promise as any).drain = async () => {
|
|
77
|
+
const resolved = await promise;
|
|
78
|
+
return resolved.drain();
|
|
79
|
+
};
|
|
80
|
+
return promise as any;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isIterator(value: unknown): value is Iterator<unknown> {
|
|
84
|
+
return typeof value === 'object' && value !== null && Symbol.iterator in value && typeof (value as any)[Symbol.iterator] === 'function';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isAsyncIterator(value: unknown): value is AsyncIterator<unknown> {
|
|
88
|
+
return (
|
|
89
|
+
typeof value === 'object' &&
|
|
90
|
+
value !== null &&
|
|
91
|
+
Symbol.asyncIterator in value &&
|
|
92
|
+
typeof (value as any)[Symbol.asyncIterator] === 'function'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Writes a command's return value to output, handling promises, iterators, and async iterators.
|
|
98
|
+
* Values are passed directly to the output function without stringification —
|
|
99
|
+
* runtimes like Node/Bun already format objects via console.log.
|
|
100
|
+
* Returns void or a Promise depending on whether async consumption is needed.
|
|
101
|
+
*/
|
|
102
|
+
export function outputValue(value: unknown, output: (...args: unknown[]) => void): void | Promise<void> {
|
|
103
|
+
if (value == null) return;
|
|
104
|
+
|
|
105
|
+
// Async iterator — consume and output each yielded value
|
|
106
|
+
if (isAsyncIterator(value)) {
|
|
107
|
+
return (async () => {
|
|
108
|
+
const iter = (value as any)[Symbol.asyncIterator]();
|
|
109
|
+
while (true) {
|
|
110
|
+
const { done, value: item } = await iter.next();
|
|
111
|
+
if (done) break;
|
|
112
|
+
if (item != null) output(item);
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sync iterator (but not a plain string/array which also have Symbol.iterator)
|
|
118
|
+
if (typeof value !== 'string' && !Array.isArray(value) && isIterator(value)) {
|
|
119
|
+
const iter = (value as any)[Symbol.iterator]();
|
|
120
|
+
while (true) {
|
|
121
|
+
const { done, value: item } = iter.next();
|
|
122
|
+
if (done) break;
|
|
123
|
+
if (item != null) output(item);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Promise — await then output
|
|
129
|
+
if (value instanceof Promise) {
|
|
130
|
+
return value.then((resolved) => outputValue(resolved, output));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Pass value directly — runtime handles formatting
|
|
134
|
+
output(value);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolves a result value by unwrapping Promises and collecting iterables into arrays.
|
|
139
|
+
* This is the runtime counterpart of the `Drained<T>` type.
|
|
140
|
+
*/
|
|
141
|
+
export async function drainValue(value: unknown): Promise<unknown> {
|
|
142
|
+
// Unwrap promises first
|
|
143
|
+
if (value instanceof Promise) {
|
|
144
|
+
return drainValue(await value);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Async iterator — collect into array
|
|
148
|
+
if (isAsyncIterator(value)) {
|
|
149
|
+
const items: unknown[] = [];
|
|
150
|
+
const iter = (value as any)[Symbol.asyncIterator]();
|
|
151
|
+
while (true) {
|
|
152
|
+
const { done, value: item } = await iter.next();
|
|
153
|
+
if (done) break;
|
|
154
|
+
items.push(item);
|
|
155
|
+
}
|
|
156
|
+
return items;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Sync iterator (but not string/array)
|
|
160
|
+
if (typeof value !== 'string' && !Array.isArray(value) && isIterator(value)) {
|
|
161
|
+
const items: unknown[] = [];
|
|
162
|
+
const iter = (value as any)[Symbol.iterator]();
|
|
163
|
+
while (true) {
|
|
164
|
+
const { done, value: item } = iter.next();
|
|
165
|
+
if (done) break;
|
|
166
|
+
items.push(item);
|
|
167
|
+
}
|
|
168
|
+
return items;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Attaches a `drain()` method to a command result object.
|
|
176
|
+
* If the result has an `error` field, `drain()` returns `{ error }`.
|
|
177
|
+
* Otherwise, resolves the result (unwrapping Promises, collecting iterables), catches errors,
|
|
178
|
+
* and returns a discriminated union `{ value } | { error }` that never throws.
|
|
179
|
+
*/
|
|
180
|
+
export function withDrain<T extends Record<string, unknown>>(obj: T): T & { drain: () => Promise<any> } {
|
|
181
|
+
(obj as any).drain = async () => {
|
|
182
|
+
if ('error' in obj && obj.error !== undefined) {
|
|
183
|
+
return { error: obj.error };
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const value = await drainValue(obj.result);
|
|
187
|
+
return { value };
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return { error: err };
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
return obj as any;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Creates an error command result with a `drain()` that returns the error.
|
|
197
|
+
*/
|
|
198
|
+
export function errorResult(error: unknown, partial?: { command?: unknown; args?: unknown; argsResult?: unknown }) {
|
|
199
|
+
return withDrain({
|
|
200
|
+
error,
|
|
201
|
+
result: undefined,
|
|
202
|
+
command: partial?.command,
|
|
203
|
+
args: partial?.args,
|
|
204
|
+
argsResult: partial?.argsResult,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function isAsyncBranded(schema: unknown): boolean {
|
|
209
|
+
return !!schema && typeof schema === 'object' && '~async' in schema && (schema as any)['~async'] === true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function hasInteractiveConfig(meta: unknown): boolean {
|
|
213
|
+
if (!meta || typeof meta !== 'object') return false;
|
|
214
|
+
const m = meta as Record<string, unknown>;
|
|
215
|
+
return m.interactive === true || Array.isArray(m.interactive) || m.optionalInteractive === true || Array.isArray(m.optionalInteractive);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function warnIfUnexpectedAsync<T>(value: T, command: AnyPadroneCommand): T {
|
|
219
|
+
const runtime = getCommandRuntime(command);
|
|
220
|
+
if (runtime.env().NODE_ENV === 'production') return value;
|
|
221
|
+
if (value instanceof Promise && !command.isAsync) {
|
|
222
|
+
runtime.error(
|
|
223
|
+
`[padrone] Command "${command.path || command.name}" returned a Promise from validation, ` +
|
|
224
|
+
`but was not marked as async. Use \`.async()\` on the builder or \`asyncSchema()\` to brand your schema. ` +
|
|
225
|
+
`Without this, TypeScript will infer a sync return type and the result will be a Promise at runtime.`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
229
|
+
}
|