@nifrajs/cli 3.3.0 → 4.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 (425) hide show
  1. package/README.md +42 -3
  2. package/dist/app-files.d.ts +11 -0
  3. package/dist/app-files.d.ts.map +1 -0
  4. package/dist/app-files.js +34 -0
  5. package/dist/app-files.js.map +1 -0
  6. package/dist/assure-hydration.d.ts +7 -1
  7. package/dist/assure-hydration.d.ts.map +1 -1
  8. package/dist/assure-hydration.js +128 -43
  9. package/dist/assure-hydration.js.map +1 -1
  10. package/dist/assure.d.ts.map +1 -1
  11. package/dist/assure.js +16 -2
  12. package/dist/assure.js.map +1 -1
  13. package/dist/capabilities-tool.d.ts +5 -1
  14. package/dist/capabilities-tool.d.ts.map +1 -1
  15. package/dist/capabilities-tool.js +32 -10
  16. package/dist/capabilities-tool.js.map +1 -1
  17. package/dist/cdn-check.d.ts +41 -0
  18. package/dist/cdn-check.d.ts.map +1 -0
  19. package/dist/cdn-check.js +207 -0
  20. package/dist/cdn-check.js.map +1 -0
  21. package/dist/check-diagnostics.d.ts +7 -15
  22. package/dist/check-diagnostics.d.ts.map +1 -1
  23. package/dist/check-diagnostics.js +162 -713
  24. package/dist/check-diagnostics.js.map +1 -1
  25. package/dist/check-scan.d.ts +58 -7
  26. package/dist/check-scan.d.ts.map +1 -1
  27. package/dist/check-scan.js +323 -62
  28. package/dist/check-scan.js.map +1 -1
  29. package/dist/check.d.ts +1 -0
  30. package/dist/check.d.ts.map +1 -1
  31. package/dist/check.js +145 -54
  32. package/dist/check.js.map +1 -1
  33. package/dist/cli.d.ts +42 -16
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/cli.js +306 -87
  36. package/dist/cli.js.map +1 -1
  37. package/dist/command-catalog.d.ts +60 -5
  38. package/dist/command-catalog.d.ts.map +1 -1
  39. package/dist/command-catalog.js +298 -33
  40. package/dist/command-catalog.js.map +1 -1
  41. package/dist/config-target.d.ts +8 -0
  42. package/dist/config-target.d.ts.map +1 -0
  43. package/dist/config-target.js +45 -0
  44. package/dist/config-target.js.map +1 -0
  45. package/dist/contracts.d.ts +3 -2
  46. package/dist/contracts.d.ts.map +1 -1
  47. package/dist/contracts.js +56 -16
  48. package/dist/contracts.js.map +1 -1
  49. package/dist/db-audit.d.ts +37 -0
  50. package/dist/db-audit.d.ts.map +1 -0
  51. package/dist/db-audit.js +99 -0
  52. package/dist/db-audit.js.map +1 -0
  53. package/dist/db-child.d.ts +64 -0
  54. package/dist/db-child.d.ts.map +1 -0
  55. package/dist/db-child.js +213 -0
  56. package/dist/db-child.js.map +1 -0
  57. package/dist/db-config.d.ts +60 -0
  58. package/dist/db-config.d.ts.map +1 -0
  59. package/dist/db-config.js +176 -0
  60. package/dist/db-config.js.map +1 -0
  61. package/dist/db-tool.d.ts +84 -0
  62. package/dist/db-tool.d.ts.map +1 -0
  63. package/dist/db-tool.js +537 -0
  64. package/dist/db-tool.js.map +1 -0
  65. package/dist/dev-bun-config.d.ts +20 -13
  66. package/dist/dev-bun-config.d.ts.map +1 -1
  67. package/dist/dev-bun-config.js +98 -41
  68. package/dist/dev-bun-config.js.map +1 -1
  69. package/dist/dev-feed-tool.d.ts +106 -0
  70. package/dist/dev-feed-tool.d.ts.map +1 -0
  71. package/dist/dev-feed-tool.js +713 -0
  72. package/dist/dev-feed-tool.js.map +1 -0
  73. package/dist/dev-server-client.d.ts +67 -0
  74. package/dist/dev-server-client.d.ts.map +1 -0
  75. package/dist/dev-server-client.js +195 -0
  76. package/dist/dev-server-client.js.map +1 -0
  77. package/dist/diagnostics.d.ts +86 -0
  78. package/dist/diagnostics.d.ts.map +1 -1
  79. package/dist/diagnostics.js +85 -0
  80. package/dist/diagnostics.js.map +1 -1
  81. package/dist/diff-tool.d.ts +3 -3
  82. package/dist/diff-tool.d.ts.map +1 -1
  83. package/dist/diff-tool.js +6 -5
  84. package/dist/diff-tool.js.map +1 -1
  85. package/dist/docs-search.d.ts.map +1 -1
  86. package/dist/docs-search.js +12 -4
  87. package/dist/docs-search.js.map +1 -1
  88. package/dist/doctor.d.ts +17 -0
  89. package/dist/doctor.d.ts.map +1 -1
  90. package/dist/doctor.js +76 -15
  91. package/dist/doctor.js.map +1 -1
  92. package/dist/env-file.d.ts +15 -0
  93. package/dist/env-file.d.ts.map +1 -1
  94. package/dist/env-file.js +73 -1
  95. package/dist/env-file.js.map +1 -1
  96. package/dist/fix-recipes.d.ts.map +1 -1
  97. package/dist/fix-recipes.js +41 -21
  98. package/dist/fix-recipes.js.map +1 -1
  99. package/dist/frontend-guidance.js +5 -5
  100. package/dist/frontend-guidance.js.map +1 -1
  101. package/dist/i18n-check.d.ts +16 -0
  102. package/dist/i18n-check.d.ts.map +1 -0
  103. package/dist/i18n-check.js +154 -0
  104. package/dist/i18n-check.js.map +1 -0
  105. package/dist/init-agents.d.ts +49 -13
  106. package/dist/init-agents.d.ts.map +1 -1
  107. package/dist/init-agents.js +239 -34
  108. package/dist/init-agents.js.map +1 -1
  109. package/dist/internal/code-unit-order.d.ts +4 -0
  110. package/dist/internal/code-unit-order.d.ts.map +1 -0
  111. package/dist/internal/code-unit-order.js +6 -0
  112. package/dist/internal/code-unit-order.js.map +1 -0
  113. package/dist/internal/script-kind.d.ts +7 -0
  114. package/dist/internal/script-kind.d.ts.map +1 -0
  115. package/dist/internal/script-kind.js +12 -0
  116. package/dist/internal/script-kind.js.map +1 -0
  117. package/dist/internal/source-facts.d.ts +5 -0
  118. package/dist/internal/source-facts.d.ts.map +1 -1
  119. package/dist/internal/source-facts.js +61 -5
  120. package/dist/internal/source-facts.js.map +1 -1
  121. package/dist/internal/typescript-import.d.ts +49 -24
  122. package/dist/internal/typescript-import.d.ts.map +1 -1
  123. package/dist/internal/typescript-import.js +507 -67
  124. package/dist/internal/typescript-import.js.map +1 -1
  125. package/dist/introspect.d.ts +6 -4
  126. package/dist/introspect.d.ts.map +1 -1
  127. package/dist/introspect.js +93 -74
  128. package/dist/introspect.js.map +1 -1
  129. package/dist/learn.js +3 -3
  130. package/dist/learn.js.map +1 -1
  131. package/dist/levels-tool.d.ts +10 -0
  132. package/dist/levels-tool.d.ts.map +1 -1
  133. package/dist/levels-tool.js +33 -3
  134. package/dist/levels-tool.js.map +1 -1
  135. package/dist/load.d.ts +71 -16
  136. package/dist/load.d.ts.map +1 -1
  137. package/dist/load.js +78 -18
  138. package/dist/load.js.map +1 -1
  139. package/dist/manifest-tool.d.ts.map +1 -1
  140. package/dist/manifest-tool.js +3 -2
  141. package/dist/manifest-tool.js.map +1 -1
  142. package/dist/mcp-app-tool.d.ts.map +1 -1
  143. package/dist/mcp-app-tool.js +2 -0
  144. package/dist/mcp-app-tool.js.map +1 -1
  145. package/dist/mcp-context.d.ts.map +1 -1
  146. package/dist/mcp-context.js +5 -6
  147. package/dist/mcp-context.js.map +1 -1
  148. package/dist/mcp-delegate.d.ts +57 -0
  149. package/dist/mcp-delegate.d.ts.map +1 -0
  150. package/dist/mcp-delegate.js +130 -0
  151. package/dist/mcp-delegate.js.map +1 -0
  152. package/dist/mcp-docs-tools.d.ts.map +1 -1
  153. package/dist/mcp-docs-tools.js +23 -5
  154. package/dist/mcp-docs-tools.js.map +1 -1
  155. package/dist/mcp-exec.d.ts +3 -0
  156. package/dist/mcp-exec.d.ts.map +1 -1
  157. package/dist/mcp-exec.js +263 -131
  158. package/dist/mcp-exec.js.map +1 -1
  159. package/dist/mcp-http.d.ts.map +1 -1
  160. package/dist/mcp-http.js +20 -5
  161. package/dist/mcp-http.js.map +1 -1
  162. package/dist/mcp-io.d.ts +26 -0
  163. package/dist/mcp-io.d.ts.map +1 -1
  164. package/dist/mcp-io.js +113 -0
  165. package/dist/mcp-io.js.map +1 -1
  166. package/dist/mcp-isolate.d.ts +48 -0
  167. package/dist/mcp-isolate.d.ts.map +1 -0
  168. package/dist/mcp-isolate.js +222 -0
  169. package/dist/mcp-isolate.js.map +1 -0
  170. package/dist/mcp-path.d.ts +10 -0
  171. package/dist/mcp-path.d.ts.map +1 -0
  172. package/dist/mcp-path.js +51 -0
  173. package/dist/mcp-path.js.map +1 -0
  174. package/dist/mcp-project-child.d.ts +74 -0
  175. package/dist/mcp-project-child.d.ts.map +1 -0
  176. package/dist/mcp-project-child.js +130 -0
  177. package/dist/mcp-project-child.js.map +1 -0
  178. package/dist/mcp-reflect.d.ts.map +1 -1
  179. package/dist/mcp-reflect.js +2 -2
  180. package/dist/mcp-reflect.js.map +1 -1
  181. package/dist/mcp-render.d.ts.map +1 -1
  182. package/dist/mcp-render.js +64 -45
  183. package/dist/mcp-render.js.map +1 -1
  184. package/dist/mcp-root.d.ts +37 -9
  185. package/dist/mcp-root.d.ts.map +1 -1
  186. package/dist/mcp-root.js +124 -25
  187. package/dist/mcp-root.js.map +1 -1
  188. package/dist/mcp-run.d.ts +18 -1
  189. package/dist/mcp-run.d.ts.map +1 -1
  190. package/dist/mcp-run.js +159 -55
  191. package/dist/mcp-run.js.map +1 -1
  192. package/dist/mcp-ws.js +4 -1
  193. package/dist/mcp-ws.js.map +1 -1
  194. package/dist/mcp.d.ts +18 -5
  195. package/dist/mcp.d.ts.map +1 -1
  196. package/dist/mcp.js +165 -82
  197. package/dist/mcp.js.map +1 -1
  198. package/dist/migrate-layout.d.ts +44 -0
  199. package/dist/migrate-layout.d.ts.map +1 -0
  200. package/dist/migrate-layout.js +982 -0
  201. package/dist/migrate-layout.js.map +1 -0
  202. package/dist/openapi-tool.d.ts +2 -2
  203. package/dist/openapi-tool.d.ts.map +1 -1
  204. package/dist/openapi-tool.js.map +1 -1
  205. package/dist/openapi-types.d.ts +2 -1
  206. package/dist/openapi-types.d.ts.map +1 -1
  207. package/dist/openapi-types.js +240 -12
  208. package/dist/openapi-types.js.map +1 -1
  209. package/dist/pipeline-report.d.ts +3 -2
  210. package/dist/pipeline-report.d.ts.map +1 -1
  211. package/dist/pipeline-report.js +19 -16
  212. package/dist/pipeline-report.js.map +1 -1
  213. package/dist/port.d.ts +5 -4
  214. package/dist/port.d.ts.map +1 -1
  215. package/dist/port.js +46 -29
  216. package/dist/port.js.map +1 -1
  217. package/dist/project-facts.d.ts +28 -4
  218. package/dist/project-facts.d.ts.map +1 -1
  219. package/dist/project-facts.js +12 -2
  220. package/dist/project-facts.js.map +1 -1
  221. package/dist/recipes/4.0.0.d.ts +9 -0
  222. package/dist/recipes/4.0.0.d.ts.map +1 -0
  223. package/dist/recipes/4.0.0.js +34 -0
  224. package/dist/recipes/4.0.0.js.map +1 -0
  225. package/dist/recipes/index.d.ts.map +1 -1
  226. package/dist/recipes/index.js +2 -0
  227. package/dist/recipes/index.js.map +1 -1
  228. package/dist/release-verification.d.ts +11 -1
  229. package/dist/release-verification.d.ts.map +1 -1
  230. package/dist/release-verification.js +47 -4
  231. package/dist/release-verification.js.map +1 -1
  232. package/dist/review.d.ts +21 -0
  233. package/dist/review.d.ts.map +1 -0
  234. package/dist/review.js +823 -0
  235. package/dist/review.js.map +1 -0
  236. package/dist/route-types.d.ts +28 -0
  237. package/dist/route-types.d.ts.map +1 -0
  238. package/dist/route-types.js +106 -0
  239. package/dist/route-types.js.map +1 -0
  240. package/dist/rules/codes.d.ts +9 -0
  241. package/dist/rules/codes.d.ts.map +1 -1
  242. package/dist/rules/codes.js +9 -0
  243. package/dist/rules/codes.js.map +1 -1
  244. package/dist/rules/data-guard.d.ts +12 -0
  245. package/dist/rules/data-guard.d.ts.map +1 -0
  246. package/dist/rules/data-guard.js +213 -0
  247. package/dist/rules/data-guard.js.map +1 -0
  248. package/dist/rules/index.d.ts +3 -0
  249. package/dist/rules/index.d.ts.map +1 -1
  250. package/dist/rules/index.js +17 -5
  251. package/dist/rules/index.js.map +1 -1
  252. package/dist/rules/islands.d.ts.map +1 -1
  253. package/dist/rules/islands.js +7 -4
  254. package/dist/rules/islands.js.map +1 -1
  255. package/dist/rules/legacy.d.ts +3 -1
  256. package/dist/rules/legacy.d.ts.map +1 -1
  257. package/dist/rules/legacy.js +829 -34
  258. package/dist/rules/legacy.js.map +1 -1
  259. package/dist/rules/nano.d.ts.map +1 -1
  260. package/dist/rules/nano.js +15 -6
  261. package/dist/rules/nano.js.map +1 -1
  262. package/dist/rules/pages.d.ts +15 -0
  263. package/dist/rules/pages.d.ts.map +1 -0
  264. package/dist/rules/pages.js +88 -0
  265. package/dist/rules/pages.js.map +1 -0
  266. package/dist/rules/routes.d.ts +11 -0
  267. package/dist/rules/routes.d.ts.map +1 -1
  268. package/dist/rules/routes.js +180 -2
  269. package/dist/rules/routes.js.map +1 -1
  270. package/dist/rules/secrets.d.ts +10 -0
  271. package/dist/rules/secrets.d.ts.map +1 -0
  272. package/dist/rules/secrets.js +169 -0
  273. package/dist/rules/secrets.js.map +1 -0
  274. package/dist/rules/security.d.ts +7 -0
  275. package/dist/rules/security.d.ts.map +1 -1
  276. package/dist/rules/security.js +171 -65
  277. package/dist/rules/security.js.map +1 -1
  278. package/dist/rules/typescript.d.ts +16 -0
  279. package/dist/rules/typescript.d.ts.map +1 -0
  280. package/dist/rules/typescript.js +59 -0
  281. package/dist/rules/typescript.js.map +1 -0
  282. package/dist/rules/zones.d.ts +17 -0
  283. package/dist/rules/zones.d.ts.map +1 -0
  284. package/dist/rules/zones.js +174 -0
  285. package/dist/rules/zones.js.map +1 -0
  286. package/dist/scaffold.d.ts +25 -12
  287. package/dist/scaffold.d.ts.map +1 -1
  288. package/dist/scaffold.js +154 -90
  289. package/dist/scaffold.js.map +1 -1
  290. package/dist/sdk.d.ts +17 -3
  291. package/dist/sdk.d.ts.map +1 -1
  292. package/dist/sdk.js +941 -153
  293. package/dist/sdk.js.map +1 -1
  294. package/dist/search-terms.d.ts +2 -0
  295. package/dist/search-terms.d.ts.map +1 -1
  296. package/dist/search-terms.js +23 -8
  297. package/dist/search-terms.js.map +1 -1
  298. package/dist/smoke.d.ts +79 -0
  299. package/dist/smoke.d.ts.map +1 -0
  300. package/dist/smoke.js +494 -0
  301. package/dist/smoke.js.map +1 -0
  302. package/dist/sync-manifest.d.ts.map +1 -1
  303. package/dist/sync-manifest.js +2 -1
  304. package/dist/sync-manifest.js.map +1 -1
  305. package/dist/sync-routes.d.ts +4 -3
  306. package/dist/sync-routes.d.ts.map +1 -1
  307. package/dist/sync-routes.js +4 -3
  308. package/dist/sync-routes.js.map +1 -1
  309. package/dist/test-tool.d.ts.map +1 -1
  310. package/dist/test-tool.js +30 -11
  311. package/dist/test-tool.js.map +1 -1
  312. package/dist/types-search.d.ts.map +1 -1
  313. package/dist/types-search.js +11 -5
  314. package/dist/types-search.js.map +1 -1
  315. package/dist/upgrade.d.ts +36 -6
  316. package/dist/upgrade.d.ts.map +1 -1
  317. package/dist/upgrade.js +167 -29
  318. package/dist/upgrade.js.map +1 -1
  319. package/dist/verification-plan.d.ts.map +1 -1
  320. package/dist/verification-plan.js +8 -0
  321. package/dist/verification-plan.js.map +1 -1
  322. package/dist/verification.d.ts.map +1 -1
  323. package/dist/verification.js +45 -12
  324. package/dist/verification.js.map +1 -1
  325. package/dist/web-app-options.d.ts +19 -0
  326. package/dist/web-app-options.d.ts.map +1 -0
  327. package/dist/web-app-options.js +49 -0
  328. package/dist/web-app-options.js.map +1 -0
  329. package/dist/work-graph.d.ts +3 -0
  330. package/dist/work-graph.d.ts.map +1 -1
  331. package/dist/work-graph.js +33 -16
  332. package/dist/work-graph.js.map +1 -1
  333. package/docs/examples.json +185 -29
  334. package/docs/llms-full.txt +4123 -548
  335. package/docs/types.json +14391 -10060
  336. package/package.json +19 -11
  337. package/src/app-files.ts +40 -0
  338. package/src/assure-hydration.ts +139 -46
  339. package/src/assure.ts +17 -2
  340. package/src/capabilities-tool.ts +35 -9
  341. package/src/cdn-check.ts +268 -0
  342. package/src/check-diagnostics.ts +199 -797
  343. package/src/check-scan.ts +394 -63
  344. package/src/check.ts +146 -58
  345. package/src/cli.ts +347 -92
  346. package/src/command-catalog.ts +407 -45
  347. package/src/config-target.ts +47 -0
  348. package/src/contracts.ts +66 -15
  349. package/src/db-audit.ts +128 -0
  350. package/src/db-child.ts +285 -0
  351. package/src/db-config.ts +224 -0
  352. package/src/db-tool.ts +690 -0
  353. package/src/dev-bun-config.ts +116 -40
  354. package/src/dev-feed-tool.ts +912 -0
  355. package/src/dev-server-client.ts +267 -0
  356. package/src/diagnostics.ts +179 -0
  357. package/src/diff-tool.ts +6 -5
  358. package/src/docs-search.ts +10 -3
  359. package/src/doctor.ts +98 -15
  360. package/src/env-file.ts +78 -1
  361. package/src/fix-recipes.ts +44 -31
  362. package/src/frontend-guidance.ts +5 -5
  363. package/src/i18n-check.ts +179 -0
  364. package/src/init-agents.ts +301 -38
  365. package/src/internal/code-unit-order.ts +5 -0
  366. package/src/internal/script-kind.ts +11 -0
  367. package/src/internal/source-facts.ts +96 -22
  368. package/src/internal/typescript-import.ts +663 -68
  369. package/src/introspect.ts +93 -82
  370. package/src/learn.ts +3 -3
  371. package/src/levels-tool.ts +47 -2
  372. package/src/load.ts +144 -32
  373. package/src/manifest-tool.ts +3 -2
  374. package/src/mcp-app-tool.ts +2 -0
  375. package/src/mcp-context.ts +5 -7
  376. package/src/mcp-delegate.ts +162 -0
  377. package/src/mcp-docs-tools.ts +23 -5
  378. package/src/mcp-exec.ts +301 -161
  379. package/src/mcp-http.ts +22 -5
  380. package/src/mcp-io.ts +130 -0
  381. package/src/mcp-isolate.ts +287 -0
  382. package/src/mcp-path.ts +50 -0
  383. package/src/mcp-project-child.ts +182 -0
  384. package/src/mcp-reflect.ts +3 -2
  385. package/src/mcp-render.ts +72 -42
  386. package/src/mcp-root.ts +130 -24
  387. package/src/mcp-run.ts +190 -56
  388. package/src/mcp-ws.ts +4 -1
  389. package/src/mcp.ts +193 -91
  390. package/src/migrate-layout.ts +1121 -0
  391. package/src/openapi-tool.ts +7 -3
  392. package/src/openapi-types.ts +315 -13
  393. package/src/pipeline-report.ts +22 -18
  394. package/src/port.ts +53 -30
  395. package/src/project-facts.ts +42 -8
  396. package/src/recipes/4.0.0.ts +35 -0
  397. package/src/recipes/index.ts +2 -0
  398. package/src/release-verification.ts +74 -5
  399. package/src/review.ts +988 -0
  400. package/src/route-types.ts +133 -0
  401. package/src/rules/codes.ts +9 -0
  402. package/src/rules/data-guard.ts +230 -0
  403. package/src/rules/index.ts +18 -5
  404. package/src/rules/islands.ts +12 -4
  405. package/src/rules/legacy.ts +947 -35
  406. package/src/rules/nano.ts +18 -6
  407. package/src/rules/pages.ts +99 -0
  408. package/src/rules/routes.ts +181 -2
  409. package/src/rules/secrets.ts +174 -0
  410. package/src/rules/security.ts +198 -70
  411. package/src/rules/typescript.ts +77 -0
  412. package/src/rules/zones.ts +195 -0
  413. package/src/scaffold.ts +175 -98
  414. package/src/sdk.ts +1215 -158
  415. package/src/search-terms.ts +24 -7
  416. package/src/smoke.ts +607 -0
  417. package/src/sync-manifest.ts +2 -1
  418. package/src/sync-routes.ts +4 -3
  419. package/src/test-tool.ts +32 -11
  420. package/src/types-search.ts +11 -4
  421. package/src/upgrade.ts +205 -25
  422. package/src/verification-plan.ts +27 -0
  423. package/src/verification.ts +50 -12
  424. package/src/web-app-options.ts +57 -0
  425. package/src/work-graph.ts +40 -16
