codebase-onboarder 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/README.md +196 -47
  2. package/bin/build-if-source.js +23 -0
  3. package/cli/commands.d.ts +26 -0
  4. package/cli/commands.js +836 -811
  5. package/cli/commands.js.map +1 -0
  6. package/cli/explorer/advanced.d.ts +14 -0
  7. package/cli/explorer/advanced.js +449 -0
  8. package/cli/explorer/advanced.js.map +1 -0
  9. package/cli/explorer/app.d.ts +1 -0
  10. package/cli/explorer/app.js +396 -229
  11. package/cli/explorer/app.js.map +1 -0
  12. package/cli/explorer/assistant.d.ts +9 -0
  13. package/cli/explorer/assistant.js +58 -0
  14. package/cli/explorer/assistant.js.map +1 -0
  15. package/cli/explorer/commands.d.ts +17 -0
  16. package/cli/explorer/commands.js +400 -162
  17. package/cli/explorer/commands.js.map +1 -0
  18. package/cli/explorer/featureViews.d.ts +124 -0
  19. package/cli/explorer/featureViews.js +179 -0
  20. package/cli/explorer/featureViews.js.map +1 -0
  21. package/cli/explorer/github.d.ts +21 -0
  22. package/cli/explorer/github.js +73 -0
  23. package/cli/explorer/github.js.map +1 -0
  24. package/cli/explorer/graphs.d.ts +7 -0
  25. package/cli/explorer/graphs.js +173 -0
  26. package/cli/explorer/graphs.js.map +1 -0
  27. package/cli/explorer/picker.d.ts +22 -0
  28. package/cli/explorer/picker.js +150 -0
  29. package/cli/explorer/picker.js.map +1 -0
  30. package/cli/explorer/report.d.ts +85 -0
  31. package/cli/explorer/report.js +60 -0
  32. package/cli/explorer/report.js.map +1 -0
  33. package/cli/explorer/session.d.ts +449 -0
  34. package/cli/explorer/session.js +184 -95
  35. package/cli/explorer/session.js.map +1 -0
  36. package/cli/explorer/views.d.ts +19 -0
  37. package/cli/explorer/views.js +496 -412
  38. package/cli/explorer/views.js.map +1 -0
  39. package/cli/explorer/wrap.d.ts +1 -0
  40. package/cli/explorer/wrap.js +33 -0
  41. package/cli/explorer/wrap.js.map +1 -0
  42. package/cli/main.d.ts +1 -0
  43. package/cli/main.js +235 -138
  44. package/cli/main.js.map +1 -0
  45. package/cli/prompt.d.ts +5 -0
  46. package/cli/prompt.js +84 -80
  47. package/cli/prompt.js.map +1 -0
  48. package/cli/scanOptions.d.ts +2 -0
  49. package/cli/scanOptions.js +11 -0
  50. package/cli/scanOptions.js.map +1 -0
  51. package/cli/ui.d.ts +33 -0
  52. package/cli/ui.js +55 -35
  53. package/cli/ui.js.map +1 -0
  54. package/cli/wizard.d.ts +187 -0
  55. package/cli/wizard.js +304 -284
  56. package/cli/wizard.js.map +1 -0
  57. package/package.json +15 -4
  58. package/public/app.d.ts +1 -0
  59. package/public/app.js +1057 -1009
  60. package/public/app.js.map +1 -0
  61. package/public/browser.d.ts +68 -0
  62. package/public/browser.js +2 -0
  63. package/public/browser.js.map +1 -0
  64. package/public/index.html +30 -13
  65. package/public/js/about.d.ts +6 -0
  66. package/public/js/about.js +165 -172
  67. package/public/js/about.js.map +1 -0
  68. package/public/js/aiDraft.d.ts +3 -0
  69. package/public/js/aiDraft.js +116 -124
  70. package/public/js/aiDraft.js.map +1 -0
  71. package/public/js/analysisPanel.d.ts +7 -0
  72. package/public/js/analysisPanel.js +166 -166
  73. package/public/js/analysisPanel.js.map +1 -0
  74. package/public/js/analysisReport.d.ts +38 -0
  75. package/public/js/analysisReport.js +105 -110
  76. package/public/js/analysisReport.js.map +1 -0
  77. package/public/js/api.d.ts +26 -0
  78. package/public/js/api.js +176 -167
  79. package/public/js/api.js.map +1 -0
  80. package/public/js/atlas.d.ts +6 -0
  81. package/public/js/atlas.js +50 -58
  82. package/public/js/atlas.js.map +1 -0
  83. package/public/js/blameView.d.ts +2 -0
  84. package/public/js/blameView.js +36 -37
  85. package/public/js/blameView.js.map +1 -0
  86. package/public/js/codeTab.d.ts +3 -0
  87. package/public/js/codeTab.js +175 -180
  88. package/public/js/codeTab.js.map +1 -0
  89. package/public/js/codeViewer.d.ts +5 -0
  90. package/public/js/codeViewer.js +162 -150
  91. package/public/js/codeViewer.js.map +1 -0
  92. package/public/js/components/FileChip.d.ts +9 -0
  93. package/public/js/components/FileChip.js +45 -49
  94. package/public/js/components/FileChip.js.map +1 -0
  95. package/public/js/components/MetricSparkline.d.ts +7 -0
  96. package/public/js/components/MetricSparkline.js +35 -44
  97. package/public/js/components/MetricSparkline.js.map +1 -0
  98. package/public/js/components/RiskBadge.d.ts +9 -0
  99. package/public/js/components/RiskBadge.js +55 -63
  100. package/public/js/components/RiskBadge.js.map +1 -0
  101. package/public/js/deepAnalysisView.d.ts +99 -0
  102. package/public/js/deepAnalysisView.js +282 -302
  103. package/public/js/deepAnalysisView.js.map +1 -0
  104. package/public/js/diagramPane.d.ts +8 -0
  105. package/public/js/diagramPane.js +133 -135
  106. package/public/js/diagramPane.js.map +1 -0
  107. package/public/js/diffView.d.ts +6 -0
  108. package/public/js/diffView.js +222 -209
  109. package/public/js/diffView.js.map +1 -0
  110. package/public/js/docsView.d.ts +2 -0
  111. package/public/js/docsView.js +441 -442
  112. package/public/js/docsView.js.map +1 -0
  113. package/public/js/dom.d.ts +189 -0
  114. package/public/js/dom.js +5 -0
  115. package/public/js/dom.js.map +1 -0
  116. package/public/js/fileSourceBrowser.d.ts +5 -0
  117. package/public/js/fileSourceBrowser.js +33 -41
  118. package/public/js/fileSourceBrowser.js.map +1 -0
  119. package/public/js/flameGraph.d.ts +5 -0
  120. package/public/js/flameGraph.js +62 -72
  121. package/public/js/flameGraph.js.map +1 -0
  122. package/public/js/forceGraph.d.ts +10 -0
  123. package/public/js/forceGraph.js +581 -556
  124. package/public/js/forceGraph.js.map +1 -0
  125. package/public/js/heatmap.d.ts +4 -0
  126. package/public/js/heatmap.js +142 -164
  127. package/public/js/heatmap.js.map +1 -0
  128. package/public/js/highlight.d.ts +2 -0
  129. package/public/js/highlight.js +116 -107
  130. package/public/js/highlight.js.map +1 -0
  131. package/public/js/html.d.ts +2 -0
  132. package/public/js/html.js +14 -44
  133. package/public/js/html.js.map +1 -0
  134. package/public/js/insightsView.d.ts +5 -0
  135. package/public/js/insightsView.js +93 -111
  136. package/public/js/insightsView.js.map +1 -0
  137. package/public/js/inspector.d.ts +37 -0
  138. package/public/js/inspector.js +416 -445
  139. package/public/js/inspector.js.map +1 -0
  140. package/public/js/llm.d.ts +113 -0
  141. package/public/js/llm.js +233 -272
  142. package/public/js/llm.js.map +1 -0
  143. package/public/js/mapView.d.ts +3 -0
  144. package/public/js/mapView.js +121 -120
  145. package/public/js/mapView.js.map +1 -0
  146. package/public/js/markdown.d.ts +3 -0
  147. package/public/js/markdown.js +84 -91
  148. package/public/js/markdown.js.map +1 -0
  149. package/public/js/mindmap.d.ts +32 -0
  150. package/public/js/mindmap.js +244 -259
  151. package/public/js/mindmap.js.map +1 -0
  152. package/public/js/mobilePanels.d.ts +5 -0
  153. package/public/js/mobilePanels.js +50 -0
  154. package/public/js/mobilePanels.js.map +1 -0
  155. package/public/js/repoFiles.d.ts +12 -0
  156. package/public/js/repoFiles.js +28 -27
  157. package/public/js/repoFiles.js.map +1 -0
  158. package/public/js/reviewView.d.ts +28 -0
  159. package/public/js/reviewView.js +256 -0
  160. package/public/js/reviewView.js.map +1 -0
  161. package/public/js/sbomView.d.ts +3 -0
  162. package/public/js/sbomView.js +59 -67
  163. package/public/js/sbomView.js.map +1 -0
  164. package/public/js/scanCache.d.ts +16 -0
  165. package/public/js/scanCache.js +67 -65
  166. package/public/js/scanCache.js.map +1 -0
  167. package/public/js/search.d.ts +7 -0
  168. package/public/js/search.js +868 -800
  169. package/public/js/search.js.map +1 -0
  170. package/public/js/searchLocal.d.ts +42 -0
  171. package/public/js/searchLocal.js +87 -0
  172. package/public/js/searchLocal.js.map +1 -0
  173. package/public/js/serverSettings.d.ts +5 -0
  174. package/public/js/serverSettings.js +153 -164
  175. package/public/js/serverSettings.js.map +1 -0
  176. package/public/js/state.d.ts +798 -0
  177. package/public/js/state.js +114 -122
  178. package/public/js/state.js.map +1 -0
  179. package/public/js/tour.d.ts +6 -0
  180. package/public/js/tour.js +5 -7
  181. package/public/js/tour.js.map +1 -0
  182. package/public/js/transitions.d.ts +2 -0
  183. package/public/js/transitions.js +9 -10
  184. package/public/js/transitions.js.map +1 -0
  185. package/public/js/tree.d.ts +12 -0
  186. package/public/js/tree.js +157 -154
  187. package/public/js/tree.js.map +1 -0
  188. package/public/js/workflowsView.d.ts +3 -0
  189. package/public/js/workflowsView.js +15 -22
  190. package/public/js/workflowsView.js.map +1 -0
  191. package/public/search.css +495 -0
  192. package/public/styles.css +163 -346
  193. package/public/workbench.css +166 -0
  194. package/server/apiAuth.d.ts +3 -0
  195. package/server/apiAuth.js +18 -20
  196. package/server/apiAuth.js.map +1 -0
  197. package/server/apiDiff.d.ts +2 -0
  198. package/server/apiDiff.js +20 -18
  199. package/server/apiDiff.js.map +1 -0
  200. package/server/apiDocs.d.ts +2 -0
  201. package/server/apiDocs.js +38 -40
  202. package/server/apiDocs.js.map +1 -0
  203. package/server/apiFile.d.ts +1 -0
  204. package/server/apiFile.js +14 -17
  205. package/server/apiFile.js.map +1 -0
  206. package/server/apiGitBlame.d.ts +2 -0
  207. package/server/apiGitBlame.js +50 -51
  208. package/server/apiGitBlame.js.map +1 -0
  209. package/server/apiMcp.d.ts +4 -0
  210. package/server/apiMcp.js +64 -66
  211. package/server/apiMcp.js.map +1 -0
  212. package/server/apiReview.d.ts +2 -0
  213. package/server/apiReview.js +45 -0
  214. package/server/apiReview.js.map +1 -0
  215. package/server/apiScan.d.ts +5 -0
  216. package/server/apiScan.js +90 -71
  217. package/server/apiScan.js.map +1 -0
  218. package/server/apiSearch.d.ts +33 -0
  219. package/server/apiSearch.js +184 -200
  220. package/server/apiSearch.js.map +1 -0
  221. package/server/apiSettings.d.ts +4 -0
  222. package/server/apiSettings.js +84 -85
  223. package/server/apiSettings.js.map +1 -0
  224. package/server/apiTools.d.ts +3 -0
  225. package/server/apiTools.js +55 -56
  226. package/server/apiTools.js.map +1 -0
  227. package/server/auth.d.ts +5 -0
  228. package/server/auth.js +54 -51
  229. package/server/auth.js.map +1 -0
  230. package/server/config.d.ts +42 -0
  231. package/server/config.js +189 -194
  232. package/server/config.js.map +1 -0
  233. package/server/daemon.d.ts +12 -0
  234. package/server/daemon.js +124 -114
  235. package/server/daemon.js.map +1 -0
  236. package/server/fileSourceNode.d.ts +2 -0
  237. package/server/fileSourceNode.js +34 -36
  238. package/server/fileSourceNode.js.map +1 -0
  239. package/server/gitClone.d.ts +9 -0
  240. package/server/gitClone.js +59 -61
  241. package/server/gitClone.js.map +1 -0
  242. package/server/gitDiff.d.ts +36 -0
  243. package/server/gitDiff.js +271 -169
  244. package/server/gitDiff.js.map +1 -0
  245. package/server/gitHistory.d.ts +12 -0
  246. package/server/gitHistory.js +80 -80
  247. package/server/gitHistory.js.map +1 -0
  248. package/server/gitRemote.d.ts +12 -0
  249. package/server/gitRemote.js +60 -0
  250. package/server/gitRemote.js.map +1 -0
  251. package/server/htmlText.d.ts +4 -0
  252. package/server/htmlText.js +16 -23
  253. package/server/htmlText.js.map +1 -0
  254. package/server/http.d.ts +5 -0
  255. package/server/http.js +27 -30
  256. package/server/http.js.map +1 -0
  257. package/server/httpGuards.d.ts +2 -0
  258. package/server/httpGuards.js +47 -43
  259. package/server/httpGuards.js.map +1 -0
  260. package/server/https.d.ts +34 -0
  261. package/server/https.js +121 -111
  262. package/server/https.js.map +1 -0
  263. package/server/index.d.ts +40 -0
  264. package/server/index.js +139 -147
  265. package/server/index.js.map +1 -0
  266. package/server/layout.d.ts +26 -0
  267. package/server/layout.js +59 -37
  268. package/server/layout.js.map +1 -0
  269. package/server/llmProxy.d.ts +3 -0
  270. package/server/llmProxy.js +137 -135
  271. package/server/llmProxy.js.map +1 -0
  272. package/server/logger.d.ts +38 -0
  273. package/server/logger.js +198 -211
  274. package/server/logger.js.map +1 -0
  275. package/server/mcp/analysis.d.ts +29 -0
  276. package/server/mcp/analysis.js +99 -120
  277. package/server/mcp/analysis.js.map +1 -0
  278. package/server/mcp/http.d.ts +9 -0
  279. package/server/mcp/http.js +128 -137
  280. package/server/mcp/http.js.map +1 -0
  281. package/server/mcp/runner.d.ts +68 -0
  282. package/server/mcp/runner.js +208 -218
  283. package/server/mcp/runner.js.map +1 -0
  284. package/server/mcp/server.d.ts +29 -0
  285. package/server/mcp/server.js +166 -179
  286. package/server/mcp/server.js.map +1 -0
  287. package/server/mcp/standalone.d.ts +1 -0
  288. package/server/mcp/standalone.js +8 -10
  289. package/server/mcp/standalone.js.map +1 -0
  290. package/server/mcp/tools.d.ts +2020 -0
  291. package/server/mcp/tools.js +523 -574
  292. package/server/mcp/tools.js.map +1 -0
  293. package/server/paths.d.ts +3 -0
  294. package/server/paths.js +17 -15
  295. package/server/paths.js.map +1 -0
  296. package/server/pidfile.d.ts +9 -0
  297. package/server/pidfile.js +43 -45
  298. package/server/pidfile.js.map +1 -0
  299. package/server/review.d.ts +5 -0
  300. package/server/review.js +35 -0
  301. package/server/review.js.map +1 -0
  302. package/server/router.d.ts +33 -0
  303. package/server/router.js +191 -197
  304. package/server/router.js.map +1 -0
  305. package/server/scanRequest.d.ts +3 -0
  306. package/server/scanRequest.js +36 -0
  307. package/server/scanRequest.js.map +1 -0
  308. package/server/searchIndex.d.ts +17 -0
  309. package/server/searchIndex.js +83 -80
  310. package/server/searchIndex.js.map +1 -0
  311. package/server/sessions.d.ts +14 -0
  312. package/server/sessions.js +62 -70
  313. package/server/sessions.js.map +1 -0
  314. package/server/startup.d.ts +118 -0
  315. package/server/startup.js +143 -148
  316. package/server/startup.js.map +1 -0
  317. package/server/static.d.ts +6 -0
  318. package/server/static.js +28 -31
  319. package/server/static.js.map +1 -0
  320. package/server/tools/install.d.ts +4 -0
  321. package/server/tools/install.js +176 -185
  322. package/server/tools/install.js.map +1 -0
  323. package/server/tools/parse.d.ts +8 -0
  324. package/server/tools/parse.js +112 -114
  325. package/server/tools/parse.js.map +1 -0
  326. package/server/tools/platform.d.ts +9 -0
  327. package/server/tools/platform.js +49 -47
  328. package/server/tools/platform.js.map +1 -0
  329. package/server/tools/registry.d.ts +137 -0
  330. package/server/tools/registry.js +173 -172
  331. package/server/tools/registry.js.map +1 -0
  332. package/server/tools/scan.d.ts +34 -0
  333. package/server/tools/scan.js +89 -98
  334. package/server/tools/scan.js.map +1 -0
  335. package/server/tools.d.ts +34 -0
  336. package/server/tools.js +94 -107
  337. package/server/tools.js.map +1 -0
  338. package/server/tunnel.d.ts +29 -0
  339. package/server/tunnel.js +61 -66
  340. package/server/tunnel.js.map +1 -0
  341. package/shared/analyzer/docs.d.ts +16 -0
  342. package/shared/analyzer/docs.js +99 -93
  343. package/shared/analyzer/docs.js.map +1 -0
  344. package/shared/analyzer/explainLocal.d.ts +4 -0
  345. package/shared/analyzer/explainLocal.js +122 -142
  346. package/shared/analyzer/explainLocal.js.map +1 -0
  347. package/shared/analyzer/github.d.ts +37 -0
  348. package/shared/analyzer/github.js +79 -0
  349. package/shared/analyzer/github.js.map +1 -0
  350. package/shared/analyzer/graph.d.ts +41 -0
  351. package/shared/analyzer/graph.js +393 -400
  352. package/shared/analyzer/graph.js.map +1 -0
  353. package/shared/analyzer/health.d.ts +33 -0
  354. package/shared/analyzer/health.js +197 -193
  355. package/shared/analyzer/health.js.map +1 -0
  356. package/shared/analyzer/history.d.ts +29 -0
  357. package/shared/analyzer/history.js +110 -111
  358. package/shared/analyzer/history.js.map +1 -0
  359. package/shared/analyzer/jsonConfig.d.ts +2 -0
  360. package/shared/analyzer/jsonConfig.js +61 -0
  361. package/shared/analyzer/jsonConfig.js.map +1 -0
  362. package/shared/analyzer/languages/csharp.d.ts +26 -0
  363. package/shared/analyzer/languages/csharp.js +83 -88
  364. package/shared/analyzer/languages/csharp.js.map +1 -0
  365. package/shared/analyzer/languages/generic.d.ts +23 -0
  366. package/shared/analyzer/languages/generic.js +94 -105
  367. package/shared/analyzer/languages/generic.js.map +1 -0
  368. package/shared/analyzer/languages/go.d.ts +24 -0
  369. package/shared/analyzer/languages/go.js +45 -54
  370. package/shared/analyzer/languages/go.js.map +1 -0
  371. package/shared/analyzer/languages/index.d.ts +6 -0
  372. package/shared/analyzer/languages/index.js +19 -20
  373. package/shared/analyzer/languages/index.js.map +1 -0
  374. package/shared/analyzer/languages/java.d.ts +20 -0
  375. package/shared/analyzer/languages/java.js +73 -78
  376. package/shared/analyzer/languages/java.js.map +1 -0
  377. package/shared/analyzer/languages/javascript.d.ts +12 -0
  378. package/shared/analyzer/languages/javascript.js +258 -209
  379. package/shared/analyzer/languages/javascript.js.map +1 -0
  380. package/shared/analyzer/languages/python.d.ts +23 -0
  381. package/shared/analyzer/languages/python.js +92 -100
  382. package/shared/analyzer/languages/python.js.map +1 -0
  383. package/shared/analyzer/languages/rust.d.ts +17 -0
  384. package/shared/analyzer/languages/rust.js +96 -98
  385. package/shared/analyzer/languages/rust.js.map +1 -0
  386. package/shared/analyzer/languages/typescript.d.ts +11 -0
  387. package/shared/analyzer/languages/typescript.js +117 -116
  388. package/shared/analyzer/languages/typescript.js.map +1 -0
  389. package/shared/analyzer/licenses.d.ts +18 -0
  390. package/shared/analyzer/licenses.js +130 -133
  391. package/shared/analyzer/licenses.js.map +1 -0
  392. package/shared/analyzer/metrics.d.ts +20 -0
  393. package/shared/analyzer/metrics.js +112 -104
  394. package/shared/analyzer/metrics.js.map +1 -0
  395. package/shared/analyzer/pathUtil.d.ts +10 -0
  396. package/shared/analyzer/pathUtil.js +29 -35
  397. package/shared/analyzer/pathUtil.js.map +1 -0
  398. package/shared/analyzer/patterns.d.ts +11 -0
  399. package/shared/analyzer/patterns.js +231 -253
  400. package/shared/analyzer/patterns.js.map +1 -0
  401. package/shared/analyzer/scan.d.ts +62 -0
  402. package/shared/analyzer/scan.js +451 -442
  403. package/shared/analyzer/scan.js.map +1 -0
  404. package/shared/analyzer/scanControl.d.ts +11 -0
  405. package/shared/analyzer/scanControl.js +62 -0
  406. package/shared/analyzer/scanControl.js.map +1 -0
  407. package/shared/analyzer/security.d.ts +18 -0
  408. package/shared/analyzer/security.js +159 -159
  409. package/shared/analyzer/security.js.map +1 -0
  410. package/shared/analyzer/services.d.ts +5 -0
  411. package/shared/analyzer/services.js +141 -138
  412. package/shared/analyzer/services.js.map +1 -0
  413. package/shared/analyzer/stack.d.ts +6 -0
  414. package/shared/analyzer/stack.js +134 -141
  415. package/shared/analyzer/stack.js.map +1 -0
  416. package/shared/analyzer/tour.d.ts +2 -0
  417. package/shared/analyzer/tour.js +45 -53
  418. package/shared/analyzer/tour.js.map +1 -0
  419. package/shared/analyzer/util.d.ts +8 -0
  420. package/shared/analyzer/util.js +130 -76
  421. package/shared/analyzer/util.js.map +1 -0
  422. package/shared/analyzer/workflows.d.ts +8 -0
  423. package/shared/analyzer/workflows.js +139 -134
  424. package/shared/analyzer/workflows.js.map +1 -0
  425. package/shared/contracts.d.ts +250 -0
  426. package/shared/contracts.js +2 -0
  427. package/shared/contracts.js.map +1 -0
  428. package/shared/diagram/aiFacts.d.ts +35 -0
  429. package/shared/diagram/aiFacts.js +102 -105
  430. package/shared/diagram/aiFacts.js.map +1 -0
  431. package/shared/diagram/aiMermaid.d.ts +5 -0
  432. package/shared/diagram/aiMermaid.js +46 -45
  433. package/shared/diagram/aiMermaid.js.map +1 -0
  434. package/shared/diagram/atlas.d.ts +16 -0
  435. package/shared/diagram/atlas.js +51 -60
  436. package/shared/diagram/atlas.js.map +1 -0
  437. package/shared/diagram/mermaid.d.ts +43 -0
  438. package/shared/diagram/mermaid.js +348 -370
  439. package/shared/diagram/mermaid.js.map +1 -0
  440. package/shared/review/contracts.d.ts +95 -0
  441. package/shared/review/contracts.js +2 -0
  442. package/shared/review/contracts.js.map +1 -0
  443. package/shared/review/review.d.ts +10 -0
  444. package/shared/review/review.js +184 -0
  445. package/shared/review/review.js.map +1 -0
  446. package/shared/search/query.d.ts +32 -0
  447. package/shared/search/query.js +395 -388
  448. package/shared/search/query.js.map +1 -0
