padrone 1.5.0 → 1.7.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 +44 -0
- package/README.md +15 -11
- package/dist/{args-D5PNDyNu.mjs → args-Cnq0nwSM.mjs} +91 -41
- package/dist/args-Cnq0nwSM.mjs.map +1 -0
- package/dist/codegen/index.mjs +4 -4
- 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} +12 -82
- package/dist/completion-BEuflbDO.mjs.map +1 -0
- package/dist/docs/index.d.mts +4 -4
- package/dist/docs/index.d.mts.map +1 -1
- package/dist/docs/index.mjs +10 -12
- package/dist/docs/index.mjs.map +1 -1
- package/dist/{errors-BiVrBgi6.mjs → errors-DA4KzK1M.mjs} +26 -3
- package/dist/errors-DA4KzK1M.mjs.map +1 -0
- package/dist/{formatter-DtHzbP22.d.mts → formatter-DrvhDMrq.d.mts} +3 -3
- package/dist/formatter-DrvhDMrq.d.mts.map +1 -0
- package/dist/{help-bbmu9-qd.mjs → help-BtxLgrF_.mjs} +190 -43
- package/dist/help-BtxLgrF_.mjs.map +1 -0
- package/dist/{types-Ch8Mk6Qb.d.mts → index-D6-7dz0l.d.mts} +634 -745
- package/dist/index-D6-7dz0l.d.mts.map +1 -0
- package/dist/index.d.mts +869 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3884 -1699
- package/dist/index.mjs.map +1 -1
- package/dist/{mcp-mLWIdUIu.mjs → mcp-6-Jw4Bpq.mjs} +13 -15
- package/dist/mcp-6-Jw4Bpq.mjs.map +1 -0
- package/dist/{serve-B0u43DK7.mjs → serve-YVTPzBCl.mjs} +12 -14
- package/dist/serve-YVTPzBCl.mjs.map +1 -0
- package/dist/{stream-BcC146Ud.mjs → stream-DC4H8YTx.mjs} +24 -3
- 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 +2 -13
- package/dist/test.mjs.map +1 -1
- package/dist/{update-check-CFX1FV3v.mjs → update-check-CZ2VqjnV.mjs} +16 -17
- package/dist/update-check-CZ2VqjnV.mjs.map +1 -0
- package/dist/zod.d.mts +2 -2
- package/dist/zod.d.mts.map +1 -1
- package/dist/zod.mjs +2 -2
- package/dist/zod.mjs.map +1 -1
- package/package.json +15 -12
- package/src/cli/completions.ts +14 -11
- package/src/cli/docs.ts +13 -10
- package/src/cli/doctor.ts +22 -18
- package/src/cli/index.ts +28 -82
- package/src/cli/init.ts +10 -7
- package/src/cli/link.ts +20 -16
- package/src/cli/wrap.ts +14 -11
- package/src/codegen/schema-to-code.ts +2 -2
- package/src/{args.ts → core/args.ts} +32 -225
- package/src/core/commands.ts +373 -0
- package/src/core/create.ts +301 -0
- package/src/core/default-runtime.ts +239 -0
- 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 +12 -13
- package/src/extension/auto-output.ts +146 -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 +44 -0
- package/src/extension/ink.ts +93 -0
- package/src/extension/interactive.ts +106 -0
- package/src/extension/logger.ts +262 -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} +12 -12
- package/src/{interactive.ts → feature/interactive.ts} +4 -4
- package/src/{mcp.ts → feature/mcp.ts} +12 -15
- package/src/{repl-loop.ts → feature/repl-loop.ts} +10 -13
- package/src/{serve.ts → feature/serve.ts} +11 -15
- 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} +10 -8
- package/src/index.ts +115 -30
- package/src/{formatter.ts → output/formatter.ts} +124 -176
- package/src/{help.ts → output/help.ts} +22 -8
- package/src/output/output-indicator.ts +87 -0
- package/src/output/primitives.ts +335 -0
- package/src/output/styling.ts +221 -0
- package/src/{zod.d.ts → schema/zod.d.ts} +1 -1
- package/src/schema/zod.ts +50 -0
- package/src/test.ts +2 -276
- package/src/types/args-meta.ts +151 -0
- package/src/types/builder.ts +718 -0
- package/src/types/command.ts +157 -0
- package/src/types/index.ts +60 -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/{stream.ts → util/stream.ts} +27 -1
- package/src/{type-helpers.ts → util/type-helpers.ts} +23 -16
- package/src/{type-utils.ts → util/type-utils.ts} +71 -33
- package/src/util/utils.ts +51 -0
- package/src/zod.ts +1 -50
- package/dist/args-D5PNDyNu.mjs.map +0 -1
- package/dist/chunk-CjcI7cDX.mjs +0 -15
- package/dist/command-utils-B1D-HqCd.mjs +0 -1117
- package/dist/command-utils-B1D-HqCd.mjs.map +0 -1
- package/dist/completion.d.mts +0 -64
- package/dist/completion.d.mts.map +0 -1
- package/dist/completion.mjs.map +0 -1
- package/dist/errors-BiVrBgi6.mjs.map +0 -1
- package/dist/formatter-DtHzbP22.d.mts.map +0 -1
- package/dist/help-bbmu9-qd.mjs.map +0 -1
- package/dist/mcp-mLWIdUIu.mjs.map +0 -1
- package/dist/serve-B0u43DK7.mjs.map +0 -1
- package/dist/stream-BcC146Ud.mjs.map +0 -1
- package/dist/types-Ch8Mk6Qb.d.mts.map +0 -1
- package/dist/update-check-CFX1FV3v.mjs.map +0 -1
- package/src/command-utils.ts +0 -882
- package/src/create.ts +0 -1829
- package/src/runtime.ts +0 -497
- package/src/types.ts +0 -1291
- package/src/utils.ts +0 -140
- /package/src/{colorizer.ts → output/colorizer.ts} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { m as serializeArgsToFlags, r as collectEndpoints, t as buildInputSchema } from "./commands-B_gufyR9.mjs";
|
|
2
|
+
import { a as readStreamAsText } from "./stream-DC4H8YTx.mjs";
|
|
3
|
+
import { t as generateHelp } from "./help-BtxLgrF_.mjs";
|
|
4
|
+
//#region src/feature/mcp.ts
|
|
4
5
|
const PROTOCOL_VERSION = "2025-11-25";
|
|
5
6
|
/** Convert an endpoint dot-path to a valid MCP tool name. Spec allows: [A-Za-z0-9_\-\.] */
|
|
6
7
|
function toToolName(path) {
|
|
@@ -117,7 +118,7 @@ function createMcpHandler(existingCommand, evalCommand, prefs) {
|
|
|
117
118
|
const output = [];
|
|
118
119
|
const errors = [];
|
|
119
120
|
const result = await evalCommand(input, {
|
|
120
|
-
|
|
121
|
+
caller: "mcp",
|
|
121
122
|
runtime: {
|
|
122
123
|
output: (...outArgs) => output.push(outArgs.map(String).join(" ")),
|
|
123
124
|
error: (text) => errors.push(text),
|
|
@@ -231,7 +232,7 @@ async function startStdioTransport(handleRequest) {
|
|
|
231
232
|
}
|
|
232
233
|
}
|
|
233
234
|
/** Streamable HTTP transport per 2025-11-25 spec. Responds with JSON or SSE based on client's Accept header. */
|
|
234
|
-
async function startHttpTransport(handleRequest, prefs, log) {
|
|
235
|
+
async function startHttpTransport(handleRequest, prefs, log, onSignal) {
|
|
235
236
|
const http = await import("node:http");
|
|
236
237
|
const crypto = await import("node:crypto");
|
|
237
238
|
const port = prefs.port ?? 3e3;
|
|
@@ -313,9 +314,7 @@ async function startHttpTransport(handleRequest, prefs, log) {
|
|
|
313
314
|
}));
|
|
314
315
|
return;
|
|
315
316
|
}
|
|
316
|
-
const
|
|
317
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
318
|
-
const body = Buffer.concat(chunks).toString("utf-8");
|
|
317
|
+
const body = await readStreamAsText(req);
|
|
319
318
|
let rpcRequest;
|
|
320
319
|
try {
|
|
321
320
|
rpcRequest = JSON.parse(body);
|
|
@@ -359,21 +358,20 @@ async function startHttpTransport(handleRequest, prefs, log) {
|
|
|
359
358
|
log(`MCP server listening on http://${host}:${port}${endpoint}`);
|
|
360
359
|
});
|
|
361
360
|
server.on("error", reject);
|
|
362
|
-
const
|
|
361
|
+
const unsubscribe = onSignal?.(() => {
|
|
363
362
|
server.close(() => resolve());
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
process.on("SIGTERM", onSignal);
|
|
363
|
+
});
|
|
364
|
+
server.on("close", () => unsubscribe?.());
|
|
367
365
|
});
|
|
368
366
|
}
|
|
369
367
|
async function startMcpServer(_program, existingCommand, evalCommand, prefs) {
|
|
370
368
|
const handleRequest = createMcpHandler(existingCommand, evalCommand, prefs);
|
|
371
369
|
if ((prefs?.transport ?? "http") === "stdio") return startStdioTransport(handleRequest);
|
|
372
|
-
const { getCommandRuntime } = await import("./
|
|
370
|
+
const { getCommandRuntime } = await import("./commands-B_gufyR9.mjs").then((n) => n.a);
|
|
373
371
|
const runtime = getCommandRuntime(existingCommand);
|
|
374
|
-
return startHttpTransport(handleRequest, prefs ?? {}, (msg) => runtime.error(msg));
|
|
372
|
+
return startHttpTransport(handleRequest, prefs ?? {}, (msg) => runtime.error(msg), runtime.onSignal);
|
|
375
373
|
}
|
|
376
374
|
//#endregion
|
|
377
375
|
export { startMcpServer };
|
|
378
376
|
|
|
379
|
-
//# sourceMappingURL=mcp-
|
|
377
|
+
//# sourceMappingURL=mcp-6-Jw4Bpq.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-6-Jw4Bpq.mjs","names":[],"sources":["../src/feature/mcp.ts"],"sourcesContent":["import { buildInputSchema, collectEndpoints, serializeArgsToFlags } from '../core/commands.ts';\nimport { generateHelp } from '../output/help.ts';\nimport type { AnyPadroneCommand, AnyPadroneProgram } from '../types/index.ts';\nimport { readStreamAsText } from '../util/stream.ts';\n\nexport type PadroneMcpPreferences = {\n /** Server name. Defaults to the program name. */\n name?: string;\n /** Server version. Defaults to the program version. */\n version?: string;\n /**\n * Transport mode.\n * - `'http'` — Start a Streamable HTTP server (default). Responds with `application/json` or `text/event-stream` based on the client's `Accept` header. Use `port` and `host` to configure.\n * - `'stdio'` — Communicate over stdin/stdout with newline-delimited JSON.\n */\n transport?: 'http' | 'stdio';\n /** HTTP port. Defaults to `3000`. Only used with `transport: 'http'`. */\n port?: number;\n /** HTTP host. Defaults to `'127.0.0.1'`. Only used with `transport: 'http'`. */\n host?: string;\n /** Base path for the MCP endpoint. Defaults to `'/mcp'`. Only used with `transport: 'http'`. */\n basePath?: string;\n /** CORS allowed origin. Defaults to `'*'`. Set to a specific origin or `false` to disable CORS headers. Only used with HTTP transports. */\n cors?: string | false;\n};\n\nconst PROTOCOL_VERSION = '2025-11-25';\n\ntype JsonRpcRequest = {\n jsonrpc: '2.0';\n id?: string | number;\n method: string;\n params?: Record<string, unknown>;\n};\n\ntype JsonRpcResponse = {\n jsonrpc: '2.0';\n id: string | number | null;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n};\n\n/** Convert an endpoint dot-path to a valid MCP tool name. Spec allows: [A-Za-z0-9_\\-\\.] */\nfunction toToolName(path: string): string {\n return path.replace(/\\s+/g, '.');\n}\n\n/** Convert a tool name back to a command path (dot → space). */\nfunction toCommandPath(toolName: string): string {\n return toolName.replace(/\\./g, ' ');\n}\n\n/** Build MCP tool annotations from a command's metadata. */\nfunction buildAnnotations(cmd: AnyPadroneCommand) {\n if (cmd.mutation == null) return undefined;\n return {\n destructiveHint: cmd.mutation || undefined,\n readOnlyHint: cmd.mutation === false || undefined,\n };\n}\n\n/** Build an MCP tool definition from a command. */\nfunction buildToolDefinition(name: string, cmd: AnyPadroneCommand) {\n return {\n name: toToolName(name),\n title: cmd.title ?? undefined,\n description: cmd.description || cmd.title || `Run the \"${name}\" command`,\n inputSchema: buildInputSchema(cmd),\n annotations: buildAnnotations(cmd),\n };\n}\n\n/** Create the MCP request handler. Returns an async function that processes a JSON-RPC request and returns a response (or undefined for notifications). */\nexport function createMcpHandler(\n existingCommand: AnyPadroneCommand,\n evalCommand: AnyPadroneProgram['eval'],\n prefs?: PadroneMcpPreferences,\n) {\n const serverName = prefs?.name ?? existingCommand.name;\n const serverVersion = prefs?.version ?? existingCommand.version ?? '0.0.0';\n\n const rootTools = collectEndpoints(existingCommand.commands, '');\n if (existingCommand.action || existingCommand.argsSchema) {\n rootTools.unshift({ name: '', command: existingCommand });\n }\n\n const toolMap = new Map(rootTools.map((t) => [toToolName(t.name), t]));\n\n const helpToolName = 'help';\n const helpToolDef = {\n name: helpToolName,\n title: 'Help',\n description: `Show help for the \"${serverName}\" program or a specific command`,\n inputSchema: {\n type: 'object' as const,\n properties: { command: { type: 'string', description: 'Command name to get help for (omit for program help)' } },\n additionalProperties: false,\n },\n };\n\n return async function handleRequest(req: JsonRpcRequest): Promise<JsonRpcResponse | undefined> {\n const { id, method, params } = req;\n\n switch (method) {\n case 'initialize':\n return {\n jsonrpc: '2.0',\n id: id ?? null,\n result: {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: { tools: {} },\n serverInfo: { name: serverName, version: serverVersion },\n },\n };\n\n case 'notifications/initialized':\n case 'notifications/cancelled':\n return undefined;\n\n case 'ping':\n return { jsonrpc: '2.0', id: id ?? null, result: {} };\n\n case 'tools/list': {\n const tools = [...rootTools.map((t) => buildToolDefinition(t.name, t.command)), helpToolDef];\n return { jsonrpc: '2.0', id: id ?? null, result: { tools } };\n }\n\n case 'tools/call': {\n const toolName = params?.name as string;\n const args = (params?.arguments ?? {}) as Record<string, unknown>;\n\n // Built-in help tool\n if (toolName === helpToolName) {\n const cmdName = args.command as string | undefined;\n const targetCmd = cmdName ? rootTools.find((t) => t.name === cmdName || toToolName(t.name) === cmdName)?.command : undefined;\n const helpText = generateHelp(existingCommand, targetCmd ?? existingCommand, { format: 'text', detail: 'full' });\n return {\n jsonrpc: '2.0',\n id: id ?? null,\n result: { content: [{ type: 'text', text: helpText }], isError: false },\n };\n }\n\n const tool = toolMap.get(toolName);\n if (!tool) {\n return {\n jsonrpc: '2.0',\n id: id ?? null,\n error: { code: -32602, message: `Unknown tool: ${toolName}` },\n };\n }\n\n // Build command string: convert tool name back to command path + serialize args as flags\n const commandPath = toCommandPath(tool.name);\n const argParts = serializeArgsToFlags(args);\n const input = [commandPath, ...argParts].filter(Boolean).join(' ') || undefined;\n\n try {\n const output: string[] = [];\n const errors: string[] = [];\n const result = await evalCommand(input as any, {\n caller: 'mcp',\n runtime: {\n output: (...outArgs: unknown[]) => output.push(outArgs.map(String).join(' ')),\n error: (text: string) => errors.push(text),\n interactive: 'unsupported',\n format: 'text',\n },\n });\n\n const content: { type: string; text: string }[] = [];\n\n if (result.error) {\n const errorMsg = result.error instanceof Error ? result.error.message : String(result.error);\n if (errors.length) content.push({ type: 'text', text: errors.join('\\n') });\n content.push({ type: 'text', text: errorMsg });\n return { jsonrpc: '2.0', id: id ?? null, result: { content, isError: true } };\n }\n\n if (result.argsResult?.issues) {\n const issueMessages = result.argsResult.issues.map((i: any) => `${i.path?.join('.') || 'root'}: ${i.message}`).join('\\n');\n content.push({ type: 'text', text: `Validation error:\\n${issueMessages}` });\n return { jsonrpc: '2.0', id: id ?? null, result: { content, isError: true } };\n }\n\n if (output.length) content.push({ type: 'text', text: output.join('\\n') });\n if (result.result !== undefined && result.result !== null) {\n const resultText = typeof result.result === 'string' ? result.result : JSON.stringify(result.result, null, 2);\n content.push({ type: 'text', text: resultText });\n }\n if (content.length === 0) content.push({ type: 'text', text: 'Done.' });\n return { jsonrpc: '2.0', id: id ?? null, result: { content, isError: false } };\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n return {\n jsonrpc: '2.0',\n id: id ?? null,\n result: { content: [{ type: 'text', text: errorMsg }], isError: true },\n };\n }\n }\n\n default: {\n if (id !== undefined) {\n return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } };\n }\n return undefined;\n }\n }\n };\n}\n\n/** stdio transport: newline-delimited JSON per 2025-11-25 spec. */\nasync function startStdioTransport(handleRequest: (req: JsonRpcRequest) => Promise<JsonRpcResponse | undefined>): Promise<void> {\n const { stdin, stdout } = await import('node:process');\n const { createInterface } = await import('node:readline');\n\n function send(msg: JsonRpcResponse) {\n stdout.write(`${JSON.stringify(msg)}\\n`);\n }\n\n const rl = createInterface({ input: stdin, crlfDelay: Infinity });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n try {\n const req = JSON.parse(line) as JsonRpcRequest;\n const res = await handleRequest(req);\n if (res) send(res);\n } catch {\n // Ignore malformed JSON\n }\n }\n}\n\n/** Streamable HTTP transport per 2025-11-25 spec. Responds with JSON or SSE based on client's Accept header. */\nasync function startHttpTransport(\n handleRequest: (req: JsonRpcRequest) => Promise<JsonRpcResponse | undefined>,\n prefs: PadroneMcpPreferences,\n log: (msg: string) => void,\n onSignal?: (callback: () => void) => () => void,\n): Promise<void> {\n const http = await import('node:http');\n const crypto = await import('node:crypto');\n\n const port = prefs.port ?? 3000;\n const host = prefs.host ?? '127.0.0.1';\n const endpoint = prefs.basePath ?? '/mcp';\n\n // Session management\n let sessionId: string | undefined;\n let negotiatedVersion: string | undefined;\n\n const corsOrigin = prefs.cors !== false ? (prefs.cors ?? '*') : undefined;\n\n const server = http.createServer(async (req, res) => {\n // CORS headers\n if (corsOrigin) {\n res.setHeader('Access-Control-Allow-Origin', corsOrigin);\n res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, MCP-Session-Id, MCP-Protocol-Version');\n res.setHeader('Access-Control-Expose-Headers', 'MCP-Session-Id');\n }\n\n if (req.method === 'OPTIONS') {\n res.writeHead(corsOrigin ? 204 : 405);\n res.end();\n return;\n }\n\n if (req.url !== endpoint) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n // DELETE: terminate session\n if (req.method === 'DELETE') {\n const reqSessionId = req.headers['mcp-session-id'] as string | undefined;\n if (sessionId && reqSessionId === sessionId) {\n sessionId = undefined;\n negotiatedVersion = undefined;\n res.writeHead(200);\n res.end();\n } else {\n res.writeHead(404);\n res.end();\n }\n return;\n }\n\n // GET: SSE stream (not implemented — return 405)\n if (req.method === 'GET') {\n res.writeHead(405, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32601, message: 'SSE stream not supported' } }));\n return;\n }\n\n if (req.method !== 'POST') {\n res.writeHead(405);\n res.end();\n return;\n }\n\n // Validate session ID on non-initialize requests\n const reqSessionId = req.headers['mcp-session-id'] as string | undefined;\n if (sessionId && reqSessionId && reqSessionId !== sessionId) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Invalid session' } }));\n return;\n }\n\n // Validate MCP-Protocol-Version header on post-init requests\n const reqProtocolVersion = req.headers['mcp-protocol-version'] as string | undefined;\n if (negotiatedVersion && reqProtocolVersion && reqProtocolVersion !== negotiatedVersion) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Protocol version mismatch' } }));\n return;\n }\n\n // Read request body\n const body = await readStreamAsText(req as AsyncIterable<Uint8Array>);\n\n let rpcRequest: JsonRpcRequest;\n try {\n rpcRequest = JSON.parse(body);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }));\n return;\n }\n\n const response = await handleRequest(rpcRequest);\n\n // On initialize response: create session and set header\n if (rpcRequest.method === 'initialize' && response?.result) {\n sessionId = crypto.randomUUID();\n negotiatedVersion = PROTOCOL_VERSION;\n res.setHeader('MCP-Session-Id', sessionId);\n }\n\n if (response) {\n const accept = req.headers.accept ?? '';\n if (accept.includes('text/event-stream')) {\n res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });\n res.write(`event: message\\ndata: ${JSON.stringify(response)}\\n\\n`);\n res.end();\n } else {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(response));\n }\n } else {\n // Notification or response from client — no body\n res.writeHead(202);\n res.end();\n }\n });\n\n return new Promise<void>((resolve, reject) => {\n server.listen(port, host, () => {\n log(`MCP server listening on http://${host}:${port}${endpoint}`);\n });\n server.on('error', reject);\n const unsubscribe = onSignal?.(() => {\n server.close(() => resolve());\n });\n server.on('close', () => unsubscribe?.());\n });\n}\n\nexport async function startMcpServer(\n _program: AnyPadroneProgram,\n existingCommand: AnyPadroneCommand,\n evalCommand: AnyPadroneProgram['eval'],\n prefs?: PadroneMcpPreferences,\n): Promise<void> {\n const handleRequest = createMcpHandler(existingCommand, evalCommand, prefs);\n const transport = prefs?.transport ?? 'http';\n\n if (transport === 'stdio') {\n return startStdioTransport(handleRequest);\n }\n\n const { getCommandRuntime } = await import('../core/commands.ts');\n const runtime = getCommandRuntime(existingCommand);\n return startHttpTransport(handleRequest, prefs ?? {}, (msg) => runtime.error(msg), runtime.onSignal);\n}\n"],"mappings":";;;;AA0BA,MAAM,mBAAmB;;AAiBzB,SAAS,WAAW,MAAsB;AACxC,QAAO,KAAK,QAAQ,QAAQ,IAAI;;;AAIlC,SAAS,cAAc,UAA0B;AAC/C,QAAO,SAAS,QAAQ,OAAO,IAAI;;;AAIrC,SAAS,iBAAiB,KAAwB;AAChD,KAAI,IAAI,YAAY,KAAM,QAAO,KAAA;AACjC,QAAO;EACL,iBAAiB,IAAI,YAAY,KAAA;EACjC,cAAc,IAAI,aAAa,SAAS,KAAA;EACzC;;;AAIH,SAAS,oBAAoB,MAAc,KAAwB;AACjE,QAAO;EACL,MAAM,WAAW,KAAK;EACtB,OAAO,IAAI,SAAS,KAAA;EACpB,aAAa,IAAI,eAAe,IAAI,SAAS,YAAY,KAAK;EAC9D,aAAa,iBAAiB,IAAI;EAClC,aAAa,iBAAiB,IAAI;EACnC;;;AAIH,SAAgB,iBACd,iBACA,aACA,OACA;CACA,MAAM,aAAa,OAAO,QAAQ,gBAAgB;CAClD,MAAM,gBAAgB,OAAO,WAAW,gBAAgB,WAAW;CAEnE,MAAM,YAAY,iBAAiB,gBAAgB,UAAU,GAAG;AAChE,KAAI,gBAAgB,UAAU,gBAAgB,WAC5C,WAAU,QAAQ;EAAE,MAAM;EAAI,SAAS;EAAiB,CAAC;CAG3D,MAAM,UAAU,IAAI,IAAI,UAAU,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;CAEtE,MAAM,eAAe;CACrB,MAAM,cAAc;EAClB,MAAM;EACN,OAAO;EACP,aAAa,sBAAsB,WAAW;EAC9C,aAAa;GACX,MAAM;GACN,YAAY,EAAE,SAAS;IAAE,MAAM;IAAU,aAAa;IAAwD,EAAE;GAChH,sBAAsB;GACvB;EACF;AAED,QAAO,eAAe,cAAc,KAA2D;EAC7F,MAAM,EAAE,IAAI,QAAQ,WAAW;AAE/B,UAAQ,QAAR;GACE,KAAK,aACH,QAAO;IACL,SAAS;IACT,IAAI,MAAM;IACV,QAAQ;KACN,iBAAiB;KACjB,cAAc,EAAE,OAAO,EAAE,EAAE;KAC3B,YAAY;MAAE,MAAM;MAAY,SAAS;MAAe;KACzD;IACF;GAEH,KAAK;GACL,KAAK,0BACH;GAEF,KAAK,OACH,QAAO;IAAE,SAAS;IAAO,IAAI,MAAM;IAAM,QAAQ,EAAE;IAAE;GAEvD,KAAK,cAAc;IACjB,MAAM,QAAQ,CAAC,GAAG,UAAU,KAAK,MAAM,oBAAoB,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,YAAY;AAC5F,WAAO;KAAE,SAAS;KAAO,IAAI,MAAM;KAAM,QAAQ,EAAE,OAAO;KAAE;;GAG9D,KAAK,cAAc;IACjB,MAAM,WAAW,QAAQ;IACzB,MAAM,OAAQ,QAAQ,aAAa,EAAE;AAGrC,QAAI,aAAa,cAAc;KAC7B,MAAM,UAAU,KAAK;KAErB,MAAM,WAAW,aAAa,kBADZ,UAAU,UAAU,MAAM,MAAM,EAAE,SAAS,WAAW,WAAW,EAAE,KAAK,KAAK,QAAQ,EAAE,UAAU,KAAA,MACvD,iBAAiB;MAAE,QAAQ;MAAQ,QAAQ;MAAQ,CAAC;AAChH,YAAO;MACL,SAAS;MACT,IAAI,MAAM;MACV,QAAQ;OAAE,SAAS,CAAC;QAAE,MAAM;QAAQ,MAAM;QAAU,CAAC;OAAE,SAAS;OAAO;MACxE;;IAGH,MAAM,OAAO,QAAQ,IAAI,SAAS;AAClC,QAAI,CAAC,KACH,QAAO;KACL,SAAS;KACT,IAAI,MAAM;KACV,OAAO;MAAE,MAAM;MAAQ,SAAS,iBAAiB;MAAY;KAC9D;IAMH,MAAM,QAAQ,CAFM,cAAc,KAAK,KAAK,EAEhB,GADX,qBAAqB,KAAK,CACH,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI,KAAA;AAEtE,QAAI;KACF,MAAM,SAAmB,EAAE;KAC3B,MAAM,SAAmB,EAAE;KAC3B,MAAM,SAAS,MAAM,YAAY,OAAc;MAC7C,QAAQ;MACR,SAAS;OACP,SAAS,GAAG,YAAuB,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC;OAC7E,QAAQ,SAAiB,OAAO,KAAK,KAAK;OAC1C,aAAa;OACb,QAAQ;OACT;MACF,CAAC;KAEF,MAAM,UAA4C,EAAE;AAEpD,SAAI,OAAO,OAAO;MAChB,MAAM,WAAW,OAAO,iBAAiB,QAAQ,OAAO,MAAM,UAAU,OAAO,OAAO,MAAM;AAC5F,UAAI,OAAO,OAAQ,SAAQ,KAAK;OAAE,MAAM;OAAQ,MAAM,OAAO,KAAK,KAAK;OAAE,CAAC;AAC1E,cAAQ,KAAK;OAAE,MAAM;OAAQ,MAAM;OAAU,CAAC;AAC9C,aAAO;OAAE,SAAS;OAAO,IAAI,MAAM;OAAM,QAAQ;QAAE;QAAS,SAAS;QAAM;OAAE;;AAG/E,SAAI,OAAO,YAAY,QAAQ;MAC7B,MAAM,gBAAgB,OAAO,WAAW,OAAO,KAAK,MAAW,GAAG,EAAE,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK;AACzH,cAAQ,KAAK;OAAE,MAAM;OAAQ,MAAM,sBAAsB;OAAiB,CAAC;AAC3E,aAAO;OAAE,SAAS;OAAO,IAAI,MAAM;OAAM,QAAQ;QAAE;QAAS,SAAS;QAAM;OAAE;;AAG/E,SAAI,OAAO,OAAQ,SAAQ,KAAK;MAAE,MAAM;MAAQ,MAAM,OAAO,KAAK,KAAK;MAAE,CAAC;AAC1E,SAAI,OAAO,WAAW,KAAA,KAAa,OAAO,WAAW,MAAM;MACzD,MAAM,aAAa,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AAC7G,cAAQ,KAAK;OAAE,MAAM;OAAQ,MAAM;OAAY,CAAC;;AAElD,SAAI,QAAQ,WAAW,EAAG,SAAQ,KAAK;MAAE,MAAM;MAAQ,MAAM;MAAS,CAAC;AACvE,YAAO;MAAE,SAAS;MAAO,IAAI,MAAM;MAAM,QAAQ;OAAE;OAAS,SAAS;OAAO;MAAE;aACvE,KAAK;KACZ,MAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AACjE,YAAO;MACL,SAAS;MACT,IAAI,MAAM;MACV,QAAQ;OAAE,SAAS,CAAC;QAAE,MAAM;QAAQ,MAAM;QAAU,CAAC;OAAE,SAAS;OAAM;MACvE;;;GAIL;AACE,QAAI,OAAO,KAAA,EACT,QAAO;KAAE,SAAS;KAAO;KAAI,OAAO;MAAE,MAAM;MAAQ,SAAS,qBAAqB;MAAU;KAAE;AAEhG;;;;;AAOR,eAAe,oBAAoB,eAA6F;CAC9H,MAAM,EAAE,OAAO,WAAW,MAAM,OAAO;CACvC,MAAM,EAAE,oBAAoB,MAAM,OAAO;CAEzC,SAAS,KAAK,KAAsB;AAClC,SAAO,MAAM,GAAG,KAAK,UAAU,IAAI,CAAC,IAAI;;CAG1C,MAAM,KAAK,gBAAgB;EAAE,OAAO;EAAO,WAAW;EAAU,CAAC;AAEjE,YAAW,MAAM,QAAQ,IAAI;AAC3B,MAAI,CAAC,KAAK,MAAM,CAAE;AAClB,MAAI;GAEF,MAAM,MAAM,MAAM,cADN,KAAK,MAAM,KAAK,CACQ;AACpC,OAAI,IAAK,MAAK,IAAI;UACZ;;;;AAOZ,eAAe,mBACb,eACA,OACA,KACA,UACe;CACf,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,OAAO;CAE5B,MAAM,OAAO,MAAM,QAAQ;CAC3B,MAAM,OAAO,MAAM,QAAQ;CAC3B,MAAM,WAAW,MAAM,YAAY;CAGnC,IAAI;CACJ,IAAI;CAEJ,MAAM,aAAa,MAAM,SAAS,QAAS,MAAM,QAAQ,MAAO,KAAA;CAEhE,MAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AAEnD,MAAI,YAAY;AACd,OAAI,UAAU,+BAA+B,WAAW;AACxD,OAAI,UAAU,gCAAgC,6BAA6B;AAC3E,OAAI,UAAU,gCAAgC,qDAAqD;AACnG,OAAI,UAAU,iCAAiC,iBAAiB;;AAGlE,MAAI,IAAI,WAAW,WAAW;AAC5B,OAAI,UAAU,aAAa,MAAM,IAAI;AACrC,OAAI,KAAK;AACT;;AAGF,MAAI,IAAI,QAAQ,UAAU;AACxB,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,aAAa,CAAC,CAAC;AAC/C;;AAIF,MAAI,IAAI,WAAW,UAAU;GAC3B,MAAM,eAAe,IAAI,QAAQ;AACjC,OAAI,aAAa,iBAAiB,WAAW;AAC3C,gBAAY,KAAA;AACZ,wBAAoB,KAAA;AACpB,QAAI,UAAU,IAAI;AAClB,QAAI,KAAK;UACJ;AACL,QAAI,UAAU,IAAI;AAClB,QAAI,KAAK;;AAEX;;AAIF,MAAI,IAAI,WAAW,OAAO;AACxB,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU;IAAE,SAAS;IAAO,IAAI;IAAM,OAAO;KAAE,MAAM;KAAQ,SAAS;KAA4B;IAAE,CAAC,CAAC;AACnH;;AAGF,MAAI,IAAI,WAAW,QAAQ;AACzB,OAAI,UAAU,IAAI;AAClB,OAAI,KAAK;AACT;;EAIF,MAAM,eAAe,IAAI,QAAQ;AACjC,MAAI,aAAa,gBAAgB,iBAAiB,WAAW;AAC3D,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU;IAAE,SAAS;IAAO,IAAI;IAAM,OAAO;KAAE,MAAM;KAAQ,SAAS;KAAmB;IAAE,CAAC,CAAC;AAC1G;;EAIF,MAAM,qBAAqB,IAAI,QAAQ;AACvC,MAAI,qBAAqB,sBAAsB,uBAAuB,mBAAmB;AACvF,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU;IAAE,SAAS;IAAO,IAAI;IAAM,OAAO;KAAE,MAAM;KAAQ,SAAS;KAA6B;IAAE,CAAC,CAAC;AACpH;;EAIF,MAAM,OAAO,MAAM,iBAAiB,IAAiC;EAErE,IAAI;AACJ,MAAI;AACF,gBAAa,KAAK,MAAM,KAAK;UACvB;AACN,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU;IAAE,SAAS;IAAO,IAAI;IAAM,OAAO;KAAE,MAAM;KAAQ,SAAS;KAAe;IAAE,CAAC,CAAC;AACtG;;EAGF,MAAM,WAAW,MAAM,cAAc,WAAW;AAGhD,MAAI,WAAW,WAAW,gBAAgB,UAAU,QAAQ;AAC1D,eAAY,OAAO,YAAY;AAC/B,uBAAoB;AACpB,OAAI,UAAU,kBAAkB,UAAU;;AAG5C,MAAI,SAEF,MADe,IAAI,QAAQ,UAAU,IAC1B,SAAS,oBAAoB,EAAE;AACxC,OAAI,UAAU,KAAK;IAAE,gBAAgB;IAAqB,iBAAiB;IAAY,YAAY;IAAc,CAAC;AAClH,OAAI,MAAM,yBAAyB,KAAK,UAAU,SAAS,CAAC,MAAM;AAClE,OAAI,KAAK;SACJ;AACL,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,SAAS,CAAC;;OAE9B;AAEL,OAAI,UAAU,IAAI;AAClB,OAAI,KAAK;;GAEX;AAEF,QAAO,IAAI,SAAe,SAAS,WAAW;AAC5C,SAAO,OAAO,MAAM,YAAY;AAC9B,OAAI,kCAAkC,KAAK,GAAG,OAAO,WAAW;IAChE;AACF,SAAO,GAAG,SAAS,OAAO;EAC1B,MAAM,cAAc,iBAAiB;AACnC,UAAO,YAAY,SAAS,CAAC;IAC7B;AACF,SAAO,GAAG,eAAe,eAAe,CAAC;GACzC;;AAGJ,eAAsB,eACpB,UACA,iBACA,aACA,OACe;CACf,MAAM,gBAAgB,iBAAiB,iBAAiB,aAAa,MAAM;AAG3E,MAFkB,OAAO,aAAa,YAEpB,QAChB,QAAO,oBAAoB,cAAc;CAG3C,MAAM,EAAE,sBAAsB,MAAM,OAAO,2BAAA,MAAA,MAAA,EAAA,EAAA;CAC3C,MAAM,UAAU,kBAAkB,gBAAgB;AAClD,QAAO,mBAAmB,eAAe,SAAS,EAAE,GAAG,QAAQ,QAAQ,MAAM,IAAI,EAAE,QAAQ,SAAS"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { m as serializeArgsToFlags, r as collectEndpoints, t as buildInputSchema } from "./commands-B_gufyR9.mjs";
|
|
2
|
+
import { a as readStreamAsText } from "./stream-DC4H8YTx.mjs";
|
|
3
|
+
import { t as generateHelp } from "./help-BtxLgrF_.mjs";
|
|
4
|
+
import { i as RoutingError, o as ValidationError } from "./errors-DA4KzK1M.mjs";
|
|
5
|
+
//#region src/feature/serve.ts
|
|
5
6
|
/** Convert an endpoint dot-path to a URL path segment. */
|
|
6
7
|
function toUrlPath(name) {
|
|
7
8
|
return name.replace(/\./g, "/");
|
|
@@ -218,7 +219,7 @@ function createServeHandler(existingCommand, evalCommand, prefs) {
|
|
|
218
219
|
const output = [];
|
|
219
220
|
const errors = [];
|
|
220
221
|
const result = await evalCommand(commandString || void 0, {
|
|
221
|
-
|
|
222
|
+
caller: "serve",
|
|
222
223
|
runtime: {
|
|
223
224
|
output: (...args) => output.push(args.map(String).join(" ")),
|
|
224
225
|
error: (text) => errors.push(text),
|
|
@@ -371,7 +372,7 @@ async function startServeServer(_program, existingCommand, evalCommand, prefs) {
|
|
|
371
372
|
const body = await response.text();
|
|
372
373
|
res.end(body);
|
|
373
374
|
});
|
|
374
|
-
const { getCommandRuntime } = await import("./
|
|
375
|
+
const { getCommandRuntime } = await import("./commands-B_gufyR9.mjs").then((n) => n.a);
|
|
375
376
|
const runtime = getCommandRuntime(existingCommand);
|
|
376
377
|
return new Promise((resolve, reject) => {
|
|
377
378
|
server.listen(port, host, () => {
|
|
@@ -385,20 +386,17 @@ async function startServeServer(_program, existingCommand, evalCommand, prefs) {
|
|
|
385
386
|
}.docs) runtime.error(`API docs: http://${host}:${port}${basePath}_docs`);
|
|
386
387
|
});
|
|
387
388
|
server.on("error", reject);
|
|
388
|
-
const
|
|
389
|
+
const unsubscribe = runtime.onSignal?.(() => {
|
|
389
390
|
server.close(() => resolve());
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
process.on("SIGTERM", onSignal);
|
|
391
|
+
});
|
|
392
|
+
server.on("close", () => unsubscribe?.());
|
|
393
393
|
});
|
|
394
394
|
}
|
|
395
395
|
/** Read the full body from a Node.js IncomingMessage. */
|
|
396
396
|
async function readBody(req) {
|
|
397
|
-
|
|
398
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
399
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
397
|
+
return readStreamAsText(req);
|
|
400
398
|
}
|
|
401
399
|
//#endregion
|
|
402
400
|
export { startServeServer };
|
|
403
401
|
|
|
404
|
-
//# sourceMappingURL=serve-
|
|
402
|
+
//# sourceMappingURL=serve-YVTPzBCl.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-YVTPzBCl.mjs","names":[],"sources":["../src/feature/serve.ts"],"sourcesContent":["import { buildInputSchema, type CollectedEndpoint, collectEndpoints, serializeArgsToFlags } from '../core/commands.ts';\nimport { RoutingError, ValidationError } from '../core/errors.ts';\nimport { generateHelp } from '../output/help.ts';\nimport type { AnyPadroneCommand, AnyPadroneProgram } from '../types/index.ts';\nimport { readStreamAsText } from '../util/stream.ts';\n\nexport type PadroneServePreferences = {\n /** Port to listen on. Default: 3000 */\n port?: number;\n /** Host to bind to. Default: '127.0.0.1' */\n host?: string;\n /** Base path prefix for all routes. Default: '/' */\n basePath?: string;\n /** CORS allowed origin. Default: '*'. Set to `false` to disable CORS headers. */\n cors?: string | false;\n /** Control built-in utility endpoints. All enabled by default. */\n builtins?: {\n /** GET /_health — returns 200 OK. */\n health?: boolean;\n /** GET /_help and GET /_help/:command — returns help text. */\n help?: boolean;\n /** GET /_schema and GET /_schema/:command — returns JSON Schema. */\n schema?: boolean;\n /** GET /_docs — Scalar OpenAPI docs viewer. */\n docs?: boolean;\n };\n /** Hook to run before each request. Return a Response to short-circuit. */\n onRequest?: (req: Request) => Response | void | Promise<Response | void>;\n /** Transform errors into responses. */\n onError?: (error: unknown, req: Request) => Response;\n};\n\n/** Convert an endpoint dot-path to a URL path segment. */\nfunction toUrlPath(name: string): string {\n return name.replace(/\\./g, '/');\n}\n\n/** Convert a URL path segment back to a command path (slash → space). */\nfunction toCommandPath(urlPath: string): string {\n return urlPath.replace(/\\//g, ' ');\n}\n\nfunction jsonResponse(body: unknown, status = 200, headers?: Record<string, string>): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json', ...headers },\n });\n}\n\nfunction errorToStatus(error: unknown): number {\n if (error instanceof RoutingError) return 404;\n if (error instanceof ValidationError) return 400;\n return 500;\n}\n\nfunction errorToResponse(error: unknown): Response {\n const status = errorToStatus(error);\n if (error instanceof ValidationError) {\n return jsonResponse(\n {\n ok: false,\n error: 'validation',\n message: error.message,\n issues: error.issues.map((i) => ({ path: i.path?.map(String), message: i.message })),\n },\n status,\n );\n }\n if (error instanceof RoutingError) {\n return jsonResponse({ ok: false, error: 'not_found', message: error.message, suggestions: error.suggestions }, status);\n }\n const message = error instanceof Error ? error.message : String(error);\n return jsonResponse({ ok: false, error: 'action_error', message }, status);\n}\n\n/** Generate an OpenAPI 3.1.0 spec from the command tree. */\nfunction buildOpenApiSpec(existingCommand: AnyPadroneCommand, endpoints: CollectedEndpoint[], basePath: string): Record<string, unknown> {\n const paths: Record<string, unknown> = {};\n\n const responseSchema = {\n '200': {\n description: 'Successful response',\n content: { 'application/json': { schema: { type: 'object', properties: { ok: { type: 'boolean', const: true }, result: {} } } } },\n },\n '400': {\n description: 'Validation error',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n ok: { type: 'boolean', const: false },\n error: { type: 'string', const: 'validation' },\n message: { type: 'string' },\n issues: { type: 'array', items: { type: 'object', properties: { path: { type: 'array' }, message: { type: 'string' } } } },\n },\n },\n },\n },\n },\n '404': {\n description: 'Command not found',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n ok: { type: 'boolean', const: false },\n error: { type: 'string', const: 'not_found' },\n message: { type: 'string' },\n },\n },\n },\n },\n },\n '500': {\n description: 'Action error',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n ok: { type: 'boolean', const: false },\n error: { type: 'string', const: 'action_error' },\n message: { type: 'string' },\n },\n },\n },\n },\n },\n };\n\n for (const { name, command: cmd } of endpoints) {\n const urlPath = `${basePath}${toUrlPath(name)}`;\n const inputSchema = buildInputSchema(cmd);\n const description = cmd.description || cmd.title || `Run the \"${name}\" command`;\n const pathItem: Record<string, unknown> = {};\n\n const postOp = {\n summary: cmd.title || name,\n description,\n operationId: `post_${name.replace(/\\./g, '_')}`,\n requestBody: { content: { 'application/json': { schema: inputSchema } } },\n responses: responseSchema,\n };\n\n if (cmd.mutation) {\n pathItem.post = postOp;\n } else {\n // GET: args as query parameters\n const properties = (inputSchema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const queryParams = Object.entries(properties).map(([key, schema]) => ({\n name: key,\n in: 'query',\n schema,\n required: (inputSchema.required as string[] | undefined)?.includes(key) ?? false,\n }));\n pathItem.get = {\n summary: cmd.title || name,\n description,\n operationId: `get_${name.replace(/\\./g, '_')}`,\n parameters: queryParams,\n responses: responseSchema,\n };\n pathItem.post = postOp;\n }\n\n paths[urlPath] = pathItem;\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: existingCommand.title || existingCommand.name,\n description: existingCommand.description,\n version: existingCommand.version ?? '0.0.0',\n },\n paths,\n };\n}\n\nfunction scalarDocsHtml(openapiUrl: string, title: string): string {\n return `<!doctype html>\n<html>\n<head>\n <title>${title} — API Docs</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n</head>\n<body>\n <script id=\"api-reference\" data-url=\"${openapiUrl}\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n</body>\n</html>`;\n}\n\n/** Create the serve request handler. */\nexport function createServeHandler(\n existingCommand: AnyPadroneCommand,\n evalCommand: AnyPadroneProgram['eval'],\n prefs?: PadroneServePreferences,\n): (req: Request) => Promise<Response> {\n const basePath = (prefs?.basePath ?? '/').replace(/\\/$/, '/');\n const corsOrigin = prefs?.cors !== false ? (prefs?.cors ?? '*') : undefined;\n const builtins = { health: true, help: true, schema: true, docs: true, ...prefs?.builtins };\n\n const endpoints = collectEndpoints(existingCommand.commands, '');\n if (existingCommand.action || existingCommand.argsSchema) {\n endpoints.unshift({ name: '', command: existingCommand });\n }\n\n const routeMap = new Map<string, CollectedEndpoint>();\n for (const ep of endpoints) {\n routeMap.set(toUrlPath(ep.name), ep);\n }\n\n let cachedOpenApiSpec: Record<string, unknown> | undefined;\n const getOpenApiSpec = () => (cachedOpenApiSpec ??= buildOpenApiSpec(existingCommand, endpoints, basePath));\n\n function addCorsHeaders(res: Response): Response {\n if (!corsOrigin) return res;\n const headers = new Headers(res.headers);\n headers.set('Access-Control-Allow-Origin', corsOrigin);\n headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n headers.set('Access-Control-Allow-Headers', 'Content-Type');\n return new Response(res.body, { status: res.status, statusText: res.statusText, headers });\n }\n\n async function evalAndRespond(commandString: string, request: Request): Promise<Response> {\n const output: string[] = [];\n const errors: string[] = [];\n const result = await evalCommand(commandString || (undefined as any), {\n caller: 'serve',\n runtime: {\n output: (...args: unknown[]) => output.push(args.map(String).join(' ')),\n error: (text: string) => errors.push(text),\n interactive: 'unsupported',\n format: 'json',\n },\n });\n\n if (result.error) {\n return prefs?.onError ? prefs.onError(result.error, request) : errorToResponse(result.error);\n }\n\n if (result.argsResult?.issues) {\n const issues = (result.argsResult.issues as { path?: PropertyKey[]; message: string }[]).map((i) => ({\n path: i.path?.map(String),\n message: i.message,\n }));\n return jsonResponse({ ok: false, error: 'validation', issues }, 400);\n }\n\n return jsonResponse({ ok: true, result: result.result ?? null });\n }\n\n return async function handleRequest(req: Request): Promise<Response> {\n // CORS preflight\n if (req.method === 'OPTIONS') {\n return addCorsHeaders(new Response(null, { status: corsOrigin ? 204 : 405 }));\n }\n\n // onRequest hook\n if (prefs?.onRequest) {\n const hookResponse = await prefs.onRequest(req);\n if (hookResponse) return addCorsHeaders(hookResponse);\n }\n\n const url = new URL(req.url, 'http://localhost');\n let pathname = url.pathname;\n\n // Strip basePath prefix\n if (basePath !== '/' && pathname.startsWith(basePath)) {\n pathname = pathname.slice(basePath.length - 1);\n }\n // Remove leading slash for route matching\n const routePath = pathname.replace(/^\\//, '');\n\n // Built-in endpoints\n if (req.method === 'GET') {\n if (builtins.health && routePath === '_health') {\n return addCorsHeaders(jsonResponse({ status: 'ok' }));\n }\n\n if (builtins.schema && routePath === '_schema') {\n const schemaMap: Record<string, unknown> = {};\n for (const ep of endpoints) {\n schemaMap[toUrlPath(ep.name) || '/'] = buildInputSchema(ep.command);\n }\n return addCorsHeaders(jsonResponse(schemaMap));\n }\n\n if (builtins.schema && routePath.startsWith('_schema/')) {\n const cmdPath = routePath.slice('_schema/'.length);\n const ep = routeMap.get(cmdPath);\n if (!ep) return addCorsHeaders(jsonResponse({ ok: false, error: 'not_found', message: `Command not found: ${cmdPath}` }, 404));\n return addCorsHeaders(jsonResponse(buildInputSchema(ep.command)));\n }\n\n if (builtins.help && routePath === '_help') {\n const accept = req.headers.get('accept') ?? '';\n const format = accept.includes('application/json') ? 'json' : 'markdown';\n const helpText = generateHelp(existingCommand, existingCommand, { format, detail: 'full' });\n if (format === 'json') return addCorsHeaders(jsonResponse(JSON.parse(helpText)));\n return addCorsHeaders(new Response(helpText, { status: 200, headers: { 'Content-Type': 'text/markdown' } }));\n }\n\n if (builtins.help && routePath.startsWith('_help/')) {\n const cmdPath = routePath.slice('_help/'.length);\n const ep = routeMap.get(cmdPath);\n if (!ep) return addCorsHeaders(jsonResponse({ ok: false, error: 'not_found', message: `Command not found: ${cmdPath}` }, 404));\n const accept = req.headers.get('accept') ?? '';\n const format = accept.includes('application/json') ? 'json' : 'markdown';\n const helpText = generateHelp(existingCommand, ep.command, { format, detail: 'full' });\n if (format === 'json') return addCorsHeaders(jsonResponse(JSON.parse(helpText)));\n return addCorsHeaders(new Response(helpText, { status: 200, headers: { 'Content-Type': 'text/markdown' } }));\n }\n\n if (builtins.docs && routePath === '_openapi') {\n return addCorsHeaders(jsonResponse(getOpenApiSpec()));\n }\n\n if (builtins.docs && routePath === '_docs') {\n const openapiUrl = `${basePath}_openapi`;\n const title = existingCommand.title || existingCommand.name;\n const html = scalarDocsHtml(openapiUrl, title);\n return addCorsHeaders(new Response(html, { status: 200, headers: { 'Content-Type': 'text/html' } }));\n }\n }\n\n // Route to command\n const endpoint = routeMap.get(routePath);\n if (!endpoint) {\n return addCorsHeaders(jsonResponse({ ok: false, error: 'not_found', message: `Command not found: ${routePath || '/'}` }, 404));\n }\n\n // Enforce method based on mutation flag\n if (endpoint.command.mutation && req.method === 'GET') {\n return addCorsHeaders(\n new Response(JSON.stringify({ ok: false, error: 'method_not_allowed', message: 'Mutation commands only accept POST' }), {\n status: 405,\n headers: { 'Content-Type': 'application/json', Allow: 'POST' },\n }),\n );\n }\n\n if (req.method !== 'GET' && req.method !== 'POST') {\n return addCorsHeaders(new Response(null, { status: 405, headers: { Allow: endpoint.command.mutation ? 'POST' : 'GET, POST' } }));\n }\n\n // Build command string from request\n const commandPath = toCommandPath(routePath);\n let argParts: string[];\n\n if (req.method === 'POST') {\n try {\n const body = (await req.json()) as Record<string, unknown>;\n argParts = serializeArgsToFlags(body);\n } catch {\n return addCorsHeaders(jsonResponse({ ok: false, error: 'bad_request', message: 'Invalid JSON body' }, 400));\n }\n } else {\n // GET: query string → flags\n argParts = [];\n for (const [key, value] of url.searchParams.entries()) {\n if (key === '_') {\n // Positional args\n argParts.push(value);\n } else {\n argParts.push(value === '' ? `--${key}` : `--${key}=${value}`);\n }\n }\n }\n\n const commandString = [commandPath, ...argParts].filter(Boolean).join(' ');\n const response = await evalAndRespond(commandString, req);\n return addCorsHeaders(response);\n };\n}\n\n/** Start the serve HTTP server. */\nexport async function startServeServer(\n _program: AnyPadroneProgram,\n existingCommand: AnyPadroneCommand,\n evalCommand: AnyPadroneProgram['eval'],\n prefs?: PadroneServePreferences,\n): Promise<void> {\n const handler = createServeHandler(existingCommand, evalCommand, prefs);\n const http = await import('node:http');\n\n const port = prefs?.port ?? 3000;\n const host = prefs?.host ?? '127.0.0.1';\n const basePath = (prefs?.basePath ?? '/').replace(/\\/$/, '/');\n\n const server = http.createServer(async (req, res) => {\n const url = `http://${host}:${port}${req.url}`;\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) headers.set(key, Array.isArray(value) ? value.join(', ') : value);\n }\n\n const fetchReq = new Request(url, {\n method: req.method,\n headers,\n body: req.method !== 'GET' && req.method !== 'HEAD' ? await readBody(req) : undefined,\n });\n\n const response = await handler(fetchReq);\n const resHeaders: Record<string, string> = {};\n response.headers.forEach((v, k) => {\n resHeaders[k] = v;\n });\n res.writeHead(response.status, resHeaders);\n const body = await response.text();\n res.end(body);\n });\n\n const { getCommandRuntime } = await import('../core/commands.ts');\n const runtime = getCommandRuntime(existingCommand);\n\n return new Promise<void>((resolve, reject) => {\n server.listen(port, host, () => {\n runtime.error(`REST server listening on http://${host}:${port}${basePath}`);\n const builtins = { health: true, help: true, schema: true, docs: true, ...prefs?.builtins };\n if (builtins.docs) runtime.error(`API docs: http://${host}:${port}${basePath}_docs`);\n });\n server.on('error', reject);\n const unsubscribe = runtime.onSignal?.(() => {\n server.close(() => resolve());\n });\n server.on('close', () => unsubscribe?.());\n });\n}\n\n/** Read the full body from a Node.js IncomingMessage. */\nasync function readBody(req: import('node:http').IncomingMessage): Promise<string> {\n return readStreamAsText(req as AsyncIterable<Uint8Array>);\n}\n"],"mappings":";;;;;;AAiCA,SAAS,UAAU,MAAsB;AACvC,QAAO,KAAK,QAAQ,OAAO,IAAI;;;AAIjC,SAAS,cAAc,SAAyB;AAC9C,QAAO,QAAQ,QAAQ,OAAO,IAAI;;AAGpC,SAAS,aAAa,MAAe,SAAS,KAAK,SAA4C;AAC7F,QAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE;EACxC;EACA,SAAS;GAAE,gBAAgB;GAAoB,GAAG;GAAS;EAC5D,CAAC;;AAGJ,SAAS,cAAc,OAAwB;AAC7C,KAAI,iBAAiB,aAAc,QAAO;AAC1C,KAAI,iBAAiB,gBAAiB,QAAO;AAC7C,QAAO;;AAGT,SAAS,gBAAgB,OAA0B;CACjD,MAAM,SAAS,cAAc,MAAM;AACnC,KAAI,iBAAiB,gBACnB,QAAO,aACL;EACE,IAAI;EACJ,OAAO;EACP,SAAS,MAAM;EACf,QAAQ,MAAM,OAAO,KAAK,OAAO;GAAE,MAAM,EAAE,MAAM,IAAI,OAAO;GAAE,SAAS,EAAE;GAAS,EAAE;EACrF,EACD,OACD;AAEH,KAAI,iBAAiB,aACnB,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO;EAAa,SAAS,MAAM;EAAS,aAAa,MAAM;EAAa,EAAE,OAAO;AAGxH,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO;EAAgB,SADxC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EACL,EAAE,OAAO;;;AAI5E,SAAS,iBAAiB,iBAAoC,WAAgC,UAA2C;CACvI,MAAM,QAAiC,EAAE;CAEzC,MAAM,iBAAiB;EACrB,OAAO;GACL,aAAa;GACb,SAAS,EAAE,oBAAoB,EAAE,QAAQ;IAAE,MAAM;IAAU,YAAY;KAAE,IAAI;MAAE,MAAM;MAAW,OAAO;MAAM;KAAE,QAAQ,EAAE;KAAE;IAAE,EAAE,EAAE;GAClI;EACD,OAAO;GACL,aAAa;GACb,SAAS,EACP,oBAAoB,EAClB,QAAQ;IACN,MAAM;IACN,YAAY;KACV,IAAI;MAAE,MAAM;MAAW,OAAO;MAAO;KACrC,OAAO;MAAE,MAAM;MAAU,OAAO;MAAc;KAC9C,SAAS,EAAE,MAAM,UAAU;KAC3B,QAAQ;MAAE,MAAM;MAAS,OAAO;OAAE,MAAM;OAAU,YAAY;QAAE,MAAM,EAAE,MAAM,SAAS;QAAE,SAAS,EAAE,MAAM,UAAU;QAAE;OAAE;MAAE;KAC3H;IACF,EACF,EACF;GACF;EACD,OAAO;GACL,aAAa;GACb,SAAS,EACP,oBAAoB,EAClB,QAAQ;IACN,MAAM;IACN,YAAY;KACV,IAAI;MAAE,MAAM;MAAW,OAAO;MAAO;KACrC,OAAO;MAAE,MAAM;MAAU,OAAO;MAAa;KAC7C,SAAS,EAAE,MAAM,UAAU;KAC5B;IACF,EACF,EACF;GACF;EACD,OAAO;GACL,aAAa;GACb,SAAS,EACP,oBAAoB,EAClB,QAAQ;IACN,MAAM;IACN,YAAY;KACV,IAAI;MAAE,MAAM;MAAW,OAAO;MAAO;KACrC,OAAO;MAAE,MAAM;MAAU,OAAO;MAAgB;KAChD,SAAS,EAAE,MAAM,UAAU;KAC5B;IACF,EACF,EACF;GACF;EACF;AAED,MAAK,MAAM,EAAE,MAAM,SAAS,SAAS,WAAW;EAC9C,MAAM,UAAU,GAAG,WAAW,UAAU,KAAK;EAC7C,MAAM,cAAc,iBAAiB,IAAI;EACzC,MAAM,cAAc,IAAI,eAAe,IAAI,SAAS,YAAY,KAAK;EACrE,MAAM,WAAoC,EAAE;EAE5C,MAAM,SAAS;GACb,SAAS,IAAI,SAAS;GACtB;GACA,aAAa,QAAQ,KAAK,QAAQ,OAAO,IAAI;GAC7C,aAAa,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,aAAa,EAAE,EAAE;GACzE,WAAW;GACZ;AAED,MAAI,IAAI,SACN,UAAS,OAAO;OACX;GAEL,MAAM,aAAc,YAAY,cAAc,EAAE;GAChD,MAAM,cAAc,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,KAAK,aAAa;IACrE,MAAM;IACN,IAAI;IACJ;IACA,UAAW,YAAY,UAAmC,SAAS,IAAI,IAAI;IAC5E,EAAE;AACH,YAAS,MAAM;IACb,SAAS,IAAI,SAAS;IACtB;IACA,aAAa,OAAO,KAAK,QAAQ,OAAO,IAAI;IAC5C,YAAY;IACZ,WAAW;IACZ;AACD,YAAS,OAAO;;AAGlB,QAAM,WAAW;;AAGnB,QAAO;EACL,SAAS;EACT,MAAM;GACJ,OAAO,gBAAgB,SAAS,gBAAgB;GAChD,aAAa,gBAAgB;GAC7B,SAAS,gBAAgB,WAAW;GACrC;EACD;EACD;;AAGH,SAAS,eAAe,YAAoB,OAAuB;AACjE,QAAO;;;WAGE,MAAM;;;;;yCAKwB,WAAW;;;;;;AAOpD,SAAgB,mBACd,iBACA,aACA,OACqC;CACrC,MAAM,YAAY,OAAO,YAAY,KAAK,QAAQ,OAAO,IAAI;CAC7D,MAAM,aAAa,OAAO,SAAS,QAAS,OAAO,QAAQ,MAAO,KAAA;CAClE,MAAM,WAAW;EAAE,QAAQ;EAAM,MAAM;EAAM,QAAQ;EAAM,MAAM;EAAM,GAAG,OAAO;EAAU;CAE3F,MAAM,YAAY,iBAAiB,gBAAgB,UAAU,GAAG;AAChE,KAAI,gBAAgB,UAAU,gBAAgB,WAC5C,WAAU,QAAQ;EAAE,MAAM;EAAI,SAAS;EAAiB,CAAC;CAG3D,MAAM,2BAAW,IAAI,KAAgC;AACrD,MAAK,MAAM,MAAM,UACf,UAAS,IAAI,UAAU,GAAG,KAAK,EAAE,GAAG;CAGtC,IAAI;CACJ,MAAM,uBAAwB,sBAAsB,iBAAiB,iBAAiB,WAAW,SAAS;CAE1G,SAAS,eAAe,KAAyB;AAC/C,MAAI,CAAC,WAAY,QAAO;EACxB,MAAM,UAAU,IAAI,QAAQ,IAAI,QAAQ;AACxC,UAAQ,IAAI,+BAA+B,WAAW;AACtD,UAAQ,IAAI,gCAAgC,qBAAqB;AACjE,UAAQ,IAAI,gCAAgC,eAAe;AAC3D,SAAO,IAAI,SAAS,IAAI,MAAM;GAAE,QAAQ,IAAI;GAAQ,YAAY,IAAI;GAAY;GAAS,CAAC;;CAG5F,eAAe,eAAe,eAAuB,SAAqC;EACxF,MAAM,SAAmB,EAAE;EAC3B,MAAM,SAAmB,EAAE;EAC3B,MAAM,SAAS,MAAM,YAAY,iBAAkB,KAAA,GAAmB;GACpE,QAAQ;GACR,SAAS;IACP,SAAS,GAAG,SAAoB,OAAO,KAAK,KAAK,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC;IACvE,QAAQ,SAAiB,OAAO,KAAK,KAAK;IAC1C,aAAa;IACb,QAAQ;IACT;GACF,CAAC;AAEF,MAAI,OAAO,MACT,QAAO,OAAO,UAAU,MAAM,QAAQ,OAAO,OAAO,QAAQ,GAAG,gBAAgB,OAAO,MAAM;AAG9F,MAAI,OAAO,YAAY,OAKrB,QAAO,aAAa;GAAE,IAAI;GAAO,OAAO;GAAc,QAJtC,OAAO,WAAW,OAAuD,KAAK,OAAO;IACnG,MAAM,EAAE,MAAM,IAAI,OAAO;IACzB,SAAS,EAAE;IACZ,EAAE;GAC2D,EAAE,IAAI;AAGtE,SAAO,aAAa;GAAE,IAAI;GAAM,QAAQ,OAAO,UAAU;GAAM,CAAC;;AAGlE,QAAO,eAAe,cAAc,KAAiC;AAEnE,MAAI,IAAI,WAAW,UACjB,QAAO,eAAe,IAAI,SAAS,MAAM,EAAE,QAAQ,aAAa,MAAM,KAAK,CAAC,CAAC;AAI/E,MAAI,OAAO,WAAW;GACpB,MAAM,eAAe,MAAM,MAAM,UAAU,IAAI;AAC/C,OAAI,aAAc,QAAO,eAAe,aAAa;;EAGvD,MAAM,MAAM,IAAI,IAAI,IAAI,KAAK,mBAAmB;EAChD,IAAI,WAAW,IAAI;AAGnB,MAAI,aAAa,OAAO,SAAS,WAAW,SAAS,CACnD,YAAW,SAAS,MAAM,SAAS,SAAS,EAAE;EAGhD,MAAM,YAAY,SAAS,QAAQ,OAAO,GAAG;AAG7C,MAAI,IAAI,WAAW,OAAO;AACxB,OAAI,SAAS,UAAU,cAAc,UACnC,QAAO,eAAe,aAAa,EAAE,QAAQ,MAAM,CAAC,CAAC;AAGvD,OAAI,SAAS,UAAU,cAAc,WAAW;IAC9C,MAAM,YAAqC,EAAE;AAC7C,SAAK,MAAM,MAAM,UACf,WAAU,UAAU,GAAG,KAAK,IAAI,OAAO,iBAAiB,GAAG,QAAQ;AAErE,WAAO,eAAe,aAAa,UAAU,CAAC;;AAGhD,OAAI,SAAS,UAAU,UAAU,WAAW,WAAW,EAAE;IACvD,MAAM,UAAU,UAAU,MAAM,EAAkB;IAClD,MAAM,KAAK,SAAS,IAAI,QAAQ;AAChC,QAAI,CAAC,GAAI,QAAO,eAAe,aAAa;KAAE,IAAI;KAAO,OAAO;KAAa,SAAS,sBAAsB;KAAW,EAAE,IAAI,CAAC;AAC9H,WAAO,eAAe,aAAa,iBAAiB,GAAG,QAAQ,CAAC,CAAC;;AAGnE,OAAI,SAAS,QAAQ,cAAc,SAAS;IAE1C,MAAM,UADS,IAAI,QAAQ,IAAI,SAAS,IAAI,IACtB,SAAS,mBAAmB,GAAG,SAAS;IAC9D,MAAM,WAAW,aAAa,iBAAiB,iBAAiB;KAAE;KAAQ,QAAQ;KAAQ,CAAC;AAC3F,QAAI,WAAW,OAAQ,QAAO,eAAe,aAAa,KAAK,MAAM,SAAS,CAAC,CAAC;AAChF,WAAO,eAAe,IAAI,SAAS,UAAU;KAAE,QAAQ;KAAK,SAAS,EAAE,gBAAgB,iBAAiB;KAAE,CAAC,CAAC;;AAG9G,OAAI,SAAS,QAAQ,UAAU,WAAW,SAAS,EAAE;IACnD,MAAM,UAAU,UAAU,MAAM,EAAgB;IAChD,MAAM,KAAK,SAAS,IAAI,QAAQ;AAChC,QAAI,CAAC,GAAI,QAAO,eAAe,aAAa;KAAE,IAAI;KAAO,OAAO;KAAa,SAAS,sBAAsB;KAAW,EAAE,IAAI,CAAC;IAE9H,MAAM,UADS,IAAI,QAAQ,IAAI,SAAS,IAAI,IACtB,SAAS,mBAAmB,GAAG,SAAS;IAC9D,MAAM,WAAW,aAAa,iBAAiB,GAAG,SAAS;KAAE;KAAQ,QAAQ;KAAQ,CAAC;AACtF,QAAI,WAAW,OAAQ,QAAO,eAAe,aAAa,KAAK,MAAM,SAAS,CAAC,CAAC;AAChF,WAAO,eAAe,IAAI,SAAS,UAAU;KAAE,QAAQ;KAAK,SAAS,EAAE,gBAAgB,iBAAiB;KAAE,CAAC,CAAC;;AAG9G,OAAI,SAAS,QAAQ,cAAc,WACjC,QAAO,eAAe,aAAa,gBAAgB,CAAC,CAAC;AAGvD,OAAI,SAAS,QAAQ,cAAc,SAAS;IAG1C,MAAM,OAAO,eAFM,GAAG,SAAS,WACjB,gBAAgB,SAAS,gBAAgB,KACT;AAC9C,WAAO,eAAe,IAAI,SAAS,MAAM;KAAE,QAAQ;KAAK,SAAS,EAAE,gBAAgB,aAAa;KAAE,CAAC,CAAC;;;EAKxG,MAAM,WAAW,SAAS,IAAI,UAAU;AACxC,MAAI,CAAC,SACH,QAAO,eAAe,aAAa;GAAE,IAAI;GAAO,OAAO;GAAa,SAAS,sBAAsB,aAAa;GAAO,EAAE,IAAI,CAAC;AAIhI,MAAI,SAAS,QAAQ,YAAY,IAAI,WAAW,MAC9C,QAAO,eACL,IAAI,SAAS,KAAK,UAAU;GAAE,IAAI;GAAO,OAAO;GAAsB,SAAS;GAAsC,CAAC,EAAE;GACtH,QAAQ;GACR,SAAS;IAAE,gBAAgB;IAAoB,OAAO;IAAQ;GAC/D,CAAC,CACH;AAGH,MAAI,IAAI,WAAW,SAAS,IAAI,WAAW,OACzC,QAAO,eAAe,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK,SAAS,EAAE,OAAO,SAAS,QAAQ,WAAW,SAAS,aAAa;GAAE,CAAC,CAAC;EAIlI,MAAM,cAAc,cAAc,UAAU;EAC5C,IAAI;AAEJ,MAAI,IAAI,WAAW,OACjB,KAAI;AAEF,cAAW,qBADG,MAAM,IAAI,MAAM,CACO;UAC/B;AACN,UAAO,eAAe,aAAa;IAAE,IAAI;IAAO,OAAO;IAAe,SAAS;IAAqB,EAAE,IAAI,CAAC;;OAExG;AAEL,cAAW,EAAE;AACb,QAAK,MAAM,CAAC,KAAK,UAAU,IAAI,aAAa,SAAS,CACnD,KAAI,QAAQ,IAEV,UAAS,KAAK,MAAM;OAEpB,UAAS,KAAK,UAAU,KAAK,KAAK,QAAQ,KAAK,IAAI,GAAG,QAAQ;;AAOpE,SAAO,eADU,MAAM,eADD,CAAC,aAAa,GAAG,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,EACrB,IAAI,CAC1B;;;;AAKnC,eAAsB,iBACpB,UACA,iBACA,aACA,OACe;CACf,MAAM,UAAU,mBAAmB,iBAAiB,aAAa,MAAM;CACvE,MAAM,OAAO,MAAM,OAAO;CAE1B,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,YAAY,OAAO,YAAY,KAAK,QAAQ,OAAO,IAAI;CAE7D,MAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;EACnD,MAAM,MAAM,UAAU,KAAK,GAAG,OAAO,IAAI;EACzC,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,QAAQ,CACpD,KAAI,MAAO,SAAQ,IAAI,KAAK,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,KAAK,GAAG,MAAM;EAS9E,MAAM,WAAW,MAAM,QANN,IAAI,QAAQ,KAAK;GAChC,QAAQ,IAAI;GACZ;GACA,MAAM,IAAI,WAAW,SAAS,IAAI,WAAW,SAAS,MAAM,SAAS,IAAI,GAAG,KAAA;GAC7E,CAAC,CAEsC;EACxC,MAAM,aAAqC,EAAE;AAC7C,WAAS,QAAQ,SAAS,GAAG,MAAM;AACjC,cAAW,KAAK;IAChB;AACF,MAAI,UAAU,SAAS,QAAQ,WAAW;EAC1C,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,MAAI,IAAI,KAAK;GACb;CAEF,MAAM,EAAE,sBAAsB,MAAM,OAAO,2BAAA,MAAA,MAAA,EAAA,EAAA;CAC3C,MAAM,UAAU,kBAAkB,gBAAgB;AAElD,QAAO,IAAI,SAAe,SAAS,WAAW;AAC5C,SAAO,OAAO,MAAM,YAAY;AAC9B,WAAQ,MAAM,mCAAmC,KAAK,GAAG,OAAO,WAAW;AAE3E,OADiB;IAAE,QAAQ;IAAM,MAAM;IAAM,QAAQ;IAAM,MAAM;IAAM,GAAG,OAAO;IAAU,CAC9E,KAAM,SAAQ,MAAM,oBAAoB,KAAK,GAAG,OAAO,SAAS,OAAO;IACpF;AACF,SAAO,GAAG,SAAS,OAAO;EAC1B,MAAM,cAAc,QAAQ,iBAAiB;AAC3C,UAAO,YAAY,SAAS,CAAC;IAC7B;AACF,SAAO,GAAG,eAAe,eAAe,CAAC;GACzC;;;AAIJ,eAAe,SAAS,KAA2D;AACjF,QAAO,iBAAiB,IAAiC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/stream.ts
|
|
1
|
+
//#region src/util/stream.ts
|
|
2
2
|
let asyncStreamIdCounter = 1;
|
|
3
3
|
const asyncStreamRegistry = /* @__PURE__ */ new Map();
|
|
4
4
|
/**
|
|
@@ -50,7 +50,28 @@ function createStdinStream(stdin, itemSchema) {
|
|
|
50
50
|
} };
|
|
51
51
|
}
|
|
52
52
|
const emptyAsyncIterable = { async *[Symbol.asyncIterator]() {} };
|
|
53
|
+
const textEncoder = /* @__PURE__ */ new TextEncoder();
|
|
54
|
+
const textDecoder = /* @__PURE__ */ new TextDecoder();
|
|
55
|
+
/** Concatenate multiple `Uint8Array` chunks into a single array. */
|
|
56
|
+
function concatBytes(chunks) {
|
|
57
|
+
if (chunks.length === 1) return chunks[0];
|
|
58
|
+
let totalLength = 0;
|
|
59
|
+
for (const chunk of chunks) totalLength += chunk.byteLength;
|
|
60
|
+
const result = new Uint8Array(totalLength);
|
|
61
|
+
let offset = 0;
|
|
62
|
+
for (const chunk of chunks) {
|
|
63
|
+
result.set(chunk, offset);
|
|
64
|
+
offset += chunk.byteLength;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
/** Read an async iterable of chunks into a UTF-8 string. */
|
|
69
|
+
async function readStreamAsText(stream) {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
for await (const chunk of stream) chunks.push(typeof chunk === "string" ? textEncoder.encode(chunk) : chunk);
|
|
72
|
+
return textDecoder.decode(concatBytes(chunks));
|
|
73
|
+
}
|
|
53
74
|
//#endregion
|
|
54
|
-
export { asyncStreamRegistry as n,
|
|
75
|
+
export { readStreamAsText as a, createStdinStream as i, asyncStreamRegistry as n, concatBytes as r, asyncStream as t };
|
|
55
76
|
|
|
56
|
-
//# sourceMappingURL=stream-
|
|
77
|
+
//# sourceMappingURL=stream-DC4H8YTx.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-DC4H8YTx.mjs","names":[],"sources":["../src/util/stream.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { PadroneSchema } from '../types/index.ts';\n\nexport interface AsyncStreamMeta {\n [x: string]: unknown;\n readonly asyncStream: number;\n readonly itemSchema?: StandardSchemaV1;\n}\n\nlet asyncStreamIdCounter = 1;\nexport const asyncStreamRegistry = new Map<number, AsyncStreamMeta>();\n\n/**\n * Returns metadata to mark a schema field as an async stream via `.meta()`.\n *\n * When used with `stdin`, padrone pipes stdin data as an `AsyncIterable` instead of\n * buffering it. Each line is validated against the item schema (if provided) as it arrives.\n *\n * @param itemSchema - Optional item schema for per-item validation.\n * Non-string schemas cause each stdin line to be `JSON.parse`'d before validation.\n *\n * @example\n * ```ts\n * import { asyncStream } from 'padrone';\n *\n * // String lines\n * z.object({ lines: z.custom<AsyncIterable<string>>().meta(asyncStream()) })\n *\n * // Typed items — each line JSON.parse'd and validated\n * z.object({ records: z.custom<AsyncIterable<{ name: string }>>().meta(asyncStream(recordSchema)) })\n * ```\n */\nexport function asyncStream<T = string>(itemSchema?: PadroneSchema<T>): AsyncStreamMeta {\n const id = asyncStreamIdCounter++;\n const meta: AsyncStreamMeta = itemSchema ? { asyncStream: id, itemSchema } : { asyncStream: id };\n asyncStreamRegistry.set(id, meta);\n return meta;\n}\n\n/** Stdin interface matching PadroneRuntime.stdin */\ninterface StdinSource {\n isTTY?: boolean;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\n/**\n * Creates an `AsyncIterable` from a stdin source, optionally validating each item.\n * When no stdin is available (TTY / undefined), yields nothing.\n *\n * - No item schema: yields raw string lines\n * - With item schema: `JSON.parse`s each line, validates, then yields\n */\nexport function createStdinStream(stdin: StdinSource | undefined, itemSchema?: StandardSchemaV1): AsyncIterable<unknown> {\n if (!stdin) return emptyAsyncIterable;\n\n if (!itemSchema) return stdin.lines();\n\n return {\n async *[Symbol.asyncIterator]() {\n for await (const line of stdin.lines()) {\n const result = itemSchema['~standard'].validate(line);\n const resolved = result instanceof Promise ? await result : result;\n if ('issues' in resolved && resolved.issues) {\n throw new Error(`Stream item validation failed: ${resolved.issues.map((i) => i.message).join(', ')}`);\n }\n yield (resolved as { value: unknown }).value;\n }\n },\n };\n}\n\nconst emptyAsyncIterable: AsyncIterable<never> = {\n async *[Symbol.asyncIterator]() {},\n};\n\nconst textEncoder = /* @__PURE__ */ new TextEncoder();\nconst textDecoder = /* @__PURE__ */ new TextDecoder();\n\n/** Concatenate multiple `Uint8Array` chunks into a single array. */\nexport function concatBytes(chunks: Uint8Array[]): Uint8Array {\n if (chunks.length === 1) return chunks[0]!;\n let totalLength = 0;\n for (const chunk of chunks) totalLength += chunk.byteLength;\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return result;\n}\n\n/** Read an async iterable of chunks into a UTF-8 string. */\nexport async function readStreamAsText(stream: AsyncIterable<Uint8Array | string>): Promise<string> {\n const chunks: Uint8Array[] = [];\n for await (const chunk of stream) {\n chunks.push(typeof chunk === 'string' ? textEncoder.encode(chunk) : chunk);\n }\n return textDecoder.decode(concatBytes(chunks));\n}\n"],"mappings":";AASA,IAAI,uBAAuB;AAC3B,MAAa,sCAAsB,IAAI,KAA8B;;;;;;;;;;;;;;;;;;;;;AAsBrE,SAAgB,YAAwB,YAAgD;CACtF,MAAM,KAAK;CACX,MAAM,OAAwB,aAAa;EAAE,aAAa;EAAI;EAAY,GAAG,EAAE,aAAa,IAAI;AAChG,qBAAoB,IAAI,IAAI,KAAK;AACjC,QAAO;;;;;;;;;AAiBT,SAAgB,kBAAkB,OAAgC,YAAuD;AACvH,KAAI,CAAC,MAAO,QAAO;AAEnB,KAAI,CAAC,WAAY,QAAO,MAAM,OAAO;AAErC,QAAO,EACL,QAAQ,OAAO,iBAAiB;AAC9B,aAAW,MAAM,QAAQ,MAAM,OAAO,EAAE;GACtC,MAAM,SAAS,WAAW,aAAa,SAAS,KAAK;GACrD,MAAM,WAAW,kBAAkB,UAAU,MAAM,SAAS;AAC5D,OAAI,YAAY,YAAY,SAAS,OACnC,OAAM,IAAI,MAAM,kCAAkC,SAAS,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK,GAAG;AAEvG,SAAO,SAAgC;;IAG5C;;AAGH,MAAM,qBAA2C,EAC/C,QAAQ,OAAO,iBAAiB,IACjC;AAED,MAAM,8BAA8B,IAAI,aAAa;AACrD,MAAM,8BAA8B,IAAI,aAAa;;AAGrD,SAAgB,YAAY,QAAkC;AAC5D,KAAI,OAAO,WAAW,EAAG,QAAO,OAAO;CACvC,IAAI,cAAc;AAClB,MAAK,MAAM,SAAS,OAAQ,gBAAe,MAAM;CACjD,MAAM,SAAS,IAAI,WAAW,YAAY;CAC1C,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ;AAC1B,SAAO,IAAI,OAAO,OAAO;AACzB,YAAU,MAAM;;AAElB,QAAO;;;AAIT,eAAsB,iBAAiB,QAA6D;CAClG,MAAM,SAAuB,EAAE;AAC/B,YAAW,MAAM,SAAS,OACxB,QAAO,KAAK,OAAO,UAAU,WAAW,YAAY,OAAO,MAAM,GAAG,MAAM;AAE5E,QAAO,YAAY,OAAO,YAAY,OAAO,CAAC"}
|
package/dist/test.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as AnyPadroneCommand, rt as PadroneRuntime } from "./index-D6-7dz0l.mjs";
|
|
2
2
|
|
|
3
|
-
//#region src/test.d.ts
|
|
3
|
+
//#region src/feature/test.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Result from a single command execution in test mode.
|
|
6
6
|
* Extends the standard PadroneCommandResult with captured I/O.
|
|
@@ -31,8 +31,7 @@ type TestReplResult = {
|
|
|
31
31
|
type TestCliBuilder = {
|
|
32
32
|
/** Set the CLI input string (e.g. `'deploy --env production'`). */args(input: string): TestCliBuilder; /** Set environment variables visible to the command. */
|
|
33
33
|
env(vars: Record<string, string | undefined>): TestCliBuilder; /** Provide mock answers for interactive prompts. Keys are field names. */
|
|
34
|
-
prompt(answers: Record<string, unknown>): TestCliBuilder; /** Provide mock
|
|
35
|
-
config(files: Record<string, Record<string, unknown>>): TestCliBuilder; /** Provide mock stdin data (simulates piped input). */
|
|
34
|
+
prompt(answers: Record<string, unknown>): TestCliBuilder; /** Provide mock stdin data (simulates piped input). */
|
|
36
35
|
stdin(data: string): TestCliBuilder;
|
|
37
36
|
/**
|
|
38
37
|
* Execute a single command via `eval()` and return the result with captured I/O.
|
|
@@ -97,9 +96,7 @@ type TestCliBuilder = {
|
|
|
97
96
|
* Avoids strict variance issues with `AnyPadroneProgram`.
|
|
98
97
|
*/
|
|
99
98
|
type TestableProgram = {
|
|
100
|
-
eval: (input: string, prefs?:
|
|
101
|
-
autoOutput?: boolean;
|
|
102
|
-
}) => any;
|
|
99
|
+
eval: (input: string, prefs?: Record<string, unknown>) => any;
|
|
103
100
|
runtime: (runtime: PadroneRuntime) => TestableProgram;
|
|
104
101
|
repl: (options?: {
|
|
105
102
|
greeting?: false;
|
|
@@ -108,5 +105,5 @@ type TestableProgram = {
|
|
|
108
105
|
};
|
|
109
106
|
declare function testCli(program: TestableProgram): TestCliBuilder;
|
|
110
107
|
//#endregion
|
|
111
|
-
export { TestCliBuilder, TestCliResult, TestReplResult, testCli };
|
|
108
|
+
export { type TestCliBuilder, type TestCliResult, type TestReplResult, testCli };
|
|
112
109
|
//# sourceMappingURL=test.d.mts.map
|
package/dist/test.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.mts","names":[],"sources":["../src/test.ts"],"mappings":";;;;AAOA;;;KAAY,aAAA;EAEV,2BAAA,OAAA,EAAS,iBAAA,EAET;EAAA,IAAA,WAIA;EAFA,MAAA,WAE2B;EAA3B,MAAA;IAAU,OAAA;IAAiB,IAAA,GAAO,WAAA;EAAA,iBAM7B;EAJL,MAAA,aAUU;EARV,MAAA;EAEA,KAAA;AAAA;;;;KAMU,cAAA;EAMJ,wGAJN,OAAA,EAAS,IAAA,CAAK,aAAA,0BAUJ;EARV,MAAA;EAEA,MAAA;AAAA;;;;KAMU,cAAA;
|
|
1
|
+
{"version":3,"file":"test.d.mts","names":[],"sources":["../src/feature/test.ts"],"mappings":";;;;AAOA;;;KAAY,aAAA;EAEV,2BAAA,OAAA,EAAS,iBAAA,EAET;EAAA,IAAA,WAIA;EAFA,MAAA,WAE2B;EAA3B,MAAA;IAAU,OAAA;IAAiB,IAAA,GAAO,WAAA;EAAA,iBAM7B;EAJL,MAAA,aAUU;EARV,MAAA;EAEA,KAAA;AAAA;;;;KAMU,cAAA;EAMJ,wGAJN,OAAA,EAAS,IAAA,CAAK,aAAA,0BAUJ;EARV,MAAA;EAEA,MAAA;AAAA;;;;KAMU,cAAA;EAamB,mEAX7B,IAAA,CAAK,KAAA,WAAgB,cAAA,EAiBW;EAfhC,GAAA,CAAI,IAAA,EAAM,MAAA,+BAAqC,cAAA,EAehB;EAb/B,MAAA,CAAO,OAAA,EAAS,MAAA,oBAA0B,cAAA,EAJ1C;EAMA,KAAA,CAAM,IAAA,WAAe,cAAA;EANA;;;;EAWrB,GAAA,CAAI,KAAA,YAAiB,OAAA,CAAQ,aAAA;EAP7B;;;;;EAaA,IAAA,CAAK,MAAA,aAAmB,OAAA,CAAQ,cAAA;AAAA;;;;;;;;;;;AAChC;;;;;;;;;;;;;;;;;;;;;;;;AA0DF;;;;;;;;;;;;;;;;KANK,eAAA;EACH,IAAA,GAAO,KAAA,UAAe,KAAA,GAAQ,MAAA;EAC9B,OAAA,GAAU,OAAA,EAAS,cAAA,KAAmB,eAAA;EACtC,IAAA,GAAO,OAAA;IAAY,QAAA;IAAkB,IAAA;EAAA,MAAmB,aAAA;AAAA;AAAA,iBAG1C,OAAA,CAAQ,OAAA,EAAS,eAAA,GAAkB,cAAA"}
|
package/dist/test.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
//#region src/test.ts
|
|
1
|
+
//#region src/feature/test.ts
|
|
2
2
|
function testCli(program) {
|
|
3
3
|
let input;
|
|
4
4
|
let envVars;
|
|
5
5
|
let promptAnswers;
|
|
6
|
-
let configFiles;
|
|
7
6
|
let stdinData;
|
|
8
7
|
const builder = {
|
|
9
8
|
args(args) {
|
|
@@ -18,10 +17,6 @@ function testCli(program) {
|
|
|
18
17
|
promptAnswers = answers;
|
|
19
18
|
return builder;
|
|
20
19
|
},
|
|
21
|
-
config(files) {
|
|
22
|
-
configFiles = files;
|
|
23
|
-
return builder;
|
|
24
|
-
},
|
|
25
20
|
stdin(data) {
|
|
26
21
|
stdinData = data;
|
|
27
22
|
return builder;
|
|
@@ -32,10 +27,9 @@ function testCli(program) {
|
|
|
32
27
|
const runtime = buildRuntime(stdout, stderr, {
|
|
33
28
|
envVars,
|
|
34
29
|
promptAnswers,
|
|
35
|
-
configFiles,
|
|
36
30
|
stdinData
|
|
37
31
|
});
|
|
38
|
-
const evalResult = await program.runtime(runtime).eval(runInput ?? input ?? "", {
|
|
32
|
+
const evalResult = await program.runtime(runtime).eval(runInput ?? input ?? "", {});
|
|
39
33
|
if (evalResult.error) stderr.push(evalResult.error instanceof Error ? evalResult.error.message : String(evalResult.error));
|
|
40
34
|
return toTestResult(evalResult, stdout, stderr);
|
|
41
35
|
},
|
|
@@ -45,7 +39,6 @@ function testCli(program) {
|
|
|
45
39
|
const runtime = buildRuntime(stdout, stderr, {
|
|
46
40
|
envVars,
|
|
47
41
|
promptAnswers,
|
|
48
|
-
configFiles,
|
|
49
42
|
readLine: createMockReadLine(inputs)
|
|
50
43
|
});
|
|
51
44
|
const testProgram = program.runtime(runtime);
|
|
@@ -89,10 +82,6 @@ function buildRuntime(stdout, stderr, opts) {
|
|
|
89
82
|
runtime.interactive = "supported";
|
|
90
83
|
runtime.prompt = async (config) => opts.promptAnswers[config.name];
|
|
91
84
|
}
|
|
92
|
-
if (opts.configFiles) {
|
|
93
|
-
runtime.loadConfigFile = (path) => opts.configFiles[path];
|
|
94
|
-
runtime.findFile = (names) => names.find((n) => n in opts.configFiles);
|
|
95
|
-
}
|
|
96
85
|
if (opts.readLine) runtime.readLine = opts.readLine;
|
|
97
86
|
if (opts.stdinData !== void 0) runtime.stdin = {
|
|
98
87
|
isTTY: false,
|
package/dist/test.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.mjs","names":[],"sources":["../src/test.ts"],"sourcesContent":["import type { InteractivePromptConfig, PadroneRuntime } from './runtime.ts';\nimport type { AnyPadroneCommand, PadroneCommandResult } from './types.ts';\n\n/**\n * Result from a single command execution in test mode.\n * Extends the standard PadroneCommandResult with captured I/O.\n */\nexport type TestCliResult = {\n /** The matched command. */\n command: AnyPadroneCommand;\n /** Validated arguments (undefined if validation failed). */\n args: unknown;\n /** Action handler return value (undefined if validation failed or no action). */\n result: unknown;\n /** Validation issues, if any. */\n issues: { message: string; path?: PropertyKey[] }[] | undefined;\n /** All values passed to `runtime.output()`. */\n stdout: unknown[];\n /** All strings passed to `runtime.error()`. */\n stderr: string[];\n /** The thrown error, if the command threw (routing error, action error, etc.). */\n error?: unknown;\n};\n\n/**\n * Result from a REPL test session.\n */\nexport type TestReplResult = {\n /** One entry per successfully executed command (validation errors are captured in stderr, not here). */\n results: Omit<TestCliResult, 'stdout' | 'stderr'>[];\n /** All output from the entire REPL session. */\n stdout: unknown[];\n /** All errors from the entire REPL session. */\n stderr: string[];\n};\n\n/**\n * Fluent builder for setting up CLI test scenarios.\n */\nexport type TestCliBuilder = {\n /** Set the CLI input string (e.g. `'deploy --env production'`). */\n args(input: string): TestCliBuilder;\n /** Set environment variables visible to the command. */\n env(vars: Record<string, string | undefined>): TestCliBuilder;\n /** Provide mock answers for interactive prompts. Keys are field names. */\n prompt(answers: Record<string, unknown>): TestCliBuilder;\n /** Provide mock config files. Keys are file paths, values are parsed config objects. */\n config(files: Record<string, Record<string, unknown>>): TestCliBuilder;\n /** Provide mock stdin data (simulates piped input). */\n stdin(data: string): TestCliBuilder;\n /**\n * Execute a single command via `eval()` and return the result with captured I/O.\n * @param input - Optional CLI input string. Overrides `.args()` if provided.\n */\n run(input?: string): Promise<TestCliResult>;\n /**\n * Run a REPL session with the given sequence of inputs.\n * Each string in the array is fed as one line of input.\n * The session ends after all inputs are consumed (EOF).\n */\n repl(inputs: string[]): Promise<TestReplResult>;\n};\n\n/**\n * Creates a fluent test builder for a Padrone program.\n * Captures all I/O and provides a clean interface for assertions.\n *\n * Works with any test framework (bun:test, vitest, jest, node:test, etc.).\n *\n * @example\n * ```ts\n * import { testCli } from 'padrone/test'\n *\n * const result = await testCli(myProgram)\n * .args('deploy --env production')\n * .env({ API_KEY: 'xxx' })\n * .run()\n *\n * expect(result.result).toBe('Deployed')\n * expect(result.stdout).toContain('Deploying...')\n * ```\n *\n * @example\n * ```ts\n * // Shorthand: pass input directly to run()\n * const result = await testCli(myProgram).run('deploy --env production')\n * ```\n *\n * @example\n * ```ts\n * // Test interactive prompts\n * const result = await testCli(myProgram)\n * .args('init')\n * .prompt({ name: 'myapp', template: 'react' })\n * .run()\n *\n * expect(result.args).toEqual({ name: 'myapp', template: 'react' })\n * ```\n *\n * @example\n * ```ts\n * // Test REPL sessions\n * const { results } = await testCli(myProgram)\n * .repl(['greet World', 'add --a=2 --b=3'])\n *\n * expect(results[0].result).toBe('Hello, World!')\n * expect(results[1].result).toBe(5)\n * ```\n */\n/**\n * Any program-like object that has `eval`, `runtime`, and `repl` methods.\n * Avoids strict variance issues with `AnyPadroneProgram`.\n */\ntype TestableProgram = {\n eval: (input: string, prefs?: { autoOutput?: boolean }) => any;\n runtime: (runtime: PadroneRuntime) => TestableProgram;\n repl: (options?: { greeting?: false; hint?: false }) => AsyncIterable<any>;\n};\n\nexport function testCli(program: TestableProgram): TestCliBuilder {\n let input: string | undefined;\n let envVars: Record<string, string | undefined> | undefined;\n let promptAnswers: Record<string, unknown> | undefined;\n let configFiles: Record<string, Record<string, unknown>> | undefined;\n let stdinData: string | undefined;\n\n const builder: TestCliBuilder = {\n args(args: string) {\n input = args;\n return builder;\n },\n env(vars) {\n envVars = vars;\n return builder;\n },\n prompt(answers) {\n promptAnswers = answers;\n return builder;\n },\n config(files) {\n configFiles = files;\n return builder;\n },\n stdin(data: string) {\n stdinData = data;\n return builder;\n },\n\n async run(runInput?: string) {\n const stdout: unknown[] = [];\n const stderr: string[] = [];\n\n const runtime = buildRuntime(stdout, stderr, { envVars, promptAnswers, configFiles, stdinData });\n const testProgram = program.runtime(runtime);\n\n const evalResult = await testProgram.eval(runInput ?? input ?? '', { autoOutput: false });\n if (evalResult.error) {\n stderr.push(evalResult.error instanceof Error ? evalResult.error.message : String(evalResult.error));\n }\n return toTestResult(evalResult, stdout, stderr);\n },\n\n async repl(inputs: string[]) {\n const stdout: unknown[] = [];\n const stderr: string[] = [];\n\n const runtime = buildRuntime(stdout, stderr, {\n envVars,\n promptAnswers,\n configFiles,\n readLine: createMockReadLine(inputs),\n });\n\n const testProgram = program.runtime(runtime);\n const results: Omit<TestCliResult, 'stdout' | 'stderr'>[] = [];\n\n for await (const r of testProgram.repl({ greeting: false, hint: false })) {\n results.push({\n command: r.command!,\n args: r.args,\n result: r.result,\n issues: r.argsResult?.issues as TestCliResult['issues'],\n });\n }\n\n return { results, stdout, stderr };\n },\n };\n\n return builder;\n}\n\nfunction toTestResult(evalResult: PadroneCommandResult, stdout: unknown[], stderr: string[]): TestCliResult {\n return {\n command: evalResult.command!,\n args: evalResult.args,\n result: evalResult.result,\n error: evalResult.error,\n issues: evalResult.argsResult?.issues as TestCliResult['issues'],\n stdout,\n stderr,\n };\n}\n\nfunction buildRuntime(\n stdout: unknown[],\n stderr: string[],\n opts: {\n envVars?: Record<string, string | undefined>;\n promptAnswers?: Record<string, unknown>;\n configFiles?: Record<string, Record<string, unknown>>;\n readLine?: (prompt: string) => Promise<string | null>;\n stdinData?: string;\n },\n): PadroneRuntime {\n const runtime: PadroneRuntime = {\n output: (...args: unknown[]) => stdout.push(...args),\n error: (text: string) => stderr.push(text),\n };\n\n if (opts.envVars) {\n runtime.env = () => opts.envVars!;\n }\n\n if (opts.promptAnswers) {\n runtime.interactive = 'supported';\n runtime.prompt = async (config: InteractivePromptConfig) => opts.promptAnswers![config.name];\n }\n\n if (opts.configFiles) {\n runtime.loadConfigFile = (path: string) => opts.configFiles![path];\n runtime.findFile = (names: string[]) => names.find((n) => n in opts.configFiles!);\n }\n\n if (opts.readLine) {\n runtime.readLine = opts.readLine;\n }\n\n if (opts.stdinData !== undefined) {\n runtime.stdin = {\n isTTY: false,\n async text() {\n return opts.stdinData!;\n },\n async *lines() {\n const lines = opts.stdinData!.split('\\n');\n // Remove trailing empty line from final newline (matches readline behavior)\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n for (const line of lines) {\n yield line;\n }\n },\n };\n } else {\n // No stdin data: simulate a TTY (no piped input) to avoid reading from process.stdin\n runtime.stdin = {\n isTTY: true,\n async text() {\n return '';\n },\n async *lines() {\n // no lines\n },\n };\n }\n\n return runtime;\n}\n\nfunction createMockReadLine(inputs: string[]): (prompt: string) => Promise<string | null> {\n let index = 0;\n return async (_prompt: string): Promise<string | null> => {\n if (index >= inputs.length) return null;\n return inputs[index++] ?? null;\n };\n}\n"],"mappings":";AAuHA,SAAgB,QAAQ,SAA0C;CAChE,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,UAA0B;EAC9B,KAAK,MAAc;AACjB,WAAQ;AACR,UAAO;;EAET,IAAI,MAAM;AACR,aAAU;AACV,UAAO;;EAET,OAAO,SAAS;AACd,mBAAgB;AAChB,UAAO;;EAET,OAAO,OAAO;AACZ,iBAAc;AACd,UAAO;;EAET,MAAM,MAAc;AAClB,eAAY;AACZ,UAAO;;EAGT,MAAM,IAAI,UAAmB;GAC3B,MAAM,SAAoB,EAAE;GAC5B,MAAM,SAAmB,EAAE;GAE3B,MAAM,UAAU,aAAa,QAAQ,QAAQ;IAAE;IAAS;IAAe;IAAa;IAAW,CAAC;GAGhG,MAAM,aAAa,MAFC,QAAQ,QAAQ,QAAQ,CAEP,KAAK,YAAY,SAAS,IAAI,EAAE,YAAY,OAAO,CAAC;AACzF,OAAI,WAAW,MACb,QAAO,KAAK,WAAW,iBAAiB,QAAQ,WAAW,MAAM,UAAU,OAAO,WAAW,MAAM,CAAC;AAEtG,UAAO,aAAa,YAAY,QAAQ,OAAO;;EAGjD,MAAM,KAAK,QAAkB;GAC3B,MAAM,SAAoB,EAAE;GAC5B,MAAM,SAAmB,EAAE;GAE3B,MAAM,UAAU,aAAa,QAAQ,QAAQ;IAC3C;IACA;IACA;IACA,UAAU,mBAAmB,OAAO;IACrC,CAAC;GAEF,MAAM,cAAc,QAAQ,QAAQ,QAAQ;GAC5C,MAAM,UAAsD,EAAE;AAE9D,cAAW,MAAM,KAAK,YAAY,KAAK;IAAE,UAAU;IAAO,MAAM;IAAO,CAAC,CACtE,SAAQ,KAAK;IACX,SAAS,EAAE;IACX,MAAM,EAAE;IACR,QAAQ,EAAE;IACV,QAAQ,EAAE,YAAY;IACvB,CAAC;AAGJ,UAAO;IAAE;IAAS;IAAQ;IAAQ;;EAErC;AAED,QAAO;;AAGT,SAAS,aAAa,YAAkC,QAAmB,QAAiC;AAC1G,QAAO;EACL,SAAS,WAAW;EACpB,MAAM,WAAW;EACjB,QAAQ,WAAW;EACnB,OAAO,WAAW;EAClB,QAAQ,WAAW,YAAY;EAC/B;EACA;EACD;;AAGH,SAAS,aACP,QACA,QACA,MAOgB;CAChB,MAAM,UAA0B;EAC9B,SAAS,GAAG,SAAoB,OAAO,KAAK,GAAG,KAAK;EACpD,QAAQ,SAAiB,OAAO,KAAK,KAAK;EAC3C;AAED,KAAI,KAAK,QACP,SAAQ,YAAY,KAAK;AAG3B,KAAI,KAAK,eAAe;AACtB,UAAQ,cAAc;AACtB,UAAQ,SAAS,OAAO,WAAoC,KAAK,cAAe,OAAO;;AAGzF,KAAI,KAAK,aAAa;AACpB,UAAQ,kBAAkB,SAAiB,KAAK,YAAa;AAC7D,UAAQ,YAAY,UAAoB,MAAM,MAAM,MAAM,KAAK,KAAK,YAAa;;AAGnF,KAAI,KAAK,SACP,SAAQ,WAAW,KAAK;AAG1B,KAAI,KAAK,cAAc,KAAA,EACrB,SAAQ,QAAQ;EACd,OAAO;EACP,MAAM,OAAO;AACX,UAAO,KAAK;;EAEd,OAAO,QAAQ;GACb,MAAM,QAAQ,KAAK,UAAW,MAAM,KAAK;AAEzC,OAAI,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,OAAO,GAAI,OAAM,KAAK;AACnE,QAAK,MAAM,QAAQ,MACjB,OAAM;;EAGX;KAGD,SAAQ,QAAQ;EACd,OAAO;EACP,MAAM,OAAO;AACX,UAAO;;EAET,OAAO,QAAQ;EAGhB;AAGH,QAAO;;AAGT,SAAS,mBAAmB,QAA8D;CACxF,IAAI,QAAQ;AACZ,QAAO,OAAO,YAA4C;AACxD,MAAI,SAAS,OAAO,OAAQ,QAAO;AACnC,SAAO,OAAO,YAAY"}
|
|
1
|
+
{"version":3,"file":"test.mjs","names":[],"sources":["../src/feature/test.ts"],"sourcesContent":["import type { InteractivePromptConfig, PadroneRuntime } from '../core/runtime.ts';\nimport type { AnyPadroneCommand, PadroneCommandResult } from '../types/index.ts';\n\n/**\n * Result from a single command execution in test mode.\n * Extends the standard PadroneCommandResult with captured I/O.\n */\nexport type TestCliResult = {\n /** The matched command. */\n command: AnyPadroneCommand;\n /** Validated arguments (undefined if validation failed). */\n args: unknown;\n /** Action handler return value (undefined if validation failed or no action). */\n result: unknown;\n /** Validation issues, if any. */\n issues: { message: string; path?: PropertyKey[] }[] | undefined;\n /** All values passed to `runtime.output()`. */\n stdout: unknown[];\n /** All strings passed to `runtime.error()`. */\n stderr: string[];\n /** The thrown error, if the command threw (routing error, action error, etc.). */\n error?: unknown;\n};\n\n/**\n * Result from a REPL test session.\n */\nexport type TestReplResult = {\n /** One entry per successfully executed command (validation errors are captured in stderr, not here). */\n results: Omit<TestCliResult, 'stdout' | 'stderr'>[];\n /** All output from the entire REPL session. */\n stdout: unknown[];\n /** All errors from the entire REPL session. */\n stderr: string[];\n};\n\n/**\n * Fluent builder for setting up CLI test scenarios.\n */\nexport type TestCliBuilder = {\n /** Set the CLI input string (e.g. `'deploy --env production'`). */\n args(input: string): TestCliBuilder;\n /** Set environment variables visible to the command. */\n env(vars: Record<string, string | undefined>): TestCliBuilder;\n /** Provide mock answers for interactive prompts. Keys are field names. */\n prompt(answers: Record<string, unknown>): TestCliBuilder;\n /** Provide mock stdin data (simulates piped input). */\n stdin(data: string): TestCliBuilder;\n /**\n * Execute a single command via `eval()` and return the result with captured I/O.\n * @param input - Optional CLI input string. Overrides `.args()` if provided.\n */\n run(input?: string): Promise<TestCliResult>;\n /**\n * Run a REPL session with the given sequence of inputs.\n * Each string in the array is fed as one line of input.\n * The session ends after all inputs are consumed (EOF).\n */\n repl(inputs: string[]): Promise<TestReplResult>;\n};\n\n/**\n * Creates a fluent test builder for a Padrone program.\n * Captures all I/O and provides a clean interface for assertions.\n *\n * Works with any test framework (bun:test, vitest, jest, node:test, etc.).\n *\n * @example\n * ```ts\n * import { testCli } from 'padrone/test'\n *\n * const result = await testCli(myProgram)\n * .args('deploy --env production')\n * .env({ API_KEY: 'xxx' })\n * .run()\n *\n * expect(result.result).toBe('Deployed')\n * expect(result.stdout).toContain('Deploying...')\n * ```\n *\n * @example\n * ```ts\n * // Shorthand: pass input directly to run()\n * const result = await testCli(myProgram).run('deploy --env production')\n * ```\n *\n * @example\n * ```ts\n * // Test interactive prompts\n * const result = await testCli(myProgram)\n * .args('init')\n * .prompt({ name: 'myapp', template: 'react' })\n * .run()\n *\n * expect(result.args).toEqual({ name: 'myapp', template: 'react' })\n * ```\n *\n * @example\n * ```ts\n * // Test REPL sessions\n * const { results } = await testCli(myProgram)\n * .repl(['greet World', 'add --a=2 --b=3'])\n *\n * expect(results[0].result).toBe('Hello, World!')\n * expect(results[1].result).toBe(5)\n * ```\n */\n/**\n * Any program-like object that has `eval`, `runtime`, and `repl` methods.\n * Avoids strict variance issues with `AnyPadroneProgram`.\n */\ntype TestableProgram = {\n eval: (input: string, prefs?: Record<string, unknown>) => any;\n runtime: (runtime: PadroneRuntime) => TestableProgram;\n repl: (options?: { greeting?: false; hint?: false }) => AsyncIterable<any>;\n};\n\nexport function testCli(program: TestableProgram): TestCliBuilder {\n let input: string | undefined;\n let envVars: Record<string, string | undefined> | undefined;\n let promptAnswers: Record<string, unknown> | undefined;\n let stdinData: string | undefined;\n\n const builder: TestCliBuilder = {\n args(args: string) {\n input = args;\n return builder;\n },\n env(vars) {\n envVars = vars;\n return builder;\n },\n prompt(answers) {\n promptAnswers = answers;\n return builder;\n },\n stdin(data: string) {\n stdinData = data;\n return builder;\n },\n\n async run(runInput?: string) {\n const stdout: unknown[] = [];\n const stderr: string[] = [];\n\n const runtime = buildRuntime(stdout, stderr, { envVars, promptAnswers, stdinData });\n const testProgram = program.runtime(runtime);\n\n const evalResult = await testProgram.eval(runInput ?? input ?? '', {});\n if (evalResult.error) {\n stderr.push(evalResult.error instanceof Error ? evalResult.error.message : String(evalResult.error));\n }\n return toTestResult(evalResult, stdout, stderr);\n },\n\n async repl(inputs: string[]) {\n const stdout: unknown[] = [];\n const stderr: string[] = [];\n\n const runtime = buildRuntime(stdout, stderr, {\n envVars,\n promptAnswers,\n readLine: createMockReadLine(inputs),\n });\n\n const testProgram = program.runtime(runtime);\n const results: Omit<TestCliResult, 'stdout' | 'stderr'>[] = [];\n\n for await (const r of testProgram.repl({ greeting: false, hint: false })) {\n results.push({\n command: r.command!,\n args: r.args,\n result: r.result,\n issues: r.argsResult?.issues as TestCliResult['issues'],\n });\n }\n\n return { results, stdout, stderr };\n },\n };\n\n return builder;\n}\n\nfunction toTestResult(evalResult: PadroneCommandResult, stdout: unknown[], stderr: string[]): TestCliResult {\n return {\n command: evalResult.command!,\n args: evalResult.args,\n result: evalResult.result,\n error: evalResult.error,\n issues: evalResult.argsResult?.issues as TestCliResult['issues'],\n stdout,\n stderr,\n };\n}\n\nfunction buildRuntime(\n stdout: unknown[],\n stderr: string[],\n opts: {\n envVars?: Record<string, string | undefined>;\n promptAnswers?: Record<string, unknown>;\n readLine?: (prompt: string) => Promise<string | null>;\n stdinData?: string;\n },\n): PadroneRuntime {\n const runtime: PadroneRuntime = {\n output: (...args: unknown[]) => stdout.push(...args),\n error: (text: string) => stderr.push(text),\n };\n\n if (opts.envVars) {\n runtime.env = () => opts.envVars!;\n }\n\n if (opts.promptAnswers) {\n runtime.interactive = 'supported';\n runtime.prompt = async (config: InteractivePromptConfig) => opts.promptAnswers![config.name];\n }\n\n if (opts.readLine) {\n runtime.readLine = opts.readLine;\n }\n\n if (opts.stdinData !== undefined) {\n runtime.stdin = {\n isTTY: false,\n async text() {\n return opts.stdinData!;\n },\n async *lines() {\n const lines = opts.stdinData!.split('\\n');\n // Remove trailing empty line from final newline (matches readline behavior)\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n for (const line of lines) {\n yield line;\n }\n },\n };\n } else {\n // No stdin data: simulate a TTY (no piped input) to avoid reading from process.stdin\n runtime.stdin = {\n isTTY: true,\n async text() {\n return '';\n },\n async *lines() {\n // no lines\n },\n };\n }\n\n return runtime;\n}\n\nfunction createMockReadLine(inputs: string[]): (prompt: string) => Promise<string | null> {\n let index = 0;\n return async (_prompt: string): Promise<string | null> => {\n if (index >= inputs.length) return null;\n return inputs[index++] ?? null;\n };\n}\n"],"mappings":";AAqHA,SAAgB,QAAQ,SAA0C;CAChE,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,UAA0B;EAC9B,KAAK,MAAc;AACjB,WAAQ;AACR,UAAO;;EAET,IAAI,MAAM;AACR,aAAU;AACV,UAAO;;EAET,OAAO,SAAS;AACd,mBAAgB;AAChB,UAAO;;EAET,MAAM,MAAc;AAClB,eAAY;AACZ,UAAO;;EAGT,MAAM,IAAI,UAAmB;GAC3B,MAAM,SAAoB,EAAE;GAC5B,MAAM,SAAmB,EAAE;GAE3B,MAAM,UAAU,aAAa,QAAQ,QAAQ;IAAE;IAAS;IAAe;IAAW,CAAC;GAGnF,MAAM,aAAa,MAFC,QAAQ,QAAQ,QAAQ,CAEP,KAAK,YAAY,SAAS,IAAI,EAAE,CAAC;AACtE,OAAI,WAAW,MACb,QAAO,KAAK,WAAW,iBAAiB,QAAQ,WAAW,MAAM,UAAU,OAAO,WAAW,MAAM,CAAC;AAEtG,UAAO,aAAa,YAAY,QAAQ,OAAO;;EAGjD,MAAM,KAAK,QAAkB;GAC3B,MAAM,SAAoB,EAAE;GAC5B,MAAM,SAAmB,EAAE;GAE3B,MAAM,UAAU,aAAa,QAAQ,QAAQ;IAC3C;IACA;IACA,UAAU,mBAAmB,OAAO;IACrC,CAAC;GAEF,MAAM,cAAc,QAAQ,QAAQ,QAAQ;GAC5C,MAAM,UAAsD,EAAE;AAE9D,cAAW,MAAM,KAAK,YAAY,KAAK;IAAE,UAAU;IAAO,MAAM;IAAO,CAAC,CACtE,SAAQ,KAAK;IACX,SAAS,EAAE;IACX,MAAM,EAAE;IACR,QAAQ,EAAE;IACV,QAAQ,EAAE,YAAY;IACvB,CAAC;AAGJ,UAAO;IAAE;IAAS;IAAQ;IAAQ;;EAErC;AAED,QAAO;;AAGT,SAAS,aAAa,YAAkC,QAAmB,QAAiC;AAC1G,QAAO;EACL,SAAS,WAAW;EACpB,MAAM,WAAW;EACjB,QAAQ,WAAW;EACnB,OAAO,WAAW;EAClB,QAAQ,WAAW,YAAY;EAC/B;EACA;EACD;;AAGH,SAAS,aACP,QACA,QACA,MAMgB;CAChB,MAAM,UAA0B;EAC9B,SAAS,GAAG,SAAoB,OAAO,KAAK,GAAG,KAAK;EACpD,QAAQ,SAAiB,OAAO,KAAK,KAAK;EAC3C;AAED,KAAI,KAAK,QACP,SAAQ,YAAY,KAAK;AAG3B,KAAI,KAAK,eAAe;AACtB,UAAQ,cAAc;AACtB,UAAQ,SAAS,OAAO,WAAoC,KAAK,cAAe,OAAO;;AAGzF,KAAI,KAAK,SACP,SAAQ,WAAW,KAAK;AAG1B,KAAI,KAAK,cAAc,KAAA,EACrB,SAAQ,QAAQ;EACd,OAAO;EACP,MAAM,OAAO;AACX,UAAO,KAAK;;EAEd,OAAO,QAAQ;GACb,MAAM,QAAQ,KAAK,UAAW,MAAM,KAAK;AAEzC,OAAI,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,OAAO,GAAI,OAAM,KAAK;AACnE,QAAK,MAAM,QAAQ,MACjB,OAAM;;EAGX;KAGD,SAAQ,QAAQ;EACd,OAAO;EACP,MAAM,OAAO;AACX,UAAO;;EAET,OAAO,QAAQ;EAGhB;AAGH,QAAO;;AAGT,SAAS,mBAAmB,QAA8D;CACxF,IAAI,QAAQ;AACZ,QAAO,OAAO,YAA4C;AACxD,MAAI,SAAS,OAAO,OAAQ,QAAO;AACnC,SAAO,OAAO,YAAY"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
//#region src/update-check.ts
|
|
1
|
+
//#region src/feature/update-check.ts
|
|
3
2
|
/**
|
|
4
3
|
* Parses an interval string like '1d', '12h', '30m', '1w' into milliseconds.
|
|
5
4
|
*/
|
|
@@ -43,9 +42,9 @@ function isNewerVersion(current, latest) {
|
|
|
43
42
|
/**
|
|
44
43
|
* Reads the update check cache file.
|
|
45
44
|
*/
|
|
46
|
-
function readCache(cachePath) {
|
|
45
|
+
async function readCache(cachePath) {
|
|
47
46
|
try {
|
|
48
|
-
const { existsSync, readFileSync } =
|
|
47
|
+
const { existsSync, readFileSync } = await import("node:fs");
|
|
49
48
|
if (!existsSync(cachePath)) return void 0;
|
|
50
49
|
const data = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
51
50
|
if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") return data;
|
|
@@ -54,10 +53,10 @@ function readCache(cachePath) {
|
|
|
54
53
|
/**
|
|
55
54
|
* Writes the update check cache file.
|
|
56
55
|
*/
|
|
57
|
-
function writeCache(cachePath, data) {
|
|
56
|
+
async function writeCache(cachePath, data) {
|
|
58
57
|
try {
|
|
59
|
-
const { existsSync, mkdirSync, writeFileSync } =
|
|
60
|
-
const { dirname } =
|
|
58
|
+
const { existsSync, mkdirSync, writeFileSync } = await import("node:fs");
|
|
59
|
+
const { dirname } = await import("node:path");
|
|
61
60
|
const dir = dirname(cachePath);
|
|
62
61
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
63
62
|
writeFileSync(cachePath, JSON.stringify(data), "utf-8");
|
|
@@ -66,9 +65,9 @@ function writeCache(cachePath, data) {
|
|
|
66
65
|
/**
|
|
67
66
|
* Resolves the cache path, expanding `~` to the home directory.
|
|
68
67
|
*/
|
|
69
|
-
function resolveCachePath(cachePath) {
|
|
70
|
-
const { homedir } =
|
|
71
|
-
const { resolve } =
|
|
68
|
+
async function resolveCachePath(cachePath) {
|
|
69
|
+
const { homedir } = await import("node:os");
|
|
70
|
+
const { resolve } = await import("node:path");
|
|
72
71
|
if (cachePath.startsWith("~")) return cachePath.replace("~", homedir());
|
|
73
72
|
return resolve(cachePath);
|
|
74
73
|
}
|
|
@@ -99,27 +98,27 @@ function formatUpdateMessage(currentVersion, latestVersion, packageName) {
|
|
|
99
98
|
* This is designed to be non-blocking: the check starts immediately but the
|
|
100
99
|
* result is only consumed after command execution completes.
|
|
101
100
|
*/
|
|
102
|
-
function createUpdateChecker(programName, currentVersion, config, runtime) {
|
|
101
|
+
async function createUpdateChecker(programName, currentVersion, config, runtime) {
|
|
103
102
|
const packageName = config.packageName ?? programName;
|
|
104
103
|
const registry = config.registry ?? "npm";
|
|
105
104
|
const intervalMs = parseInterval(config.interval ?? "1d");
|
|
106
105
|
const disableEnvVar = config.disableEnvVar ?? `${programName.toUpperCase().replace(/-/g, "_")}_NO_UPDATE_CHECK`;
|
|
107
106
|
const defaultCachePath = `~/.config/${programName}-update-check.json`;
|
|
108
|
-
const cachePath = resolveCachePath(config.cache ?? defaultCachePath);
|
|
107
|
+
const cachePath = await resolveCachePath(config.cache ?? defaultCachePath);
|
|
109
108
|
const env = runtime.env();
|
|
110
109
|
if (env.CI || env.CONTINUOUS_INTEGRATION) return noop;
|
|
111
110
|
if (env[disableEnvVar]) return noop;
|
|
112
|
-
if (
|
|
113
|
-
const cached = readCache(cachePath);
|
|
111
|
+
if (runtime.terminal && !runtime.terminal.isTTY) return noop;
|
|
112
|
+
const cached = await readCache(cachePath);
|
|
114
113
|
if (cached && Date.now() - cached.lastCheck < intervalMs) {
|
|
115
114
|
if (isNewerVersion(currentVersion, cached.latestVersion)) return () => {
|
|
116
115
|
runtime.error(formatUpdateMessage(currentVersion, cached.latestVersion, packageName));
|
|
117
116
|
};
|
|
118
117
|
return noop;
|
|
119
118
|
}
|
|
120
|
-
const fetchPromise = fetchLatestVersion(packageName, registry).then((latestVersion) => {
|
|
119
|
+
const fetchPromise = fetchLatestVersion(packageName, registry).then(async (latestVersion) => {
|
|
121
120
|
if (latestVersion) {
|
|
122
|
-
writeCache(cachePath, {
|
|
121
|
+
await writeCache(cachePath, {
|
|
123
122
|
lastCheck: Date.now(),
|
|
124
123
|
latestVersion
|
|
125
124
|
});
|
|
@@ -143,4 +142,4 @@ function noop() {}
|
|
|
143
142
|
//#endregion
|
|
144
143
|
export { createUpdateChecker };
|
|
145
144
|
|
|
146
|
-
//# sourceMappingURL=update-check-
|
|
145
|
+
//# sourceMappingURL=update-check-CZ2VqjnV.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check-CZ2VqjnV.mjs","names":[],"sources":["../src/feature/update-check.ts"],"sourcesContent":["import type { ResolvedPadroneRuntime } from '../core/runtime.ts';\n\n/**\n * Configuration for the update check feature.\n */\nexport type UpdateCheckConfig = {\n /**\n * The npm package name to check. Defaults to the program name.\n */\n packageName?: string;\n /**\n * Registry to check for updates.\n * - `'npm'` — checks the npm registry (default)\n * - A URL string — custom registry endpoint that returns JSON with a `version` or `dist-tags.latest` field\n */\n registry?: 'npm' | string;\n /**\n * How often to check for updates. Accepts shorthand like `'1d'`, `'12h'`, `'30m'`.\n * Defaults to `'1d'` (once per day).\n */\n interval?: string;\n /**\n * Path to the cache file for storing the last check timestamp and latest version.\n * Defaults to `~/.config/<programName>-update-check.json`.\n */\n cache?: string;\n /**\n * Environment variable name to disable update checks (e.g. `'MYAPP_NO_UPDATE_CHECK'`).\n * When set to a truthy value, update checks are skipped.\n * Defaults to `'<PROGRAM_NAME>_NO_UPDATE_CHECK'` (uppercased, hyphens to underscores).\n */\n disableEnvVar?: string;\n};\n\ntype CacheData = {\n lastCheck: number;\n latestVersion: string;\n};\n\n/**\n * Parses an interval string like '1d', '12h', '30m', '1w' into milliseconds.\n */\nexport function parseInterval(interval: string): number {\n const match = interval.match(/^(\\d+)\\s*(ms|s|m|h|d|w)$/);\n if (!match) return 86_400_000; // default 1d\n\n const value = parseInt(match[1]!, 10);\n const unit = match[2]!;\n\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60_000;\n case 'h':\n return value * 3_600_000;\n case 'd':\n return value * 86_400_000;\n case 'w':\n return value * 604_800_000;\n default:\n return 86_400_000;\n }\n}\n\n/**\n * Compares two semver version strings.\n * Returns true if `latest` is newer than `current`.\n */\nexport function isNewerVersion(current: string, latest: string): boolean {\n const parse = (v: string) => {\n const cleaned = v.replace(/^v/, '');\n const parts = cleaned.split('-');\n const nums = parts[0]!.split('.').map(Number);\n return { major: nums[0] ?? 0, minor: nums[1] ?? 0, patch: nums[2] ?? 0, prerelease: parts[1] };\n };\n\n const c = parse(current);\n const l = parse(latest);\n\n // Don't notify about pre-release versions unless user is already on a pre-release\n if (l.prerelease && !c.prerelease) return false;\n\n if (l.major !== c.major) return l.major > c.major;\n if (l.minor !== c.minor) return l.minor > c.minor;\n if (l.patch !== c.patch) return l.patch > c.patch;\n return false;\n}\n\n/**\n * Reads the update check cache file.\n */\nasync function readCache(cachePath: string): Promise<CacheData | undefined> {\n try {\n const { existsSync, readFileSync } = await import('node:fs');\n if (!existsSync(cachePath)) return undefined;\n const data = JSON.parse(readFileSync(cachePath, 'utf-8'));\n if (typeof data.lastCheck === 'number' && typeof data.latestVersion === 'string') {\n return data as CacheData;\n }\n } catch {\n // Ignore errors\n }\n return undefined;\n}\n\n/**\n * Writes the update check cache file.\n */\nasync function writeCache(cachePath: string, data: CacheData): Promise<void> {\n try {\n const { existsSync, mkdirSync, writeFileSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n const dir = dirname(cachePath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(cachePath, JSON.stringify(data), 'utf-8');\n } catch {\n // Ignore errors — cache is best-effort\n }\n}\n\n/**\n * Resolves the cache path, expanding `~` to the home directory.\n */\nasync function resolveCachePath(cachePath: string): Promise<string> {\n const { homedir } = await import('node:os');\n const { resolve } = await import('node:path');\n if (cachePath.startsWith('~')) {\n return cachePath.replace('~', homedir());\n }\n return resolve(cachePath);\n}\n\n/**\n * Fetches the latest version from the registry.\n */\nasync function fetchLatestVersion(packageName: string, registry: string): Promise<string | undefined> {\n const url = registry === 'npm' ? `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest` : registry;\n\n try {\n const response = await fetch(url);\n if (!response.ok) return undefined;\n const data = (await response.json()) as Record<string, unknown>;\n\n // npm registry returns { version: \"x.y.z\" }\n if (typeof data.version === 'string') return data.version;\n\n // Custom endpoint may return { \"dist-tags\": { latest: \"x.y.z\" } }\n const distTags = data['dist-tags'] as Record<string, string> | undefined;\n if (distTags?.latest) return distTags.latest;\n } catch {\n // Network errors are expected (offline, firewall, etc.)\n }\n return undefined;\n}\n\n/**\n * Formats the update notification message.\n */\nexport function formatUpdateMessage(currentVersion: string, latestVersion: string, packageName: string): string {\n const updateCommand = `npm update -g ${packageName}`;\n return `\\n Update available: ${currentVersion} \\u2192 ${latestVersion}\\n Run \"${updateCommand}\" to update\\n`;\n}\n\n/**\n * Checks for updates in the background. Returns a function that, when called,\n * prints the update notification if a newer version was found.\n *\n * This is designed to be non-blocking: the check starts immediately but the\n * result is only consumed after command execution completes.\n */\nexport async function createUpdateChecker(\n programName: string,\n currentVersion: string,\n config: UpdateCheckConfig,\n runtime: ResolvedPadroneRuntime,\n): Promise<() => void> {\n const packageName = config.packageName ?? programName;\n const registry = config.registry ?? 'npm';\n const intervalMs = parseInterval(config.interval ?? '1d');\n const disableEnvVar = config.disableEnvVar ?? `${programName.toUpperCase().replace(/-/g, '_')}_NO_UPDATE_CHECK`;\n\n const defaultCachePath = `~/.config/${programName}-update-check.json`;\n const cachePath = await resolveCachePath(config.cache ?? defaultCachePath);\n\n // Check if disabled\n const env = runtime.env();\n if (env.CI || env.CONTINUOUS_INTEGRATION) return noop;\n if (env[disableEnvVar]) return noop;\n if (runtime.terminal && !runtime.terminal.isTTY) return noop;\n\n // Check cache — if we checked recently, use cached result\n const cached = await readCache(cachePath);\n if (cached && Date.now() - cached.lastCheck < intervalMs) {\n // Use cached version for display\n if (isNewerVersion(currentVersion, cached.latestVersion)) {\n return () => {\n runtime.error(formatUpdateMessage(currentVersion, cached.latestVersion, packageName));\n };\n }\n return noop;\n }\n\n // Start background fetch\n const fetchPromise = fetchLatestVersion(packageName, registry).then(async (latestVersion) => {\n if (latestVersion) {\n await writeCache(cachePath, { lastCheck: Date.now(), latestVersion });\n if (isNewerVersion(currentVersion, latestVersion)) {\n return latestVersion;\n }\n }\n return undefined;\n });\n\n // Return a function that blocks on the result (briefly — the fetch should be done by now)\n let resolved: string | undefined | null = null; // null = not yet resolved\n fetchPromise.then(\n (v) => {\n resolved = v;\n },\n () => {\n resolved = undefined;\n },\n );\n\n return () => {\n // If the fetch already resolved, use the result synchronously\n if (resolved !== null) {\n if (resolved) {\n runtime.error(formatUpdateMessage(currentVersion, resolved, packageName));\n }\n return;\n }\n\n // Otherwise, we can't block — just skip this time.\n // The cache will be written when the promise resolves, so next invocation will show the message.\n };\n}\n\nfunction noop() {}\n"],"mappings":"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"}
|
package/dist/zod.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { U as PadroneSchema } from "./index-D6-7dz0l.mjs";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
3
|
|
|
4
|
-
//#region src/zod.d.ts
|
|
4
|
+
//#region src/schema/zod.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Creates a Zod schema for an async stream field, ready to use in `.arguments()`.
|
|
7
7
|
* Wraps `z.custom<AsyncIterable<T>>()` with the `asyncStream()` metadata automatically.
|
package/dist/zod.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod.d.mts","names":[],"sources":["../src/zod.ts"],"mappings":";;;;;;AAsBA;;;;;;;;;;;;;;;;iBAAgB,cAAA,YAAA,CAA2B,UAAA,GAAa,aAAA,UAAuB,CAAA,IAAE,CAAA,CAAA,SAAA,CAAA,aAAA,CAAA,CAAA,GAAA,aAAA,CAAA,CAAA;;;;;;cASpE,SAAA,aAAuB,CAAA,CAAE,OAAA,EAAS,MAAA,EAAQ,CAAA,KAAC,CAAA,CAAA,QAAA,CAAA,CAAA,CAAA,QAAA,WAAA,CAAA,CAAA,SAAA,EAAA,CAAA,CAAA,UAAA,IAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"zod.d.mts","names":[],"sources":["../src/schema/zod.ts"],"mappings":";;;;;;AAsBA;;;;;;;;;;;;;;;;iBAAgB,cAAA,YAAA,CAA2B,UAAA,GAAa,aAAA,UAAuB,CAAA,IAAE,CAAA,CAAA,SAAA,CAAA,aAAA,CAAA,CAAA,GAAA,aAAA,CAAA,CAAA;;;;;;cASpE,SAAA,aAAuB,CAAA,CAAE,OAAA,EAAS,MAAA,EAAQ,CAAA,KAAC,CAAA,CAAA,QAAA,CAAA,CAAA,CAAA,QAAA,WAAA,CAAA,CAAA,SAAA,EAAA,CAAA,CAAA,UAAA,IAAA,CAAA"}
|
package/dist/zod.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as asyncStream } from "./stream-
|
|
1
|
+
import { t as asyncStream } from "./stream-DC4H8YTx.mjs";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
|
-
//#region src/zod.ts
|
|
3
|
+
//#region src/schema/zod.ts
|
|
4
4
|
/**
|
|
5
5
|
* Creates a Zod schema for an async stream field, ready to use in `.arguments()`.
|
|
6
6
|
* Wraps `z.custom<AsyncIterable<T>>()` with the `asyncStream()` metadata automatically.
|
package/dist/zod.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod.mjs","names":[],"sources":["../src/zod.ts"],"sourcesContent":["import * as z from 'zod/v4';\nimport {
|
|
1
|
+
{"version":3,"file":"zod.mjs","names":[],"sources":["../src/schema/zod.ts"],"sourcesContent":["import * as z from 'zod/v4';\nimport type { PadroneSchema } from '../types/index.ts';\nimport { asyncStream } from '../util/stream.ts';\n\n/**\n * Creates a Zod schema for an async stream field, ready to use in `.arguments()`.\n * Wraps `z.custom<AsyncIterable<T>>()` with the `asyncStream()` metadata automatically.\n *\n * @param itemSchema - Optional item schema for per-item validation.\n *\n * @example\n * ```ts\n * import { zodAsyncStream } from 'padrone/zod';\n *\n * // String lines\n * z.object({ lines: zodAsyncStream() })\n *\n * // Typed items — each line JSON.parse'd and validated\n * const recordSchema = z.object({ name: z.string(), age: z.number() });\n * z.object({ records: zodAsyncStream(jsonCodec(recordSchema)) })\n * ```\n */\nexport function zodAsyncStream<T = string>(itemSchema?: PadroneSchema<unknown, T>) {\n return z.custom<AsyncIterable<T>>().meta(asyncStream(itemSchema));\n}\n\n/**\n * JSON codec for Zod schemas\n * @see https://zod.dev/codecs?id=jsonschema\n * Unlike the example in the docs, this codec also handles the case where the input is already an object\n */\nexport const jsonCodec = <T extends z.ZodType>(schema: T) =>\n z.codec(z.union([z.string(), z.unknown()]), schema, {\n decode: (jsonString, ctx) => {\n try {\n // HACK: in some cases the object is already deserialized, we just need to validate it\n if (typeof jsonString !== 'string') return jsonString as z.input<T>;\n return JSON.parse(jsonString) as z.input<T>;\n } catch (err: any) {\n ctx.issues.push({\n code: 'invalid_format',\n format: 'json',\n input: typeof jsonString === 'string' ? jsonString : JSON.stringify(jsonString),\n message: err.message,\n });\n return z.NEVER;\n }\n },\n encode: (value) => JSON.stringify(value),\n });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,eAA2B,YAAwC;AACjF,QAAO,EAAE,QAA0B,CAAC,KAAK,YAAY,WAAW,CAAC;;;;;;;AAQnE,MAAa,aAAkC,WAC7C,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,EAAE,QAAQ;CAClD,SAAS,YAAY,QAAQ;AAC3B,MAAI;AAEF,OAAI,OAAO,eAAe,SAAU,QAAO;AAC3C,UAAO,KAAK,MAAM,WAAW;WACtB,KAAU;AACjB,OAAI,OAAO,KAAK;IACd,MAAM;IACN,QAAQ;IACR,OAAO,OAAO,eAAe,WAAW,aAAa,KAAK,UAAU,WAAW;IAC/E,SAAS,IAAI;IACd,CAAC;AACF,UAAO,EAAE;;;CAGb,SAAS,UAAU,KAAK,UAAU,MAAM;CACzC,CAAC"}
|