package/src/mcp-run.ts CHANGED
@@ -8,9 +8,25 @@
8
8
  import { existsSync, realpathSync } from "node:fs"
9
9
  import { isAbsolute, relative, resolve, sep } from "node:path"
10
10
  import { pathToFileURL } from "node:url"
11
- import { type AppLike, runApp } from "@nifrajs/runner"
11
+ import { type AppLike, type RequestSpec, type RunResult, runApp } from "@nifrajs/runner"
12
+ import {
13
+ captureInto,
14
+ createDevFeed,
15
+ type DevFeed,
16
+ type DevLogLevel,
17
+ runWithDevRequest,
18
+ } from "@nifrajs/web/dev-feed"
19
+ import type { Diagnostic } from "@nifrajs/web/diagnostic"
20
+ import { BACKEND_APP_FILE } from "./app-files.ts"
21
+ import {
22
+ CHILD_INPUT_MAX_BYTES,
23
+ CHILD_OUTPUT_MAX_BYTES,
24
+ readBoundedLines,
25
+ readBoundedStream,
26
+ serializeBoundedJson,
27
+ } from "./mcp-io.ts"
12
28
 
13
- const ENTRY_CANDIDATES = ["backend.ts", "app.ts", "src/backend.ts", "src/app.ts"]
29
+ const ENTRY_CANDIDATES = [BACKEND_APP_FILE, "app.ts", "src/app.ts"]
14
30
 