package/server/daemon.js CHANGED
@@ -18,178 +18,188 @@
18
18
  // 2. Detach properly, or "background" is a lie. `detached: true` puts the
19
19
  // child in its own process group, and `unref()` drops our handle on it, so
20
20
  // a Ctrl-C in the parent terminal does not take the server down with it.
21
-
22
21
  import { spawn } from 'node:child_process';
23
22
  import fs from 'node:fs';
24
23
  import fsp from 'node:fs/promises';
25
24
  import http from 'node:http';
26
25
  import path from 'node:path';
27
26
  import { fileURLToPath } from 'node:url';
28
-
29
27
  import { configPath } from './config.js';
30
28
  import { readPidFile } from './pidfile.js';
31
-
32
29
  const HERE = path.dirname(fileURLToPath(import.meta.url));
33
-
34
30
  // The installed entry point, not `process.argv[1]`: a globally installed
35
31
  // `onboarder` is a symlink into node_modules, and the child has to run the real
36
32
  // file to find its siblings.
37
33
  export const CLI_ENTRY = path.resolve(HERE, '..', 'bin', 'onboarder.js');
38
-
39
34
  // One file per config, beside the config, so `--config` isolation (tests,
40
35
  // containers, several profiles) carries the log with it.
41
36
  export function logPath(configFile = configPath()) {
42
- return path.join(path.dirname(path.resolve(configFile)), 'onboarder.log');
37
+ return path.join(path.dirname(path.resolve(configFile)), 'onboarder.log');
43
38
  }
