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.
- package/README.md +196 -47
- package/bin/build-if-source.js +23 -0
- package/cli/commands.d.ts +26 -0
- package/cli/commands.js +836 -811
- package/cli/commands.js.map +1 -0
- package/cli/explorer/advanced.d.ts +14 -0
- package/cli/explorer/advanced.js +449 -0
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +396 -229
- package/cli/explorer/app.js.map +1 -0
- package/cli/explorer/assistant.d.ts +9 -0
- package/cli/explorer/assistant.js +58 -0
- package/cli/explorer/assistant.js.map +1 -0
- package/cli/explorer/commands.d.ts +17 -0
- package/cli/explorer/commands.js +400 -162
- package/cli/explorer/commands.js.map +1 -0
- package/cli/explorer/featureViews.d.ts +124 -0
- package/cli/explorer/featureViews.js +179 -0
- package/cli/explorer/featureViews.js.map +1 -0
- package/cli/explorer/github.d.ts +21 -0
- package/cli/explorer/github.js +73 -0
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +173 -0
- package/cli/explorer/graphs.js.map +1 -0
- package/cli/explorer/picker.d.ts +22 -0
- package/cli/explorer/picker.js +150 -0
- package/cli/explorer/picker.js.map +1 -0
- package/cli/explorer/report.d.ts +85 -0
- package/cli/explorer/report.js +60 -0
- package/cli/explorer/report.js.map +1 -0
- package/cli/explorer/session.d.ts +449 -0
- package/cli/explorer/session.js +184 -95
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +496 -412
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +33 -0
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +235 -138
- package/cli/main.js.map +1 -0
- package/cli/prompt.d.ts +5 -0
- package/cli/prompt.js +84 -80
- package/cli/prompt.js.map +1 -0
- package/cli/scanOptions.d.ts +2 -0
- package/cli/scanOptions.js +11 -0
- package/cli/scanOptions.js.map +1 -0
- package/cli/ui.d.ts +33 -0
- package/cli/ui.js +55 -35
- package/cli/ui.js.map +1 -0
- package/cli/wizard.d.ts +187 -0
- package/cli/wizard.js +304 -284
- package/cli/wizard.js.map +1 -0
- package/package.json +15 -4
- package/public/app.d.ts +1 -0
- package/public/app.js +1057 -1009
- package/public/app.js.map +1 -0
- package/public/browser.d.ts +68 -0
- package/public/browser.js +2 -0
- package/public/browser.js.map +1 -0
- package/public/index.html +30 -13
- package/public/js/about.d.ts +6 -0
- package/public/js/about.js +165 -172
- package/public/js/about.js.map +1 -0
- package/public/js/aiDraft.d.ts +3 -0
- package/public/js/aiDraft.js +116 -124
- package/public/js/aiDraft.js.map +1 -0
- package/public/js/analysisPanel.d.ts +7 -0
- package/public/js/analysisPanel.js +166 -166
- package/public/js/analysisPanel.js.map +1 -0
- package/public/js/analysisReport.d.ts +38 -0
- package/public/js/analysisReport.js +105 -110
- package/public/js/analysisReport.js.map +1 -0
- package/public/js/api.d.ts +26 -0
- package/public/js/api.js +176 -167
- package/public/js/api.js.map +1 -0
- package/public/js/atlas.d.ts +6 -0
- package/public/js/atlas.js +50 -58
- package/public/js/atlas.js.map +1 -0
- package/public/js/blameView.d.ts +2 -0
- package/public/js/blameView.js +36 -37
- package/public/js/blameView.js.map +1 -0
- package/public/js/codeTab.d.ts +3 -0
- package/public/js/codeTab.js +175 -180
- package/public/js/codeTab.js.map +1 -0
- package/public/js/codeViewer.d.ts +5 -0
- package/public/js/codeViewer.js +162 -150
- package/public/js/codeViewer.js.map +1 -0
- package/public/js/components/FileChip.d.ts +9 -0
- package/public/js/components/FileChip.js +45 -49
- package/public/js/components/FileChip.js.map +1 -0
- package/public/js/components/MetricSparkline.d.ts +7 -0
- package/public/js/components/MetricSparkline.js +35 -44
- package/public/js/components/MetricSparkline.js.map +1 -0
- package/public/js/components/RiskBadge.d.ts +9 -0
- package/public/js/components/RiskBadge.js +55 -63
- package/public/js/components/RiskBadge.js.map +1 -0
- package/public/js/deepAnalysisView.d.ts +99 -0
- package/public/js/deepAnalysisView.js +282 -302
- package/public/js/deepAnalysisView.js.map +1 -0
- package/public/js/diagramPane.d.ts +8 -0
- package/public/js/diagramPane.js +133 -135
- package/public/js/diagramPane.js.map +1 -0
- package/public/js/diffView.d.ts +6 -0
- package/public/js/diffView.js +222 -209
- package/public/js/diffView.js.map +1 -0
- package/public/js/docsView.d.ts +2 -0
- package/public/js/docsView.js +441 -442
- package/public/js/docsView.js.map +1 -0
- package/public/js/dom.d.ts +189 -0
- package/public/js/dom.js +5 -0
- package/public/js/dom.js.map +1 -0
- package/public/js/fileSourceBrowser.d.ts +5 -0
- package/public/js/fileSourceBrowser.js +33 -41
- package/public/js/fileSourceBrowser.js.map +1 -0
- package/public/js/flameGraph.d.ts +5 -0
- package/public/js/flameGraph.js +62 -72
- package/public/js/flameGraph.js.map +1 -0
- package/public/js/forceGraph.d.ts +10 -0
- package/public/js/forceGraph.js +581 -556
- package/public/js/forceGraph.js.map +1 -0
- package/public/js/heatmap.d.ts +4 -0
- package/public/js/heatmap.js +142 -164
- package/public/js/heatmap.js.map +1 -0
- package/public/js/highlight.d.ts +2 -0
- package/public/js/highlight.js +116 -107
- package/public/js/highlight.js.map +1 -0
- package/public/js/html.d.ts +2 -0
- package/public/js/html.js +14 -44
- package/public/js/html.js.map +1 -0
- package/public/js/insightsView.d.ts +5 -0
- package/public/js/insightsView.js +93 -111
- package/public/js/insightsView.js.map +1 -0
- package/public/js/inspector.d.ts +37 -0
- package/public/js/inspector.js +416 -445
- package/public/js/inspector.js.map +1 -0
- package/public/js/llm.d.ts +113 -0
- package/public/js/llm.js +233 -272
- package/public/js/llm.js.map +1 -0
- package/public/js/mapView.d.ts +3 -0
- package/public/js/mapView.js +121 -120
- package/public/js/mapView.js.map +1 -0
- package/public/js/markdown.d.ts +3 -0
- package/public/js/markdown.js +84 -91
- package/public/js/markdown.js.map +1 -0
- package/public/js/mindmap.d.ts +32 -0
- package/public/js/mindmap.js +244 -259
- package/public/js/mindmap.js.map +1 -0
- package/public/js/mobilePanels.d.ts +5 -0
- package/public/js/mobilePanels.js +50 -0
- package/public/js/mobilePanels.js.map +1 -0
- package/public/js/repoFiles.d.ts +12 -0
- package/public/js/repoFiles.js +28 -27
- package/public/js/repoFiles.js.map +1 -0
- package/public/js/reviewView.d.ts +28 -0
- package/public/js/reviewView.js +256 -0
- package/public/js/reviewView.js.map +1 -0
- package/public/js/sbomView.d.ts +3 -0
- package/public/js/sbomView.js +59 -67
- package/public/js/sbomView.js.map +1 -0
- package/public/js/scanCache.d.ts +16 -0
- package/public/js/scanCache.js +67 -65
- package/public/js/scanCache.js.map +1 -0
- package/public/js/search.d.ts +7 -0
- package/public/js/search.js +868 -800
- package/public/js/search.js.map +1 -0
- package/public/js/searchLocal.d.ts +42 -0
- package/public/js/searchLocal.js +87 -0
- package/public/js/searchLocal.js.map +1 -0
- package/public/js/serverSettings.d.ts +5 -0
- package/public/js/serverSettings.js +153 -164
- package/public/js/serverSettings.js.map +1 -0
- package/public/js/state.d.ts +798 -0
- package/public/js/state.js +114 -122
- package/public/js/state.js.map +1 -0
- package/public/js/tour.d.ts +6 -0
- package/public/js/tour.js +5 -7
- package/public/js/tour.js.map +1 -0
- package/public/js/transitions.d.ts +2 -0
- package/public/js/transitions.js +9 -10
- package/public/js/transitions.js.map +1 -0
- package/public/js/tree.d.ts +12 -0
- package/public/js/tree.js +157 -154
- package/public/js/tree.js.map +1 -0
- package/public/js/workflowsView.d.ts +3 -0
- package/public/js/workflowsView.js +15 -22
- package/public/js/workflowsView.js.map +1 -0
- package/public/search.css +495 -0
- package/public/styles.css +163 -346
- package/public/workbench.css +166 -0
- package/server/apiAuth.d.ts +3 -0
- package/server/apiAuth.js +18 -20
- package/server/apiAuth.js.map +1 -0
- package/server/apiDiff.d.ts +2 -0
- package/server/apiDiff.js +20 -18
- package/server/apiDiff.js.map +1 -0
- package/server/apiDocs.d.ts +2 -0
- package/server/apiDocs.js +38 -40
- package/server/apiDocs.js.map +1 -0
- package/server/apiFile.d.ts +1 -0
- package/server/apiFile.js +14 -17
- package/server/apiFile.js.map +1 -0
- package/server/apiGitBlame.d.ts +2 -0
- package/server/apiGitBlame.js +50 -51
- package/server/apiGitBlame.js.map +1 -0
- package/server/apiMcp.d.ts +4 -0
- package/server/apiMcp.js +64 -66
- package/server/apiMcp.js.map +1 -0
- package/server/apiReview.d.ts +2 -0
- package/server/apiReview.js +45 -0
- package/server/apiReview.js.map +1 -0
- package/server/apiScan.d.ts +5 -0
- package/server/apiScan.js +90 -71
- package/server/apiScan.js.map +1 -0
- package/server/apiSearch.d.ts +33 -0
- package/server/apiSearch.js +184 -200
- package/server/apiSearch.js.map +1 -0
- package/server/apiSettings.d.ts +4 -0
- package/server/apiSettings.js +84 -85
- package/server/apiSettings.js.map +1 -0
- package/server/apiTools.d.ts +3 -0
- package/server/apiTools.js +55 -56
- package/server/apiTools.js.map +1 -0
- package/server/auth.d.ts +5 -0
- package/server/auth.js +54 -51
- package/server/auth.js.map +1 -0
- package/server/config.d.ts +42 -0
- package/server/config.js +189 -194
- package/server/config.js.map +1 -0
- package/server/daemon.d.ts +12 -0
- package/server/daemon.js +124 -114
- package/server/daemon.js.map +1 -0
- package/server/fileSourceNode.d.ts +2 -0
- package/server/fileSourceNode.js +34 -36
- package/server/fileSourceNode.js.map +1 -0
- package/server/gitClone.d.ts +9 -0
- package/server/gitClone.js +59 -61
- package/server/gitClone.js.map +1 -0
- package/server/gitDiff.d.ts +36 -0
- package/server/gitDiff.js +271 -169
- package/server/gitDiff.js.map +1 -0
- package/server/gitHistory.d.ts +12 -0
- package/server/gitHistory.js +80 -80
- package/server/gitHistory.js.map +1 -0
- package/server/gitRemote.d.ts +12 -0
- package/server/gitRemote.js +60 -0
- package/server/gitRemote.js.map +1 -0
- package/server/htmlText.d.ts +4 -0
- package/server/htmlText.js +16 -23
- package/server/htmlText.js.map +1 -0
- package/server/http.d.ts +5 -0
- package/server/http.js +27 -30
- package/server/http.js.map +1 -0
- package/server/httpGuards.d.ts +2 -0
- package/server/httpGuards.js +47 -43
- package/server/httpGuards.js.map +1 -0
- package/server/https.d.ts +34 -0
- package/server/https.js +121 -111
- package/server/https.js.map +1 -0
- package/server/index.d.ts +40 -0
- package/server/index.js +139 -147
- package/server/index.js.map +1 -0
- package/server/layout.d.ts +26 -0
- package/server/layout.js +59 -37
- package/server/layout.js.map +1 -0
- package/server/llmProxy.d.ts +3 -0
- package/server/llmProxy.js +137 -135
- package/server/llmProxy.js.map +1 -0
- package/server/logger.d.ts +38 -0
- package/server/logger.js +198 -211
- package/server/logger.js.map +1 -0
- package/server/mcp/analysis.d.ts +29 -0
- package/server/mcp/analysis.js +99 -120
- package/server/mcp/analysis.js.map +1 -0
- package/server/mcp/http.d.ts +9 -0
- package/server/mcp/http.js +128 -137
- package/server/mcp/http.js.map +1 -0
- package/server/mcp/runner.d.ts +68 -0
- package/server/mcp/runner.js +208 -218
- package/server/mcp/runner.js.map +1 -0
- package/server/mcp/server.d.ts +29 -0
- package/server/mcp/server.js +166 -179
- package/server/mcp/server.js.map +1 -0
- package/server/mcp/standalone.d.ts +1 -0
- package/server/mcp/standalone.js +8 -10
- package/server/mcp/standalone.js.map +1 -0
- package/server/mcp/tools.d.ts +2020 -0
- package/server/mcp/tools.js +523 -574
- package/server/mcp/tools.js.map +1 -0
- package/server/paths.d.ts +3 -0
- package/server/paths.js +17 -15
- package/server/paths.js.map +1 -0
- package/server/pidfile.d.ts +9 -0
- package/server/pidfile.js +43 -45
- package/server/pidfile.js.map +1 -0
- package/server/review.d.ts +5 -0
- package/server/review.js +35 -0
- package/server/review.js.map +1 -0
- package/server/router.d.ts +33 -0
- package/server/router.js +191 -197
- package/server/router.js.map +1 -0
- package/server/scanRequest.d.ts +3 -0
- package/server/scanRequest.js +36 -0
- package/server/scanRequest.js.map +1 -0
- package/server/searchIndex.d.ts +17 -0
- package/server/searchIndex.js +83 -80
- package/server/searchIndex.js.map +1 -0
- package/server/sessions.d.ts +14 -0
- package/server/sessions.js +62 -70
- package/server/sessions.js.map +1 -0
- package/server/startup.d.ts +118 -0
- package/server/startup.js +143 -148
- package/server/startup.js.map +1 -0
- package/server/static.d.ts +6 -0
- package/server/static.js +28 -31
- package/server/static.js.map +1 -0
- package/server/tools/install.d.ts +4 -0
- package/server/tools/install.js +176 -185
- package/server/tools/install.js.map +1 -0
- package/server/tools/parse.d.ts +8 -0
- package/server/tools/parse.js +112 -114
- package/server/tools/parse.js.map +1 -0
- package/server/tools/platform.d.ts +9 -0
- package/server/tools/platform.js +49 -47
- package/server/tools/platform.js.map +1 -0
- package/server/tools/registry.d.ts +137 -0
- package/server/tools/registry.js +173 -172
- package/server/tools/registry.js.map +1 -0
- package/server/tools/scan.d.ts +34 -0
- package/server/tools/scan.js +89 -98
- package/server/tools/scan.js.map +1 -0
- package/server/tools.d.ts +34 -0
- package/server/tools.js +94 -107
- package/server/tools.js.map +1 -0
- package/server/tunnel.d.ts +29 -0
- package/server/tunnel.js +61 -66
- package/server/tunnel.js.map +1 -0
- package/shared/analyzer/docs.d.ts +16 -0
- package/shared/analyzer/docs.js +99 -93
- package/shared/analyzer/docs.js.map +1 -0
- package/shared/analyzer/explainLocal.d.ts +4 -0
- package/shared/analyzer/explainLocal.js +122 -142
- package/shared/analyzer/explainLocal.js.map +1 -0
- package/shared/analyzer/github.d.ts +37 -0
- package/shared/analyzer/github.js +79 -0
- package/shared/analyzer/github.js.map +1 -0
- package/shared/analyzer/graph.d.ts +41 -0
- package/shared/analyzer/graph.js +393 -400
- package/shared/analyzer/graph.js.map +1 -0
- package/shared/analyzer/health.d.ts +33 -0
- package/shared/analyzer/health.js +197 -193
- package/shared/analyzer/health.js.map +1 -0
- package/shared/analyzer/history.d.ts +29 -0
- package/shared/analyzer/history.js +110 -111
- package/shared/analyzer/history.js.map +1 -0
- package/shared/analyzer/jsonConfig.d.ts +2 -0
- package/shared/analyzer/jsonConfig.js +61 -0
- package/shared/analyzer/jsonConfig.js.map +1 -0
- package/shared/analyzer/languages/csharp.d.ts +26 -0
- package/shared/analyzer/languages/csharp.js +83 -88
- package/shared/analyzer/languages/csharp.js.map +1 -0
- package/shared/analyzer/languages/generic.d.ts +23 -0
- package/shared/analyzer/languages/generic.js +94 -105
- package/shared/analyzer/languages/generic.js.map +1 -0
- package/shared/analyzer/languages/go.d.ts +24 -0
- package/shared/analyzer/languages/go.js +45 -54
- package/shared/analyzer/languages/go.js.map +1 -0
- package/shared/analyzer/languages/index.d.ts +6 -0
- package/shared/analyzer/languages/index.js +19 -20
- package/shared/analyzer/languages/index.js.map +1 -0
- package/shared/analyzer/languages/java.d.ts +20 -0
- package/shared/analyzer/languages/java.js +73 -78
- package/shared/analyzer/languages/java.js.map +1 -0
- package/shared/analyzer/languages/javascript.d.ts +12 -0
- package/shared/analyzer/languages/javascript.js +258 -209
- package/shared/analyzer/languages/javascript.js.map +1 -0
- package/shared/analyzer/languages/python.d.ts +23 -0
- package/shared/analyzer/languages/python.js +92 -100
- package/shared/analyzer/languages/python.js.map +1 -0
- package/shared/analyzer/languages/rust.d.ts +17 -0
- package/shared/analyzer/languages/rust.js +96 -98
- package/shared/analyzer/languages/rust.js.map +1 -0
- package/shared/analyzer/languages/typescript.d.ts +11 -0
- package/shared/analyzer/languages/typescript.js +117 -116
- package/shared/analyzer/languages/typescript.js.map +1 -0
- package/shared/analyzer/licenses.d.ts +18 -0
- package/shared/analyzer/licenses.js +130 -133
- package/shared/analyzer/licenses.js.map +1 -0
- package/shared/analyzer/metrics.d.ts +20 -0
- package/shared/analyzer/metrics.js +112 -104
- package/shared/analyzer/metrics.js.map +1 -0
- package/shared/analyzer/pathUtil.d.ts +10 -0
- package/shared/analyzer/pathUtil.js +29 -35
- package/shared/analyzer/pathUtil.js.map +1 -0
- package/shared/analyzer/patterns.d.ts +11 -0
- package/shared/analyzer/patterns.js +231 -253
- package/shared/analyzer/patterns.js.map +1 -0
- package/shared/analyzer/scan.d.ts +62 -0
- package/shared/analyzer/scan.js +451 -442
- package/shared/analyzer/scan.js.map +1 -0
- package/shared/analyzer/scanControl.d.ts +11 -0
- package/shared/analyzer/scanControl.js +62 -0
- package/shared/analyzer/scanControl.js.map +1 -0
- package/shared/analyzer/security.d.ts +18 -0
- package/shared/analyzer/security.js +159 -159
- package/shared/analyzer/security.js.map +1 -0
- package/shared/analyzer/services.d.ts +5 -0
- package/shared/analyzer/services.js +141 -138
- package/shared/analyzer/services.js.map +1 -0
- package/shared/analyzer/stack.d.ts +6 -0
- package/shared/analyzer/stack.js +134 -141
- package/shared/analyzer/stack.js.map +1 -0
- package/shared/analyzer/tour.d.ts +2 -0
- package/shared/analyzer/tour.js +45 -53
- package/shared/analyzer/tour.js.map +1 -0
- package/shared/analyzer/util.d.ts +8 -0
- package/shared/analyzer/util.js +130 -76
- package/shared/analyzer/util.js.map +1 -0
- package/shared/analyzer/workflows.d.ts +8 -0
- package/shared/analyzer/workflows.js +139 -134
- package/shared/analyzer/workflows.js.map +1 -0
- package/shared/contracts.d.ts +250 -0
- package/shared/contracts.js +2 -0
- package/shared/contracts.js.map +1 -0
- package/shared/diagram/aiFacts.d.ts +35 -0
- package/shared/diagram/aiFacts.js +102 -105
- package/shared/diagram/aiFacts.js.map +1 -0
- package/shared/diagram/aiMermaid.d.ts +5 -0
- package/shared/diagram/aiMermaid.js +46 -45
- package/shared/diagram/aiMermaid.js.map +1 -0
- package/shared/diagram/atlas.d.ts +16 -0
- package/shared/diagram/atlas.js +51 -60
- package/shared/diagram/atlas.js.map +1 -0
- package/shared/diagram/mermaid.d.ts +43 -0
- package/shared/diagram/mermaid.js +348 -370
- package/shared/diagram/mermaid.js.map +1 -0
- package/shared/review/contracts.d.ts +95 -0
- package/shared/review/contracts.js +2 -0
- package/shared/review/contracts.js.map +1 -0
- package/shared/review/review.d.ts +10 -0
- package/shared/review/review.js +184 -0
- package/shared/review/review.js.map +1 -0
- package/shared/search/query.d.ts +32 -0
- package/shared/search/query.js +395 -388
- package/shared/search/query.js.map +1 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Rendering primitives that the terminal can do and a browser cannot afford to.
|
|
2
|
+
//
|
|
3
|
+
// The site's coupling matrix is a canvas heat grid and its mind map is SVG. In a
|
|
4
|
+
// terminal the honest translation of "a heat grid" is block characters, and the
|
|
5
|
+
// honest translation of "a dependency tree" is the one everybody already knows
|
|
6
|
+
// how to read: an indented tree with arrows. Neither is a worse version of the
|
|
7
|
+
// web view — they are the terminal's own idiom, drawn from the same numbers.
|
|
8
|
+
import { fit, termWidth } from '../ui.js';
|
|
9
|
+
import { width } from '../../server/layout.js';
|
|
10
|
+
// Five ramp steps. Unicode 2591–2588, with an ASCII floor for terminals that
|
|
11
|
+
// cannot show them. Density reads as "how dark", which survives being printed in
|
|
12
|
+
// black and white, redirected to a file, or read by someone who cannot tell the
|
|
13
|
+
// ramp apart — the thing a color-only heatmap cannot promise.
|
|
14
|
+
const RAMP_UNICODE = [' ', '░', '▒', '▓', '█'];
|
|
15
|
+
const RAMP_ASCII = ['.', ':', '+', '*', '#'];
|
|
16
|
+
// A row that is `marker + value + tail`, where the tail is the first thing to go
|
|
17
|
+
// when the terminal is too narrow to hold it.
|
|
18
|
+
//
|
|
19
|
+
// This is the same mistake in five places — health risks, security findings,
|
|
20
|
+
// entry points, symbol rows, and layer notes — and fixing each by hand means the
|
|
21
|
+
// sixth gets it wrong too. The rule in one place: if the tail leaves less than
|
|
22
|
+
// `MIN_PATH` cells for the value, drop the tail and give the value the rest.
|
|
23
|
+
//
|
|
24
|
+
// `marker` may be styled, so its width is measured with `width()` (which strips
|
|
25
|
+
// ANSI) rather than `.length`. Measuring a styled string counts escape-sequence
|
|
26
|
+
// characters as if they occupied cells, which is how a "fitted" row still
|
|
27
|
+
// overflows by the length of the color codes.
|
|
28
|
+
const MIN_PATH = 8;
|
|
29
|
+
// `pad` is the indent *inside* the width budget, not a prefix glued on
|
|
30
|
+
// afterwards. An earlier version returned `marker + value` and let the caller
|
|
31
|
+
// prepend `' '`, which is four cells the helper could not see — so every row
|
|
32
|
+
// came out four cells too wide, which is exactly the overflow this exists to
|
|
33
|
+
// prevent. The indent is part of the row now.
|
|
34
|
+
//
|
|
35
|
+
// The value always gets at least one cell and never more than the terminal has
|
|
36
|
+
// left, even when the marker alone is wider than the terminal. That last case is
|
|
37
|
+
// the one a `Math.max(1, …)` floor gets wrong: a floor of 1 plus an
|
|
38
|
+
// over-wide marker is still an overflow, so the value is clamped by `w` itself
|
|
39
|
+
// and the marker is fitted too.
|
|
40
|
+
export function labelled(marker, value, tail = '', { pad = 0 } = {}) {
|
|
41
|
+
const w = termWidth();
|
|
42
|
+
const indent = ' '.repeat(Math.max(0, Math.min(pad, Math.max(0, w - 1))));
|
|
43
|
+
const markerRoom = Math.max(0, w - width(indent) - 1);
|
|
44
|
+
const head = fit(marker, markerRoom);
|
|
45
|
+
const lead = width(indent) + width(head);
|
|
46
|
+
const withTail = w - lead - width(tail);
|
|
47
|
+
if (tail && withTail > MIN_PATH)
|
|
48
|
+
return indent + head + fit(value, withTail) + tail;
|
|
49
|
+
return indent + head + fit(value, Math.max(0, w - lead));
|
|
50
|
+
}
|
|
51
|
+
const glyphs = () => (process.platform === 'win32' && !process.env.WT_SESSION ? RAMP_ASCII : RAMP_UNICODE);
|
|
52
|
+
// The strongest signal a number gets, as a fraction of the largest, in five
|
|
53
|
+
// steps. Zero is always the blank cell rather than the faintest mark: "nothing"
|
|
54
|
+
// and "a little" are different facts, and a heat grid that gives zero a speck of
|
|
55
|
+
// ink makes every map look busier than it is.
|
|
56
|
+
//
|
|
57
|
+
// Returns the *character*, always a string — an early version returned the
|
|
58
|
+
// number 0 for "empty", which then blew up on `.padEnd()` the first time a
|
|
59
|
+
// genuinely-zero cell was drawn, i.e. only on repos with real structure.
|
|
60
|
+
function step(value, max) {
|
|
61
|
+
const ramp = glyphs();
|
|
62
|
+
if (!value || max <= 0)
|
|
63
|
+
return ramp[0];
|
|
64
|
+
const i = Math.min(ramp.length - 1, Math.max(1, Math.ceil((value / max) * (ramp.length - 1))));
|
|
65
|
+
return ramp[i];
|
|
66
|
+
}
|
|
67
|
+
export { step };
|
|
68
|
+
// A horizontal bar, sized as a fraction of the largest value. Exported so every
|
|
69
|
+
// ranked list draws its bars identically instead of each view re-deriving the
|
|
70
|
+
// scale and drifting.
|
|
71
|
+
export function bar(value, max, room) {
|
|
72
|
+
if (room <= 0 || !value || max <= 0)
|
|
73
|
+
return '';
|
|
74
|
+
const ramp = glyphs();
|
|
75
|
+
const full = Math.max(1, Math.round((value / max) * room));
|
|
76
|
+
return ramp[ramp.length - 1].repeat(Math.min(room, full));
|
|
77
|
+
}
|
|
78
|
+
// A vertical bar chart, because some questions are about magnitude across
|
|
79
|
+
// categories and reading them off a horizontal list is harder than it needs to
|
|
80
|
+
// be. Columns collapse to a single row when the terminal is too narrow to hold
|
|
81
|
+
// them, rather than drawing a squeezed, unreadable grid.
|
|
82
|
+
export function columns(items, { room = 40, rows = 10 } = {}) {
|
|
83
|
+
if (!items.length)
|
|
84
|
+
return [];
|
|
85
|
+
const width = Math.max(1, Math.min(items.length, room));
|
|
86
|
+
const stepSize = Math.ceil(items.length / width);
|
|
87
|
+
const shown = stepSize > 1 ? items.filter((_, i) => i % stepSize === 0).slice(0, width) : items;
|
|
88
|
+
const top = Math.max(...shown.map((i) => i.value)) || 1;
|
|
89
|
+
const solid = glyphs()[glyphs().length - 1];
|
|
90
|
+
const out = [];
|
|
91
|
+
for (let level = rows; level >= 1; level--) {
|
|
92
|
+
const threshold = (level / rows) * top;
|
|
93
|
+
const line = shown.map((i) => (i.value >= threshold ? solid : ' ')).join('');
|
|
94
|
+
if (line.trim())
|
|
95
|
+
out.push(' ' + line);
|
|
96
|
+
}
|
|
97
|
+
out.push(' ' + shown.map((i) => String(i.label).slice(0, 1)).join(''));
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
// The folder-to-folder coupling grid, as a matrix. Row label, then one cell per
|
|
101
|
+
// folder, heaviest traffic darkest. Columns are addressed by letter so a cell
|
|
102
|
+
// can be named rather than counted. The diagonal is blank: a folder's traffic
|
|
103
|
+
// with itself is not coupling, it is just being that folder.
|
|
104
|
+
export function couplingGrid(repo, { maxFolders = 8, columns: cols } = {}) {
|
|
105
|
+
const matrix = repo.coupling;
|
|
106
|
+
if (!matrix || !matrix.folders.length)
|
|
107
|
+
return [];
|
|
108
|
+
const folders = matrix.folders.slice(0, maxFolders);
|
|
109
|
+
const w = cols || termWidth();
|
|
110
|
+
const labelRoom = Math.min(18, Math.max(...folders.map((f) => f.length)) + 1);
|
|
111
|
+
const cellRoom = Math.max(1, Math.floor((w - labelRoom - 4) / folders.length));
|
|
112
|
+
const cellW = Math.max(1, Math.min(2, cellRoom));
|
|
113
|
+
const head = ' '.repeat(labelRoom) + folders.map((_, i) => String.fromCharCode(65 + i).padEnd(cellW)).join('');
|
|
114
|
+
// The rule is sized to the grid but never past the terminal, so a very narrow
|
|
115
|
+
// window gets a short rule rather than a wrapped one.
|
|
116
|
+
const rule = '─'.repeat(Math.max(1, Math.min(labelRoom + folders.length * cellW, Math.max(1, w - 2))));
|
|
117
|
+
const out = [' ' + fit(head, w - 2), ' ' + rule];
|
|
118
|
+
folders.forEach((from, r) => {
|
|
119
|
+
let line = fit(from, labelRoom);
|
|
120
|
+
folders.forEach((to, c) => {
|
|
121
|
+
if (r === c) {
|
|
122
|
+
line += ' '.repeat(cellW);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const n = matrix.counts.get(from + '->' + to) || 0;
|
|
126
|
+
line += step(n, matrix.max).padEnd(cellW);
|
|
127
|
+
});
|
|
128
|
+
out.push(' ' + fit(line, w - 2));
|
|
129
|
+
});
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
// A dependency tree rooted at a file, drawn as a tree with the one piece of
|
|
133
|
+
// information that makes a tree readable — which way each edge points. This is
|
|
134
|
+
// the terminal's answer to the site's force-directed graph: a browser can show
|
|
135
|
+
// every edge at once, a terminal cannot, so it shows the part that answers "what
|
|
136
|
+
// breaks if I break this".
|
|
137
|
+
//
|
|
138
|
+
// The `seen` set makes the walk terminate on a cyclic graph — without it, a
|
|
139
|
+
// two-file import cycle would recurse until the stack gave out. The cost is that
|
|
140
|
+
// a file reached by two different paths is drawn only under the first, which is
|
|
141
|
+
// the right trade for a bounded tree.
|
|
142
|
+
export function depTree(repo, rootPath, { depth = 2, direction = 'both', room } = {}) {
|
|
143
|
+
const w = room || termWidth();
|
|
144
|
+
const out = [];
|
|
145
|
+
const seen = new Set([rootPath]);
|
|
146
|
+
const walk = (path, prefix, left, arrow) => {
|
|
147
|
+
out.push(fit(prefix + arrow + ' ' + path, w - 2));
|
|
148
|
+
if (left <= 0)
|
|
149
|
+
return;
|
|
150
|
+
const kids = [];
|
|
151
|
+
if (direction !== 'up') {
|
|
152
|
+
for (const child of repo.facts.importsOf[path] || []) {
|
|
153
|
+
if (!seen.has(child))
|
|
154
|
+
kids.push({ path: child, arrow: '→' });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (direction !== 'down') {
|
|
158
|
+
for (const parent of repo.facts.importers[path] || []) {
|
|
159
|
+
if (!seen.has(parent))
|
|
160
|
+
kids.push({ path: parent, arrow: '←' });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
kids.forEach((k, i) => {
|
|
164
|
+
if (seen.has(k.path))
|
|
165
|
+
return;
|
|
166
|
+
seen.add(k.path);
|
|
167
|
+
walk(k.path, prefix + (i === kids.length - 1 ? ' ' : '│ '), left - 1, k.arrow);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
walk(rootPath, '', depth, '●');
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=graphs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphs.js","sourceRoot":"","sources":["../../src/cli/explorer/graphs.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAE7E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAE/C,6EAA6E;AAC7E,iFAAiF;AACjF,gFAAgF;AAChF,8DAA8D;AAC9D,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC/C,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE7C,iFAAiF;AACjF,8CAA8C;AAC9C,EAAE;AACF,6EAA6E;AAC7E,iFAAiF;AACjF,+EAA+E;AAC/E,6EAA6E;AAC7E,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF,0EAA0E;AAC1E,8CAA8C;AAC9C,MAAM,QAAQ,GAAG,CAAC,CAAC;AAEnB,uEAAuE;AACvE,8EAA8E;AAC9E,gFAAgF;AAChF,6EAA6E;AAC7E,8CAA8C;AAC9C,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,oEAAoE;AACpE,+EAA+E;AAC/E,gCAAgC;AAChC,MAAM,UAAU,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,EAAE,GAAwB,EAAE;IACtF,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,IAAI,IAAI,QAAQ,GAAG,QAAQ;QAAE,OAAO,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IACpF,OAAO,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;AAE3G,4EAA4E;AAC5E,gFAAgF;AAChF,iFAAiF;AACjF,8CAA8C;AAC9C,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,yEAAyE;AACzE,SAAS,IAAI,CAAC,KAAK,EAAE,GAAG;IACtB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,gFAAgF;AAChF,8EAA8E;AAC9E,sBAAsB;AACtB,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI;IAClC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,0EAA0E;AAC1E,+EAA+E;AAC/E,+EAA+E;AAC/E,yDAAyD;AACzD,MAAM,UAAU,OAAO,CAAC,KAAK,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,GAAwB,EAAE;IAC/E,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,EAAE,CAAC;IACf,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QAC3C,MAAM,SAAS,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,IAAI,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACxE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,8EAA8E;AAC9E,8EAA8E;AAC9E,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,IAAI,EAAE,EAAE,UAAU,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAwB,EAAE;IAC5F,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC7B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEjD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/G,8EAA8E;IAC9E,sDAAsD;IACtD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvG,MAAM,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;IAEnD,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAChC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;YACxB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACZ,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC1B,OAAO;YACT,CAAC;YACD,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACnD,IAAI,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,iFAAiF;AACjF,2BAA2B;AAC3B,EAAE;AACF,4EAA4E;AAC5E,iFAAiF;AACjF,gFAAgF;AAChF,sCAAsC;AACtC,MAAM,UAAU,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,IAAI,EAAE,GAAwB,EAAE;IACvG,MAAM,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,EAAE,CAAC;IACf,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEjC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACzC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAClD,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO;QAEtB,MAAM,IAAI,GAAG,EAAE,CAAC;QAChB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACrD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QACD,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;YACzB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACtD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACpB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC7B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAC/B,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["// Rendering primitives that the terminal can do and a browser cannot afford to.\n//\n// The site's coupling matrix is a canvas heat grid and its mind map is SVG. In a\n// terminal the honest translation of \"a heat grid\" is block characters, and the\n// honest translation of \"a dependency tree\" is the one everybody already knows\n// how to read: an indented tree with arrows. Neither is a worse version of the\n// web view — they are the terminal's own idiom, drawn from the same numbers.\n\nimport { fit, termWidth } from '../ui.js';\nimport { width } from '../../server/layout.js';\n\n// Five ramp steps. Unicode 2591–2588, with an ASCII floor for terminals that\n// cannot show them. Density reads as \"how dark\", which survives being printed in\n// black and white, redirected to a file, or read by someone who cannot tell the\n// ramp apart — the thing a color-only heatmap cannot promise.\nconst RAMP_UNICODE = [' ', '░', '▒', '▓', '█'];\nconst RAMP_ASCII = ['.', ':', '+', '*', '#'];\n\n// A row that is `marker + value + tail`, where the tail is the first thing to go\n// when the terminal is too narrow to hold it.\n//\n// This is the same mistake in five places — health risks, security findings,\n// entry points, symbol rows, and layer notes — and fixing each by hand means the\n// sixth gets it wrong too. The rule in one place: if the tail leaves less than\n// `MIN_PATH` cells for the value, drop the tail and give the value the rest.\n//\n// `marker` may be styled, so its width is measured with `width()` (which strips\n// ANSI) rather than `.length`. Measuring a styled string counts escape-sequence\n// characters as if they occupied cells, which is how a \"fitted\" row still\n// overflows by the length of the color codes.\nconst MIN_PATH = 8;\n\n// `pad` is the indent *inside* the width budget, not a prefix glued on\n// afterwards. An earlier version returned `marker + value` and let the caller\n// prepend `' '`, which is four cells the helper could not see — so every row\n// came out four cells too wide, which is exactly the overflow this exists to\n// prevent. The indent is part of the row now.\n//\n// The value always gets at least one cell and never more than the terminal has\n// left, even when the marker alone is wider than the terminal. That last case is\n// the one a `Math.max(1, …)` floor gets wrong: a floor of 1 plus an\n// over-wide marker is still an overflow, so the value is clamped by `w` itself\n// and the marker is fitted too.\nexport function labelled(marker, value, tail = '', { pad = 0 }: Record<string, any> = {}) {\n const w = termWidth();\n const indent = ' '.repeat(Math.max(0, Math.min(pad, Math.max(0, w - 1))));\n const markerRoom = Math.max(0, w - width(indent) - 1);\n const head = fit(marker, markerRoom);\n const lead = width(indent) + width(head);\n const withTail = w - lead - width(tail);\n if (tail && withTail > MIN_PATH) return indent + head + fit(value, withTail) + tail;\n return indent + head + fit(value, Math.max(0, w - lead));\n}\n\nconst glyphs = () => (process.platform === 'win32' && !process.env.WT_SESSION ? RAMP_ASCII : RAMP_UNICODE);\n\n// The strongest signal a number gets, as a fraction of the largest, in five\n// steps. Zero is always the blank cell rather than the faintest mark: \"nothing\"\n// and \"a little\" are different facts, and a heat grid that gives zero a speck of\n// ink makes every map look busier than it is.\n//\n// Returns the *character*, always a string — an early version returned the\n// number 0 for \"empty\", which then blew up on `.padEnd()` the first time a\n// genuinely-zero cell was drawn, i.e. only on repos with real structure.\nfunction step(value, max) {\n const ramp = glyphs();\n if (!value || max <= 0) return ramp[0];\n const i = Math.min(ramp.length - 1, Math.max(1, Math.ceil((value / max) * (ramp.length - 1))));\n return ramp[i];\n}\n\nexport { step };\n\n// A horizontal bar, sized as a fraction of the largest value. Exported so every\n// ranked list draws its bars identically instead of each view re-deriving the\n// scale and drifting.\nexport function bar(value, max, room) {\n if (room <= 0 || !value || max <= 0) return '';\n const ramp = glyphs();\n const full = Math.max(1, Math.round((value / max) * room));\n return ramp[ramp.length - 1].repeat(Math.min(room, full));\n}\n\n// A vertical bar chart, because some questions are about magnitude across\n// categories and reading them off a horizontal list is harder than it needs to\n// be. Columns collapse to a single row when the terminal is too narrow to hold\n// them, rather than drawing a squeezed, unreadable grid.\nexport function columns(items, { room = 40, rows = 10 }: Record<string, any> = {}) {\n if (!items.length) return [];\n const width = Math.max(1, Math.min(items.length, room));\n const stepSize = Math.ceil(items.length / width);\n const shown = stepSize > 1 ? items.filter((_, i) => i % stepSize === 0).slice(0, width) : items;\n const top = Math.max(...shown.map((i) => i.value)) || 1;\n const solid = glyphs()[glyphs().length - 1];\n const out = [];\n for (let level = rows; level >= 1; level--) {\n const threshold = (level / rows) * top;\n const line = shown.map((i) => (i.value >= threshold ? solid : ' ')).join('');\n if (line.trim()) out.push(' ' + line);\n }\n out.push(' ' + shown.map((i) => String(i.label).slice(0, 1)).join(''));\n return out;\n}\n\n// The folder-to-folder coupling grid, as a matrix. Row label, then one cell per\n// folder, heaviest traffic darkest. Columns are addressed by letter so a cell\n// can be named rather than counted. The diagonal is blank: a folder's traffic\n// with itself is not coupling, it is just being that folder.\nexport function couplingGrid(repo, { maxFolders = 8, columns: cols }: Record<string, any> = {}) {\n const matrix = repo.coupling;\n if (!matrix || !matrix.folders.length) return [];\n const folders = matrix.folders.slice(0, maxFolders);\n const w = cols || termWidth();\n const labelRoom = Math.min(18, Math.max(...folders.map((f) => f.length)) + 1);\n const cellRoom = Math.max(1, Math.floor((w - labelRoom - 4) / folders.length));\n const cellW = Math.max(1, Math.min(2, cellRoom));\n\n const head = ' '.repeat(labelRoom) + folders.map((_, i) => String.fromCharCode(65 + i).padEnd(cellW)).join('');\n // The rule is sized to the grid but never past the terminal, so a very narrow\n // window gets a short rule rather than a wrapped one.\n const rule = '─'.repeat(Math.max(1, Math.min(labelRoom + folders.length * cellW, Math.max(1, w - 2))));\n const out = [' ' + fit(head, w - 2), ' ' + rule];\n\n folders.forEach((from, r) => {\n let line = fit(from, labelRoom);\n folders.forEach((to, c) => {\n if (r === c) {\n line += ' '.repeat(cellW);\n return;\n }\n const n = matrix.counts.get(from + '->' + to) || 0;\n line += step(n, matrix.max).padEnd(cellW);\n });\n out.push(' ' + fit(line, w - 2));\n });\n return out;\n}\n\n// A dependency tree rooted at a file, drawn as a tree with the one piece of\n// information that makes a tree readable — which way each edge points. This is\n// the terminal's answer to the site's force-directed graph: a browser can show\n// every edge at once, a terminal cannot, so it shows the part that answers \"what\n// breaks if I break this\".\n//\n// The `seen` set makes the walk terminate on a cyclic graph — without it, a\n// two-file import cycle would recurse until the stack gave out. The cost is that\n// a file reached by two different paths is drawn only under the first, which is\n// the right trade for a bounded tree.\nexport function depTree(repo, rootPath, { depth = 2, direction = 'both', room }: Record<string, any> = {}) {\n const w = room || termWidth();\n const out = [];\n const seen = new Set([rootPath]);\n\n const walk = (path, prefix, left, arrow) => {\n out.push(fit(prefix + arrow + ' ' + path, w - 2));\n if (left <= 0) return;\n\n const kids = [];\n if (direction !== 'up') {\n for (const child of repo.facts.importsOf[path] || []) {\n if (!seen.has(child)) kids.push({ path: child, arrow: '→' });\n }\n }\n if (direction !== 'down') {\n for (const parent of repo.facts.importers[path] || []) {\n if (!seen.has(parent)) kids.push({ path: parent, arrow: '←' });\n }\n }\n kids.forEach((k, i) => {\n if (seen.has(k.path)) return;\n seen.add(k.path);\n walk(k.path, prefix + (i === kids.length - 1 ? ' ' : '│ '), left - 1, k.arrow);\n });\n };\n\n walk(rootPath, '', depth, '●');\n return out;\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type PickerResult = {
|
|
2
|
+
done: true;
|
|
3
|
+
target: string | null;
|
|
4
|
+
message: string;
|
|
5
|
+
} | {
|
|
6
|
+
done: false;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
/** A prompt-driven folder picker that can share an existing readline session. */
|
|
10
|
+
export declare class SourcePicker {
|
|
11
|
+
private mode;
|
|
12
|
+
private location;
|
|
13
|
+
private page;
|
|
14
|
+
private directories;
|
|
15
|
+
private readonly pageSize;
|
|
16
|
+
constructor(start?: string);
|
|
17
|
+
get prompt(): string;
|
|
18
|
+
view(): Promise<string>;
|
|
19
|
+
choose(input: string): Promise<PickerResult>;
|
|
20
|
+
private browseView;
|
|
21
|
+
private validateTarget;
|
|
22
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function render(lines) {
|
|
5
|
+
const width = Math.max(1, Number(process.env.COLUMNS) || process.stdout.columns || 80);
|
|
6
|
+
return lines.map((line) => {
|
|
7
|
+
const chars = [...line];
|
|
8
|
+
if (chars.length <= width)
|
|
9
|
+
return line;
|
|
10
|
+
const lead = Math.ceil((width - 1) * 0.4);
|
|
11
|
+
return chars.slice(0, lead).join('') + '…' + chars.slice(chars.length - (width - 1 - lead)).join('');
|
|
12
|
+
}).join('\n');
|
|
13
|
+
}
|
|
14
|
+
/** A prompt-driven folder picker that can share an existing readline session. */
|
|
15
|
+
export class SourcePicker {
|
|
16
|
+
mode = 'menu';
|
|
17
|
+
location;
|
|
18
|
+
page = 0;
|
|
19
|
+
directories = [];
|
|
20
|
+
pageSize = 12;
|
|
21
|
+
constructor(start = process.cwd()) {
|
|
22
|
+
this.location = path.resolve(start);
|
|
23
|
+
}
|
|
24
|
+
get prompt() { return ' choose > '; }
|
|
25
|
+
async view() {
|
|
26
|
+
if (this.mode === 'menu')
|
|
27
|
+
return render([
|
|
28
|
+
'',
|
|
29
|
+
' SELECT A REPOSITORY',
|
|
30
|
+
` 1 Use current folder ${this.location}`,
|
|
31
|
+
' 2 Browse folders on this device',
|
|
32
|
+
' 3 Enter a folder path or Git URL',
|
|
33
|
+
' q Cancel',
|
|
34
|
+
'',
|
|
35
|
+
]);
|
|
36
|
+
if (this.mode === 'entry')
|
|
37
|
+
return render([
|
|
38
|
+
'',
|
|
39
|
+
' Enter an existing folder path or a Git URL.',
|
|
40
|
+
' Examples: ~/Projects/my-app · https://github.com/org/repo',
|
|
41
|
+
' Type `back` to return to the menu.',
|
|
42
|
+
'',
|
|
43
|
+
]);
|
|
44
|
+
return this.browseView();
|
|
45
|
+
}
|
|
46
|
+
async choose(input) {
|
|
47
|
+
const value = input.trim();
|
|
48
|
+
if (value === 'q' || value === 'quit' || value === 'cancel') {
|
|
49
|
+
return { done: true, target: null, message: ' Selection cancelled.' };
|
|
50
|
+
}
|
|
51
|
+
if (this.mode === 'menu') {
|
|
52
|
+
if (value === '1' || value === '')
|
|
53
|
+
return { done: true, target: this.location, message: '' };
|
|
54
|
+
if (value === '2') {
|
|
55
|
+
this.mode = 'browse';
|
|
56
|
+
this.page = 0;
|
|
57
|
+
return { done: false, message: await this.view() };
|
|
58
|
+
}
|
|
59
|
+
if (value === '3') {
|
|
60
|
+
this.mode = 'entry';
|
|
61
|
+
return { done: false, message: await this.view() };
|
|
62
|
+
}
|
|
63
|
+
// A pasted path or URL should work immediately, without selecting 3 first.
|
|
64
|
+
return this.validateTarget(value);
|
|
65
|
+
}
|
|
66
|
+
if (this.mode === 'entry') {
|
|
67
|
+
if (value === 'back' || !value) {
|
|
68
|
+
this.mode = 'menu';
|
|
69
|
+
return { done: false, message: await this.view() };
|
|
70
|
+
}
|
|
71
|
+
return this.validateTarget(value);
|
|
72
|
+
}
|
|
73
|
+
if (value === 'back') {
|
|
74
|
+
this.mode = 'menu';
|
|
75
|
+
return { done: false, message: await this.view() };
|
|
76
|
+
}
|
|
77
|
+
if (value === 'home') {
|
|
78
|
+
this.location = os.homedir();
|
|
79
|
+
this.page = 0;
|
|
80
|
+
return { done: false, message: await this.view() };
|
|
81
|
+
}
|
|
82
|
+
if (value === '..') {
|
|
83
|
+
this.location = path.dirname(this.location);
|
|
84
|
+
this.page = 0;
|
|
85
|
+
return { done: false, message: await this.view() };
|
|
86
|
+
}
|
|
87
|
+
if (value === 'use' || value === '.')
|
|
88
|
+
return { done: true, target: this.location, message: '' };
|
|
89
|
+
if (value === 'n' && (this.page + 1) * this.pageSize < this.directories.length) {
|
|
90
|
+
this.page++;
|
|
91
|
+
return { done: false, message: await this.view() };
|
|
92
|
+
}
|
|
93
|
+
if (value === 'p' && this.page > 0) {
|
|
94
|
+
this.page--;
|
|
95
|
+
return { done: false, message: await this.view() };
|
|
96
|
+
}
|
|
97
|
+
const number = Number(value);
|
|
98
|
+
if (Number.isInteger(number) && number >= 1 && number <= this.pageSize) {
|
|
99
|
+
const selected = this.directories[this.page * this.pageSize + number - 1];
|
|
100
|
+
if (selected) {
|
|
101
|
+
this.location = path.join(this.location, selected);
|
|
102
|
+
this.page = 0;
|
|
103
|
+
return { done: false, message: await this.view() };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (value) {
|
|
107
|
+
const result = await this.validateTarget(value);
|
|
108
|
+
if (result.done)
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
return { done: false, message: ' Choose a listed folder, `use`, `..`, `home`, or paste a path.' };
|
|
112
|
+
}
|
|
113
|
+
async browseView() {
|
|
114
|
+
try {
|
|
115
|
+
const entries = await fs.readdir(this.location, { withFileTypes: true });
|
|
116
|
+
this.directories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
117
|
+
.sort((a, b) => a.localeCompare(b));
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.mode = 'menu';
|
|
121
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
+
return ` Cannot browse ${this.location}: ${message}\n${await this.view()}`;
|
|
123
|
+
}
|
|
124
|
+
const pages = Math.max(1, Math.ceil(this.directories.length / this.pageSize));
|
|
125
|
+
this.page = Math.min(this.page, pages - 1);
|
|
126
|
+
const shown = this.directories.slice(this.page * this.pageSize, (this.page + 1) * this.pageSize);
|
|
127
|
+
return render([
|
|
128
|
+
'',
|
|
129
|
+
` FOLDERS ${this.location}`,
|
|
130
|
+
...shown.map((name, index) => ` ${String(index + 1).padStart(2)} ${name}/`),
|
|
131
|
+
...(shown.length ? [] : [' No subfolders here.']),
|
|
132
|
+
` Page ${this.page + 1}/${pages} · n/p next/previous`,
|
|
133
|
+
' use Select this folder .. Parent home Home back Menu',
|
|
134
|
+
'',
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
async validateTarget(value) {
|
|
138
|
+
if (/^(https?:\/\/|git@|ssh:\/\/)/i.test(value)) {
|
|
139
|
+
return { done: true, target: value, message: '' };
|
|
140
|
+
}
|
|
141
|
+
const expanded = value === '~' ? os.homedir() : value.startsWith('~/')
|
|
142
|
+
? path.join(os.homedir(), value.slice(2)) : value;
|
|
143
|
+
const target = path.resolve(expanded);
|
|
144
|
+
const stat = await fs.stat(target).catch(() => null);
|
|
145
|
+
if (stat?.isDirectory())
|
|
146
|
+
return { done: true, target, message: '' };
|
|
147
|
+
return { done: false, message: ` No folder found at ${target}. Try another path or back.` };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../src/cli/explorer/picker.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\ntype Mode = 'menu' | 'browse' | 'entry';\n\nfunction render(lines: string[]): string {\n const width = Math.max(1, Number(process.env.COLUMNS) || process.stdout.columns || 80);\n return lines.map((line) => {\n const chars = [...line];\n if (chars.length <= width) return line;\n const lead = Math.ceil((width - 1) * 0.4);\n return chars.slice(0, lead).join('') + '…' + chars.slice(chars.length - (width - 1 - lead)).join('');\n }).join('\\n');\n}\n\nexport type PickerResult =\n | { done: true; target: string | null; message: string }\n | { done: false; message: string };\n\n/** A prompt-driven folder picker that can share an existing readline session. */\nexport class SourcePicker {\n private mode: Mode = 'menu';\n private location: string;\n private page = 0;\n private directories: string[] = [];\n private readonly pageSize = 12;\n\n constructor(start = process.cwd()) {\n this.location = path.resolve(start);\n }\n\n get prompt(): string { return ' choose > '; }\n\n async view(): Promise<string> {\n if (this.mode === 'menu') return render([\n '',\n ' SELECT A REPOSITORY',\n ` 1 Use current folder ${this.location}`,\n ' 2 Browse folders on this device',\n ' 3 Enter a folder path or Git URL',\n ' q Cancel',\n '',\n ]);\n if (this.mode === 'entry') return render([\n '',\n ' Enter an existing folder path or a Git URL.',\n ' Examples: ~/Projects/my-app · https://github.com/org/repo',\n ' Type `back` to return to the menu.',\n '',\n ]);\n return this.browseView();\n }\n\n async choose(input: string): Promise<PickerResult> {\n const value = input.trim();\n if (value === 'q' || value === 'quit' || value === 'cancel') {\n return { done: true, target: null, message: ' Selection cancelled.' };\n }\n if (this.mode === 'menu') {\n if (value === '1' || value === '') return { done: true, target: this.location, message: '' };\n if (value === '2') {\n this.mode = 'browse';\n this.page = 0;\n return { done: false, message: await this.view() };\n }\n if (value === '3') {\n this.mode = 'entry';\n return { done: false, message: await this.view() };\n }\n // A pasted path or URL should work immediately, without selecting 3 first.\n return this.validateTarget(value);\n }\n if (this.mode === 'entry') {\n if (value === 'back' || !value) {\n this.mode = 'menu';\n return { done: false, message: await this.view() };\n }\n return this.validateTarget(value);\n }\n if (value === 'back') {\n this.mode = 'menu';\n return { done: false, message: await this.view() };\n }\n if (value === 'home') {\n this.location = os.homedir();\n this.page = 0;\n return { done: false, message: await this.view() };\n }\n if (value === '..') {\n this.location = path.dirname(this.location);\n this.page = 0;\n return { done: false, message: await this.view() };\n }\n if (value === 'use' || value === '.') return { done: true, target: this.location, message: '' };\n if (value === 'n' && (this.page + 1) * this.pageSize < this.directories.length) {\n this.page++;\n return { done: false, message: await this.view() };\n }\n if (value === 'p' && this.page > 0) {\n this.page--;\n return { done: false, message: await this.view() };\n }\n const number = Number(value);\n if (Number.isInteger(number) && number >= 1 && number <= this.pageSize) {\n const selected = this.directories[this.page * this.pageSize + number - 1];\n if (selected) {\n this.location = path.join(this.location, selected);\n this.page = 0;\n return { done: false, message: await this.view() };\n }\n }\n if (value) {\n const result = await this.validateTarget(value);\n if (result.done) return result;\n }\n return { done: false, message: ' Choose a listed folder, `use`, `..`, `home`, or paste a path.' };\n }\n\n private async browseView(): Promise<string> {\n try {\n const entries = await fs.readdir(this.location, { withFileTypes: true });\n this.directories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)\n .sort((a, b) => a.localeCompare(b));\n } catch (error) {\n this.mode = 'menu';\n const message = error instanceof Error ? error.message : String(error);\n return ` Cannot browse ${this.location}: ${message}\\n${await this.view()}`;\n }\n const pages = Math.max(1, Math.ceil(this.directories.length / this.pageSize));\n this.page = Math.min(this.page, pages - 1);\n const shown = this.directories.slice(this.page * this.pageSize, (this.page + 1) * this.pageSize);\n return render([\n '',\n ` FOLDERS ${this.location}`,\n ...shown.map((name, index) => ` ${String(index + 1).padStart(2)} ${name}/`),\n ...(shown.length ? [] : [' No subfolders here.']),\n ` Page ${this.page + 1}/${pages} · n/p next/previous`,\n ' use Select this folder .. Parent home Home back Menu',\n '',\n ]);\n }\n\n private async validateTarget(value: string): Promise<PickerResult> {\n if (/^(https?:\\/\\/|git@|ssh:\\/\\/)/i.test(value)) {\n return { done: true, target: value, message: '' };\n }\n const expanded = value === '~' ? os.homedir() : value.startsWith('~/')\n ? path.join(os.homedir(), value.slice(2)) : value;\n const target = path.resolve(expanded);\n const stat = await fs.stat(target).catch(() => null);\n if (stat?.isDirectory()) return { done: true, target, message: '' };\n return { done: false, message: ` No folder found at ${target}. Try another path or back.` };\n }\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** A stable, small snapshot for scripts and CI. It contains no source text. */
|
|
2
|
+
export interface RepositoryReport {
|
|
3
|
+
schemaVersion: 1;
|
|
4
|
+
repository: {
|
|
5
|
+
name: string;
|
|
6
|
+
root: string;
|
|
7
|
+
};
|
|
8
|
+
scan: {
|
|
9
|
+
parsedFiles: number;
|
|
10
|
+
skipped: number;
|
|
11
|
+
partial: boolean;
|
|
12
|
+
imports: {
|
|
13
|
+
total: number;
|
|
14
|
+
internal: number;
|
|
15
|
+
external: number;
|
|
16
|
+
unresolved: number;
|
|
17
|
+
confidence: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
architecture: {
|
|
21
|
+
entryPoints: string[];
|
|
22
|
+
cycles: number;
|
|
23
|
+
topHubs: Array<{
|
|
24
|
+
path: string;
|
|
25
|
+
importedBy: number;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
health: {
|
|
29
|
+
grade: string;
|
|
30
|
+
score: number;
|
|
31
|
+
riskiestFiles: Array<{
|
|
32
|
+
path: string;
|
|
33
|
+
risk: number;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
security: {
|
|
37
|
+
grade: string;
|
|
38
|
+
findings: number;
|
|
39
|
+
};
|
|
40
|
+
testReachability: {
|
|
41
|
+
reachable: number;
|
|
42
|
+
sourceFiles: number;
|
|
43
|
+
percent: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
interface RepositoryInput {
|
|
47
|
+
name: string;
|
|
48
|
+
root: string;
|
|
49
|
+
scan: {
|
|
50
|
+
stats: {
|
|
51
|
+
filesParsed: number;
|
|
52
|
+
skipped: number;
|
|
53
|
+
truncated: unknown;
|
|
54
|
+
imports: RepositoryReport['scan']['imports'];
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
facts: {
|
|
58
|
+
entries: string[];
|
|
59
|
+
cycles: unknown[];
|
|
60
|
+
hubs: Array<{
|
|
61
|
+
path: string;
|
|
62
|
+
fanIn: number;
|
|
63
|
+
}>;
|
|
64
|
+
testCoverage: {
|
|
65
|
+
testedCount: number;
|
|
66
|
+
totalNonTest: number;
|
|
67
|
+
ratio: number;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
health: {
|
|
71
|
+
grade: string;
|
|
72
|
+
score: number;
|
|
73
|
+
perFile: Array<{
|
|
74
|
+
path: string;
|
|
75
|
+
risk: number;
|
|
76
|
+
}>;
|
|
77
|
+
};
|
|
78
|
+
security: {
|
|
79
|
+
grade: string;
|
|
80
|
+
total: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export declare function createReport(repo: RepositoryInput): RepositoryReport;
|
|
84
|
+
export declare function formatReport(report: RepositoryReport): string;
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function createReport(repo) {
|
|
2
|
+
const stats = repo.scan.stats;
|
|
3
|
+
return {
|
|
4
|
+
schemaVersion: 1,
|
|
5
|
+
repository: { name: repo.name, root: repo.root },
|
|
6
|
+
scan: {
|
|
7
|
+
parsedFiles: stats.filesParsed,
|
|
8
|
+
skipped: stats.skipped,
|
|
9
|
+
partial: Boolean(stats.truncated),
|
|
10
|
+
imports: {
|
|
11
|
+
total: stats.imports.total,
|
|
12
|
+
internal: stats.imports.internal,
|
|
13
|
+
external: stats.imports.external,
|
|
14
|
+
unresolved: stats.imports.unresolved,
|
|
15
|
+
confidence: stats.imports.confidence,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
architecture: {
|
|
19
|
+
entryPoints: repo.facts.entries,
|
|
20
|
+
cycles: repo.facts.cycles.length,
|
|
21
|
+
topHubs: repo.facts.hubs.slice(0, 5).map((hub) => ({ path: hub.path, importedBy: hub.fanIn })),
|
|
22
|
+
},
|
|
23
|
+
health: {
|
|
24
|
+
grade: repo.health.grade,
|
|
25
|
+
score: repo.health.score,
|
|
26
|
+
riskiestFiles: repo.health.perFile.slice(0, 5).map(({ path, risk }) => ({ path, risk })),
|
|
27
|
+
},
|
|
28
|
+
security: { grade: repo.security.grade, findings: repo.security.total },
|
|
29
|
+
testReachability: {
|
|
30
|
+
reachable: repo.facts.testCoverage.testedCount,
|
|
31
|
+
sourceFiles: repo.facts.testCoverage.totalNonTest,
|
|
32
|
+
percent: repo.facts.testCoverage.ratio,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function formatReport(report) {
|
|
37
|
+
const lines = [
|
|
38
|
+
`${report.repository.name} — codebase report`,
|
|
39
|
+
report.repository.root,
|
|
40
|
+
'',
|
|
41
|
+
`Files ${report.scan.parsedFiles} parsed${report.scan.partial ? ' (partial scan)' : ''}, ${report.scan.skipped} skipped`,
|
|
42
|
+
`Imports ${report.scan.imports.internal} internal, ${report.scan.imports.external} external, ${report.scan.imports.unresolved} unresolved (${report.scan.imports.confidence}% placed)`,
|
|
43
|
+
`Architecture ${report.architecture.entryPoints.length} entry points, ${report.architecture.cycles} cycles`,
|
|
44
|
+
`Health ${report.health.grade} (${report.health.score}/100)`,
|
|
45
|
+
`Security ${report.security.grade} (${report.security.findings} heuristic findings)`,
|
|
46
|
+
`Test reachability ${report.testReachability.reachable}/${report.testReachability.sourceFiles} source files (${report.testReachability.percent}%)`,
|
|
47
|
+
];
|
|
48
|
+
if (report.architecture.topHubs.length) {
|
|
49
|
+
lines.push('', 'Most imported files');
|
|
50
|
+
for (const hub of report.architecture.topHubs)
|
|
51
|
+
lines.push(` ${hub.path} (${hub.importedBy} importers)`);
|
|
52
|
+
}
|
|
53
|
+
if (report.health.riskiestFiles.length) {
|
|
54
|
+
lines.push('', 'Highest risk files');
|
|
55
|
+
for (const file of report.health.riskiestFiles)
|
|
56
|
+
lines.push(` ${file.path} (risk ${file.risk})`);
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/cli/explorer/report.ts"],"names":[],"mappings":"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","sourcesContent":["/** A stable, small snapshot for scripts and CI. It contains no source text. */\nexport interface RepositoryReport {\n schemaVersion: 1;\n repository: { name: string; root: string };\n scan: {\n parsedFiles: number;\n skipped: number;\n partial: boolean;\n imports: { total: number; internal: number; external: number; unresolved: number; confidence: number };\n };\n architecture: {\n entryPoints: string[];\n cycles: number;\n topHubs: Array<{ path: string; importedBy: number }>;\n };\n health: {\n grade: string;\n score: number;\n riskiestFiles: Array<{ path: string; risk: number }>;\n };\n security: { grade: string; findings: number };\n testReachability: { reachable: number; sourceFiles: number; percent: number };\n}\n\ninterface RepositoryInput {\n name: string;\n root: string;\n scan: { stats: {\n filesParsed: number;\n skipped: number;\n truncated: unknown;\n imports: RepositoryReport['scan']['imports'];\n } };\n facts: {\n entries: string[];\n cycles: unknown[];\n hubs: Array<{ path: string; fanIn: number }>;\n testCoverage: { testedCount: number; totalNonTest: number; ratio: number };\n };\n health: { grade: string; score: number; perFile: Array<{ path: string; risk: number }> };\n security: { grade: string; total: number };\n}\n\nexport function createReport(repo: RepositoryInput): RepositoryReport {\n const stats = repo.scan.stats;\n return {\n schemaVersion: 1,\n repository: { name: repo.name, root: repo.root },\n scan: {\n parsedFiles: stats.filesParsed,\n skipped: stats.skipped,\n partial: Boolean(stats.truncated),\n imports: {\n total: stats.imports.total,\n internal: stats.imports.internal,\n external: stats.imports.external,\n unresolved: stats.imports.unresolved,\n confidence: stats.imports.confidence,\n },\n },\n architecture: {\n entryPoints: repo.facts.entries,\n cycles: repo.facts.cycles.length,\n topHubs: repo.facts.hubs.slice(0, 5).map((hub) => ({ path: hub.path, importedBy: hub.fanIn })),\n },\n health: {\n grade: repo.health.grade,\n score: repo.health.score,\n riskiestFiles: repo.health.perFile.slice(0, 5).map(({ path, risk }) => ({ path, risk })),\n },\n security: { grade: repo.security.grade, findings: repo.security.total },\n testReachability: {\n reachable: repo.facts.testCoverage.testedCount,\n sourceFiles: repo.facts.testCoverage.totalNonTest,\n percent: repo.facts.testCoverage.ratio,\n },\n };\n}\n\nexport function formatReport(report: RepositoryReport): string {\n const lines = [\n `${report.repository.name} — codebase report`,\n report.repository.root,\n '',\n `Files ${report.scan.parsedFiles} parsed${report.scan.partial ? ' (partial scan)' : ''}, ${report.scan.skipped} skipped`,\n `Imports ${report.scan.imports.internal} internal, ${report.scan.imports.external} external, ${report.scan.imports.unresolved} unresolved (${report.scan.imports.confidence}% placed)`,\n `Architecture ${report.architecture.entryPoints.length} entry points, ${report.architecture.cycles} cycles`,\n `Health ${report.health.grade} (${report.health.score}/100)`,\n `Security ${report.security.grade} (${report.security.findings} heuristic findings)`,\n `Test reachability ${report.testReachability.reachable}/${report.testReachability.sourceFiles} source files (${report.testReachability.percent}%)`,\n ];\n if (report.architecture.topHubs.length) {\n lines.push('', 'Most imported files');\n for (const hub of report.architecture.topHubs) lines.push(` ${hub.path} (${hub.importedBy} importers)`);\n }\n if (report.health.riskiestFiles.length) {\n lines.push('', 'Highest risk files');\n for (const file of report.health.riskiestFiles) lines.push(` ${file.path} (risk ${file.risk})`);\n }\n return lines.join('\\n');\n}\n"]}
|