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
package/shared/analyzer/scan.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { parseJsonConfig } from './jsonConfig.js';
|
|
2
|
+
import { DEFAULT_LIMITS, normalizeScanOptions, throwIfAborted, abortable, utf8ByteLength } from './scanControl.js';
|
|
3
|
+
export { DEFAULT_LIMITS } from './scanControl.js';
|
|
1
4
|
// The scanner: walks a FileSource, dispatches per-language analyzers,
|
|
2
5
|
// resolves imports into edges, and hands back one big plain-data result.
|
|
3
6
|
//
|
|
@@ -5,500 +8,506 @@
|
|
|
5
8
|
// list(dir) -> [{ name, path, type: 'file'|'dir' }]
|
|
6
9
|
// read(path) -> string
|
|
7
10
|
// Paths are repo-relative, POSIX style ('' is the root).
|
|
8
|
-
|
|
9
11
|
import { languageFor } from './languages/index.js';
|
|
10
12
|
import { dirOf, baseName, extOf } from './pathUtil.js';
|
|
11
13
|
import { codeStats, complexityOf } from './metrics.js';
|
|
12
14
|
import { analyzeSecurityFile } from './security.js';
|
|
13
15
|
import { analyzeLicenses } from './licenses.js';
|
|
14
16
|
import { analyzeWorkflows } from './workflows.js';
|
|
15
|
-
|
|
17
|
+
import { detectManifest } from './services.js';
|
|
16
18
|
const SKIP_DIRS = new Set([
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
'node_modules', '.git', '.hg', '.svn', 'dist', 'build', 'out', '.next',
|
|
20
|
+
'.nuxt', 'target', 'vendor', '__pycache__', '.venv', 'venv', 'env',
|
|
21
|
+
'.idea', '.vscode', 'coverage', '.cache', '.turbo', 'tmp', 'temp',
|
|
22
|
+
'DerivedData', 'Pods', '.gradle', 'bin', 'obj',
|
|
21
23
|
]);
|
|
22
|
-
|
|
23
24
|
const SKIP_FILES = new Set([
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'composer.lock',
|
|
26
|
+
'Gemfile.lock', 'Cargo.lock', 'poetry.lock', '.DS_Store',
|
|
26
27
|
]);
|
|
27
|
-
|
|
28
28
|
const SKIP_EXTS = new Set([
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.icns', '.svg',
|
|
30
|
+
'.woff', '.woff2', '.ttf', '.otf', '.eot', '.mp4', '.mov', '.mp3',
|
|
31
|
+
'.wav', '.zip', '.gz', '.tar', '.rar', '.7z', '.pdf', '.jar', '.war',
|
|
32
|
+
'.class', '.o', '.a', '.so', '.dylib', '.dll', '.exe', '.pyc', '.wasm',
|
|
33
|
+
'.min.js', '.min.css', '.map', '.lock', '.bin', '.db', '.sqlite',
|
|
34
34
|
]);
|
|
35
|
-
|
|
36
|
-
export const DEFAULT_LIMITS = { maxFiles: 4000, maxFileSize: 200 * 1024 };
|
|
37
|
-
|
|
38
35
|
function shouldSkipFile(name) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
if (SKIP_FILES.has(name))
|
|
37
|
+
return true;
|
|
38
|
+
if (SKIP_EXTS.has(extOf(name)))
|
|
39
|
+
return true;
|
|
40
|
+
// extOf() only sees the final suffix, so name the common bundle shapes here.
|
|
41
|
+
return /\.min\.(js|css)$/i.test(name);
|
|
43
42
|
}
|
|
44
|
-
|
|
45
43
|
export async function scanRepo(source, options = {}) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} catch {
|
|
77
|
-
skips.listFailed++;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
for (const entry of entries) {
|
|
81
|
-
if (allFiles.length >= limits.maxFiles) {
|
|
82
|
-
// Stop the entire walk, not just this directory. A plain `break` here
|
|
83
|
-
// left every already-queued directory to be listed anyway, so the cap
|
|
84
|
-
// was both slower and less predictable than it looked.
|
|
85
|
-
truncated = { atFiles: allFiles.length, dirsQueued: queue.length };
|
|
86
|
-
break walk;
|
|
87
|
-
}
|
|
88
|
-
if (ignore(entry.path)) {
|
|
89
|
-
skips.ignored++;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (entry.type === 'dir') {
|
|
93
|
-
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) {
|
|
94
|
-
skips.vendorDir++;
|
|
95
|
-
continue;
|
|
44
|
+
const limits = normalizeScanOptions(options);
|
|
45
|
+
const signal = options.signal;
|
|
46
|
+
throwIfAborted(signal);
|
|
47
|
+
const onProgress = options.onProgress || ((..._args) => { });
|
|
48
|
+
const startedAt = Date.now();
|
|
49
|
+
const ignore = await abortable(buildIgnoreMatcher(source), signal);
|
|
50
|
+
// Every way a file or directory can drop out of the scan, counted apart.
|
|
51
|
+
// One integer for all of them hid the difference between "we ignored a
|
|
52
|
+
// lockfile" and "we could not read your source" — and only one of those is
|
|
53
|
+
// something the reader needs to know about.
|
|
54
|
+
const skips = {
|
|
55
|
+
ignored: 0, // matched a .gitignore rule
|
|
56
|
+
vendorDir: 0, // node_modules, dist, .git, any dotfolder
|
|
57
|
+
notSource: 0, // images, lockfiles, minified bundles
|
|
58
|
+
listFailed: 0, // the directory would not list
|
|
59
|
+
notCode: 0, // no analyzer for this extension (README.md, .yml, …)
|
|
60
|
+
readFailed: 0, // the file would not read
|
|
61
|
+
tooLarge: 0, // over maxFileSize
|
|
62
|
+
analyzeFailed: 0, // the language analyzer threw
|
|
63
|
+
};
|
|
64
|
+
// ---- Walk --------------------------------------------------------------
|
|
65
|
+
const allFiles = [];
|
|
66
|
+
let truncated = null;
|
|
67
|
+
const queue = [''];
|
|
68
|
+
walk: while (queue.length) {
|
|
69
|
+
throwIfAborted(signal);
|
|
70
|
+
const dir = queue.shift();
|
|
71
|
+
let entries;
|
|
72
|
+
try {
|
|
73
|
+
entries = await abortable(source.list(dir), signal);
|
|
96
74
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
continue;
|
|
75
|
+
catch {
|
|
76
|
+
throwIfAborted(signal);
|
|
77
|
+
skips.listFailed++;
|
|
78
|
+
continue;
|
|
102
79
|
}
|
|
103
|
-
|
|
104
|
-
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (allFiles.length >= limits.maxFiles) {
|
|
82
|
+
// Stop the entire walk, not just this directory. A plain `break` here
|
|
83
|
+
// left every already-queued directory to be listed anyway, so the cap
|
|
84
|
+
// was both slower and less predictable than it looked.
|
|
85
|
+
truncated = { atFiles: allFiles.length, dirsQueued: queue.length };
|
|
86
|
+
break walk;
|
|
87
|
+
}
|
|
88
|
+
if (ignore(entry.path)) {
|
|
89
|
+
skips.ignored++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (entry.type === 'dir') {
|
|
93
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) {
|
|
94
|
+
skips.vendorDir++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
queue.push(entry.path);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
if (shouldSkipFile(entry.name)) {
|
|
101
|
+
skips.notSource++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
allFiles.push(entry.path);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
onProgress({ phase: 'walk', found: allFiles.length });
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
d = dirOf(d);
|
|
109
|
+
allFiles.sort();
|
|
110
|
+
const fileSet = new Set(allFiles);
|
|
111
|
+
const dirSet = new Set(['']);
|
|
112
|
+
for (const p of allFiles) {
|
|
113
|
+
let d = dirOf(p);
|
|
114
|
+
while (true) {
|
|
115
|
+
if (dirSet.has(d))
|
|
116
|
+
break;
|
|
117
|
+
dirSet.add(d);
|
|
118
|
+
d = dirOf(d);
|
|
119
|
+
}
|
|
118
120
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
const findByName = (n) => nameIndex.get(n) || null;
|
|
128
|
-
|
|
129
|
-
// Find a directory whose path *ends with* a run of segments. This is what C#
|
|
130
|
-
// needs and Java does not: a C# file's folder mirrors the namespace, but the
|
|
131
|
-
// root namespace (the company or project name) is normally not a directory,
|
|
132
|
-
// so `namespace Acme.Services` lives in `src/Services/`. Matching on a path
|
|
133
|
-
// suffix is the only thing that finds it — stripping the whole namespace, the
|
|
134
|
-
// Java approach, matches nothing and silently drops every import.
|
|
135
|
-
//
|
|
136
|
-
// Keyed by the last three segments, which is enough to tell
|
|
137
|
-
// `Acme/Models/Entities` apart from a bare `Entities` elsewhere while staying
|
|
138
|
-
// O(1) per lookup. A collision at the shortest key keeps the first match, and
|
|
139
|
-
// callers query longest-first, so the most specific answer is tried first.
|
|
140
|
-
const dirByTail = new Map();
|
|
141
|
-
for (const d of dirSet) {
|
|
142
|
-
if (!d) continue;
|
|
143
|
-
const segments = d.split('/');
|
|
144
|
-
for (let take = 1; take <= 3 && take <= segments.length; take += 1) {
|
|
145
|
-
const key = segments.slice(segments.length - take).join('/');
|
|
146
|
-
if (!dirByTail.has(key)) dirByTail.set(key, d);
|
|
121
|
+
const has = (p) => fileSet.has(p);
|
|
122
|
+
const hasDir = (d) => dirSet.has(d);
|
|
123
|
+
const nameIndex = new Map();
|
|
124
|
+
for (const p of allFiles) {
|
|
125
|
+
const n = baseName(p);
|
|
126
|
+
if (!nameIndex.has(n))
|
|
127
|
+
nameIndex.set(n, p);
|
|
147
128
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
// The language is resolved here and kept. The resolve phase needs the same
|
|
172
|
-
// answer per file and used to ask `languageFor` for it a second time.
|
|
173
|
-
const files = [];
|
|
174
|
-
const langOf = new Map();
|
|
175
|
-
let parsed = 0;
|
|
176
|
-
const codeFiles = [];
|
|
177
|
-
for (const path of allFiles) {
|
|
178
|
-
const lang = languageFor(path);
|
|
179
|
-
if (!lang) {
|
|
180
|
-
skips.notCode++;
|
|
181
|
-
continue;
|
|
129
|
+
const findByName = (n) => nameIndex.get(n) || null;
|
|
130
|
+
// Find a directory whose path *ends with* a run of segments. This is what C#
|
|
131
|
+
// needs and Java does not: a C# file's folder mirrors the namespace, but the
|
|
132
|
+
// root namespace (the company or project name) is normally not a directory,
|
|
133
|
+
// so `namespace Acme.Services` lives in `src/Services/`. Matching on a path
|
|
134
|
+
// suffix is the only thing that finds it — stripping the whole namespace, the
|
|
135
|
+
// Java approach, matches nothing and silently drops every import.
|
|
136
|
+
//
|
|
137
|
+
// Keyed by the last three segments, which is enough to tell
|
|
138
|
+
// `Acme/Models/Entities` apart from a bare `Entities` elsewhere while staying
|
|
139
|
+
// O(1) per lookup. A collision at the shortest key keeps the first match, and
|
|
140
|
+
// callers query longest-first, so the most specific answer is tried first.
|
|
141
|
+
const dirByTail = new Map();
|
|
142
|
+
for (const d of dirSet) {
|
|
143
|
+
if (!d)
|
|
144
|
+
continue;
|
|
145
|
+
const segments = d.split('/');
|
|
146
|
+
for (let take = 1; take <= 3 && take <= segments.length; take += 1) {
|
|
147
|
+
const key = segments.slice(segments.length - take).join('/');
|
|
148
|
+
if (!dirByTail.has(key))
|
|
149
|
+
dirByTail.set(key, d);
|
|
150
|
+
}
|
|
182
151
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
152
|
+
const findDirEndingWith = (suffix) => (suffix ? dirByTail.get(suffix) || null : null);
|
|
153
|
+
const context = {
|
|
154
|
+
hasDir,
|
|
155
|
+
findDirEndingWith,
|
|
156
|
+
findByName,
|
|
157
|
+
modulePath: await abortable(readModulePath(source), signal),
|
|
158
|
+
tsPaths: await abortable(readTsConfigPaths(source), signal),
|
|
159
|
+
};
|
|
160
|
+
// ---- Parse ---------------------------------------------------------------
|
|
161
|
+
// Reads run a window ahead of the analysis; the analysis stays strictly in
|
|
162
|
+
// path order. Waiting for one `read` to land before starting the next was the
|
|
163
|
+
// largest wall-clock cost of a scan and the one with the least to show for
|
|
164
|
+
// it — every adapter behind `FileSource` is I/O, and neither `node:fs` nor the
|
|
165
|
+
// File System Access API cares how many requests are outstanding.
|
|
166
|
+
//
|
|
167
|
+
// Order matters more than it looks: `files`, `edges` and every skip count
|
|
168
|
+
// have to come out identical no matter how the reads interleave, or two scans
|
|
169
|
+
// of one repo would disagree. So the window only prefetches — the loop below
|
|
170
|
+
// consumes it one file at a time, in the order `allFiles` was sorted into.
|
|
171
|
+
//
|
|
172
|
+
// The language is resolved here and kept. The resolve phase needs the same
|
|
173
|
+
// answer per file and used to ask `languageFor` for it a second time.
|
|
174
|
+
const files = [];
|
|
175
|
+
const langOf = new Map();
|
|
176
|
+
let parsed = 0;
|
|
177
|
+
const codeFiles = [];
|
|
178
|
+
for (const path of allFiles) {
|
|
179
|
+
const lang = languageFor(path);
|
|
180
|
+
if (!lang) {
|
|
181
|
+
skips.notCode++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
langOf.set(path, lang);
|
|
185
|
+
codeFiles.push(path);
|
|
191
186
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
for await (const { path, text, failed } of readAhead(source, codeFiles, limits.readConcurrency, signal)) {
|
|
188
|
+
throwIfAborted(signal);
|
|
189
|
+
if (failed) {
|
|
190
|
+
skips.readFailed++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const size = utf8ByteLength(text);
|
|
194
|
+
if (size > limits.maxFileSize) {
|
|
195
|
+
skips.tooLarge++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const lang = langOf.get(path);
|
|
199
|
+
let analysis;
|
|
200
|
+
try {
|
|
201
|
+
analysis = lang.analyze(text, path);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
skips.analyzeFailed++;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const cs = codeStats(text, lang.id);
|
|
208
|
+
const findings = analyzeSecurityFile(text, lang.id);
|
|
209
|
+
const docRatio = cs.code + cs.comment > 0 ? Math.round((cs.comment / (cs.code + cs.comment)) * 100) : 0;
|
|
210
|
+
files.push({
|
|
211
|
+
path,
|
|
212
|
+
dir: dirOf(path),
|
|
213
|
+
name: baseName(path),
|
|
214
|
+
ext: extOf(path),
|
|
215
|
+
lang: lang.id,
|
|
216
|
+
size,
|
|
217
|
+
loc: cs.code,
|
|
218
|
+
comment: cs.comment,
|
|
219
|
+
blank: cs.blank,
|
|
220
|
+
lines: cs.lines,
|
|
221
|
+
docRatio,
|
|
222
|
+
complexity: complexityOf(text, lang.id),
|
|
223
|
+
findings: findings.length ? findings : undefined,
|
|
224
|
+
...analysis,
|
|
225
|
+
});
|
|
226
|
+
parsed++;
|
|
227
|
+
if (parsed % 25 === 0)
|
|
228
|
+
onProgress({ phase: 'parse', done: parsed, total: codeFiles.length });
|
|
195
229
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
230
|
+
onProgress({ phase: 'parse', done: parsed, total: codeFiles.length });
|
|
231
|
+
// ---- Resolve imports into edges -------------------------------------------
|
|
232
|
+
const edges = [];
|
|
233
|
+
const edgeKeys = new Map(); // key -> edge
|
|
234
|
+
const externals = new Map(); // package name -> Set of files
|
|
235
|
+
// What share of the imports we found did we manage to place? A regex-based
|
|
236
|
+
// analyzer owes its reader this number more than any other: it is the
|
|
237
|
+
// engine's own estimate of how much of the graph is missing.
|
|
238
|
+
const tally = { total: 0, internal: 0, external: 0, unresolved: 0 };
|
|
239
|
+
const unresolvedSpecs = new Map(); // spec -> { count, from }
|
|
240
|
+
// A resolver may answer with a *directory* instead of a file: Go's import is a
|
|
241
|
+
// package, and a C# `using Acme.Models;` names a namespace whose classes we
|
|
242
|
+
// cannot know from the import alone. Every file in that directory is a
|
|
243
|
+
// target. Indexed once here rather than scanned per import — a Go repo with
|
|
244
|
+
// 1,000 files and 8,000 imports was doing eight million comparisons for it.
|
|
245
|
+
//
|
|
246
|
+
// Language-neutral on purpose: this used to be `goFilesByDir`, built only from
|
|
247
|
+
// Go files, which meant no other language could use the mechanism that already
|
|
248
|
+
// existed for exactly this problem.
|
|
249
|
+
const filesByDir = new Map();
|
|
250
|
+
for (const f of files) {
|
|
251
|
+
const bucket = filesByDir.get(f.dir);
|
|
252
|
+
if (bucket)
|
|
253
|
+
bucket.push(f.path);
|
|
254
|
+
else
|
|
255
|
+
filesByDir.set(f.dir, [f.path]);
|
|
203
256
|
}
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Language-neutral on purpose: this used to be `goFilesByDir`, built only from
|
|
245
|
-
// Go files, which meant no other language could use the mechanism that already
|
|
246
|
-
// existed for exactly this problem.
|
|
247
|
-
const filesByDir = new Map();
|
|
248
|
-
for (const f of files) {
|
|
249
|
-
const bucket = filesByDir.get(f.dir);
|
|
250
|
-
if (bucket) bucket.push(f.path);
|
|
251
|
-
else filesByDir.set(f.dir, [f.path]);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
for (const file of files) {
|
|
255
|
-
const lang = langOf.get(file.path);
|
|
256
|
-
for (const imp of file.imports) {
|
|
257
|
-
tally.total++;
|
|
258
|
-
const res = lang.resolveImport(imp.spec, file.path, has, context, imp);
|
|
259
|
-
if (res.path) {
|
|
260
|
-
tally.internal++;
|
|
261
|
-
addEdge(edges, edgeKeys, file.path, res.path, 'imports', imp.symbols);
|
|
262
|
-
} else if (res.packageDir) {
|
|
263
|
-
tally.internal++;
|
|
264
|
-
// A directory import means "everything in there". addEdge de-duplicates,
|
|
265
|
-
// so a package with fifty files yields fifty honest edges rather than
|
|
266
|
-
// one edge standing in for all of them.
|
|
267
|
-
for (const target of filesByDir.get(res.packageDir) || []) {
|
|
268
|
-
addEdge(edges, edgeKeys, file.path, target, 'imports', imp.symbols);
|
|
257
|
+
let resolved = 0;
|
|
258
|
+
for (const file of files) {
|
|
259
|
+
throwIfAborted(signal);
|
|
260
|
+
if (resolved++ % 25 === 0)
|
|
261
|
+
onProgress({ phase: 'resolve', done: resolved, total: files.length });
|
|
262
|
+
const lang = langOf.get(file.path);
|
|
263
|
+
for (const imp of file.imports) {
|
|
264
|
+
tally.total++;
|
|
265
|
+
const res = lang.resolveImport(imp.spec, file.path, has, context, imp);
|
|
266
|
+
if (res.path) {
|
|
267
|
+
tally.internal++;
|
|
268
|
+
addEdge(edges, edgeKeys, file.path, res.path, 'imports', imp.symbols);
|
|
269
|
+
}
|
|
270
|
+
else if (res.packageDir) {
|
|
271
|
+
tally.internal++;
|
|
272
|
+
// A directory import means "everything in there". addEdge de-duplicates,
|
|
273
|
+
// so a package with fifty files yields fifty honest edges rather than
|
|
274
|
+
// one edge standing in for all of them.
|
|
275
|
+
for (const target of filesByDir.get(res.packageDir) || []) {
|
|
276
|
+
addEdge(edges, edgeKeys, file.path, target, 'imports', imp.symbols);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else if (res.external) {
|
|
280
|
+
tally.external++;
|
|
281
|
+
if (!externals.has(res.external))
|
|
282
|
+
externals.set(res.external, new Set());
|
|
283
|
+
externals.get(res.external).add(file.path);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Every resolver ends with `return { unresolved: spec }`. This branch
|
|
287
|
+
// is where that used to disappear for want of an `else` — the import
|
|
288
|
+
// was counted nowhere and the graph simply came up short in silence.
|
|
289
|
+
tally.unresolved++;
|
|
290
|
+
const spec = res.unresolved || imp.spec;
|
|
291
|
+
const seen = unresolvedSpecs.get(spec);
|
|
292
|
+
if (seen)
|
|
293
|
+
seen.count++;
|
|
294
|
+
else
|
|
295
|
+
unresolvedSpecs.set(spec, { spec, count: 1, from: file.path });
|
|
296
|
+
}
|
|
269
297
|
}
|
|
270
|
-
} else if (res.external) {
|
|
271
|
-
tally.external++;
|
|
272
|
-
if (!externals.has(res.external)) externals.set(res.external, new Set());
|
|
273
|
-
externals.get(res.external).add(file.path);
|
|
274
|
-
} else {
|
|
275
|
-
// Every resolver ends with `return { unresolved: spec }`. This branch
|
|
276
|
-
// is where that used to disappear for want of an `else` — the import
|
|
277
|
-
// was counted nowhere and the graph simply came up short in silence.
|
|
278
|
-
tally.unresolved++;
|
|
279
|
-
const spec = res.unresolved || imp.spec;
|
|
280
|
-
const seen = unresolvedSpecs.get(spec);
|
|
281
|
-
if (seen) seen.count++;
|
|
282
|
-
else unresolvedSpecs.set(spec, { spec, count: 1, from: file.path });
|
|
283
|
-
}
|
|
284
298
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (entry) entry.hasReadme = true;
|
|
299
|
+
// Resolved-or-correctly-external over everything we saw. An external package
|
|
300
|
+
// counts as a success: the resolver knew what it was looking at.
|
|
301
|
+
const confidence = tally.total
|
|
302
|
+
? Math.round(((tally.internal + tally.external) / tally.total) * 100)
|
|
303
|
+
: 100;
|
|
304
|
+
const worstUnresolved = [...unresolvedSpecs.values()]
|
|
305
|
+
.sort((a, b) => b.count - a.count || a.spec.localeCompare(b.spec))
|
|
306
|
+
.slice(0, 12);
|
|
307
|
+
const externalList = [...externals.entries()]
|
|
308
|
+
.map(([name, usedBySet]) => ({ name, usedBy: [...usedBySet].sort() }))
|
|
309
|
+
.sort((a, b) => b.usedBy.length - a.usedBy.length || a.name.localeCompare(b.name));
|
|
310
|
+
// Folder rollups with full size/LOC/documentation breakdown
|
|
311
|
+
const filesPerDir = new Map();
|
|
312
|
+
const folderData = new Map();
|
|
313
|
+
for (const p of allFiles) {
|
|
314
|
+
const d = dirOf(p);
|
|
315
|
+
filesPerDir.set(d, (filesPerDir.get(d) || 0) + 1);
|
|
316
|
+
}
|
|
317
|
+
for (const path of dirSet) {
|
|
318
|
+
if (!path)
|
|
319
|
+
continue;
|
|
320
|
+
folderData.set(path, {
|
|
321
|
+
path,
|
|
322
|
+
name: baseName(path),
|
|
323
|
+
depth: path.split('/').length,
|
|
324
|
+
fileCount: filesPerDir.get(path) || 0,
|
|
325
|
+
loc: 0,
|
|
326
|
+
comment: 0,
|
|
327
|
+
blank: 0,
|
|
328
|
+
size: 0,
|
|
329
|
+
langs: {},
|
|
330
|
+
hasReadme: false,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
for (const p of allFiles) {
|
|
334
|
+
const d = dirOf(p);
|
|
335
|
+
const bn = baseName(p).toLowerCase();
|
|
336
|
+
if (bn.startsWith('readme.') || bn === 'readme') {
|
|
337
|
+
const entry = folderData.get(d);
|
|
338
|
+
if (entry)
|
|
339
|
+
entry.hasReadme = true;
|
|
340
|
+
}
|
|
328
341
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
342
|
+
for (const f of files) {
|
|
343
|
+
const entry = folderData.get(f.dir);
|
|
344
|
+
if (entry) {
|
|
345
|
+
entry.loc += f.loc || 0;
|
|
346
|
+
entry.comment += f.comment || 0;
|
|
347
|
+
entry.blank += f.blank || 0;
|
|
348
|
+
entry.size += f.size || 0;
|
|
349
|
+
entry.langs[f.lang] = (entry.langs[f.lang] || 0) + (f.loc || 0);
|
|
350
|
+
}
|
|
338
351
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
docRatio: f.loc + f.comment > 0 ? Math.round((f.comment / (f.loc + f.comment)) * 100) : 0,
|
|
352
|
+
const folders = [...folderData.values()]
|
|
353
|
+
.map((f) => ({
|
|
354
|
+
...f,
|
|
355
|
+
docRatio: f.loc + f.comment > 0 ? Math.round((f.comment / (f.loc + f.comment)) * 100) : 0,
|
|
344
356
|
}))
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
};
|
|
357
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
358
|
+
const skippedTotal = Object.values(skips).reduce((a, b) => a + b, 0);
|
|
359
|
+
const stats = {
|
|
360
|
+
filesTotal: allFiles.length,
|
|
361
|
+
filesParsed: parsed,
|
|
362
|
+
skipped: skippedTotal,
|
|
363
|
+
skips,
|
|
364
|
+
truncated,
|
|
365
|
+
edgeCount: edges.length,
|
|
366
|
+
imports: {
|
|
367
|
+
total: tally.total,
|
|
368
|
+
internal: tally.internal,
|
|
369
|
+
external: tally.external,
|
|
370
|
+
unresolved: tally.unresolved,
|
|
371
|
+
confidence,
|
|
372
|
+
worst: worstUnresolved,
|
|
373
|
+
},
|
|
374
|
+
tookMs: Date.now() - startedAt,
|
|
375
|
+
languages: countBy(files, (f) => f.lang),
|
|
376
|
+
languageLoc: files.reduce((acc, f) => {
|
|
377
|
+
acc[f.lang] = (acc[f.lang] || 0) + (f.loc || 0);
|
|
378
|
+
return acc;
|
|
379
|
+
}, {}),
|
|
380
|
+
};
|
|
381
|
+
const [workflows, licenseReport] = await abortable(Promise.all([
|
|
382
|
+
analyzeWorkflows(source).catch(() => []),
|
|
383
|
+
detectManifest(source).then((manifest) => analyzeLicenses(source, manifest)).catch(() => null),
|
|
384
|
+
]), signal);
|
|
385
|
+
throwIfAborted(signal);
|
|
386
|
+
onProgress({ phase: 'complete', files: parsed, edges: edges.length });
|
|
387
|
+
const result = {
|
|
388
|
+
root: source.root || '(unknown)',
|
|
389
|
+
name: source.name || baseName(source.root || '') || 'repo',
|
|
390
|
+
scannedAt: new Date().toISOString(),
|
|
391
|
+
stats,
|
|
392
|
+
allFiles,
|
|
393
|
+
files,
|
|
394
|
+
edges,
|
|
395
|
+
externals: externalList,
|
|
396
|
+
folders,
|
|
397
|
+
workflows: workflows || [],
|
|
398
|
+
sbom: licenseReport?.sbom || [],
|
|
399
|
+
license: licenseReport?.projectLicense || { id: 'UNKNOWN', name: 'Unknown' },
|
|
400
|
+
licenseReport: licenseReport || undefined,
|
|
401
|
+
};
|
|
402
|
+
return result;
|
|
392
403
|
}
|
|
393
|
-
|
|
394
404
|
function addEdge(edges, edgeKeys, from, to, kind, symbols = []) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (
|
|
400
|
-
|
|
405
|
+
if (from === to)
|
|
406
|
+
return;
|
|
407
|
+
const key = from + '->' + to;
|
|
408
|
+
const existing = edgeKeys.get(key);
|
|
409
|
+
if (existing) {
|
|
410
|
+
if (symbols && symbols.length) {
|
|
411
|
+
existing.symbols = Array.from(new Set([...(existing.symbols || []), ...symbols]));
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
401
414
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
edgeKeys.set(key, edge);
|
|
406
|
-
edges.push(edge);
|
|
415
|
+
const edge = { from, to, kind, symbols: symbols ? symbols.slice() : [] };
|
|
416
|
+
edgeKeys.set(key, edge);
|
|
417
|
+
edges.push(edge);
|
|
407
418
|
}
|
|
408
|
-
|
|
409
419
|
// How many reads may be in flight at once. Small on purpose: a file is read
|
|
410
420
|
// whole before its size can be checked (a `FileSource` has no `stat`), so this
|
|
411
421
|
// is also a multiplier on peak memory. Eight is enough to keep a disk or the
|
|
412
422
|
// browser's file layer busy without holding a heap of large files at once.
|
|
413
423
|
const READ_AHEAD = 8;
|
|
414
|
-
|
|
415
424
|
// Yields `{ path, text, failed }` for each path, in the order given, while
|
|
416
425
|
// keeping up to READ_AHEAD reads in flight. Failures are yielded rather than
|
|
417
426
|
// thrown so the caller can count them and keep going — one unreadable file
|
|
418
427
|
// should not end a scan.
|
|
419
|
-
async function* readAhead(source, paths, width = READ_AHEAD) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
428
|
+
async function* readAhead(source, paths, width = READ_AHEAD, signal) {
|
|
429
|
+
const read = (path) => Promise.resolve()
|
|
430
|
+
.then(() => { throwIfAborted(signal); return source.read(path); })
|
|
431
|
+
.then((text) => ({ path, text, failed: false }), () => ({ path, text: '', failed: true }));
|
|
432
|
+
const inFlight = [];
|
|
433
|
+
let next = 0;
|
|
434
|
+
while (next < paths.length && inFlight.length < width)
|
|
435
|
+
inFlight.push(read(paths[next++]));
|
|
436
|
+
while (inFlight.length) {
|
|
437
|
+
throwIfAborted(signal);
|
|
438
|
+
// Refill only after this read settles: refilling before awaiting it made
|
|
439
|
+
// the configured window run one extra read. The next read still starts
|
|
440
|
+
// before this file is analyzed, while the total stays within the limit.
|
|
441
|
+
const settled = await abortable(inFlight.shift(), signal);
|
|
442
|
+
throwIfAborted(signal);
|
|
443
|
+
if (next < paths.length)
|
|
444
|
+
inFlight.push(read(paths[next++]));
|
|
445
|
+
yield settled;
|
|
446
|
+
}
|
|
436
447
|
}
|
|
437
|
-
|
|
438
448
|
function countBy(items, fn) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
449
|
+
const out = {};
|
|
450
|
+
for (const item of items) {
|
|
451
|
+
const k = fn(item);
|
|
452
|
+
out[k] = (out[k] || 0) + 1;
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
445
455
|
}
|
|
446
|
-
|
|
447
456
|
async function readModulePath(source) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
457
|
+
try {
|
|
458
|
+
const text = await source.read('go.mod');
|
|
459
|
+
const m = text.match(/^module\s+(\S+)/m);
|
|
460
|
+
return m ? m[1] : '';
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return '';
|
|
464
|
+
}
|
|
455
465
|
}
|
|
456
|
-
|
|
457
466
|
// Simplified .gitignore: plain names, dir/, *.ext, /anchored. No negation —
|
|
458
467
|
// documented limitation.
|
|
459
468
|
async function buildIgnoreMatcher(source) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
line
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return (path) => rules.some((r) => r.re.test(path));
|
|
469
|
+
let text = '';
|
|
470
|
+
try {
|
|
471
|
+
text = await source.read('.gitignore');
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return () => false;
|
|
475
|
+
}
|
|
476
|
+
const rules = [];
|
|
477
|
+
for (let line of text.split('\n')) {
|
|
478
|
+
line = line.trim();
|
|
479
|
+
if (!line || line.startsWith('#') || line.startsWith('!'))
|
|
480
|
+
continue;
|
|
481
|
+
const anchored = line.startsWith('/');
|
|
482
|
+
const pattern = line.replace(/^\//, '').replace(/\/$/, '');
|
|
483
|
+
if (!pattern)
|
|
484
|
+
continue;
|
|
485
|
+
const re = new RegExp((anchored ? '^' : '(^|/)') +
|
|
486
|
+
pattern.split(/(\*\*|\*)/).map((part) => part === '**' ? '.*' : part === '*' ? '[^/]*' : escapeRe(part)).join('') +
|
|
487
|
+
'($|/)');
|
|
488
|
+
rules.push({ re });
|
|
489
|
+
}
|
|
490
|
+
return (path) => rules.some((r) => r.re.test(path));
|
|
483
491
|
}
|
|
484
|
-
|
|
485
492
|
function escapeRe(s) {
|
|
486
|
-
|
|
493
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
487
494
|
}
|
|
488
|
-
|
|
489
495
|
async function readTsConfigPaths(source) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
496
|
+
for (const name of ['tsconfig.json', 'jsconfig.json']) {
|
|
497
|
+
try {
|
|
498
|
+
const text = await source.read(name);
|
|
499
|
+
const json = parseJsonConfig(text);
|
|
500
|
+
const compilerOptions = json.compilerOptions || {};
|
|
501
|
+
const baseUrl = compilerOptions.baseUrl ? compilerOptions.baseUrl.replace(/^\.?\//, '').replace(/\/$/, '') : '';
|
|
502
|
+
const paths = compilerOptions.paths || {};
|
|
503
|
+
const sourceRoot = compilerOptions.rootDir?.replace(/^\.?\//, '').replace(/\/$/, '');
|
|
504
|
+
const outputRoot = compilerOptions.outDir?.replace(/^\.?\//, '').replace(/\/$/, '').replace(/^\.$/, '');
|
|
505
|
+
if (Object.keys(paths).length || baseUrl || sourceRoot) {
|
|
506
|
+
return { baseUrl, paths, sourceRoot, outputRoot };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch { }
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
504
512
|
}
|
|
513
|
+
//# sourceMappingURL=scan.js.map
|