codebase-onboarder 0.6.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 +174 -46
  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 +321 -329
  8. package/cli/explorer/advanced.js.map +1 -0
  9. package/cli/explorer/app.d.ts +1 -0
  10. package/cli/explorer/app.js +384 -313
  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 +368 -302
  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 +42 -44
  23. package/cli/explorer/github.js.map +1 -0
  24. package/cli/explorer/graphs.d.ts +7 -0
  25. package/cli/explorer/graphs.js +90 -95
  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 +143 -144
  35. package/cli/explorer/session.js.map +1 -0
  36. package/cli/explorer/views.d.ts +19 -0
  37. package/cli/explorer/views.js +465 -482
  38. package/cli/explorer/views.js.map +1 -0
  39. package/cli/explorer/wrap.d.ts +1 -0
  40. package/cli/explorer/wrap.js +28 -44
  41. package/cli/explorer/wrap.js.map +1 -0
  42. package/cli/main.d.ts +1 -0
  43. package/cli/main.js +214 -161
  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 +35 -44
  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 +152 -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 +37 -38
  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 -64
  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 +41 -43
  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/public/js/tree.js CHANGED
@@ -1,48 +1,47 @@
1
1
  // Sidebar file tree. Built from the plain path list, rendered with
2
2
  // collapsible folders, badges for entries and hubs, and a substring filter.
3
-
4
3
  import { escapeHtml } from './html.js';