15
31
  const errString = (err: unknown): string =>
16
32
  err instanceof Error ? `${err.name}: ${err.message}` : String(err)
@@ -78,12 +94,75 @@ export async function runBackend(
78
94
  if ("error" in loaded) return loaded
79
95
 
80
96
  try {
81
- return { results: await runApp(loaded.app, requests as Parameters<typeof runApp>[1]) }
97
+ return { results: await runCaptured(cwd, loaded.app, requests) }
82
98
  } catch (err) {
83
99
  return { error: errString(err) }
84
100
  }
85
101
  }
86
102
 
103
+ /** What `nifra_run` adds to the runner's result: what the app printed, and what failed, structured. */
104
+ export interface RunCapture {
105
+ readonly logs?: ReadonlyArray<{ readonly level: DevLogLevel; readonly message: string }>
106
+ readonly errors?: readonly Diagnostic[]
107
+ }
108
+
109
+ const MAX_LOGS_PER_REQUEST = 100
110
+
111
+ /**
112
+ * Run `requests` one at a time, each inside its own request context, so what a handler logs - and
113
+ * core's `unhandled request error` line behind a bare 500 - lands on the result of the request that
114
+ * produced it, redacted, with the error as a Diagnostic (codeframe, cause, fix). Without this a 500 came
115
+ * back as `internal_error` and the reason only ever reached a stderr nobody reads.
116
+ */
117
+ export async function runCaptured(
118
+ cwd: string,
119
+ app: AppLike,
120
+ requests: readonly unknown[],
121
+ ): Promise<Array<RunResult & RunCapture>> {
122
+ const feed = createDevFeed({ root: cwd, persist: false })
123
+ const detach = captureInto(feed)
124
+ const runs: Array<{ readonly requestId: string; readonly result: RunResult }> = []
125
+ try {
126
+ for (const [index, spec] of requests.entries()) {
127
+ const requestId = `run${index + 1}`
128
+ const [method, path] = describeSpec(spec)
129
+ const [result] = await runWithDevRequest(
130
+ { feedId: feed.id, requestId, method, path },
131
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: runApp validates each spec itself and reports a malformed one as that request's error.
132
+ () => runApp(app, [spec as RequestSpec]),
133
+ )
134
+ if (result !== undefined) runs.push({ requestId, result })
135
+ }
136
+ // A line a handler scheduled for later (a promise continuation, a 0 ms timer) still lands.
137
+ await new Promise((resolve) => setTimeout(resolve, 0))
138
+ } finally {
139
+ detach()
140
+ }
141
+ return runs.map(({ requestId, result }) => withCapture(feed, requestId, result))
142
+ }
143
+
144
+ function describeSpec(spec: unknown): [string, string] {
145
+ if (typeof spec !== "object" || spec === null) return ["GET", "/"]
146
+ const method = "method" in spec && typeof spec.method === "string" ? spec.method : "GET"
147
+ const path = "path" in spec && typeof spec.path === "string" ? spec.path : "/"
148
+ return [method, path]
149
+ }
150
+
151
+ function withCapture(feed: DevFeed, requestId: string, result: RunResult): RunResult & RunCapture {
152
+ if (result.error !== undefined) {
153
+ const thrown = new Error(result.error.message)
154
+ thrown.name = result.error.name
155
+ if (result.error.stack !== undefined) thrown.stack = result.error.stack
156
+ feed.recordError(thrown, { category: "api", requestId })
157
+ }
158
+ const capture: { -readonly [K in keyof RunCapture]: RunCapture[K] } = {}
159
+ const logs = feed.logs({ requestId, limit: MAX_LOGS_PER_REQUEST }).logs
160
+ if (logs.length > 0) capture.logs = logs.map(({ level, message }) => ({ level, message }))
161
+ const errors = feed.errors({ requestId }).errors
162
+ if (errors.length > 0) capture.errors = errors.map((entry) => entry.diagnostic)
163
+ return { ...result, ...capture }
164
+ }
165
+
87
166
  interface WorkerMessage {
88
167
  readonly id?: unknown
89
168
  readonly input?: { readonly requests?: unknown; readonly entry?: string }
@@ -100,54 +179,95 @@ function redirectConsoleToStderr(): void {
100
179
  console.error = (...args: unknown[]) => write("error", args)
101
180
  }
102
181
 
103
- async function runWorker(cwd: string): Promise<void> {
182
+ function safeResponseId(value: unknown): number | string | null {
183
+ if (typeof value === "number" && Number.isSafeInteger(value)) return value
184
+ if (typeof value === "string" && value.length <= 128) return value
185
+ return null
186
+ }
187
+
188
+ /**
189
+ * Keep stdout for the protocol. The app's console and any direct `process.stdout.write` go to stderr,
190
+ * or a stray `console.log` in a handler would corrupt the JSON the parent reads. Returns the writer
191
+ * that still reaches the real stdout.
192
+ */
193
+ function reserveStdout(): (text: string) => void {
104
194
  redirectConsoleToStderr()
195
+ const original: unknown = Reflect.get(process.stdout, "write")
196
+ const write = typeof original === "function" ? original.bind(process.stdout) : undefined
197
+ // Bound now, before any capture patches stderr: a redirected stdout line is captured once, as stdout.
198
+ const toStderr = process.stderr.write.bind(process.stderr)
199
+ Reflect.set(process.stdout, "write", (chunk: unknown, ...rest: unknown[]) =>
200
+ Reflect.apply(toStderr, process.stderr, [chunk, ...rest]),
201
+ )
202
+ return (text) => {
203
+ write?.(text)
204
+ }
205
+ }
206
+
207
+ async function runWorker(cwd: string, protocol: (text: string) => void): Promise<void> {
105
208
  const apps = new Map<string, Promise<{ app: AppLike } | { error: string }>>()
106
- const decoder = new TextDecoder()
107
- let buffer = ""
108
209
  const send = (id: unknown, output: unknown): void => {
109
- process.stdout.write(`${JSON.stringify({ id, output })}\n`)
210
+ const responseId = safeResponseId(id)
211
+ const encoded = serializeBoundedJson({ id: responseId, output }, CHILD_OUTPUT_MAX_BYTES - 1)
212
+ protocol(
213
+ `${encoded ?? JSON.stringify({ id: responseId, output: { error: "worker output exceeded the size limit" } })}\n`,
214
+ )
110
215
  }
111
- for await (const chunk of Bun.stdin.stream()) {
112
- buffer += decoder.decode(chunk as Uint8Array, { stream: true })
113
- let nl = buffer.indexOf("\n")
114
- while (nl !== -1) {
115
- const line = buffer.slice(0, nl).trim()
116
- buffer = buffer.slice(nl + 1)
117
- nl = buffer.indexOf("\n")
118
- if (line === "") continue
119
-
120
- let message: WorkerMessage
121
- try {
122
- message = JSON.parse(line) as WorkerMessage
123
- } catch {
124
- send(null, { error: "invalid worker input: expected JSON line" })
125
- continue
126
- }
127
- const requests = message.input?.requests
128
- const entry = message.input?.entry
129
- if (!Array.isArray(requests)) {
130
- send(message.id, { error: "expected { requests: [...] }" })
131
- continue
132
- }
133
- const key = entry ?? ""
134
- let loaded = apps.get(key)
135
- if (loaded === undefined) {
136
- loaded = loadBackend(cwd, entry)
137
- apps.set(key, loaded)
138
- }
139
- const app = await loaded
140
- if ("error" in app) {
141
- send(message.id, app)
142
- continue
143
- }
144
- try {
145
- send(message.id, {
146
- results: await runApp(app.app, requests as Parameters<typeof runApp>[1]),
147
- })
148
- } catch (err) {
149
- send(message.id, { error: errString(err) })
150
- }
216
+ for await (const item of readBoundedLines(Bun.stdin.stream(), CHILD_INPUT_MAX_BYTES)) {
217
+ if (item.kind === "too-large") {
218
+ send(null, { error: `worker input exceeded ${CHILD_INPUT_MAX_BYTES} bytes` })
219
+ continue
220
+ }
221
+ const line = item.text.trim()
222
+ if (line === "") continue
223
+
224
+ let message: unknown
225
+ try {
226
+ message = JSON.parse(line) as unknown
227
+ } catch {
228
+ send(null, { error: "invalid worker input: expected JSON line" })
229
+ continue
230
+ }
231
+ const record =
232
+ message !== null && typeof message === "object" && !Array.isArray(message)
233
+ ? (message as WorkerMessage)
234
+ : undefined
235
+ const input =
236
+ record?.input !== null && typeof record?.input === "object" && !Array.isArray(record.input)
237
+ ? record.input
238
+ : undefined
239
+ const id = record?.id
240
+ const requests = input?.requests
241
+ const entry = input?.entry
242
+ if (!Array.isArray(requests)) {
243
+ send(id, { error: "expected { requests: [...] }" })
244
+ continue
245
+ }
246
+ if (entry !== undefined && typeof entry !== "string") {
247
+ send(id, { error: "entry must be a string" })
248
+ continue
249
+ }
250
+ const key = entry ?? ""
251
+ let loaded = apps.get(key)
252
+ if (loaded === undefined) {
253
+ loaded = loadBackend(cwd, entry)
254
+ apps.set(key, loaded)
255
+ }
256
+ let app: Awaited<typeof loaded>
257
+ try {
258
+ app = await loaded
259
+ } catch (err) {
260
+ send(id, { error: errString(err) })
261
+ continue
262
+ }
263
+ if ("error" in app) {
264
+ send(id, app)
265
+ continue
266
+ }
267
+ try {
268
+ send(id, { results: await runCaptured(cwd, app.app, requests) })
269
+ } catch (err) {
270
+ send(id, { error: errString(err) })
151
271
  }
152
272
  }
153
273
  }
@@ -156,19 +276,33 @@ async function runWorker(cwd: string): Promise<void> {
156
276
  // this only executes when run directly (not when imported by a test).
157
277
  if (import.meta.main) {
158
278
  const cwd = process.argv[2] ?? process.cwd()
279
+ const protocol = reserveStdout()
159
280
  if (process.argv.includes("--worker")) {
160
- await runWorker(cwd)
281
+ await runWorker(cwd, protocol)
161
282
  process.exit(0)
162
283
  }
163
284
  let output: unknown
164
- try {
165
- const { requests, entry } = JSON.parse(await Bun.stdin.text()) as {
166
- requests: unknown
167
- entry?: string
285
+ const input = await readBoundedStream(Bun.stdin.stream(), CHILD_INPUT_MAX_BYTES)
286
+ if (input.truncated) {
287
+ output = { error: `input exceeded ${CHILD_INPUT_MAX_BYTES} bytes` }
288
+ } else {
289
+ try {
290
+ const { requests, entry } = JSON.parse(input.text) as {
291
+ requests: unknown
292
+ entry?: string
293
+ }
294
+ output = await runBackend(cwd, requests, entry)
295
+ } catch {
296
+ output = { error: "invalid input: expected JSON { requests: [...] }" }
168
297
  }
169
- output = await runBackend(cwd, requests, entry)
170
- } catch {
171
- output = { error: "invalid input: expected JSON { requests: [...] }" }
172
298
  }
173
- process.stdout.write(JSON.stringify(output, null, 2))
299
+ // `Bun.write` to the fd, not the patched `process.stdout.write`, so the answer is flushed when it
300
+ // resolves. Then exit: the app's own handles (a pool, an interval) would keep this single-use
301
+ // process alive until the parent's timeout, and the parent waits for it to exit.
302
+ await Bun.write(
303
+ Bun.stdout,
304
+ serializeBoundedJson(output, CHILD_OUTPUT_MAX_BYTES, 2) ??
305
+ JSON.stringify({ error: `output exceeded ${CHILD_OUTPUT_MAX_BYTES} bytes` }),
306
+ )
307
+ process.exit(0)
174
308
  }
package/src/mcp-ws.ts CHANGED
@@ -324,5 +324,8 @@ if (import.meta.main) {
324
324
  error: 'invalid input: expected JSON { "path": "/..." }',
325
325
  } satisfies WebSocketVerificationResult
326
326
  }
327
- process.stdout.write(JSON.stringify(output, null, 2))
327
+ // Flushed, then exit: the app's own handles would keep this single-use process alive until the
328
+ // parent's timeout, as in mcp-run and mcp-render.
329
+ await Bun.write(Bun.stdout, JSON.stringify(output, null, 2))
330
+ process.exit(0)
328
331
  }