44
-
45
39
  // Rotated history. One generation is deliberate: a log that grows without bound
46
40
  // is a bug report waiting to happen, and one previous file is enough to see what
47
41
  // happened just before a crash.
48
42
  export const MAX_LOG_BYTES = 2 * 1024 * 1024;
49
-
50
43
  export async function rotateLogIfNeeded(file = logPath(), maxBytes = MAX_LOG_BYTES) {
51
- try {
52
- const { size } = await fsp.stat(file);
53
- if (size <= maxBytes) return false;
54
- await fsp.rename(file, file + '.1');
55
- return true;
56
- } catch {
57
- return false; // no log yet, or not ours to move
58
- }
44
+ try {
45
+ const { size } = await fsp.stat(file);
46
+ if (size <= maxBytes)
47
+ return false;
48
+ await fsp.rename(file, file + '.1');
49
+ return true;
50
+ }
51
+ catch {
52
+ return false; // no log yet, or not ours to move
53
+ }
59
54
  }
60
-
61
55
  export async function readLog(file = logPath(), bytes = 64 * 1024) {
62
- try {
63
- const handle = await fsp.open(file, 'r');
64
56
  try {
65
- const { size } = await handle.stat();
66
- const start = Math.max(0, size - bytes);
67
- const buffer = Buffer.alloc(size - start);
68
- await handle.read(buffer, 0, buffer.length, start);
69
- return buffer.toString('utf8');
70
- } finally {
71
- await handle.close();
57
+ const handle = await fsp.open(file, 'r');
58
+ try {
59
+ const { size } = await handle.stat();
60
+ const start = Math.max(0, size - bytes);
61
+ const buffer = Buffer.alloc(size - start);
62
+ await handle.read(buffer, 0, buffer.length, start);
63
+ return buffer.toString('utf8');
64
+ }
65
+ finally {
66
+ await handle.close();
67
+ }
68
+ }
69
+ catch {
70
+ return '';
72
71
  }
73
- } catch {
74
- return '';
75
- }
76
72
  }