5
-
6
4
  export function buildTree(allFiles) {
7
- const root = { name: '', path: '', dirs: new Map(), files: [] };
8
- for (const path of allFiles) {
9
- const parts = path.split('/');
10
- let node = root;
11
- for (let i = 0; i < parts.length - 1; i++) {
12
- const name = parts[i];
13
- if (!node.dirs.has(name)) {
14
- const dirPath = parts.slice(0, i + 1).join('/');
15
- node.dirs.set(name, { name, path: dirPath, dirs: new Map(), files: [] });
16
- }
17
- node = node.dirs.get(name);
5
+ const root = { name: '', path: '', dirs: new Map(), files: [] };
6
+ for (const path of allFiles) {
7
+ const parts = path.split('/');
8
+ let node = root;
9
+ for (let i = 0; i < parts.length - 1; i++) {
10
+ const name = parts[i];
11
+ if (!node.dirs.has(name)) {
12
+ const dirPath = parts.slice(0, i + 1).join('/');
13
+ node.dirs.set(name, { name, path: dirPath, dirs: new Map(), files: [] });
14
+ }
15
+ node = node.dirs.get(name);
16
+ }
17
+ node.files.push(path);
18
18
  }
19
- node.files.push(path);
20
- }
21
- return root;
19
+ return root;
22
20
  }
23
-
24
21
  // The folder node at `path`, or null if no such folder exists. `''` is the root,
25
22
  // which is a real folder here and not "no folder" — several callers pass the
26
23
  // empty string on purpose.
27
24
  export function findNode(root, path) {
28
- if (!path) return root;
29
- let node = root;
30
- for (const part of path.split('/')) {
31
- node = node.dirs?.get(part);
32
- if (!node) return null;
33
- }
34
- return node;
25
+ if (!path)
26
+ return root;
27
+ let node = root;
28
+ for (const part of path.split('/')) {
29
+ node = node.dirs?.get(part);
30
+ if (!node)
31
+ return null;
32
+ }
33
+ return node;
35
34
  }
36
-
37
35
  // Files at or below a node. Used to rank folders by size when choosing which
38
36
  // ones are worth documenting.
39
37
  export function countFiles(node) {
40
- if (!node) return 0;
41
- let n = node.files.length;
42
- for (const dir of node.dirs.values()) n += countFiles(dir);
43
- return n;
38
+ if (!node)
39
+ return 0;
40
+ let n = node.files.length;
41
+ for (const dir of node.dirs.values())
42
+ n += countFiles(dir);
43
+ return n;
44
44
  }
45
-
46
45
  // A copy of the tree with the filtered-out files removed, and with folders that
47
46
  // end up holding nothing removed too — otherwise the tree map fills with empty
48
47
  // scaffolding whose labels no longer match anything inside them.
@@ -56,145 +55,149 @@ export function countFiles(node) {
56
55
  // The predicates are passed in rather than built here: this file has to stay
57
56
  // importable in Node to be testable, which rules out reaching into `/shared/`.
58
57
  export function pruneTree(root, include, matchesFolder = null) {
59
- if (!include) return root;
60
- const walk = (node) => {
61
- if (node.path && matchesFolder?.(node.path)) return node;
62
- const dirs = new Map();
63
- for (const [name, child] of node.dirs) {
64
- const pruned = walk(child);
65
- if (pruned) dirs.set(name, pruned);
66
- }
67
- const files = node.files.filter(include);
68
- if (!node.path) return { ...node, dirs, files }; // the root always survives
69
- if (!dirs.size && !files.length) return null;
70
- return { ...node, dirs, files };
71
- };
72
- return walk(root);
58
+ if (!include)
59
+ return root;
60
+ const walk = (node) => {
61
+ if (node.path && matchesFolder?.(node.path))
62
+ return node;
63
+ const dirs = new Map();
64
+ for (const [name, child] of node.dirs) {
65
+ const pruned = walk(child);
66
+ if (pruned)
67
+ dirs.set(name, pruned);
68
+ }
69
+ const files = node.files.filter(include);
70
+ if (!node.path)
71
+ return { ...node, dirs, files }; // the root always survives
72
+ if (!dirs.size && !files.length)
73
+ return null;
74
+ return { ...node, dirs, files };
75
+ };
76
+ return walk(root);
73
77
  }
74
-
75
78
  // The set of folder ids to treat as open when the reader picks a depth from the
76
79
  // stage's depth selector. Ids are `dir:` + path, which is the vocabulary the tree
77
80
  // map's cells use; `dir:` alone is the root.
78
81
  export function expandedToDepth(root, maxDepth) {
79
- const out = new Set();
80
- const walk = (node, depth) => {
81
- if (depth > maxDepth) return;
82
- out.add('dir:' + node.path);
83
- for (const d of node.dirs.values()) walk(d, depth + 1);
84
- };
85
- walk(root, 0);
86
- return out;
82
+ const out = new Set();
83
+ const walk = (node, depth) => {
84
+ if (depth > maxDepth)
85
+ return;
86
+ out.add('dir:' + node.path);
87
+ for (const d of node.dirs.values())
88
+ walk(d, depth + 1);
89
+ };
90
+ walk(root, 0);
91
+ return out;
87
92
  }
88
-
89
93
  const openState = new Set();
90
-
91
94
  export function resetOpenState() {
92
- openState.clear();
95
+ openState.clear();
93
96
  }
94
-
95
97
  export function renderTree(container, tree, ctx) {
96
- const { facts, filter = '', selected, onFile, onFolder } = ctx;
97
- container.innerHTML = '';
98
- const parsedSet = new Set(ctx.parsedPaths || []);
99
- const hubSet = new Set((facts?.hubs || []).filter((h) => h.fanIn >= 4).map((h) => h.path));
100
- const entrySet = new Set(facts?.entries || []);
101
- const needle = filter.trim().toLowerCase();
102
-
103
- const frag = document.createDocumentFragment();
104
-
105
- const renderDir = (node, parent) => {
106
- const dirNames = [...node.dirs.keys()].sort();
107
- const filePaths = node.files.slice().sort();
108
-
109
- for (const name of dirNames) {
110
- const child = node.dirs.get(name);
111
- if (needle && !subtreeHas(child, needle)) continue;
112
-
113
- const wrap = document.createElement('div');
114
- wrap.className = 'tree-dir' + (openState.has(child.path) || needle ? ' is-open' : '');
115
-
116
- const head = document.createElement('div');
117
- head.className = 'tree-dir-head';
118
- const count = countFiles(child);
119
- head.innerHTML = `<span class="tree-caret">▸</span><span>${escapeHtml(name)}/</span><span class="tree-count">${count}</span>`;
120
- head.addEventListener('click', () => {
121
- if (openState.has(child.path)) openState.delete(child.path);
122
- else openState.add(child.path);
123
- wrap.classList.toggle('is-open');
124
- onFolder?.(child.path);
125
- });
126
- wrap.appendChild(head);
127
-
128
- const kids = document.createElement('div');
129
- kids.className = 'tree-children';
130
- renderDir(child, kids);
131
- wrap.appendChild(kids);
132
- parent.appendChild(wrap);
133
- }
134
-
135
- for (const path of filePaths) {
136
- const name = path.split('/').pop();
137
- if (needle && !path.toLowerCase().includes(needle)) continue;
138
- const el = document.createElement('div');
139
- el.className = 'tree-file' + (path === selected ? ' is-selected' : '');
140
- el.dataset.path = path;
141
-
142
- const dot = document.createElement('span');
143
- dot.className = 'tree-dot';
144
- dot.style.backgroundColor = getExtColor(path);
145
- el.appendChild(dot);
146
-
147
- const label = document.createElement('span');
148
- label.textContent = name;
149
- if (!parsedSet.has(path)) label.style.color = 'var(--faint)';
150
- el.appendChild(label);
151
-
152
- if (entrySet.has(path)) {
153
- el.appendChild(badge('in', 'is-entry'));
154
- } else if (hubSet.has(path)) {
155
- el.appendChild(badge('hub', 'is-hub'));
156
- }
157
- el.addEventListener('click', () => onFile?.(path));
158
- parent.appendChild(el);
98
+ const { facts, filter = '', selected, onFile, onFolder } = ctx;
99
+ container.innerHTML = '';
100
+ const parsedSet = new Set(ctx.parsedPaths || []);
101
+ const hubSet = new Set((facts?.hubs || []).filter((h) => h.fanIn >= 4).map((h) => h.path));
102
+ const entrySet = new Set(facts?.entries || []);
103
+ const needle = filter.trim().toLowerCase();
104
+ const frag = document.createDocumentFragment();
105
+ const renderDir = (node, parent) => {
106
+ const dirNames = [...node.dirs.keys()].sort();
107
+ const filePaths = node.files.slice().sort();
108
+ for (const name of dirNames) {
109
+ const child = node.dirs.get(name);
110
+ if (needle && !subtreeHas(child, needle))
111
+ continue;
112
+ const wrap = document.createElement('div');
113
+ wrap.className = 'tree-dir' + (openState.has(child.path) || needle ? ' is-open' : '');
114
+ const head = document.createElement('div');
115
+ head.className = 'tree-dir-head';
116
+ const count = countFiles(child);
117
+ head.innerHTML = `<span class="tree-caret">▸</span><span>${escapeHtml(name)}/</span><span class="tree-count">${count}</span>`;
118
+ head.addEventListener('click', () => {
119
+ if (openState.has(child.path))
120
+ openState.delete(child.path);
121
+ else
122
+ openState.add(child.path);
123
+ wrap.classList.toggle('is-open');
124
+ onFolder?.(child.path);
125
+ });
126
+ wrap.appendChild(head);
127
+ const kids = document.createElement('div');
128
+ kids.className = 'tree-children';
129
+ renderDir(child, kids);
130
+ wrap.appendChild(kids);
131
+ parent.appendChild(wrap);
132
+ }
133
+ for (const path of filePaths) {
134
+ const name = path.split('/').pop();
135
+ if (needle && !path.toLowerCase().includes(needle))
136
+ continue;
137
+ const el = document.createElement('div');
138
+ el.className = 'tree-file' + (path === selected ? ' is-selected' : '');
139
+ el.dataset.path = path;
140
+ const dot = document.createElement('span');
141
+ dot.className = 'tree-dot';
142
+ dot.style.backgroundColor = getExtColor(path);
143
+ el.appendChild(dot);
144
+ const label = document.createElement('span');
145
+ label.textContent = name;
146
+ if (!parsedSet.has(path))
147
+ label.style.color = 'var(--faint)';
148
+ el.appendChild(label);
149
+ if (entrySet.has(path)) {
150
+ el.appendChild(badge('in', 'is-entry'));
151
+ }
152
+ else if (hubSet.has(path)) {
153
+ el.appendChild(badge('hub', 'is-hub'));
154
+ }
155
+ el.addEventListener('click', () => onFile?.(path));
156
+ parent.appendChild(el);
157
+ }
158
+ };
159
+ renderDir(tree, frag);
160
+ container.appendChild(frag);
161
+ if (!container.children.length) {
162
+ container.innerHTML = '<p style="padding:10px;color:var(--faint)">Nothing matches that filter.</p>';
159
163
  }
160
- };
161
-
162
- renderDir(tree, frag);
163
- container.appendChild(frag);
164
-
165
- if (!container.children.length) {
166
- container.innerHTML = '<p style="padding:10px;color:var(--faint)">Nothing matches that filter.</p>';
167
- }
168
164
  }
169
-
170
165
  function subtreeHas(node, needle) {
171
- if (node.path.toLowerCase().includes(needle)) return true;
172
- for (const f of node.files) if (f.toLowerCase().includes(needle)) return true;
173
- for (const child of node.dirs.values()) if (subtreeHas(child, needle)) return true;
174
- return false;
166
+ if (node.path.toLowerCase().includes(needle))
167
+ return true;
168
+ for (const f of node.files)
169
+ if (f.toLowerCase().includes(needle))
170
+ return true;
171
+ for (const child of node.dirs.values())
172
+ if (subtreeHas(child, needle))
173
+ return true;
174
+ return false;
175
175
  }
176
-
177
176
  function badge(text, cls) {
178
- const b = document.createElement('span');
179
- b.className = 'tree-badge ' + cls;
180
- b.textContent = text;
181
- return b;
177
+ const b = document.createElement('span');
178
+ b.className = 'tree-badge ' + cls;
179
+ b.textContent = text;
180
+ return b;
182
181
  }
183
-
184
182
  function getExtColor(path) {
185
- const ext = (path.match(/\.([A-Za-z0-9]+)$/) || [, ''])[1].toLowerCase();
186
- switch (ext) {
187
- case 'ts': case 'tsx': return '#3178c6';
188
- case 'js': case 'jsx': case 'mjs': case 'cjs': return '#f7df1e';
189
- case 'py': return '#3572A5';
190
- case 'rs': return '#dea584';
191
- case 'go': return '#00ADD8';
192
- case 'java': return '#b07219';
193
- case 'cs': return '#178600';
194
- case 'html': return '#e34c26';
195
- case 'css': return '#563d7c';
196
- case 'json': return '#cbcb41';
197
- case 'md': return '#083fa1';
198
- default: return 'var(--faint)';
199
- }
183
+ const ext = (path.match(/\.([A-Za-z0-9]+)$/) || [, ''])[1].toLowerCase();
184
+ switch (ext) {
185
+ case 'ts':
186
+ case 'tsx': return '#3178c6';
187
+ case 'js':
188
+ case 'jsx':
189
+ case 'mjs':
190
+ case 'cjs': return '#f7df1e';
191
+ case 'py': return '#3572A5';
192
+ case 'rs': return '#dea584';
193
+ case 'go': return '#00ADD8';
194
+ case 'java': return '#b07219';
195
+ case 'cs': return '#178600';
196
+ case 'html': return '#e34c26';
197
+ case 'css': return '#563d7c';
198
+ case 'json': return '#cbcb41';
199
+ case 'md': return '#083fa1';
200
+ default: return 'var(--faint)';
201
+ }
200
202
  }
203
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/public/js/tree.ts"],"names":[],"mappings":"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","sourcesContent":["// Sidebar file tree. Built from the plain path list, rendered with\n// collapsible folders, badges for entries and hubs, and a substring filter.\n\nimport { escapeHtml } from './html.js';\n\nexport function buildTree(allFiles) {\n const root = { name: '', path: '', dirs: new Map(), files: [] };\n for (const path of allFiles) {\n const parts = path.split('/');\n let node = root;\n for (let i = 0; i < parts.length - 1; i++) {\n const name = parts[i];\n if (!node.dirs.has(name)) {\n const dirPath = parts.slice(0, i + 1).join('/');\n node.dirs.set(name, { name, path: dirPath, dirs: new Map(), files: [] });\n }\n node = node.dirs.get(name);\n }\n node.files.push(path);\n }\n return root;\n}\n\n// The folder node at `path`, or null if no such folder exists. `''` is the root,\n// which is a real folder here and not \"no folder\" — several callers pass the\n// empty string on purpose.\nexport function findNode(root, path) {\n if (!path) return root;\n let node = root;\n for (const part of path.split('/')) {\n node = node.dirs?.get(part);\n if (!node) return null;\n }\n return node;\n}\n\n// Files at or below a node. Used to rank folders by size when choosing which\n// ones are worth documenting.\nexport function countFiles(node) {\n if (!node) return 0;\n let n = node.files.length;\n for (const dir of node.dirs.values()) n += countFiles(dir);\n return n;\n}\n\n// A copy of the tree with the filtered-out files removed, and with folders that\n// end up holding nothing removed too — otherwise the tree map fills with empty\n// scaffolding whose labels no longer match anything inside them.\n//\n// `include` is a predicate over file paths, and `matchesFolder` an optional one\n// over folder paths: a folder whose own name matches keeps its whole branch,\n// because searching for \"lib\" should show you lib's contents rather than an empty\n// folder named lib. The root always survives, even when nothing matches, so the\n// caller has something to render instead of null.\n//\n// The predicates are passed in rather than built here: this file has to stay\n// importable in Node to be testable, which rules out reaching into `/shared/`.\nexport function pruneTree(root, include, matchesFolder = null) {\n if (!include) return root;\n const walk = (node) => {\n if (node.path && matchesFolder?.(node.path)) return node;\n const dirs = new Map();\n for (const [name, child] of node.dirs) {\n const pruned = walk(child);\n if (pruned) dirs.set(name, pruned);\n }\n const files = node.files.filter(include);\n if (!node.path) return { ...node, dirs, files }; // the root always survives\n if (!dirs.size && !files.length) return null;\n return { ...node, dirs, files };\n };\n return walk(root);\n}\n\n// The set of folder ids to treat as open when the reader picks a depth from the\n// stage's depth selector. Ids are `dir:` + path, which is the vocabulary the tree\n// map's cells use; `dir:` alone is the root.\nexport function expandedToDepth(root, maxDepth): Set<string> {\n const out = new Set<string>();\n const walk = (node, depth) => {\n if (depth > maxDepth) return;\n out.add('dir:' + node.path);\n for (const d of node.dirs.values()) walk(d, depth + 1);\n };\n walk(root, 0);\n return out;\n}\n\nconst openState = new Set();\n\nexport function resetOpenState() {\n openState.clear();\n}\n\nexport function renderTree(container, tree, ctx) {\n const { facts, filter = '', selected, onFile, onFolder } = ctx;\n container.innerHTML = '';\n const parsedSet = new Set(ctx.parsedPaths || []);\n const hubSet = new Set((facts?.hubs || []).filter((h) => h.fanIn >= 4).map((h) => h.path));\n const entrySet = new Set(facts?.entries || []);\n const needle = filter.trim().toLowerCase();\n\n const frag = document.createDocumentFragment();\n\n const renderDir = (node, parent) => {\n const dirNames = [...node.dirs.keys()].sort();\n const filePaths = node.files.slice().sort();\n\n for (const name of dirNames) {\n const child = node.dirs.get(name);\n if (needle && !subtreeHas(child, needle)) continue;\n\n const wrap = document.createElement('div');\n wrap.className = 'tree-dir' + (openState.has(child.path) || needle ? ' is-open' : '');\n\n const head = document.createElement('div');\n head.className = 'tree-dir-head';\n const count = countFiles(child);\n head.innerHTML = `<span class=\"tree-caret\">▸</span><span>${escapeHtml(name)}/</span><span class=\"tree-count\">${count}</span>`;\n head.addEventListener('click', () => {\n if (openState.has(child.path)) openState.delete(child.path);\n else openState.add(child.path);\n wrap.classList.toggle('is-open');\n onFolder?.(child.path);\n });\n wrap.appendChild(head);\n\n const kids = document.createElement('div');\n kids.className = 'tree-children';\n renderDir(child, kids);\n wrap.appendChild(kids);\n parent.appendChild(wrap);\n }\n\n for (const path of filePaths) {\n const name = path.split('/').pop();\n if (needle && !path.toLowerCase().includes(needle)) continue;\n const el = document.createElement('div');\n el.className = 'tree-file' + (path === selected ? ' is-selected' : '');\n el.dataset.path = path;\n\n const dot = document.createElement('span');\n dot.className = 'tree-dot';\n dot.style.backgroundColor = getExtColor(path);\n el.appendChild(dot);\n\n const label = document.createElement('span');\n label.textContent = name;\n if (!parsedSet.has(path)) label.style.color = 'var(--faint)';\n el.appendChild(label);\n\n if (entrySet.has(path)) {\n el.appendChild(badge('in', 'is-entry'));\n } else if (hubSet.has(path)) {\n el.appendChild(badge('hub', 'is-hub'));\n }\n el.addEventListener('click', () => onFile?.(path));\n parent.appendChild(el);\n }\n };\n\n renderDir(tree, frag);\n container.appendChild(frag);\n\n if (!container.children.length) {\n container.innerHTML = '<p style=\"padding:10px;color:var(--faint)\">Nothing matches that filter.</p>';\n }\n}\n\nfunction subtreeHas(node, needle) {\n if (node.path.toLowerCase().includes(needle)) return true;\n for (const f of node.files) if (f.toLowerCase().includes(needle)) return true;\n for (const child of node.dirs.values()) if (subtreeHas(child, needle)) return true;\n return false;\n}\n\nfunction badge(text, cls) {\n const b = document.createElement('span');\n b.className = 'tree-badge ' + cls;\n b.textContent = text;\n return b;\n}\n\nfunction getExtColor(path) {\n const ext = (path.match(/\\.([A-Za-z0-9]+)$/) || [, ''])[1].toLowerCase();\n switch (ext) {\n case 'ts': case 'tsx': return '#3178c6';\n case 'js': case 'jsx': case 'mjs': case 'cjs': return '#f7df1e';\n case 'py': return '#3572A5';\n case 'rs': return '#dea584';\n case 'go': return '#00ADD8';\n case 'java': return '#b07219';\n case 'cs': return '#178600';\n case 'html': return '#e34c26';\n case 'css': return '#563d7c';\n case 'json': return '#cbcb41';\n case 'md': return '#083fa1';\n default: return 'var(--faint)';\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare function renderWorkflows(container: any, { scan }: {
2
+ scan: any;
3
+ }): void;
@@ -1,25 +1,21 @@
1
1
  // GitHub Actions & CI/CD Workflows View.
2
2
  // Renders workflow triggers, job dependency DAGs, and execution steps.
3
-
4
3
  import { escapeHtml } from './html.js';
5
-
6
4
  export function renderWorkflows(container, { scan }) {
7
- if (!container) return;
8
-
9
- const workflows = scan?.workflows || [];
10
-
11
- if (!workflows.length) {
12
- container.innerHTML = `
5
+ if (!container)
6
+ return;
7
+ const workflows = scan?.workflows || [];
8
+ if (!workflows.length) {
9
+ container.innerHTML = `
13
10
  <div class="workflows-empty-wrap">
14
11
  <div class="wf-empty-icon">⚙️</div>
15
12
  <h3>No CI/CD Workflows Detected</h3>
16
13
  <p>No GitHub Actions workflows were found under <code>.github/workflows/</code> in this repository.</p>
17
14
  </div>
18
15
  `;
19
- return;
20
- }
21
-
22
- container.innerHTML = `
16
+ return;
17
+ }
18
+ container.innerHTML = `
23
19
  <div class="workflows-layout">
24
20
  <header class="workflows-header">
25
21
  <div>
@@ -35,12 +31,10 @@ export function renderWorkflows(container, { scan }) {
35
31
  </div>
36
32
  `;
37
33
  }
38
-
39
34
  function renderWorkflowCard(wf) {
40
- const triggers = wf.triggers || [];
41
- const jobs = wf.jobs || [];
42
-
43
- return `
35
+ const triggers = wf.triggers || [];
36
+ const jobs = wf.jobs || [];
37
+ return `
44
38
  <article class="workflow-card">
45
39
  <div class="wf-card-top">
46
40
  <div class="wf-title-area">
@@ -62,12 +56,10 @@ function renderWorkflowCard(wf) {
62
56
  </article>
63
57
  `;
64
58
  }
65
-
66
59
  function renderJobCard(job) {
67
- const steps = job.steps || [];
68
- const needs = job.needs || [];
69
-
70
- return `
60
+ const steps = job.steps || [];
61
+ const needs = job.needs || [];
62
+ return `
71
63
  <div class="wf-job-card">
72
64
  <div class="wf-job-head">
73
65
  <span class="wf-job-name">${escapeHtml(job.name)}</span>
@@ -93,3 +85,4 @@ function renderJobCard(job) {
93
85
  </div>
94
86
  `;
95
87
  }
88
+ //# sourceMappingURL=workflowsView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowsView.js","sourceRoot":"","sources":["../../src/public/js/workflowsView.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,uEAAuE;AAEvE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,MAAM,UAAU,eAAe,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE;IACjD,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC;IAExC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QACtB,SAAS,CAAC,SAAS,GAAG;;;;;;KAMrB,CAAC;QACF,OAAO;IACT,CAAC;IAED,SAAS,CAAC,SAAS,GAAG;;;;;;;sCAOc,SAAS,CAAC,MAAM,YAAY,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG;;;;UAIzF,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;GAG7D,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,EAAE;IAC5B,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;IAE3B,OAAO;;;;kCAIyB,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC;uCACd,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC;;;;YAI9C,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iCAAiC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,mDAAmD;;;;;oDAKpG,IAAI,CAAC,MAAM;;YAEnD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,2CAA2C;;;;GAIlG,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAAG;IACxB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAE9B,OAAO;;;oCAG2B,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;kCACtB,UAAU,CAAC,GAAG,CAAC,MAAM,IAAI,eAAe,CAAC;;;QAGnE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;;;YAGX,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,6BAA6B,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;OAEnF,CAAC,CAAC,CAAC,EAAE;;;UAGF,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC;;wCAEQ,GAAG,GAAG,CAAC;gDACC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;cAC3E,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,uCAAuC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;SAE1H,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;GAGhB,CAAC;AACJ,CAAC","sourcesContent":["// GitHub Actions & CI/CD Workflows View.\n// Renders workflow triggers, job dependency DAGs, and execution steps.\n\nimport { escapeHtml } from './html.js';\n\nexport function renderWorkflows(container, { scan }) {\n if (!container) return;\n\n const workflows = scan?.workflows || [];\n\n if (!workflows.length) {\n container.innerHTML = `\n <div class=\"workflows-empty-wrap\">\n <div class=\"wf-empty-icon\">⚙️</div>\n <h3>No CI/CD Workflows Detected</h3>\n <p>No GitHub Actions workflows were found under <code>.github/workflows/</code> in this repository.</p>\n </div>\n `;\n return;\n }\n\n container.innerHTML = `\n <div class=\"workflows-layout\">\n <header class=\"workflows-header\">\n <div>\n <h2>CI/CD Workflows</h2>\n <span class=\"wf-sub\">Automated build, test, and release pipelines</span>\n </div>\n <div class=\"wf-count-badge\">${workflows.length} workflow${workflows.length === 1 ? '' : 's'}</div>\n </header>\n\n <div class=\"workflows-list\">\n ${workflows.map((wf) => renderWorkflowCard(wf)).join('')}\n </div>\n </div>\n `;\n}\n\nfunction renderWorkflowCard(wf) {\n const triggers = wf.triggers || [];\n const jobs = wf.jobs || [];\n\n return `\n <article class=\"workflow-card\">\n <div class=\"wf-card-top\">\n <div class=\"wf-title-area\">\n <span class=\"wf-name\">${escapeHtml(wf.name)}</span>\n <span class=\"wf-file-path\">${escapeHtml(wf.file)}</span>\n </div>\n <div class=\"wf-triggers\">\n <span class=\"wf-trigger-label\">Triggers:</span>\n ${triggers.map((t) => `<span class=\"wf-trigger-chip\">${escapeHtml(t)}</span>`).join('') || '<span class=\"wf-trigger-chip is-none\">none</span>'}\n </div>\n </div>\n\n <div class=\"wf-jobs-pipeline\">\n <div class=\"wf-jobs-title\">Pipeline Jobs (${jobs.length})</div>\n <div class=\"wf-jobs-grid\">\n ${jobs.map((j) => renderJobCard(j)).join('') || '<p class=\"wf-no-jobs\">No jobs defined</p>'}\n </div>\n </div>\n </article>\n `;\n}\n\nfunction renderJobCard(job) {\n const steps = job.steps || [];\n const needs = job.needs || [];\n\n return `\n <div class=\"wf-job-card\">\n <div class=\"wf-job-head\">\n <span class=\"wf-job-name\">${escapeHtml(job.name)}</span>\n <span class=\"wf-job-os\">${escapeHtml(job.runsOn || 'ubuntu-latest')}</span>\n </div>\n\n ${needs.length ? `\n <div class=\"wf-job-needs\">\n <span>Depends on:</span>\n ${needs.map((n) => `<span class=\"wf-need-tag\">${escapeHtml(n)}</span>`).join('')}\n </div>\n ` : ''}\n\n <div class=\"wf-steps-list\">\n ${steps.map((s, idx) => `\n <div class=\"wf-step-item\">\n <span class=\"wf-step-num\">${idx + 1}</span>\n <span class=\"wf-step-name\" title=\"${escapeHtml(s.name)}\">${escapeHtml(s.name)}</span>\n ${s.uses ? `<span class=\"wf-step-action\" title=\"${escapeHtml(s.uses)}\">${escapeHtml(s.uses.split('@')[0])}</span>` : ''}\n </div>\n `).join('')}\n </div>\n </div>\n `;\n}\n"]}