padrone 1.8.2 → 2.0.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.
Files changed (333) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +2 -2
  3. package/dist/codegen/code-builder.d.mts +10 -0
  4. package/dist/codegen/code-builder.d.mts.map +1 -0
  5. package/dist/codegen/code-builder.mjs +185 -0
  6. package/dist/codegen/code-builder.mjs.map +1 -0
  7. package/dist/codegen/discovery.d.mts +44 -0
  8. package/dist/codegen/discovery.d.mts.map +1 -0
  9. package/dist/codegen/discovery.mjs +179 -0
  10. package/dist/codegen/discovery.mjs.map +1 -0
  11. package/dist/codegen/file-emitter.d.mts +10 -0
  12. package/dist/codegen/file-emitter.d.mts.map +1 -0
  13. package/dist/codegen/file-emitter.mjs +58 -0
  14. package/dist/codegen/file-emitter.mjs.map +1 -0
  15. package/dist/codegen/generators/barrel-file.d.mts +10 -0
  16. package/dist/codegen/generators/barrel-file.d.mts.map +1 -0
  17. package/dist/codegen/generators/barrel-file.mjs +16 -0
  18. package/dist/codegen/generators/barrel-file.mjs.map +1 -0
  19. package/dist/codegen/generators/command-file.d.mts +25 -0
  20. package/dist/codegen/generators/command-file.d.mts.map +1 -0
  21. package/dist/codegen/generators/command-file.mjs +130 -0
  22. package/dist/codegen/generators/command-file.mjs.map +1 -0
  23. package/dist/codegen/generators/command-tree.d.mts +17 -0
  24. package/dist/codegen/generators/command-tree.d.mts.map +1 -0
  25. package/dist/codegen/generators/command-tree.mjs +72 -0
  26. package/dist/codegen/generators/command-tree.mjs.map +1 -0
  27. package/dist/codegen/index.d.mts +15 -330
  28. package/dist/codegen/index.mjs +13 -1507
  29. package/dist/codegen/parsers/bash.d.mts +19 -0
  30. package/dist/codegen/parsers/bash.d.mts.map +1 -0
  31. package/dist/codegen/parsers/bash.mjs +112 -0
  32. package/dist/codegen/parsers/bash.mjs.map +1 -0
  33. package/dist/codegen/parsers/fish.d.mts +13 -0
  34. package/dist/codegen/parsers/fish.d.mts.map +1 -0
  35. package/dist/codegen/parsers/fish.mjs +108 -0
  36. package/dist/codegen/parsers/fish.mjs.map +1 -0
  37. package/dist/codegen/parsers/help.d.mts +15 -0
  38. package/dist/codegen/parsers/help.d.mts.map +1 -0
  39. package/dist/codegen/parsers/help.mjs +271 -0
  40. package/dist/codegen/parsers/help.mjs.map +1 -0
  41. package/dist/codegen/parsers/merge.d.mts +14 -0
  42. package/dist/codegen/parsers/merge.d.mts.map +1 -0
  43. package/dist/codegen/parsers/merge.mjs +76 -0
  44. package/dist/codegen/parsers/merge.mjs.map +1 -0
  45. package/dist/codegen/parsers/zsh.d.mts +16 -0
  46. package/dist/codegen/parsers/zsh.d.mts.map +1 -0
  47. package/dist/codegen/parsers/zsh.mjs +161 -0
  48. package/dist/codegen/parsers/zsh.mjs.map +1 -0
  49. package/dist/codegen/schema-to-code.d.mts +24 -0
  50. package/dist/codegen/schema-to-code.d.mts.map +1 -0
  51. package/dist/codegen/schema-to-code.mjs +147 -0
  52. package/dist/codegen/schema-to-code.mjs.map +1 -0
  53. package/dist/codegen/template.d.mts +18 -0
  54. package/dist/codegen/template.d.mts.map +1 -0
  55. package/dist/codegen/template.mjs +37 -0
  56. package/dist/codegen/template.mjs.map +1 -0
  57. package/dist/codegen/types.d.mts +144 -0
  58. package/dist/codegen/types.d.mts.map +1 -0
  59. package/dist/completion.d.mts +2 -0
  60. package/dist/completion.mjs +2 -0
  61. package/dist/core/args.d.mts +3 -0
  62. package/dist/{args-DrCXxXeP.mjs → core/args.mjs} +49 -113
  63. package/dist/core/args.mjs.map +1 -0
  64. package/dist/core/commands.d.mts +13 -0
  65. package/dist/core/commands.d.mts.map +1 -0
  66. package/dist/{commands-DLR0rFgq.mjs → core/commands.mjs} +4 -232
  67. package/dist/core/commands.mjs.map +1 -0
  68. package/dist/core/create.d.mts +63 -0
  69. package/dist/core/create.d.mts.map +1 -0
  70. package/dist/core/create.mjs +230 -0
  71. package/dist/core/create.mjs.map +1 -0
  72. package/dist/core/default-runtime.mjs +198 -0
  73. package/dist/core/default-runtime.mjs.map +1 -0
  74. package/dist/core/errors.d.mts +107 -0
  75. package/dist/core/errors.d.mts.map +1 -0
  76. package/dist/{errors-DA4KzK1M.mjs → core/errors.mjs} +2 -2
  77. package/dist/core/errors.mjs.map +1 -0
  78. package/dist/core/exec.mjs +191 -0
  79. package/dist/core/exec.mjs.map +1 -0
  80. package/dist/core/interceptors.d.mts +33 -0
  81. package/dist/core/interceptors.d.mts.map +1 -0
  82. package/dist/core/interceptors.mjs +253 -0
  83. package/dist/core/interceptors.mjs.map +1 -0
  84. package/dist/core/parse.mjs +181 -0
  85. package/dist/core/parse.mjs.map +1 -0
  86. package/dist/core/program-methods.mjs +256 -0
  87. package/dist/core/program-methods.mjs.map +1 -0
  88. package/dist/core/results.d.mts +13 -0
  89. package/dist/core/results.d.mts.map +1 -0
  90. package/dist/core/results.mjs +150 -0
  91. package/dist/core/results.mjs.map +1 -0
  92. package/dist/core/runtime.d.mts +213 -0
  93. package/dist/core/runtime.d.mts.map +1 -0
  94. package/dist/core/runtime.mjs +10 -0
  95. package/dist/core/runtime.mjs.map +1 -0
  96. package/dist/core/validate.mjs +220 -0
  97. package/dist/core/validate.mjs.map +1 -0
  98. package/dist/docs/index.d.mts +1 -1
  99. package/dist/docs/index.d.mts.map +1 -1
  100. package/dist/docs/index.mjs +2 -2
  101. package/dist/docs/index.mjs.map +1 -1
  102. package/dist/extension/auto-output.d.mts +53 -0
  103. package/dist/extension/auto-output.d.mts.map +1 -0
  104. package/dist/extension/auto-output.mjs +116 -0
  105. package/dist/extension/auto-output.mjs.map +1 -0
  106. package/dist/extension/color.d.mts +19 -0
  107. package/dist/extension/color.d.mts.map +1 -0
  108. package/dist/extension/color.mjs +37 -0
  109. package/dist/extension/color.mjs.map +1 -0
  110. package/dist/extension/completion.d.mts +25 -0
  111. package/dist/extension/completion.d.mts.map +1 -0
  112. package/dist/extension/completion.mjs +40 -0
  113. package/dist/extension/completion.mjs.map +1 -0
  114. package/dist/extension/config.d.mts +55 -0
  115. package/dist/extension/config.d.mts.map +1 -0
  116. package/dist/extension/config.mjs +162 -0
  117. package/dist/extension/config.mjs.map +1 -0
  118. package/dist/extension/env.d.mts +42 -0
  119. package/dist/extension/env.d.mts.map +1 -0
  120. package/dist/extension/env.mjs +44 -0
  121. package/dist/extension/env.mjs.map +1 -0
  122. package/dist/extension/help.d.mts +29 -0
  123. package/dist/extension/help.d.mts.map +1 -0
  124. package/dist/extension/help.mjs +161 -0
  125. package/dist/extension/help.mjs.map +1 -0
  126. package/dist/extension/index.d.mts +18 -0
  127. package/dist/extension/ink.d.mts +31 -0
  128. package/dist/extension/ink.d.mts.map +1 -0
  129. package/dist/extension/ink.mjs +69 -0
  130. package/dist/extension/ink.mjs.map +1 -0
  131. package/dist/extension/interactive.d.mts +16 -0
  132. package/dist/extension/interactive.d.mts.map +1 -0
  133. package/dist/extension/interactive.mjs +89 -0
  134. package/dist/extension/interactive.mjs.map +1 -0
  135. package/dist/extension/logger.d.mts +67 -0
  136. package/dist/extension/logger.d.mts.map +1 -0
  137. package/dist/extension/logger.mjs +188 -0
  138. package/dist/extension/logger.mjs.map +1 -0
  139. package/dist/extension/man.d.mts +25 -0
  140. package/dist/extension/man.d.mts.map +1 -0
  141. package/dist/extension/man.mjs +41 -0
  142. package/dist/extension/man.mjs.map +1 -0
  143. package/dist/extension/mcp.d.mts +28 -0
  144. package/dist/extension/mcp.d.mts.map +1 -0
  145. package/dist/extension/mcp.mjs +44 -0
  146. package/dist/extension/mcp.mjs.map +1 -0
  147. package/dist/extension/progress-renderer.d.mts +13 -0
  148. package/dist/extension/progress-renderer.d.mts.map +1 -0
  149. package/dist/extension/progress-renderer.mjs +351 -0
  150. package/dist/extension/progress-renderer.mjs.map +1 -0
  151. package/dist/extension/progress.d.mts +76 -0
  152. package/dist/extension/progress.d.mts.map +1 -0
  153. package/dist/extension/progress.mjs +225 -0
  154. package/dist/extension/progress.mjs.map +1 -0
  155. package/dist/extension/repl.d.mts +26 -0
  156. package/dist/extension/repl.d.mts.map +1 -0
  157. package/dist/extension/repl.mjs +67 -0
  158. package/dist/extension/repl.mjs.map +1 -0
  159. package/dist/extension/serve.d.mts +27 -0
  160. package/dist/extension/serve.d.mts.map +1 -0
  161. package/dist/extension/serve.mjs +41 -0
  162. package/dist/extension/serve.mjs.map +1 -0
  163. package/dist/extension/signal.d.mts +19 -0
  164. package/dist/extension/signal.d.mts.map +1 -0
  165. package/dist/extension/signal.mjs +81 -0
  166. package/dist/extension/signal.mjs.map +1 -0
  167. package/dist/extension/stdin.d.mts +19 -0
  168. package/dist/extension/stdin.d.mts.map +1 -0
  169. package/dist/extension/stdin.mjs +52 -0
  170. package/dist/extension/stdin.mjs.map +1 -0
  171. package/dist/extension/suggestions.d.mts +6 -0
  172. package/dist/extension/suggestions.d.mts.map +1 -0
  173. package/dist/extension/suggestions.mjs +113 -0
  174. package/dist/extension/suggestions.mjs.map +1 -0
  175. package/dist/extension/timing.d.mts +27 -0
  176. package/dist/extension/timing.d.mts.map +1 -0
  177. package/dist/extension/timing.mjs +71 -0
  178. package/dist/extension/timing.mjs.map +1 -0
  179. package/dist/extension/tracing.d.mts +85 -0
  180. package/dist/extension/tracing.d.mts.map +1 -0
  181. package/dist/extension/tracing.mjs +107 -0
  182. package/dist/extension/tracing.mjs.map +1 -0
  183. package/dist/extension/update-check.d.mts +20 -0
  184. package/dist/extension/update-check.d.mts.map +1 -0
  185. package/dist/extension/update-check.mjs +62 -0
  186. package/dist/extension/update-check.mjs.map +1 -0
  187. package/dist/extension/utils.mjs +43 -0
  188. package/dist/extension/utils.mjs.map +1 -0
  189. package/dist/extension/version.d.mts +20 -0
  190. package/dist/extension/version.d.mts.map +1 -0
  191. package/dist/extension/version.mjs +51 -0
  192. package/dist/extension/version.mjs.map +1 -0
  193. package/dist/{completion-UnBKfGuk.mjs → feature/completion.mjs} +4 -3
  194. package/dist/feature/completion.mjs.map +1 -0
  195. package/dist/feature/interactive.mjs +145 -0
  196. package/dist/feature/interactive.mjs.map +1 -0
  197. package/dist/feature/mcp.d.mts +18 -0
  198. package/dist/feature/mcp.d.mts.map +1 -0
  199. package/dist/{mcp-D6PdtjIs.mjs → feature/mcp.mjs} +6 -6
  200. package/dist/feature/mcp.mjs.map +1 -0
  201. package/dist/feature/repl-loop.mjs +217 -0
  202. package/dist/feature/repl-loop.mjs.map +1 -0
  203. package/dist/feature/serve.d.mts +18 -0
  204. package/dist/feature/serve.d.mts.map +1 -0
  205. package/dist/{serve-PaCLsNoD.mjs → feature/serve.mjs} +7 -7
  206. package/dist/feature/serve.mjs.map +1 -0
  207. package/dist/feature/test.d.mts +109 -0
  208. package/dist/feature/test.d.mts.map +1 -0
  209. package/dist/feature/test.mjs +116 -0
  210. package/dist/feature/test.mjs.map +1 -0
  211. package/dist/feature/update-check.d.mts +35 -0
  212. package/dist/feature/update-check.d.mts.map +1 -0
  213. package/dist/{update-check-CZ2VqjnV.mjs → feature/update-check.mjs} +2 -2
  214. package/dist/feature/update-check.mjs.map +1 -0
  215. package/dist/feature/wrap.d.mts +58 -0
  216. package/dist/feature/wrap.d.mts.map +1 -0
  217. package/dist/feature/wrap.mjs +77 -0
  218. package/dist/feature/wrap.mjs.map +1 -0
  219. package/dist/index.d.mts +38 -901
  220. package/dist/index.mjs +25 -4337
  221. package/dist/ink.d.mts +2 -0
  222. package/dist/ink.mjs +2 -0
  223. package/dist/man.d.mts +2 -0
  224. package/dist/man.mjs +2 -0
  225. package/dist/mcp.d.mts +3 -0
  226. package/dist/mcp.mjs +2 -0
  227. package/dist/output/colorizer.d.mts +28 -0
  228. package/dist/output/colorizer.d.mts.map +1 -0
  229. package/dist/output/colorizer.mjs +94 -0
  230. package/dist/output/colorizer.mjs.map +1 -0
  231. package/dist/{formatter-CY3KrOEd.d.mts → output/formatter.d.mts} +2 -28
  232. package/dist/output/formatter.d.mts.map +1 -0
  233. package/dist/output/formatter.mjs +360 -0
  234. package/dist/output/formatter.mjs.map +1 -0
  235. package/dist/output/help.d.mts +18 -0
  236. package/dist/output/help.d.mts.map +1 -0
  237. package/dist/output/help.mjs +267 -0
  238. package/dist/output/help.mjs.map +1 -0
  239. package/dist/output/output-indicator.d.mts +27 -0
  240. package/dist/output/output-indicator.d.mts.map +1 -0
  241. package/dist/output/output-indicator.mjs +54 -0
  242. package/dist/output/output-indicator.mjs.map +1 -0
  243. package/dist/output/primitives.d.mts +33 -0
  244. package/dist/output/primitives.d.mts.map +1 -0
  245. package/dist/output/primitives.mjs +206 -0
  246. package/dist/output/primitives.mjs.map +1 -0
  247. package/dist/output/styling.d.mts +38 -0
  248. package/dist/output/styling.d.mts.map +1 -0
  249. package/dist/output/styling.mjs +146 -0
  250. package/dist/output/styling.mjs.map +1 -0
  251. package/dist/schema/zod.d.mts +6 -0
  252. package/dist/schema/zod.d.mts.map +1 -0
  253. package/dist/schema/zod.mjs +50 -0
  254. package/dist/schema/zod.mjs.map +1 -0
  255. package/dist/serve.d.mts +2 -0
  256. package/dist/serve.mjs +2 -0
  257. package/dist/test.d.mts +2 -109
  258. package/dist/test.mjs +1 -115
  259. package/dist/tracing.d.mts +2 -0
  260. package/dist/tracing.mjs +2 -0
  261. package/dist/types/args-meta.d.mts +129 -0
  262. package/dist/types/args-meta.d.mts.map +1 -0
  263. package/dist/types/builder.d.mts +212 -0
  264. package/dist/types/builder.d.mts.map +1 -0
  265. package/dist/types/command.d.mts +112 -0
  266. package/dist/types/command.d.mts.map +1 -0
  267. package/dist/types/index.d.mts +8 -0
  268. package/dist/types/interceptor.d.mts +232 -0
  269. package/dist/types/interceptor.d.mts.map +1 -0
  270. package/dist/types/preferences.d.mts +79 -0
  271. package/dist/types/preferences.d.mts.map +1 -0
  272. package/dist/types/result.d.mts +61 -0
  273. package/dist/types/result.d.mts.map +1 -0
  274. package/dist/types/schema.d.mts +24 -0
  275. package/dist/types/schema.d.mts.map +1 -0
  276. package/dist/util/dotenv.mjs +174 -0
  277. package/dist/util/dotenv.mjs.map +1 -0
  278. package/dist/util/shell-utils.mjs +80 -0
  279. package/dist/util/shell-utils.mjs.map +1 -0
  280. package/dist/util/stream.d.mts +33 -0
  281. package/dist/util/stream.d.mts.map +1 -0
  282. package/dist/{stream-DC4H8YTx.mjs → util/stream.mjs} +2 -2
  283. package/dist/util/stream.mjs.map +1 -0
  284. package/dist/util/type-helpers.d.mts +74 -0
  285. package/dist/util/type-helpers.d.mts.map +1 -0
  286. package/dist/util/type-utils.d.mts +186 -0
  287. package/dist/util/type-utils.d.mts.map +1 -0
  288. package/dist/util/utils.mjs +43 -0
  289. package/dist/util/utils.mjs.map +1 -0
  290. package/dist/zod.d.mts +1 -32
  291. package/dist/zod.mjs +1 -49
  292. package/package.json +65 -17
  293. package/src/completion.ts +2 -0
  294. package/src/core/args.ts +63 -37
  295. package/src/core/runtime.ts +11 -2
  296. package/src/extension/completion.ts +3 -0
  297. package/src/extension/index.ts +0 -12
  298. package/src/extension/ink.ts +2 -1
  299. package/src/extension/man.ts +3 -0
  300. package/src/extension/mcp.ts +3 -0
  301. package/src/extension/progress-renderer.ts +32 -8
  302. package/src/extension/progress.ts +19 -9
  303. package/src/extension/serve.ts +3 -0
  304. package/src/extension/tracing.ts +2 -0
  305. package/src/index.ts +1 -20
  306. package/src/ink.ts +2 -0
  307. package/src/man.ts +2 -0
  308. package/src/mcp.ts +3 -0
  309. package/src/serve.ts +2 -0
  310. package/src/tracing.ts +9 -0
  311. package/src/types/builder.ts +2 -2
  312. package/src/zod.ts +1 -1
  313. package/dist/args-DrCXxXeP.mjs.map +0 -1
  314. package/dist/codegen/index.d.mts.map +0 -1
  315. package/dist/codegen/index.mjs.map +0 -1
  316. package/dist/commands-DLR0rFgq.mjs.map +0 -1
  317. package/dist/completion-UnBKfGuk.mjs.map +0 -1
  318. package/dist/errors-DA4KzK1M.mjs.map +0 -1
  319. package/dist/formatter-CY3KrOEd.d.mts.map +0 -1
  320. package/dist/help-B-ZMYyn-.mjs +0 -892
  321. package/dist/help-B-ZMYyn-.mjs.map +0 -1
  322. package/dist/index-D-Dpz7l_.d.mts +0 -1427
  323. package/dist/index-D-Dpz7l_.d.mts.map +0 -1
  324. package/dist/index.d.mts.map +0 -1
  325. package/dist/index.mjs.map +0 -1
  326. package/dist/mcp-D6PdtjIs.mjs.map +0 -1
  327. package/dist/serve-PaCLsNoD.mjs.map +0 -1
  328. package/dist/stream-DC4H8YTx.mjs.map +0 -1
  329. package/dist/test.d.mts.map +0 -1
  330. package/dist/test.mjs.map +0 -1
  331. package/dist/update-check-CZ2VqjnV.mjs.map +0 -1
  332. package/dist/zod.d.mts.map +0 -1
  333. package/dist/zod.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-loop.mjs","names":[],"sources":["../../src/feature/repl-loop.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { buildReplCompleter, findCommandByName, getCommandRuntime } from '../core/commands.ts';\nimport { createTerminalReplSession } from '../core/default-runtime.ts';\nimport { REPL_SIGINT, type ReplSessionConfig } from '../core/runtime.ts';\nimport type { AnyPadroneCommand, PadroneEvalPreferences, PadroneReplPreferences } from '../types/index.ts';\nimport { getVersion } from '../util/utils.ts';\n\nexport type ReplDeps = {\n existingCommand: AnyPadroneCommand;\n evalCommand: (input: string, prefs?: PadroneEvalPreferences) => any;\n replActiveRef: { value: boolean };\n};\n\n/**\n * Creates a REPL async iterable for running commands interactively.\n */\nexport function createReplIterator(deps: ReplDeps, options?: PadroneReplPreferences): AsyncIterable<any> & { drain: () => Promise<any> } {\n const { existingCommand, evalCommand, replActiveRef } = deps;\n\n if (replActiveRef.value) {\n const runtime = getCommandRuntime(existingCommand);\n runtime.error('REPL is already running. Nested REPL sessions are not supported.');\n return (async function* () {})() as any;\n }\n\n const runtime = getCommandRuntime(existingCommand);\n\n const programName = existingCommand.name || 'padrone';\n const env = runtime.env();\n const useAnsi = runtime.format === 'ansi' || (runtime.format === 'auto' && !env.NO_COLOR && !env.CI && runtime.terminal?.isTTY === true);\n\n // Track command history for .history built-in\n const commandHistory: string[] = [];\n\n // Resolve the initial scope command from options.scope (command path like 'db' or 'db migrate')\n const resolveScope = (scope: string): AnyPadroneCommand[] => {\n const parts = scope.split(/\\s+/);\n const stack: AnyPadroneCommand[] = [];\n let current = existingCommand;\n for (const part of parts) {\n const found = findCommandByName(part, current.commands);\n if (!found) break;\n stack.push(found);\n current = found;\n }\n return stack;\n };\n\n async function* replIterator() {\n replActiveRef.value = true;\n const showGreeting = options?.greeting !== false;\n const showHint = options?.hint !== false;\n\n // Empty line before greeting/hint block\n if (showGreeting || showHint) runtime.output('');\n\n // Greeting: default shows program title (or name) + version, like \"Welcome to My App v1.0.0\"\n if (showGreeting) {\n if (options?.greeting) {\n runtime.output(options.greeting);\n } else {\n const displayName = existingCommand.title || programName;\n const version = existingCommand.version ? await getVersion(existingCommand.version) : undefined;\n const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;\n runtime.output(greeting);\n }\n }\n\n // Hint: dimmed text below greeting\n if (showHint) {\n const hintText =\n (typeof options?.hint === 'string' ? options.hint : undefined) ?? 'Type \".help\" for more information, \".exit\" to quit.';\n runtime.output(useAnsi ? `\\x1b[2m${hintText}\\x1b[0m` : hintText);\n }\n\n // Empty line after greeting/hint block\n if (showGreeting || showHint) runtime.output('');\n\n // Scope stack for nested/contextual REPLs.\n // `cd <subcommand>` pushes, `cd ..`/`..` pops. The scope path is prepended to all eval input.\n const scopeStack: AnyPadroneCommand[] = options?.scope ? resolveScope(options.scope) : [];\n\n const getScopeCommand = () => (scopeStack.length ? scopeStack[scopeStack.length - 1]! : existingCommand);\n const getScopePath = () => scopeStack.map((c) => c.name).join(' ');\n\n const buildPrompt = () => {\n if (options?.prompt) return typeof options.prompt === 'function' ? options.prompt() : options.prompt;\n const scopePath = getScopePath();\n const label = scopePath ? `${programName}/${scopePath.replace(/ /g, '/')}` : programName;\n return useAnsi ? `\\x1b[1m${label}\\x1b[0m ❯ ` : `${label} ❯ `;\n };\n\n // Build completer scoped to the current command\n const buildScopedCompleter = () => {\n const scopeCmd = getScopeCommand();\n const inScope = scopeStack.length > 0;\n return buildReplCompleter(scopeCmd, { inScope });\n };\n\n // Build session config with completer\n const sessionConfig: ReplSessionConfig = { history: options?.history };\n if (options?.completion !== false) {\n sessionConfig.completer = buildScopedCompleter();\n }\n\n // If the runtime provides a custom readLine, use it (stateless, no history/completion).\n // Otherwise, create a persistent terminal session with history + tab completion.\n const session = runtime.readLine ? undefined : createTerminalReplSession(sessionConfig);\n const questionFn = session ? (prompt: string) => session.question(prompt) : runtime.readLine!;\n\n // Update the session's completer when scope changes\n const updateCompleter = () => {\n if (options?.completion === false) return;\n const completer = buildScopedCompleter();\n if (session) session.completer = completer;\n sessionConfig.completer = completer;\n };\n\n // Track last SIGINT time for double Ctrl+C to exit\n let lastSigintTime = 0;\n\n try {\n while (true) {\n const promptStr = buildPrompt();\n const input = await questionFn(promptStr);\n\n // EOF (Ctrl+D, closed connection)\n if (input === null) break;\n\n // Handle Ctrl+C (SIGINT sentinel from terminal session)\n if (input === REPL_SIGINT) {\n const now = Date.now();\n if (now - lastSigintTime < 2000) break; // Double Ctrl+C within 2s → exit\n lastSigintTime = now;\n runtime.output('(press Ctrl+C again to exit, or Ctrl+D)');\n continue;\n }\n\n const trimmed = input.trim();\n if (!trimmed) continue;\n\n // Reset SIGINT timer on any real input\n lastSigintTime = 0;\n\n // Track command history for .history\n commandHistory.push(trimmed);\n\n // Dot-prefixed built-in REPL commands\n if (trimmed === '.exit' || trimmed === '.quit') break;\n if (trimmed === '.clear') {\n runtime.output('\\x1B[2J\\x1B[H');\n continue;\n }\n if (trimmed === '.help') {\n const lines = [\n 'REPL Commands:',\n ' . Execute the current scoped command',\n ' .help Print this help message',\n ' .exit Exit the REPL',\n ' .clear Clear the screen',\n ' .history Show command history',\n ' .scope <cmd> Scope into a subcommand',\n ' .scope .. Go up one scope level',\n ];\n lines.push(\n '',\n 'Keybindings:',\n ' Ctrl+C Cancel current line (press twice to exit)',\n ' Ctrl+D Exit the REPL',\n ' Up/Down Navigate history',\n ' Tab Auto-complete',\n '',\n 'Type \"help\" to see available commands.',\n );\n runtime.output(lines.join('\\n'));\n continue;\n }\n if (trimmed === '.history') {\n // Show all previous entries (excluding the .history command itself)\n const entries = commandHistory.slice(0, -1);\n if (entries.length === 0) {\n runtime.output('No history.');\n } else {\n runtime.output(entries.map((entry, i) => `${i + 1} ${entry}`).join('\\n'));\n }\n continue;\n }\n\n // `.scope <subcommand>` — scope the REPL to a command subtree\n // `.scope ..` or `..` — go up one scope level\n if (trimmed.startsWith('.scope ') || trimmed === '.scope') {\n const target = trimmed.slice(6).trim();\n if (target === '..' || target === '') {\n if (scopeStack.length > 0) {\n scopeStack.pop();\n updateCompleter();\n }\n } else {\n const scopeCmd = getScopeCommand();\n const found = findCommandByName(target, scopeCmd.commands);\n if (found) {\n if (found.commands?.length) {\n scopeStack.push(found);\n updateCompleter();\n } else {\n runtime.error(`\"${target}\" has no subcommands to scope into.`);\n }\n } else {\n runtime.error(`Unknown command: ${target}`);\n }\n }\n continue;\n }\n\n // `..` shorthand for `.scope ..`\n if (trimmed === '..') {\n if (scopeStack.length > 0) {\n scopeStack.pop();\n updateCompleter();\n }\n continue;\n }\n\n // `.` (bare dot) — execute the current command (scoped or root)\n let evalInput = trimmed;\n if (trimmed === '.') {\n evalInput = '';\n }\n\n const prefix = options?.outputPrefix;\n const prefixLines = prefix\n ? (text: string) =>\n text\n .split('\\n')\n .map((l) => prefix + l)\n .join('\\n')\n : undefined;\n\n // Temporarily patch runtime on all commands so handler output gets prefixed.\n // Commands store parent refs from build time, so we patch each command directly.\n const savedRuntimes: { cmd: AnyPadroneCommand; runtime: typeof existingCommand.runtime }[] = [];\n if (prefixLines) {\n const prefixedRuntime = {\n ...existingCommand.runtime,\n output: (...args: unknown[]) => {\n const first = args[0];\n runtime.output(typeof first === 'string' ? prefixLines(first) : first, ...args.slice(1));\n },\n error: (text: string) => runtime.error(prefixLines(text)),\n };\n const patchAll = (cmd: AnyPadroneCommand) => {\n savedRuntimes.push({ cmd, runtime: cmd.runtime });\n cmd.runtime = prefixedRuntime;\n cmd.commands?.forEach(patchAll);\n };\n patchAll(existingCommand);\n }\n\n // Resolve before/after spacing from the shorthand or object form\n const sp = options?.spacing;\n const isSpacingObject = typeof sp === 'object' && sp !== null && !Array.isArray(sp);\n const spacingBefore = isSpacingObject ? sp.before : sp;\n const spacingAfter = isSpacingObject ? sp.after : sp;\n\n const emitSpacingLine = (value: boolean | string) => {\n if (typeof value === 'string') {\n const sep = value.length === 1 ? value.repeat(runtime.terminal?.columns ?? 80) : value;\n runtime.output(sep);\n } else if (value) {\n runtime.output('');\n }\n };\n const emitSpacing = (value: typeof spacingBefore) => {\n if (!value) return;\n if (Array.isArray(value)) {\n for (const line of value) emitSpacingLine(line);\n } else {\n emitSpacingLine(value);\n }\n };\n\n emitSpacing(spacingBefore);\n\n // Prepend scope path so evalCommand resolves relative to root\n const scopePath = getScopePath();\n const scopedInput = scopePath ? (evalInput ? `${scopePath} ${evalInput}` : scopePath) : evalInput;\n\n try {\n const replEvalPrefs: PadroneEvalPreferences | undefined = { caller: 'repl' };\n const result = await evalCommand(scopedInput, replEvalPrefs);\n if (result.error) {\n const msg = result.error instanceof Error ? result.error.message : String(result.error);\n runtime.error(prefixLines ? prefixLines(msg) : msg);\n } else if (result.argsResult?.issues) {\n const issueMessages = result.argsResult.issues\n .map((i: StandardSchemaV1.Issue) => ` - ${i.path?.join('.') || 'root'}: ${i.message}`)\n .join('\\n');\n const msg = `Validation error:\\n${issueMessages}`;\n runtime.error(prefixLines ? prefixLines(msg) : msg);\n }\n yield result as any;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n runtime.error(prefixLines ? prefixLines(msg) : msg);\n } finally {\n for (const { cmd, runtime: saved } of savedRuntimes) cmd.runtime = saved;\n emitSpacing(spacingAfter);\n }\n }\n } finally {\n replActiveRef.value = false;\n session?.close();\n }\n }\n\n const iterable = replIterator();\n (iterable as any).drain = async () => {\n try {\n const results: any[] = [];\n for await (const result of iterable) results.push(result);\n return { value: results };\n } catch (err) {\n return { error: err };\n }\n };\n return iterable as any;\n}\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ //#region src/feature/serve.d.ts
2
+ type PadroneServePreferences = {
3
+ /** Port to listen on. Default: 3000 */port?: number; /** Host to bind to. Default: '127.0.0.1' */
4
+ host?: string; /** Base path prefix for all routes. Default: '/' */
5
+ basePath?: string; /** CORS allowed origin. Default: '*'. Set to `false` to disable CORS headers. */
6
+ cors?: string | false; /** Control built-in utility endpoints. All enabled by default. */
7
+ builtins?: {
8
+ /** GET /_health — returns 200 OK. */health?: boolean; /** GET /_help and GET /_help/:command — returns help text. */
9
+ help?: boolean; /** GET /_schema and GET /_schema/:command — returns JSON Schema. */
10
+ schema?: boolean; /** GET /_docs — Scalar OpenAPI docs viewer. */
11
+ docs?: boolean;
12
+ }; /** Hook to run before each request. Return a Response to short-circuit. */
13
+ onRequest?: (req: Request) => Response | void | Promise<Response | void>; /** Transform errors into responses. */
14
+ onError?: (error: unknown, req: Request) => Response;
15
+ };
16
+ //#endregion
17
+ export { PadroneServePreferences };
18
+ //# sourceMappingURL=serve.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.mts","names":[],"sources":["../../src/feature/serve.ts"],"mappings":";KAMY,uBAAA;yCAEV,IAAA,WAFiC;EAIjC,IAAA,WAiBkB;EAflB,QAAA,WAewD;EAbxD,IAAA,mBAegC;EAbhC,QAAA;IAaoD,qCAXlD,MAAA,YAVF;IAYE,IAAA,YARF;IAUE,MAAA,YANF;IAQE,IAAA;EAAA,GAFA;EAKF,SAAA,IAAa,GAAA,EAAK,OAAA,KAAY,QAAA,UAAkB,OAAA,CAAQ,QAAA,UAAxD;EAEA,OAAA,IAAW,KAAA,WAAgB,GAAA,EAAK,OAAA,KAAY,QAAA;AAAA"}
@@ -1,7 +1,7 @@
1
- import { m as serializeArgsToFlags, r as collectEndpoints, t as buildInputSchema } from "./commands-DLR0rFgq.mjs";
2
- import { a as readStreamAsText } from "./stream-DC4H8YTx.mjs";
3
- import { t as generateHelp } from "./help-B-ZMYyn-.mjs";
4
- import { i as RoutingError, o as ValidationError } from "./errors-DA4KzK1M.mjs";
1
+ import { readStreamAsText } from "../util/stream.mjs";
2
+ import { buildInputSchema, collectEndpoints, serializeArgsToFlags } from "../core/commands.mjs";
3
+ import { RoutingError, ValidationError } from "../core/errors.mjs";
4
+ import { generateHelp } from "../output/help.mjs";
5
5
  //#region src/feature/serve.ts
6
6
  /** Convert an endpoint dot-path to a URL path segment. */
7
7
  function toUrlPath(name) {
@@ -372,7 +372,7 @@ async function startServeServer(_program, existingCommand, evalCommand, prefs) {
372
372
  const body = await response.text();
373
373
  res.end(body);
374
374
  });
375
- const { getCommandRuntime } = await import("./commands-DLR0rFgq.mjs").then((n) => n.a);
375
+ const { getCommandRuntime } = await import("../core/commands.mjs");
376
376
  const runtime = getCommandRuntime(existingCommand);
377
377
  return new Promise((resolve, reject) => {
378
378
  server.listen(port, host, () => {
@@ -397,6 +397,6 @@ async function readBody(req) {
397
397
  return readStreamAsText(req);
398
398
  }
399
399
  //#endregion
400
- export { startServeServer };
400
+ export { createServeHandler, startServeServer };
401
401
 
402
- //# sourceMappingURL=serve-PaCLsNoD.mjs.map
402
+ //# sourceMappingURL=serve.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.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":"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"}
@@ -0,0 +1,109 @@
1
+ import { PadroneRuntime } from "../core/runtime.mjs";
2
+ import { AnyPadroneCommand } from "../types/command.mjs";
3
+ //#region src/feature/test.d.ts
4
+ /**
5
+ * Result from a single command execution in test mode.
6
+ * Extends the standard PadroneCommandResult with captured I/O.
7
+ */
8
+ type TestCliResult = {
9
+ /** The matched command. */command: AnyPadroneCommand; /** Validated arguments (undefined if validation failed). */
10
+ args: unknown; /** Action handler return value (undefined if validation failed or no action). */
11
+ result: unknown; /** Validation issues, if any. */
12
+ issues: {
13
+ message: string;
14
+ path?: PropertyKey[];
15
+ }[] | undefined; /** All values passed to `runtime.output()`. */
16
+ stdout: unknown[]; /** All strings passed to `runtime.error()`. */
17
+ stderr: string[]; /** The thrown error, if the command threw (routing error, action error, etc.). */
18
+ error?: unknown;
19
+ };
20
+ /**
21
+ * Result from a REPL test session.
22
+ */
23
+ type TestReplResult = {
24
+ /** One entry per successfully executed command (validation errors are captured in stderr, not here). */results: Omit<TestCliResult, 'stdout' | 'stderr'>[]; /** All output from the entire REPL session. */
25
+ stdout: unknown[]; /** All errors from the entire REPL session. */
26
+ stderr: string[];
27
+ };
28
+ /**
29
+ * Fluent builder for setting up CLI test scenarios.
30
+ */
31
+ type TestCliBuilder = {
32
+ /** Set the CLI input string (e.g. `'deploy --env production'`). */args(input: string): TestCliBuilder; /** Set environment variables visible to the command. */
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 stdin data (simulates piped input). */
35
+ stdin(data: string): TestCliBuilder;
36
+ /**
37
+ * Execute a single command via `eval()` and return the result with captured I/O.
38
+ * @param input - Optional CLI input string. Overrides `.args()` if provided.
39
+ */
40
+ run(input?: string): Promise<TestCliResult>;
41
+ /**
42
+ * Run a REPL session with the given sequence of inputs.
43
+ * Each string in the array is fed as one line of input.
44
+ * The session ends after all inputs are consumed (EOF).
45
+ */
46
+ repl(inputs: string[]): Promise<TestReplResult>;
47
+ };
48
+ /**
49
+ * Creates a fluent test builder for a Padrone program.
50
+ * Captures all I/O and provides a clean interface for assertions.
51
+ *
52
+ * Works with any test framework (bun:test, vitest, jest, node:test, etc.).
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { testCli } from 'padrone/test'
57
+ *
58
+ * const result = await testCli(myProgram)
59
+ * .args('deploy --env production')
60
+ * .env({ API_KEY: 'xxx' })
61
+ * .run()
62
+ *
63
+ * expect(result.result).toBe('Deployed')
64
+ * expect(result.stdout).toContain('Deploying...')
65
+ * ```
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // Shorthand: pass input directly to run()
70
+ * const result = await testCli(myProgram).run('deploy --env production')
71
+ * ```
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * // Test interactive prompts
76
+ * const result = await testCli(myProgram)
77
+ * .args('init')
78
+ * .prompt({ name: 'myapp', template: 'react' })
79
+ * .run()
80
+ *
81
+ * expect(result.args).toEqual({ name: 'myapp', template: 'react' })
82
+ * ```
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * // Test REPL sessions
87
+ * const { results } = await testCli(myProgram)
88
+ * .repl(['greet World', 'add --a=2 --b=3'])
89
+ *
90
+ * expect(results[0].result).toBe('Hello, World!')
91
+ * expect(results[1].result).toBe(5)
92
+ * ```
93
+ */
94
+ /**
95
+ * Any program-like object that has `eval`, `runtime`, and `repl` methods.
96
+ * Avoids strict variance issues with `AnyPadroneProgram`.
97
+ */
98
+ type TestableProgram = {
99
+ eval: (input: string, prefs?: Record<string, unknown>) => any;
100
+ runtime: (runtime: PadroneRuntime) => TestableProgram;
101
+ repl: (options?: {
102
+ greeting?: false;
103
+ hint?: false;
104
+ }) => AsyncIterable<any>;
105
+ };
106
+ declare function testCli(program: TestableProgram): TestCliBuilder;
107
+ //#endregion
108
+ export { TestCliBuilder, TestCliResult, TestReplResult, testCli };
109
+ //# sourceMappingURL=test.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.mts","names":[],"sources":["../../src/feature/test.ts"],"mappings":";;;;;;AAOA;KAAY,aAAA;6BAEV,OAAA,EAAS,iBAAA,EAAT;EAEA,IAAA;EAEA,MAAA,WAEA;EAAA,MAAA;IAAU,OAAA;IAAiB,IAAA,GAAO,WAAW;EAAA,iBAI7C;EAFA,MAAA,aAIK;EAFL,MAAA,YAQU;EANV,KAAA;AAAA;;;;KAMU,cAAA;EAIV,wGAFA,OAAA,EAAS,IAAI,CAAC,aAAA,0BAIR;EAFN,MAAA,aAQU;EANV,MAAA;AAAA;;;;KAMU,cAAA;EAMgC,mEAJ1C,IAAA,CAAK,KAAA,WAAgB,cAAA,EAWQ;EAT7B,GAAA,CAAI,IAAA,EAAM,MAAA,+BAAqC,cAAA,EAef;EAbhC,MAAA,CAAO,OAAA,EAAS,MAAA,oBAA0B,cAAA,EAaX;EAX/B,KAAA,CAAM,IAAA,WAAe,cAAA;EANrB;;;;EAWA,GAAA,CAAI,KAAA,YAAiB,OAAA,CAAQ,aAAA;EATzB;;;;;EAeJ,IAAA,CAAK,MAAA,aAAmB,OAAA,CAAQ,cAAA;AAAA;;;;;;;;;;;;AAAc;AAC9C;;;;;;;;;;;;;;;;;;;;;;;AAuDqE;AAGvE;;;;;;;;AAAiE;;;;;;KAN5D,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,cAAc"}
@@ -0,0 +1,116 @@
1
+ //#region src/feature/test.ts
2
+ function testCli(program) {
3
+ let input;
4
+ let envVars;
5
+ let promptAnswers;
6
+ let stdinData;
7
+ const builder = {
8
+ args(args) {
9
+ input = args;
10
+ return builder;
11
+ },
12
+ env(vars) {
13
+ envVars = vars;
14
+ return builder;
15
+ },
16
+ prompt(answers) {
17
+ promptAnswers = answers;
18
+ return builder;
19
+ },
20
+ stdin(data) {
21
+ stdinData = data;
22
+ return builder;
23
+ },
24
+ async run(runInput) {
25
+ const stdout = [];
26
+ const stderr = [];
27
+ const runtime = buildRuntime(stdout, stderr, {
28
+ envVars,
29
+ promptAnswers,
30
+ stdinData
31
+ });
32
+ const evalResult = await program.runtime(runtime).eval(runInput ?? input ?? "", {});
33
+ if (evalResult.error) stderr.push(evalResult.error instanceof Error ? evalResult.error.message : String(evalResult.error));
34
+ return toTestResult(evalResult, stdout, stderr);
35
+ },
36
+ async repl(inputs) {
37
+ const stdout = [];
38
+ const stderr = [];
39
+ const runtime = buildRuntime(stdout, stderr, {
40
+ envVars,
41
+ promptAnswers,
42
+ readLine: createMockReadLine(inputs)
43
+ });
44
+ const testProgram = program.runtime(runtime);
45
+ const results = [];
46
+ for await (const r of testProgram.repl({
47
+ greeting: false,
48
+ hint: false
49
+ })) results.push({
50
+ command: r.command,
51
+ args: r.args,
52
+ result: r.result,
53
+ issues: r.argsResult?.issues
54
+ });
55
+ return {
56
+ results,
57
+ stdout,
58
+ stderr
59
+ };
60
+ }
61
+ };
62
+ return builder;
63
+ }
64
+ function toTestResult(evalResult, stdout, stderr) {
65
+ return {
66
+ command: evalResult.command,
67
+ args: evalResult.args,
68
+ result: evalResult.result,
69
+ error: evalResult.error,
70
+ issues: evalResult.argsResult?.issues,
71
+ stdout,
72
+ stderr
73
+ };
74
+ }
75
+ function buildRuntime(stdout, stderr, opts) {
76
+ const runtime = {
77
+ output: (...args) => stdout.push(...args),
78
+ error: (text) => stderr.push(text)
79
+ };
80
+ if (opts.envVars) runtime.env = () => opts.envVars;
81
+ if (opts.promptAnswers) {
82
+ runtime.interactive = "supported";
83
+ runtime.prompt = async (config) => opts.promptAnswers[config.name];
84
+ }
85
+ if (opts.readLine) runtime.readLine = opts.readLine;
86
+ if (opts.stdinData !== void 0) runtime.stdin = {
87
+ isTTY: false,
88
+ async text() {
89
+ return opts.stdinData;
90
+ },
91
+ async *lines() {
92
+ const lines = opts.stdinData.split("\n");
93
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
94
+ for (const line of lines) yield line;
95
+ }
96
+ };
97
+ else runtime.stdin = {
98
+ isTTY: true,
99
+ async text() {
100
+ return "";
101
+ },
102
+ async *lines() {}
103
+ };
104
+ return runtime;
105
+ }
106
+ function createMockReadLine(inputs) {
107
+ let index = 0;
108
+ return async (_prompt) => {
109
+ if (index >= inputs.length) return null;
110
+ return inputs[index++] ?? null;
111
+ };
112
+ }
113
+ //#endregion
114
+ export { testCli };
115
+
116
+ //# sourceMappingURL=test.mjs.map
@@ -0,0 +1 @@
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;GACjB,QAAQ;GACR,OAAO;EACT;EACA,IAAI,MAAM;GACR,UAAU;GACV,OAAO;EACT;EACA,OAAO,SAAS;GACd,gBAAgB;GAChB,OAAO;EACT;EACA,MAAM,MAAc;GAClB,YAAY;GACZ,OAAO;EACT;EAEA,MAAM,IAAI,UAAmB;GAC3B,MAAM,SAAoB,CAAC;GAC3B,MAAM,SAAmB,CAAC;GAE1B,MAAM,UAAU,aAAa,QAAQ,QAAQ;IAAE;IAAS;IAAe;GAAU,CAAC;GAGlF,MAAM,aAAa,MAFC,QAAQ,QAAQ,OAED,CAAC,CAAC,KAAK,YAAY,SAAS,IAAI,CAAC,CAAC;GACrE,IAAI,WAAW,OACb,OAAO,KAAK,WAAW,iBAAiB,QAAQ,WAAW,MAAM,UAAU,OAAO,WAAW,KAAK,CAAC;GAErG,OAAO,aAAa,YAAY,QAAQ,MAAM;EAChD;EAEA,MAAM,KAAK,QAAkB;GAC3B,MAAM,SAAoB,CAAC;GAC3B,MAAM,SAAmB,CAAC;GAE1B,MAAM,UAAU,aAAa,QAAQ,QAAQ;IAC3C;IACA;IACA,UAAU,mBAAmB,MAAM;GACrC,CAAC;GAED,MAAM,cAAc,QAAQ,QAAQ,OAAO;GAC3C,MAAM,UAAsD,CAAC;GAE7D,WAAW,MAAM,KAAK,YAAY,KAAK;IAAE,UAAU;IAAO,MAAM;GAAM,CAAC,GACrE,QAAQ,KAAK;IACX,SAAS,EAAE;IACX,MAAM,EAAE;IACR,QAAQ,EAAE;IACV,QAAQ,EAAE,YAAY;GACxB,CAAC;GAGH,OAAO;IAAE;IAAS;IAAQ;GAAO;EACnC;CACF;CAEA,OAAO;AACT;AAEA,SAAS,aAAa,YAAkC,QAAmB,QAAiC;CAC1G,OAAO;EACL,SAAS,WAAW;EACpB,MAAM,WAAW;EACjB,QAAQ,WAAW;EACnB,OAAO,WAAW;EAClB,QAAQ,WAAW,YAAY;EAC/B;EACA;CACF;AACF;AAEA,SAAS,aACP,QACA,QACA,MAMgB;CAChB,MAAM,UAA0B;EAC9B,SAAS,GAAG,SAAoB,OAAO,KAAK,GAAG,IAAI;EACnD,QAAQ,SAAiB,OAAO,KAAK,IAAI;CAC3C;CAEA,IAAI,KAAK,SACP,QAAQ,YAAY,KAAK;CAG3B,IAAI,KAAK,eAAe;EACtB,QAAQ,cAAc;EACtB,QAAQ,SAAS,OAAO,WAAoC,KAAK,cAAe,OAAO;CACzF;CAEA,IAAI,KAAK,UACP,QAAQ,WAAW,KAAK;CAG1B,IAAI,KAAK,cAAc,KAAA,GACrB,QAAQ,QAAQ;EACd,OAAO;EACP,MAAM,OAAO;GACX,OAAO,KAAK;EACd;EACA,OAAO,QAAQ;GACb,MAAM,QAAQ,KAAK,UAAW,MAAM,IAAI;GAExC,IAAI,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,OAAO,IAAI,MAAM,IAAI;GAClE,KAAK,MAAM,QAAQ,OACjB,MAAM;EAEV;CACF;MAGA,QAAQ,QAAQ;EACd,OAAO;EACP,MAAM,OAAO;GACX,OAAO;EACT;EACA,OAAO,QAAQ,CAEf;CACF;CAGF,OAAO;AACT;AAEA,SAAS,mBAAmB,QAA8D;CACxF,IAAI,QAAQ;CACZ,OAAO,OAAO,YAA4C;EACxD,IAAI,SAAS,OAAO,QAAQ,OAAO;EACnC,OAAO,OAAO,YAAY;CAC5B;AACF"}
@@ -0,0 +1,35 @@
1
+ //#region src/feature/update-check.d.ts
2
+ /**
3
+ * Configuration for the update check feature.
4
+ */
5
+ type UpdateCheckConfig = {
6
+ /**
7
+ * The npm package name to check. Defaults to the program name.
8
+ */
9
+ packageName?: string;
10
+ /**
11
+ * Registry to check for updates.
12
+ * - `'npm'` — checks the npm registry (default)
13
+ * - A URL string — custom registry endpoint that returns JSON with a `version` or `dist-tags.latest` field
14
+ */
15
+ registry?: 'npm' | string;
16
+ /**
17
+ * How often to check for updates. Accepts shorthand like `'1d'`, `'12h'`, `'30m'`.
18
+ * Defaults to `'1d'` (once per day).
19
+ */
20
+ interval?: string;
21
+ /**
22
+ * Path to the cache file for storing the last check timestamp and latest version.
23
+ * Defaults to `~/.config/<programName>-update-check.json`.
24
+ */
25
+ cache?: string;
26
+ /**
27
+ * Environment variable name to disable update checks (e.g. `'MYAPP_NO_UPDATE_CHECK'`).
28
+ * When set to a truthy value, update checks are skipped.
29
+ * Defaults to `'<PROGRAM_NAME>_NO_UPDATE_CHECK'` (uppercased, hyphens to underscores).
30
+ */
31
+ disableEnvVar?: string;
32
+ };
33
+ //#endregion
34
+ export { UpdateCheckConfig };
35
+ //# sourceMappingURL=update-check.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.d.mts","names":[],"sources":["../../src/feature/update-check.ts"],"mappings":";AAKA;;;AAAA,KAAY,iBAAA;EAIV;;;EAAA,WAAA;EAsBA;;AAAa;;;EAhBb,QAAA;;;;;EAKA,QAAA;;;;;EAKA,KAAA;;;;;;EAMA,aAAA;AAAA"}
@@ -140,6 +140,6 @@ async function createUpdateChecker(programName, currentVersion, config, runtime)
140
140
  }
141
141
  function noop() {}
142
142
  //#endregion
143
- export { createUpdateChecker };
143
+ export { createUpdateChecker, formatUpdateMessage, isNewerVersion, parseInterval };
144
144
 
145
- //# sourceMappingURL=update-check-CZ2VqjnV.mjs.map
145
+ //# sourceMappingURL=update-check.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.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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"}
@@ -0,0 +1,58 @@
1
+ import { PadroneSchema } from "../types/schema.mjs";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
+
4
+ //#region src/feature/wrap.d.ts
5
+ /**
6
+ * Configuration for wrapping an external CLI tool.
7
+ */
8
+ type WrapConfig<TCommandArgs extends PadroneSchema = PadroneSchema, TWrapArgs extends PadroneSchema = TCommandArgs> = {
9
+ /**
10
+ * The command to execute (e.g., 'git', 'docker', 'npm').
11
+ */
12
+ command: string;
13
+ /**
14
+ * Optional fixed arguments that always precede the arguments (e.g., ['commit'] for 'git commit').
15
+ */
16
+ args?: string[];
17
+ /**
18
+ * Positional argument configuration for the external command.
19
+ * If not provided, defaults to the wrapping command's positional configuration.
20
+ */
21
+ positional?: string[];
22
+ /**
23
+ * Whether to inherit stdio streams (stdin, stdout, stderr) from the parent process.
24
+ * Default: true
25
+ */
26
+ inheritStdio?: boolean;
27
+ /**
28
+ * Optional schema that transforms command arguments to external CLI arguments.
29
+ * The schema's input type should match the command arguments, and its output type defines
30
+ * the arguments expected by the external command.
31
+ * If not provided, command arguments are passed through as-is.
32
+ */
33
+ schema?: TWrapArgs | ((commandArguments: TCommandArgs) => TWrapArgs);
34
+ };
35
+ /**
36
+ * Result from executing a wrapped CLI tool.
37
+ */
38
+ type WrapResult = {
39
+ /**
40
+ * The exit code of the process.
41
+ */
42
+ exitCode: number;
43
+ /**
44
+ * Standard output from the process (only if inheritStdio is false).
45
+ */
46
+ stdout?: string;
47
+ /**
48
+ * Standard error from the process (only if inheritStdio is false).
49
+ */
50
+ stderr?: string;
51
+ /**
52
+ * Whether the process exited successfully (exit code 0).
53
+ */
54
+ success: boolean;
55
+ };
56
+ //#endregion
57
+ export { WrapConfig, WrapResult };
58
+ //# sourceMappingURL=wrap.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.d.mts","names":[],"sources":["../../src/feature/wrap.ts"],"mappings":";;;;;;;KAQY,UAAA,sBAAgC,aAAA,GAAgB,aAAA,oBAAiC,aAAA,GAAgB,YAAA;EAAvF;;;EAIpB,OAAA;EAJ2F;;;EAQ3F,IAAA;EAiB0D;;;;EAZ1D,UAAA;EAb0D;;;;EAkB1D,YAAA;EAVA;;;;;;EAiBA,MAAA,GAAS,SAAA,KAAc,gBAAA,EAAkB,YAAA,KAAiB,SAAA;AAAA;;AAAS;AAMrE;KAAY,UAAA;;;;EAIV,QAAA;EAQA;;;EAJA,MAAA;;;;EAIA,MAAA;;;;EAIA,OAAA;AAAA"}
@@ -0,0 +1,77 @@
1
+ import { concatBytes } from "../util/stream.mjs";
2
+ import { ValidationError } from "../core/errors.mjs";
3
+ //#region src/feature/wrap.ts
4
+ /**
5
+ * Converts parsed arguments to CLI arguments for an external command.
6
+ */
7
+ function argsToCliArgs(input, positional = []) {
8
+ const args = [];
9
+ if (!input) return args;
10
+ const positionalValues = {};
11
+ const regularArguments = {};
12
+ for (const [key, value] of Object.entries(input)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
13
+ else regularArguments[key] = value;
14
+ for (const [key, value] of Object.entries(regularArguments)) {
15
+ if (value === void 0 || value === null) continue;
16
+ const flag = `--${key}`;
17
+ if (typeof value === "boolean") {
18
+ if (value) args.push(flag);
19
+ } else if (Array.isArray(value)) for (const item of value) args.push(flag, String(item));
20
+ else args.push(flag, String(value));
21
+ }
22
+ for (const posKey of positional) {
23
+ const isVariadic = posKey.startsWith("...");
24
+ const value = positionalValues[isVariadic ? posKey.slice(3) : posKey];
25
+ if (value === void 0 || value === null) continue;
26
+ if (isVariadic && Array.isArray(value)) args.push(...value.map(String));
27
+ else args.push(String(value));
28
+ }
29
+ return args;
30
+ }
31
+ /**
32
+ * Creates an action handler that wraps an external CLI tool.
33
+ * @param config - Configuration for wrapping the external command (includes optional schema)
34
+ * @param commandArguments - The command's arguments schema
35
+ * @param commandPositional - Default positional config from the wrapping command
36
+ */
37
+ function createWrapHandler(config, commandArguments, commandPositional) {
38
+ return async (args) => {
39
+ const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
40
+ const validationResult = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandArguments) : wrapSchema : commandArguments)["~standard"].validate(args);
41
+ const processResult = (result) => {
42
+ if (result.issues) throw new ValidationError(`Wrap schema validation failed:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, result.issues);
43
+ return result.value;
44
+ };
45
+ const regularArgs = argsToCliArgs(validationResult instanceof Promise ? await validationResult.then(processResult) : processResult(validationResult), positional);
46
+ const allArgs = [...fixedArgs, ...regularArgs];
47
+ const { spawn } = await import("node:child_process");
48
+ return new Promise((resolve, reject) => {
49
+ const proc = spawn(command, allArgs, { stdio: inheritStdio ? "inherit" : [
50
+ "ignore",
51
+ "pipe",
52
+ "pipe"
53
+ ] });
54
+ const stdoutChunks = [];
55
+ const stderrChunks = [];
56
+ if (!inheritStdio) {
57
+ proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
58
+ proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
59
+ }
60
+ const decoder = new TextDecoder();
61
+ proc.on("error", reject);
62
+ proc.on("close", (code) => {
63
+ const exitCode = code ?? 1;
64
+ resolve({
65
+ exitCode,
66
+ stdout: inheritStdio ? void 0 : decoder.decode(concatBytes(stdoutChunks)),
67
+ stderr: inheritStdio ? void 0 : decoder.decode(concatBytes(stderrChunks)),
68
+ success: exitCode === 0
69
+ });
70
+ });
71
+ });
72
+ };
73
+ }
74
+ //#endregion
75
+ export { createWrapHandler };
76
+
77
+ //# sourceMappingURL=wrap.mjs.map