77
-
78
73
  // The last `count` lines, oldest first — the shape `onboarder logs` prints and
79
74
  // the shape an error report wants pasted into it.
80
75
  export async function tailLog(file = logPath(), count = 40) {
81
- const text = await readLog(file);
82
- const lines = text.split('\n').filter((line) => line.trim());
83
- return lines.slice(-Math.max(1, count));
76
+ const text = await readLog(file);
77
+ const lines = text.split('\n').filter((line) => line.trim());
78
+ return lines.slice(-Math.max(1, count));
84
79
  }
85
-
86
80
  export async function logExists(file = logPath()) {
87
- try { await fsp.access(file); return true; } catch { return false; }
81
+ try {
82
+ await fsp.access(file);
83
+ return true;
84
+ }
85
+ catch {
86
+ return false;
87
+ }
88
88
  }
89
-
90
89
  // Ask the server whether it is up. A 2xx–4xx from /api/health means the socket
91
90
  // is bound and the router is serving; the body is not interesting, the answer is.
92
91
  export function probe(url, timeoutMs = 1000) {
93
- return new Promise((resolve) => {
94
- const request = http.get(url, { timeout: timeoutMs }, (res) => {
95
- res.resume();
96
- resolve(res.statusCode >= 200 && res.statusCode < 500);
92
+ return new Promise((resolve) => {
93
+ const request = http.get(url, { timeout: timeoutMs }, (res) => {
94
+ res.resume();
95
+ resolve(res.statusCode >= 200 && res.statusCode < 500);
96
+ });
97
+ request.on('timeout', () => { request.destroy(); resolve(false); });
98
+ request.on('error', () => resolve(false));
97
99
  });
98
- request.on('timeout', () => { request.destroy(); resolve(false); });
99
- request.on('error', () => resolve(false));
100
- });
101
100
  }
102
-
103
101
  export async function waitForReady(url, { timeoutMs = 20000, intervalMs = 200, check = probe } = {}) {
104
- const deadline = Date.now() + timeoutMs;
105
- for (;;) {
106
- if (await check(url)) return true;
107
- if (Date.now() >= deadline) return false;
108
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
109
- }
102
+ const deadline = Date.now() + timeoutMs;
103
+ for (;;) {
104
+ if (await check(url))
105
+ return true;
106
+ if (Date.now() >= deadline)
107
+ return false;
108
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
109
+ }
110
110
  }
111
-
112
111
  // Re-run this same CLI, detached. The child is a plain foreground `start` — it
113
112
  // writes the pid file, prints its own banner, and handles signals exactly as it
114
113
  // always has. Nothing about the server changes; only who is holding the terminal
115
114
  // does.
116
115
  export function spawnDetached({ configFile = configPath(), entry = CLI_ENTRY, env = process.env, log = logPath(configFile) } = {}) {
117
- const fd = fs.openSync(log, 'a');
118
- try {
119
- const child = spawn(process.execPath, [entry, 'start', '--config', configFile], {
120
- detached: true,
121
- stdio: ['ignore', fd, fd],
122
- env: { ...env, ONBOARDER_BACKGROUND: '1' },
123
- });
124
- child.on('error', () => {});
125
- child.unref();
126
- return child.pid;
127
- } finally {
128
- fs.closeSync(fd);
129
- }
116
+ const fd = fs.openSync(log, 'a');
117
+ try {
118
+ const child = spawn(process.execPath, [entry, 'start', '--config', configFile], {
119
+ detached: true,
120
+ stdio: ['ignore', fd, fd],
121
+ env: { ...env, ONBOARDER_BACKGROUND: '1' },
122
+ });
123
+ child.on('error', (..._args) => { });
124
+ child.unref();
125
+ return child.pid;
126
+ }
127
+ finally {
128
+ fs.closeSync(fd);
129
+ }
130
130
  }
131
-
132
131
  // Does the OS still have this process? Signal 0 asks without delivering.
133
132
  function alive(pid) {
134
- try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
133
+ try {
134
+ process.kill(pid, 0);
135
+ return true;
136
+ }
137
+ catch (e) {
138
+ return e.code === 'EPERM';
139
+ }
135
140
  }
136
-
137
141
  // Wait for the detached child to become the *recorded* server. The pid file is
138
142
  // the authority here, not the spawn pid: it is what `status` and `stop` read,
139
143
  // so waiting on it means the process we report is the one the user can control.
140
144
  export async function waitForPidFile(configFile, { timeoutMs = 20000, intervalMs = 150, readPid = readPidFile, isAlive = alive } = {}) {
141
- const deadline = Date.now() + timeoutMs;
142
- for (;;) {
143
- const pid = readPid(configFile);
144
- if (pid && isAlive(pid)) return pid;
145
- if (Date.now() >= deadline) return null;
146
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
147
- }
145
+ const deadline = Date.now() + timeoutMs;
146
+ for (;;) {
147
+ const pid = readPid(configFile);
148
+ if (pid && isAlive(pid))
149
+ return pid;
150
+ if (Date.now() >= deadline)
151
+ return null;
152
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
153
+ }
148
154
  }
149
-
150
155
  // Follow a growing file. Polling rather than fs.watch: the log is appended by a
151
156
  // *different* process, and watchers on a file another process holds open are
152
157
  // unreliable across platforms (and absent on some network mounts). Half a second
153
158
  // of latency on a human-facing log tail is invisible.
154
159
  export function followLog(file, onLine, { intervalMs = 500, from = 'end' } = {}) {
155
- let position = 0;
156
- let partial = '';
157
- let stopped = false;
158
- const stop = () => { stopped = true; };
159
-
160
- if (from === 'start') {
161
- fsp.readFile(file, 'utf8').then(
162
- (text) => { for (const line of text.split('\n')) if (line) onLine(line); },
163
- () => {},
164
- );
165
- }
166
-
167
- const tick = async () => {
168
- if (stopped) return;
169
- try {
170
- const { size } = await fsp.stat(file);
171
- if (size < position) { position = 0; partial = ''; } // rotated under us
172
- if (size > position) {
173
- const handle = await fsp.open(file, 'r');
160
+ let position = 0;
161
+ let partial = '';
162
+ let stopped = false;
163
+ const stop = () => { stopped = true; };
164
+ if (from === 'start') {
165
+ fsp.readFile(file, 'utf8').then((text) => { for (const line of text.split('\n'))
166
+ if (line)
167
+ onLine(line); }, (..._args) => { });
168
+ }
169
+ const tick = async () => {
170
+ if (stopped)
171
+ return;
174
172
  try {
175
- const length = size - position;
176
- const buffer = Buffer.alloc(length);
177
- await handle.read(buffer, 0, length, position);
178
- position = size;
179
- // A read can land mid-line; hold the remainder until its newline shows.
180
- const lines = (partial + buffer.toString('utf8')).split('\n');
181
- partial = lines.pop() ?? '';
182
- for (const line of lines) onLine(line);
183
- } finally {
184
- await handle.close();
173
+ const { size } = await fsp.stat(file);
174
+ if (size < position) {
175
+ position = 0;
176
+ partial = '';
177
+ } // rotated under us
178
+ if (size > position) {
179
+ const handle = await fsp.open(file, 'r');
180
+ try {
181
+ const length = size - position;
182
+ const buffer = Buffer.alloc(length);
183
+ await handle.read(buffer, 0, length, position);
184
+ position = size;
185
+ // A read can land mid-line; hold the remainder until its newline shows.
186
+ const lines = (partial + buffer.toString('utf8')).split('\n');
187
+ partial = lines.pop() ?? '';
188
+ for (const line of lines)
189
+ onLine(line);
190
+ }
191
+ finally {
192
+ await handle.close();
193
+ }
194
+ }
185
195
  }
186
- }
187
- } catch {
188
- // The file may not exist yet (first run) — try again on the next tick.
189
- }
190
- if (!stopped) setTimeout(tick, intervalMs).unref();
191
- };
192
-
193
- setTimeout(tick, intervalMs).unref();
194
- return stop;
195
- }
196
+ catch {
197
+ // The file may not exist yet (first run) — try again on the next tick.
198
+ }
199
+ if (!stopped)
200
+ setTimeout(tick, intervalMs).unref();
201
+ };
202
+ setTimeout(tick, intervalMs).unref();
203
+ return stop;
204
+ }
205
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/server/daemon.ts"],"names":[],"mappings":"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","sourcesContent":["// Running the server without the terminal that asked for it.\n//\n// `onboarder start` keeps the process attached: closing the shell kills it, and\n// that is the right behavior for a foreground command. `onboarder start\n// background` wants the opposite — the whole point is that the terminal can go\n// away — so this module does what a shell job control cannot do portably: it\n// re-launches the CLI as a **detached** child with its stdio pointed at a log\n// file, unrefs it, and then waits for the port to actually answer before it\n// claims success.\n//\n// Two rules keep this honest:\n//\n// 1. Never report \"running\" on the strength of the spawn alone. `spawn`\n// returning a pid proves a process was created, not that it bound the port\n// or survived settings validation. Readiness is an HTTP answer from\n// `/api/health`, polled with a deadline; on timeout the caller gets the\n// child's last log lines, not a false green light.\n// 2. Detach properly, or \"background\" is a lie. `detached: true` puts the\n// child in its own process group, and `unref()` drops our handle on it, so\n// a Ctrl-C in the parent terminal does not take the server down with it.\n\nimport { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport fsp from 'node:fs/promises';\nimport http from 'node:http';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { configPath } from './config.js';\nimport { readPidFile } from './pidfile.js';\n\nconst HERE = path.dirname(fileURLToPath(import.meta.url));\n\n// The installed entry point, not `process.argv[1]`: a globally installed\n// `onboarder` is a symlink into node_modules, and the child has to run the real\n// file to find its siblings.\nexport const CLI_ENTRY = path.resolve(HERE, '..', 'bin', 'onboarder.js');\n\n// One file per config, beside the config, so `--config` isolation (tests,\n// containers, several profiles) carries the log with it.\nexport function logPath(configFile = configPath()) {\n return path.join(path.dirname(path.resolve(configFile)), 'onboarder.log');\n}\n\n// Rotated history. One generation is deliberate: a log that grows without bound\n// is a bug report waiting to happen, and one previous file is enough to see what\n// happened just before a crash.\nexport const MAX_LOG_BYTES = 2 * 1024 * 1024;\n\nexport async function rotateLogIfNeeded(file = logPath(), maxBytes = MAX_LOG_BYTES) {\n try {\n const { size } = await fsp.stat(file);\n if (size <= maxBytes) return false;\n await fsp.rename(file, file + '.1');\n return true;\n } catch {\n return false; // no log yet, or not ours to move\n }\n}\n\nexport async function readLog(file = logPath(), bytes = 64 * 1024) {\n try {\n const handle = await fsp.open(file, 'r');\n try {\n const { size } = await handle.stat();\n const start = Math.max(0, size - bytes);\n const buffer = Buffer.alloc(size - start);\n await handle.read(buffer, 0, buffer.length, start);\n return buffer.toString('utf8');\n } finally {\n await handle.close();\n }\n } catch {\n return '';\n }\n}\n\n// The last `count` lines, oldest first — the shape `onboarder logs` prints and\n// the shape an error report wants pasted into it.\nexport async function tailLog(file = logPath(), count = 40) {\n const text = await readLog(file);\n const lines = text.split('\\n').filter((line) => line.trim());\n return lines.slice(-Math.max(1, count));\n}\n\nexport async function logExists(file = logPath()) {\n try { await fsp.access(file); return true; } catch { return false; }\n}\n\n// Ask the server whether it is up. A 2xx–4xx from /api/health means the socket\n// is bound and the router is serving; the body is not interesting, the answer is.\nexport function probe(url, timeoutMs = 1000) {\n return new Promise((resolve) => {\n const request = http.get(url, { timeout: timeoutMs }, (res) => {\n res.resume();\n resolve(res.statusCode >= 200 && res.statusCode < 500);\n });\n request.on('timeout', () => { request.destroy(); resolve(false); });\n request.on('error', () => resolve(false));\n });\n}\n\nexport async function waitForReady(url, { timeoutMs = 20000, intervalMs = 200, check = probe }: Record<string, any> = {}) {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (await check(url)) return true;\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\n// Re-run this same CLI, detached. The child is a plain foreground `start` — it\n// writes the pid file, prints its own banner, and handles signals exactly as it\n// always has. Nothing about the server changes; only who is holding the terminal\n// does.\nexport function spawnDetached({ configFile = configPath(), entry = CLI_ENTRY, env = process.env, log = logPath(configFile) }: Record<string, any> = {}) {\n const fd = fs.openSync(log, 'a');\n try {\n const child = spawn(process.execPath, [entry, 'start', '--config', configFile], {\n detached: true,\n stdio: ['ignore', fd, fd],\n env: { ...env, ONBOARDER_BACKGROUND: '1' },\n });\n child.on('error', (..._args: unknown[]) => {});\n child.unref();\n return child.pid;\n } finally {\n fs.closeSync(fd);\n }\n}\n\n// Does the OS still have this process? Signal 0 asks without delivering.\nfunction alive(pid) {\n try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; }\n}\n\n// Wait for the detached child to become the *recorded* server. The pid file is\n// the authority here, not the spawn pid: it is what `status` and `stop` read,\n// so waiting on it means the process we report is the one the user can control.\nexport async function waitForPidFile(configFile, { timeoutMs = 20000, intervalMs = 150, readPid = readPidFile, isAlive = alive }: Record<string, any> = {}) {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const pid = readPid(configFile);\n if (pid && isAlive(pid)) return pid;\n if (Date.now() >= deadline) return null;\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\n// Follow a growing file. Polling rather than fs.watch: the log is appended by a\n// *different* process, and watchers on a file another process holds open are\n// unreliable across platforms (and absent on some network mounts). Half a second\n// of latency on a human-facing log tail is invisible.\nexport function followLog(file, onLine, { intervalMs = 500, from = 'end' }: Record<string, any> = {}) {\n let position = 0;\n let partial = '';\n let stopped = false;\n const stop = () => { stopped = true; };\n\n if (from === 'start') {\n fsp.readFile(file, 'utf8').then(\n (text) => { for (const line of text.split('\\n')) if (line) onLine(line); },\n (..._args: unknown[]) => {},\n );\n }\n\n const tick = async () => {\n if (stopped) return;\n try {\n const { size } = await fsp.stat(file);\n if (size < position) { position = 0; partial = ''; } // rotated under us\n if (size > position) {\n const handle = await fsp.open(file, 'r');\n try {\n const length = size - position;\n const buffer = Buffer.alloc(length);\n await handle.read(buffer, 0, length, position);\n position = size;\n // A read can land mid-line; hold the remainder until its newline shows.\n const lines = (partial + buffer.toString('utf8')).split('\\n');\n partial = lines.pop() ?? '';\n for (const line of lines) onLine(line);\n } finally {\n await handle.close();\n }\n }\n } catch {\n // The file may not exist yet (first run) — try again on the next tick.\n }\n if (!stopped) setTimeout(tick, intervalMs).unref();\n };\n\n setTimeout(tick, intervalMs).unref();\n return stop;\n}"]}
@@ -0,0 +1,2 @@
1
+ import type { FileSource } from '../shared/contracts.js';
2
+ export declare function nodeFileSource(rootAbs: string): FileSource;
@@ -1,44 +1,42 @@
1
1
  // FileSource over node:fs. Paths crossing the boundary are repo-relative
2
2
  // POSIX style; this adapter is the only place that knows about the real
3
3
  // filesystem layout.
4
-
5
4
  import { promises as fs } from 'node:fs';
6
5
  import path from 'node:path';
7
-
8
6
  import { resolveInside } from './paths.js';
9
-
10
7
  export function nodeFileSource(rootAbs) {
11
- const toAbs = (rel) => {
12
- // `list('')` is how the scanner asks for the top of the repo. Everywhere
13
- // else an empty path is a caller mistake, which is why resolveInside
14
- // refuses it rather than quietly meaning "the root".
15
- if (rel === '' || rel == null) return rootAbs;
16
- // The scanner only ever produces paths from directory listings, so this
17
- // should never fire — but it's the same check the HTTP routes use, and a
18
- // free one.
19
- const abs = resolveInside(rootAbs, String(rel));
20
- if (!abs) throw new Error('Path escapes the repository root');
21
- return abs;
22
- };
23
-
24
- return {
25
- kind: 'node',
26
- root: rootAbs,
27
- name: path.basename(rootAbs),
28
-
29
- async list(dir) {
30
- const entries = await fs.readdir(toAbs(dir), { withFileTypes: true });
31
- const out = [];
32
- for (const e of entries) {
33
- if (e.isSymbolicLink()) continue; // no loops, no surprises
34
- const rel = dir ? dir + '/' + e.name : e.name;
35
- out.push({ name: e.name, path: rel, type: e.isDirectory() ? 'dir' : 'file' });
36
- }
37
- return out;
38
- },
39
-
40
- async read(rel) {
41
- return fs.readFile(toAbs(rel), 'utf8');
42
- },
43
- };
8
+ const toAbs = (rel) => {
9
+ // `list('')` is how the scanner asks for the top of the repo. Everywhere
10
+ // else an empty path is a caller mistake, which is why resolveInside
11
+ // refuses it rather than quietly meaning "the root".
12
+ if (rel === '' || rel == null)
13
+ return rootAbs;
14
+ // The scanner only ever produces paths from directory listings, so this
15
+ // should never fire — but it's the same check the HTTP routes use, and a
16
+ // free one.
17
+ const abs = resolveInside(rootAbs, String(rel));
18
+ if (!abs)
19
+ throw new Error('Path escapes the repository root');
20
+ return abs;
21
+ };
22
+ return {
23
+ kind: 'node',
24
+ root: rootAbs,
25
+ name: path.basename(rootAbs),
26
+ async list(dir) {
27
+ const entries = await fs.readdir(toAbs(dir), { withFileTypes: true });
28
+ const out = [];
29
+ for (const e of entries) {
30
+ if (e.isSymbolicLink())
31
+ continue; // no loops, no surprises
32
+ const rel = dir ? dir + '/' + e.name : e.name;
33
+ out.push({ name: e.name, path: rel, type: e.isDirectory() ? 'dir' : 'file' });
34
+ }
35
+ return out;
36
+ },
37
+ async read(rel) {
38
+ return fs.readFile(toAbs(rel), 'utf8');
39
+ },
40
+ };
44
41
  }
42
+ //# sourceMappingURL=fileSourceNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSourceNode.js","sourceRoot":"","sources":["../src/server/fileSourceNode.ts"],"names":[],"mappings":"AACA,yEAAyE;AACzE,wEAAwE;AACxE,qBAAqB;AAErB,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,KAAK,GAAG,CAAC,GAAW,EAAU,EAAE;QACpC,yEAAyE;QACzE,qEAAqE;QACrE,qDAAqD;QACrD,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,OAAO,CAAC;QAC9C,wEAAwE;QACxE,yEAAyE;QACzE,YAAY;QACZ,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC9D,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAE5B,KAAK,CAAC,IAAI,CAAC,GAAW;YACpB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,GAAG,GAAgB,EAAE,CAAC;YAC5B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,CAAC,cAAc,EAAE;oBAAE,SAAS,CAAC,yBAAyB;gBAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAChF,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,GAAW;YACpB,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { FileSource, FileEntry } from '../shared/contracts.js';\n// FileSource over node:fs. Paths crossing the boundary are repo-relative\n// POSIX style; this adapter is the only place that knows about the real\n// filesystem layout.\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { resolveInside } from './paths.js';\n\nexport function nodeFileSource(rootAbs: string): FileSource {\n const toAbs = (rel: string): string => {\n // `list('')` is how the scanner asks for the top of the repo. Everywhere\n // else an empty path is a caller mistake, which is why resolveInside\n // refuses it rather than quietly meaning \"the root\".\n if (rel === '' || rel == null) return rootAbs;\n // The scanner only ever produces paths from directory listings, so this\n // should never fire — but it's the same check the HTTP routes use, and a\n // free one.\n const abs = resolveInside(rootAbs, String(rel));\n if (!abs) throw new Error('Path escapes the repository root');\n return abs;\n };\n\n return {\n kind: 'node',\n root: rootAbs,\n name: path.basename(rootAbs),\n\n async list(dir: string): Promise<FileEntry[]> {\n const entries = await fs.readdir(toAbs(dir), { withFileTypes: true });\n const out: FileEntry[] = [];\n for (const e of entries) {\n if (e.isSymbolicLink()) continue; // no loops, no surprises\n const rel = dir ? dir + '/' + e.name : e.name;\n out.push({ name: e.name, path: rel, type: e.isDirectory() ? 'dir' : 'file' });\n }\n return out;\n },\n\n async read(rel: string): Promise<string> {\n return fs.readFile(toAbs(rel), 'utf8');\n },\n };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export declare function isCloneDir(dir: any): boolean;
2
+ export declare function assertGitUrl(url: any): string;
3
+ export declare function repoNameFromUrl(url: any): string;
4
+ export declare function cloneRepo(url: any): Promise<{
5
+ id: string;
6
+ dir: string;
7
+ }>;
8
+ export declare function removeClone(dir: any): Promise<void>;
9
+ export declare function removeCloneSync(dir: any): void;
@@ -1,95 +1,93 @@
1
1
  // Shallow-cloning for the git-URL flow. The URL is validated, then passed to
2
2
  // git as an argument array — never through a shell — so nothing in it gets
3
3
  // executed. Clones land in the OS temp dir and are removed on request.
4
-
5
4
  import { spawn } from 'node:child_process';
6
5
  import { promises as fs, rmSync } from 'node:fs';
7
6
  import os from 'node:os';
8
7
  import path from 'node:path';
9
8
  import crypto from 'node:crypto';
10
-
11
9
  const GIT_URL_RE = /^(https?:\/\/[^\s]+|git@[^\s:]+:[^\s]+|ssh:\/\/[^\s]+)$/;
12
10
  const CLONE_TIMEOUT_MS = 180_000;
13
-
14
11
  // Where clones go, and the only shape of path the removals below will touch.
15
12
  // One definition: a delete that walks a directory tree should not be guarded by
16
13
  // a condition restated at each call site.
17
14
  const CLONE_PREFIX = path.join(os.tmpdir(), 'onboarder-');
18
-
19
15
  export function isCloneDir(dir) {
20
- return typeof dir === 'string' && dir.startsWith(CLONE_PREFIX) && dir !== CLONE_PREFIX;
16
+ return typeof dir === 'string' && dir.startsWith(CLONE_PREFIX) && dir !== CLONE_PREFIX;
21
17
  }
22
-
23
18
  export function assertGitUrl(url) {
24
- if (typeof url !== 'string' || !GIT_URL_RE.test(url.trim())) {
25
- throw new Error('That doesn’t look like a git URL. https://, ssh:// or git@ forms work.');
26
- }
27
- return url.trim();
19
+ if (typeof url !== 'string' || !GIT_URL_RE.test(url.trim())) {
20
+ throw new Error('That doesn’t look like a git URL. https://, ssh:// or git@ forms work.');
21
+ }
22
+ return url.trim();
28
23
  }
29
-
30
24
  // The name people recognize, from any URL shape we accept:
31
25
  // https://github.com/org/repo.git, ssh://host/org/repo, git@host:org/repo
32
26
  export function repoNameFromUrl(url) {
33
- const clean = String(url).trim().replace(/\.git$/, '').replace(/\/+$/, '');
34
- const tail = !clean.includes('://') && clean.includes(':') ? clean.split(':').pop() : clean;
35
- return tail.split('/').pop() || 'repo';
27
+ const clean = String(url).trim().replace(/\.git$/, '').replace(/\/+$/, '');
28
+ const tail = !clean.includes('://') && clean.includes(':') ? clean.split(':').pop() : clean;
29
+ return tail.split('/').pop() || 'repo';
36
30
  }
37
-
38
31
  export async function cloneRepo(url) {
39
- const id = crypto.randomBytes(6).toString('hex');
40
- const dir = CLONE_PREFIX + id;
41
- try {
42
- // Blobless rather than shallow: `--depth 1` threw the history away, and
43
- // history is half of what this tool has to say (churn × complexity).
44
- // `--filter=blob:none` fetches every commit and tree but no file versions —
45
- // full history at a fraction of a full clone's weight; blobs arrive on
46
- // demand if anything ever checks one out. `--single-branch` keeps it to the
47
- // default branch: the newcomer cares about main, not every fork line.
48
- await run('git', ['clone', '--filter=blob:none', '--single-branch', '--quiet', url, dir]);
49
- } catch (err) {
50
- await fs.rm(dir, { recursive: true, force: true }).catch(() => {});
51
- throw err;
52
- }
53
- return { id, dir };
32
+ const id = crypto.randomBytes(6).toString('hex');
33
+ const dir = CLONE_PREFIX + id;
34
+ try {
35
+ // Blobless rather than shallow: `--depth 1` threw the history away, and
36
+ // history is half of what this tool has to say (churn × complexity).
37
+ // `--filter=blob:none` fetches every commit and tree but no file versions —
38
+ // full history at a fraction of a full clone's weight; blobs arrive on
39
+ // demand if anything ever checks one out. `--single-branch` keeps it to the
40
+ // default branch: the newcomer cares about main, not every fork line.
41
+ await run('git', ['clone', '--filter=blob:none', '--single-branch', '--quiet', url, dir]);
42
+ }
43
+ catch (err) {
44
+ await fs.rm(dir, { recursive: true, force: true }).catch((..._args) => { });
45
+ throw err;
46
+ }
47
+ return { id, dir };
54
48
  }
55
-
56
49
  export async function removeClone(dir) {
57
- if (!isCloneDir(dir)) return;
58
- await fs.rm(dir, { recursive: true, force: true }).catch(() => {});
50
+ if (!isCloneDir(dir))
51
+ return;
52
+ await fs.rm(dir, { recursive: true, force: true }).catch((..._args) => { });
59
53
  }
60
-
61
54
  // The version an exit handler can actually finish. A process on its way out will
62
55
  // not come back to resolve a promise, so the sweep on shutdown has to be
63
56
  // synchronous or it does not happen at all.
64
57
  export function removeCloneSync(dir) {
65
- if (!isCloneDir(dir)) return;
66
- try {
67
- rmSync(dir, { recursive: true, force: true });
68
- } catch {
69
- /* going down anyway; a leftover temp dir is not worth a crash on exit */
70
- }
58
+ if (!isCloneDir(dir))
59
+ return;
60
+ try {
61
+ rmSync(dir, { recursive: true, force: true });
62
+ }
63
+ catch {
64
+ /* going down anyway; a leftover temp dir is not worth a crash on exit */
65
+ }
71
66
  }
72
-
73
67
  function run(cmd, args) {
74
- return new Promise((resolve, reject) => {
75
- const child = spawn(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });
76
- let stderr = '';
77
- child.stderr.on('data', (chunk) => {
78
- stderr += chunk;
79
- if (stderr.length > 4000) stderr = stderr.slice(-4000);
68
+ return new Promise((resolve, reject) => {
69
+ const child = spawn(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });
70
+ let stderr = '';
71
+ child.stderr.on('data', (chunk) => {
72
+ stderr += chunk;
73
+ if (stderr.length > 4000)
74
+ stderr = stderr.slice(-4000);
75
+ });
76
+ const timer = setTimeout(() => {
77
+ child.kill('SIGKILL');
78
+ reject(new Error('git clone took too long and was stopped.'));
79
+ }, CLONE_TIMEOUT_MS);
80
+ child.on('error', (err) => {
81
+ clearTimeout(timer);
82
+ reject(new Error('Could not run git: ' + err.message));
83
+ });
84
+ child.on('close', (code) => {
85
+ clearTimeout(timer);
86
+ if (code === 0)
87
+ resolve();
88
+ else
89
+ reject(new Error('git clone failed. ' + (stderr.trim().split('\n').pop() || '')));
90
+ });
80
91
  });
81
- const timer = setTimeout(() => {
82
- child.kill('SIGKILL');
83
- reject(new Error('git clone took too long and was stopped.'));
84
- }, CLONE_TIMEOUT_MS);
85
- child.on('error', (err) => {
86
- clearTimeout(timer);
87
- reject(new Error('Could not run git: ' + err.message));
88
- });
89
- child.on('close', (code) => {
90
- clearTimeout(timer);
91
- if (code === 0) resolve();
92
- else reject(new Error('git clone failed. ' + (stderr.trim().split('\n').pop() || '')));
93
- });
94
- });
95
92
  }
93
+ //# sourceMappingURL=gitClone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitClone.js","sourceRoot":"","sources":["../src/server/gitClone.ts"],"names":[],"mappings":"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","sourcesContent":["// Shallow-cloning for the git-URL flow. The URL is validated, then passed to\n// git as an argument array — never through a shell — so nothing in it gets\n// executed. Clones land in the OS temp dir and are removed on request.\n\nimport { spawn } from 'node:child_process';\nimport { promises as fs, rmSync } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\n\nconst GIT_URL_RE = /^(https?:\\/\\/[^\\s]+|git@[^\\s:]+:[^\\s]+|ssh:\\/\\/[^\\s]+)$/;\nconst CLONE_TIMEOUT_MS = 180_000;\n\n// Where clones go, and the only shape of path the removals below will touch.\n// One definition: a delete that walks a directory tree should not be guarded by\n// a condition restated at each call site.\nconst CLONE_PREFIX = path.join(os.tmpdir(), 'onboarder-');\n\nexport function isCloneDir(dir) {\n return typeof dir === 'string' && dir.startsWith(CLONE_PREFIX) && dir !== CLONE_PREFIX;\n}\n\nexport function assertGitUrl(url) {\n if (typeof url !== 'string' || !GIT_URL_RE.test(url.trim())) {\n throw new Error('That doesn’t look like a git URL. https://, ssh:// or git@ forms work.');\n }\n return url.trim();\n}\n\n// The name people recognize, from any URL shape we accept:\n// https://github.com/org/repo.git, ssh://host/org/repo, git@host:org/repo\nexport function repoNameFromUrl(url) {\n const clean = String(url).trim().replace(/\\.git$/, '').replace(/\\/+$/, '');\n const tail = !clean.includes('://') && clean.includes(':') ? clean.split(':').pop() : clean;\n return tail.split('/').pop() || 'repo';\n}\n\nexport async function cloneRepo(url) {\n const id = crypto.randomBytes(6).toString('hex');\n const dir = CLONE_PREFIX + id;\n try {\n // Blobless rather than shallow: `--depth 1` threw the history away, and\n // history is half of what this tool has to say (churn × complexity).\n // `--filter=blob:none` fetches every commit and tree but no file versions —\n // full history at a fraction of a full clone's weight; blobs arrive on\n // demand if anything ever checks one out. `--single-branch` keeps it to the\n // default branch: the newcomer cares about main, not every fork line.\n await run('git', ['clone', '--filter=blob:none', '--single-branch', '--quiet', url, dir]);\n } catch (err) {\n await fs.rm(dir, { recursive: true, force: true }).catch((..._args: unknown[]) => {});\n throw err;\n }\n return { id, dir };\n}\n\nexport async function removeClone(dir) {\n if (!isCloneDir(dir)) return;\n await fs.rm(dir, { recursive: true, force: true }).catch((..._args: unknown[]) => {});\n}\n\n// The version an exit handler can actually finish. A process on its way out will\n// not come back to resolve a promise, so the sweep on shutdown has to be\n// synchronous or it does not happen at all.\nexport function removeCloneSync(dir) {\n if (!isCloneDir(dir)) return;\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n /* going down anyway; a leftover temp dir is not worth a crash on exit */\n }\n}\n\nfunction run(cmd, args) {\n return new Promise<void>((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });\n let stderr = '';\n child.stderr.on('data', (chunk) => {\n stderr += chunk;\n if (stderr.length > 4000) stderr = stderr.slice(-4000);\n });\n const timer = setTimeout(() => {\n child.kill('SIGKILL');\n reject(new Error('git clone took too long and was stopped.'));\n }, CLONE_TIMEOUT_MS);\n child.on('error', (err) => {\n clearTimeout(timer);\n reject(new Error('Could not run git: ' + err.message));\n });\n child.on('close', (code) => {\n clearTimeout(timer);\n if (code === 0) resolve();\n else reject(new Error('git clone failed. ' + (stderr.trim().split('\\n').pop() || '')));\n });\n });\n}\n"]}