codebase-onboarder 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -46
- 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 +321 -329
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +384 -313
- 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 +368 -302
- 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 +42 -44
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +90 -95
- 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 +143 -144
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +465 -482
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +28 -44
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +214 -161
- 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 +35 -44
- 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 +152 -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 +37 -38
- 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 -64
- 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 +41 -43
- 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/server/daemon.js
CHANGED
|
@@ -18,178 +18,188 @@
|
|
|
18
18
|
// 2. Detach properly, or "background" is a lie. `detached: true` puts the
|
|
19
19
|
// child in its own process group, and `unref()` drops our handle on it, so
|
|
20
20
|
// a Ctrl-C in the parent terminal does not take the server down with it.
|
|
21
|
-
|
|
22
21
|
import { spawn } from 'node:child_process';
|
|
23
22
|
import fs from 'node:fs';
|
|
24
23
|
import fsp from 'node:fs/promises';
|
|
25
24
|
import http from 'node:http';
|
|
26
25
|
import path from 'node:path';
|
|
27
26
|
import { fileURLToPath } from 'node:url';
|
|
28
|
-
|
|
29
27
|
import { configPath } from './config.js';
|
|
30
28
|
import { readPidFile } from './pidfile.js';
|
|
31
|
-
|
|
32
29
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
|
|
34
30
|
// The installed entry point, not `process.argv[1]`: a globally installed
|
|
35
31
|
// `onboarder` is a symlink into node_modules, and the child has to run the real
|
|
36
32
|
// file to find its siblings.
|
|
37
33
|
export const CLI_ENTRY = path.resolve(HERE, '..', 'bin', 'onboarder.js');
|
|
38
|
-
|
|
39
34
|
// One file per config, beside the config, so `--config` isolation (tests,
|
|
40
35
|
// containers, several profiles) carries the log with it.
|
|
41
36
|
export function logPath(configFile = configPath()) {
|
|
42
|
-
|
|
37
|
+
return path.join(path.dirname(path.resolve(configFile)), 'onboarder.log');
|
|
43
38
|
}
|
|
44
|
-
|
|
45
39
|
// Rotated history. One generation is deliberate: a log that grows without bound
|
|
46
40
|
// is a bug report waiting to happen, and one previous file is enough to see what
|
|
47
41
|
// happened just before a crash.
|
|
48
42
|
export const MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
49
|
-
|
|
50
43
|
export async function rotateLogIfNeeded(file = logPath(), maxBytes = MAX_LOG_BYTES) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
try {
|
|
45
|
+
const { size } = await fsp.stat(file);
|
|
46
|
+
if (size <= maxBytes)
|
|
47
|
+
return false;
|
|
48
|
+
await fsp.rename(file, file + '.1');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false; // no log yet, or not ours to move
|
|
53
|
+
}
|
|
59
54
|
}
|
|
60
|
-
|
|
61
55
|
export async function readLog(file = logPath(), bytes = 64 * 1024) {
|
|
62
|
-
try {
|
|
63
|
-
const handle = await fsp.open(file, 'r');
|
|
64
56
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
const handle = await fsp.open(file, 'r');
|
|
58
|
+
try {
|
|
59
|
+
const { size } = await handle.stat();
|
|
60
|
+
const start = Math.max(0, size - bytes);
|
|
61
|
+
const buffer = Buffer.alloc(size - start);
|
|
62
|
+
await handle.read(buffer, 0, buffer.length, start);
|
|
63
|
+
return buffer.toString('utf8');
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await handle.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return '';
|
|
72
71
|
}
|
|
73
|
-
} catch {
|
|
74
|
-
return '';
|
|
75
|
-
}
|
|
76
72
|
}
|
|
77
|
-
|
|
78
73
|
// The last `count` lines, oldest first — the shape `onboarder logs` prints and
|
|
79
74
|
// the shape an error report wants pasted into it.
|
|
80
75
|
export async function tailLog(file = logPath(), count = 40) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
const text = await readLog(file);
|
|
77
|
+
const lines = text.split('\n').filter((line) => line.trim());
|
|
78
|
+
return lines.slice(-Math.max(1, count));
|
|
84
79
|
}
|
|
85
|
-
|
|
86
80
|
export async function logExists(file = logPath()) {
|
|
87
|
-
|
|
81
|
+
try {
|
|
82
|
+
await fsp.access(file);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
88
|
}
|
|
89
|
-
|
|
90
89
|
// Ask the server whether it is up. A 2xx–4xx from /api/health means the socket
|
|
91
90
|
// is bound and the router is serving; the body is not interesting, the answer is.
|
|
92
91
|
export function probe(url, timeoutMs = 1000) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const request = http.get(url, { timeout: timeoutMs }, (res) => {
|
|
94
|
+
res.resume();
|
|
95
|
+
resolve(res.statusCode >= 200 && res.statusCode < 500);
|
|
96
|
+
});
|
|
97
|
+
request.on('timeout', () => { request.destroy(); resolve(false); });
|
|
98
|
+
request.on('error', () => resolve(false));
|
|
97
99
|
});
|
|
98
|
-
request.on('timeout', () => { request.destroy(); resolve(false); });
|
|
99
|
-
request.on('error', () => resolve(false));
|
|
100
|
-
});
|
|
101
100
|
}
|
|
102
|
-
|
|
103
101
|
export async function waitForReady(url, { timeoutMs = 20000, intervalMs = 200, check = probe } = {}) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
const deadline = Date.now() + timeoutMs;
|
|
103
|
+
for (;;) {
|
|
104
|
+
if (await check(url))
|
|
105
|
+
return true;
|
|
106
|
+
if (Date.now() >= deadline)
|
|
107
|
+
return false;
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
109
|
+
}
|
|
110
110
|
}
|
|
111
|
-
|
|
112
111
|
// Re-run this same CLI, detached. The child is a plain foreground `start` — it
|
|
113
112
|
// writes the pid file, prints its own banner, and handles signals exactly as it
|
|
114
113
|
// always has. Nothing about the server changes; only who is holding the terminal
|
|
115
114
|
// does.
|
|
116
115
|
export function spawnDetached({ configFile = configPath(), entry = CLI_ENTRY, env = process.env, log = logPath(configFile) } = {}) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
const fd = fs.openSync(log, 'a');
|
|
117
|
+
try {
|
|
118
|
+
const child = spawn(process.execPath, [entry, 'start', '--config', configFile], {
|
|
119
|
+
detached: true,
|
|
120
|
+
stdio: ['ignore', fd, fd],
|
|
121
|
+
env: { ...env, ONBOARDER_BACKGROUND: '1' },
|
|
122
|
+
});
|
|
123
|
+
child.on('error', (..._args) => { });
|
|
124
|
+
child.unref();
|
|
125
|
+
return child.pid;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
fs.closeSync(fd);
|
|
129
|
+
}
|
|
130
130
|
}
|
|
131
|
-
|
|
132
131
|
// Does the OS still have this process? Signal 0 asks without delivering.
|
|
133
132
|
function alive(pid) {
|
|
134
|
-
|
|
133
|
+
try {
|
|
134
|
+
process.kill(pid, 0);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
return e.code === 'EPERM';
|
|
139
|
+
}
|
|
135
140
|
}
|
|
136
|
-
|
|
137
141
|
// Wait for the detached child to become the *recorded* server. The pid file is
|
|
138
142
|
// the authority here, not the spawn pid: it is what `status` and `stop` read,
|
|
139
143
|
// so waiting on it means the process we report is the one the user can control.
|
|
140
144
|
export async function waitForPidFile(configFile, { timeoutMs = 20000, intervalMs = 150, readPid = readPidFile, isAlive = alive } = {}) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const deadline = Date.now() + timeoutMs;
|
|
146
|
+
for (;;) {
|
|
147
|
+
const pid = readPid(configFile);
|
|
148
|
+
if (pid && isAlive(pid))
|
|
149
|
+
return pid;
|
|
150
|
+
if (Date.now() >= deadline)
|
|
151
|
+
return null;
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
|
-
|
|
150
155
|
// Follow a growing file. Polling rather than fs.watch: the log is appended by a
|
|
151
156
|
// *different* process, and watchers on a file another process holds open are
|
|
152
157
|
// unreliable across platforms (and absent on some network mounts). Half a second
|
|
153
158
|
// of latency on a human-facing log tail is invisible.
|
|
154
159
|
export function followLog(file, onLine, { intervalMs = 500, from = 'end' } = {}) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const tick = async () => {
|
|
168
|
-
if (stopped) return;
|
|
169
|
-
try {
|
|
170
|
-
const { size } = await fsp.stat(file);
|
|
171
|
-
if (size < position) { position = 0; partial = ''; } // rotated under us
|
|
172
|
-
if (size > position) {
|
|
173
|
-
const handle = await fsp.open(file, 'r');
|
|
160
|
+
let position = 0;
|
|
161
|
+
let partial = '';
|
|
162
|
+
let stopped = false;
|
|
163
|
+
const stop = () => { stopped = true; };
|
|
164
|
+
if (from === 'start') {
|
|
165
|
+
fsp.readFile(file, 'utf8').then((text) => { for (const line of text.split('\n'))
|
|
166
|
+
if (line)
|
|
167
|
+
onLine(line); }, (..._args) => { });
|
|
168
|
+
}
|
|
169
|
+
const tick = async () => {
|
|
170
|
+
if (stopped)
|
|
171
|
+
return;
|
|
174
172
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
const { size } = await fsp.stat(file);
|
|
174
|
+
if (size < position) {
|
|
175
|
+
position = 0;
|
|
176
|
+
partial = '';
|
|
177
|
+
} // rotated under us
|
|
178
|
+
if (size > position) {
|
|
179
|
+
const handle = await fsp.open(file, 'r');
|
|
180
|
+
try {
|
|
181
|
+
const length = size - position;
|
|
182
|
+
const buffer = Buffer.alloc(length);
|
|
183
|
+
await handle.read(buffer, 0, length, position);
|
|
184
|
+
position = size;
|
|
185
|
+
// A read can land mid-line; hold the remainder until its newline shows.
|
|
186
|
+
const lines = (partial + buffer.toString('utf8')).split('\n');
|
|
187
|
+
partial = lines.pop() ?? '';
|
|
188
|
+
for (const line of lines)
|
|
189
|
+
onLine(line);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await handle.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
185
195
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
catch {
|
|
197
|
+
// The file may not exist yet (first run) — try again on the next tick.
|
|
198
|
+
}
|
|
199
|
+
if (!stopped)
|
|
200
|
+
setTimeout(tick, intervalMs).unref();
|
|
201
|
+
};
|
|
202
|
+
setTimeout(tick, intervalMs).unref();
|
|
203
|
+
return stop;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/server/daemon.ts"],"names":[],"mappings":"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","sourcesContent":["// Running the server without the terminal that asked for it.\n//\n// `onboarder start` keeps the process attached: closing the shell kills it, and\n// that is the right behavior for a foreground command. `onboarder start\n// background` wants the opposite — the whole point is that the terminal can go\n// away — so this module does what a shell job control cannot do portably: it\n// re-launches the CLI as a **detached** child with its stdio pointed at a log\n// file, unrefs it, and then waits for the port to actually answer before it\n// claims success.\n//\n// Two rules keep this honest:\n//\n// 1. Never report \"running\" on the strength of the spawn alone. `spawn`\n// returning a pid proves a process was created, not that it bound the port\n// or survived settings validation. Readiness is an HTTP answer from\n// `/api/health`, polled with a deadline; on timeout the caller gets the\n// child's last log lines, not a false green light.\n// 2. Detach properly, or \"background\" is a lie. `detached: true` puts the\n// child in its own process group, and `unref()` drops our handle on it, so\n// a Ctrl-C in the parent terminal does not take the server down with it.\n\nimport { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport fsp from 'node:fs/promises';\nimport http from 'node:http';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { configPath } from './config.js';\nimport { readPidFile } from './pidfile.js';\n\nconst HERE = path.dirname(fileURLToPath(import.meta.url));\n\n// The installed entry point, not `process.argv[1]`: a globally installed\n// `onboarder` is a symlink into node_modules, and the child has to run the real\n// file to find its siblings.\nexport const CLI_ENTRY = path.resolve(HERE, '..', 'bin', 'onboarder.js');\n\n// One file per config, beside the config, so `--config` isolation (tests,\n// containers, several profiles) carries the log with it.\nexport function logPath(configFile = configPath()) {\n return path.join(path.dirname(path.resolve(configFile)), 'onboarder.log');\n}\n\n// Rotated history. One generation is deliberate: a log that grows without bound\n// is a bug report waiting to happen, and one previous file is enough to see what\n// happened just before a crash.\nexport const MAX_LOG_BYTES = 2 * 1024 * 1024;\n\nexport async function rotateLogIfNeeded(file = logPath(), maxBytes = MAX_LOG_BYTES) {\n try {\n const { size } = await fsp.stat(file);\n if (size <= maxBytes) return false;\n await fsp.rename(file, file + '.1');\n return true;\n } catch {\n return false; // no log yet, or not ours to move\n }\n}\n\nexport async function readLog(file = logPath(), bytes = 64 * 1024) {\n try {\n const handle = await fsp.open(file, 'r');\n try {\n const { size } = await handle.stat();\n const start = Math.max(0, size - bytes);\n const buffer = Buffer.alloc(size - start);\n await handle.read(buffer, 0, buffer.length, start);\n return buffer.toString('utf8');\n } finally {\n await handle.close();\n }\n } catch {\n return '';\n }\n}\n\n// The last `count` lines, oldest first — the shape `onboarder logs` prints and\n// the shape an error report wants pasted into it.\nexport async function tailLog(file = logPath(), count = 40) {\n const text = await readLog(file);\n const lines = text.split('\\n').filter((line) => line.trim());\n return lines.slice(-Math.max(1, count));\n}\n\nexport async function logExists(file = logPath()) {\n try { await fsp.access(file); return true; } catch { return false; }\n}\n\n// Ask the server whether it is up. A 2xx–4xx from /api/health means the socket\n// is bound and the router is serving; the body is not interesting, the answer is.\nexport function probe(url, timeoutMs = 1000) {\n return new Promise((resolve) => {\n const request = http.get(url, { timeout: timeoutMs }, (res) => {\n res.resume();\n resolve(res.statusCode >= 200 && res.statusCode < 500);\n });\n request.on('timeout', () => { request.destroy(); resolve(false); });\n request.on('error', () => resolve(false));\n });\n}\n\nexport async function waitForReady(url, { timeoutMs = 20000, intervalMs = 200, check = probe }: Record<string, any> = {}) {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (await check(url)) return true;\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\n// Re-run this same CLI, detached. The child is a plain foreground `start` — it\n// writes the pid file, prints its own banner, and handles signals exactly as it\n// always has. Nothing about the server changes; only who is holding the terminal\n// does.\nexport function spawnDetached({ configFile = configPath(), entry = CLI_ENTRY, env = process.env, log = logPath(configFile) }: Record<string, any> = {}) {\n const fd = fs.openSync(log, 'a');\n try {\n const child = spawn(process.execPath, [entry, 'start', '--config', configFile], {\n detached: true,\n stdio: ['ignore', fd, fd],\n env: { ...env, ONBOARDER_BACKGROUND: '1' },\n });\n child.on('error', (..._args: unknown[]) => {});\n child.unref();\n return child.pid;\n } finally {\n fs.closeSync(fd);\n }\n}\n\n// Does the OS still have this process? Signal 0 asks without delivering.\nfunction alive(pid) {\n try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; }\n}\n\n// Wait for the detached child to become the *recorded* server. The pid file is\n// the authority here, not the spawn pid: it is what `status` and `stop` read,\n// so waiting on it means the process we report is the one the user can control.\nexport async function waitForPidFile(configFile, { timeoutMs = 20000, intervalMs = 150, readPid = readPidFile, isAlive = alive }: Record<string, any> = {}) {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const pid = readPid(configFile);\n if (pid && isAlive(pid)) return pid;\n if (Date.now() >= deadline) return null;\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\n// Follow a growing file. Polling rather than fs.watch: the log is appended by a\n// *different* process, and watchers on a file another process holds open are\n// unreliable across platforms (and absent on some network mounts). Half a second\n// of latency on a human-facing log tail is invisible.\nexport function followLog(file, onLine, { intervalMs = 500, from = 'end' }: Record<string, any> = {}) {\n let position = 0;\n let partial = '';\n let stopped = false;\n const stop = () => { stopped = true; };\n\n if (from === 'start') {\n fsp.readFile(file, 'utf8').then(\n (text) => { for (const line of text.split('\\n')) if (line) onLine(line); },\n (..._args: unknown[]) => {},\n );\n }\n\n const tick = async () => {\n if (stopped) return;\n try {\n const { size } = await fsp.stat(file);\n if (size < position) { position = 0; partial = ''; } // rotated under us\n if (size > position) {\n const handle = await fsp.open(file, 'r');\n try {\n const length = size - position;\n const buffer = Buffer.alloc(length);\n await handle.read(buffer, 0, length, position);\n position = size;\n // A read can land mid-line; hold the remainder until its newline shows.\n const lines = (partial + buffer.toString('utf8')).split('\\n');\n partial = lines.pop() ?? '';\n for (const line of lines) onLine(line);\n } finally {\n await handle.close();\n }\n }\n } catch {\n // The file may not exist yet (first run) — try again on the next tick.\n }\n if (!stopped) setTimeout(tick, intervalMs).unref();\n };\n\n setTimeout(tick, intervalMs).unref();\n return stop;\n}"]}
|
package/server/fileSourceNode.js
CHANGED
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
// FileSource over node:fs. Paths crossing the boundary are repo-relative
|
|
2
2
|
// POSIX style; this adapter is the only place that knows about the real
|
|
3
3
|
// filesystem layout.
|
|
4
|
-
|
|
5
4
|
import { promises as fs } from 'node:fs';
|
|
6
5
|
import path from 'node:path';
|
|
7
|
-
|
|
8
6
|
import { resolveInside } from './paths.js';
|
|
9
|
-
|
|
10
7
|
export function nodeFileSource(rootAbs) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
8
|
+
const toAbs = (rel) => {
|
|
9
|
+
// `list('')` is how the scanner asks for the top of the repo. Everywhere
|
|
10
|
+
// else an empty path is a caller mistake, which is why resolveInside
|
|
11
|
+
// refuses it rather than quietly meaning "the root".
|
|
12
|
+
if (rel === '' || rel == null)
|
|
13
|
+
return rootAbs;
|
|
14
|
+
// The scanner only ever produces paths from directory listings, so this
|
|
15
|
+
// should never fire — but it's the same check the HTTP routes use, and a
|
|
16
|
+
// free one.
|
|
17
|
+
const abs = resolveInside(rootAbs, String(rel));
|
|
18
|
+
if (!abs)
|
|
19
|
+
throw new Error('Path escapes the repository root');
|
|
20
|
+
return abs;
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
kind: 'node',
|
|
24
|
+
root: rootAbs,
|
|
25
|
+
name: path.basename(rootAbs),
|
|
26
|
+
async list(dir) {
|
|
27
|
+
const entries = await fs.readdir(toAbs(dir), { withFileTypes: true });
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
if (e.isSymbolicLink())
|
|
31
|
+
continue; // no loops, no surprises
|
|
32
|
+
const rel = dir ? dir + '/' + e.name : e.name;
|
|
33
|
+
out.push({ name: e.name, path: rel, type: e.isDirectory() ? 'dir' : 'file' });
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
},
|
|
37
|
+
async read(rel) {
|
|
38
|
+
return fs.readFile(toAbs(rel), 'utf8');
|
|
39
|
+
},
|
|
40
|
+
};
|
|
44
41
|
}
|
|
42
|
+
//# sourceMappingURL=fileSourceNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileSourceNode.js","sourceRoot":"","sources":["../src/server/fileSourceNode.ts"],"names":[],"mappings":"AACA,yEAAyE;AACzE,wEAAwE;AACxE,qBAAqB;AAErB,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,KAAK,GAAG,CAAC,GAAW,EAAU,EAAE;QACpC,yEAAyE;QACzE,qEAAqE;QACrE,qDAAqD;QACrD,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,OAAO,CAAC;QAC9C,wEAAwE;QACxE,yEAAyE;QACzE,YAAY;QACZ,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC9D,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAE5B,KAAK,CAAC,IAAI,CAAC,GAAW;YACpB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,GAAG,GAAgB,EAAE,CAAC;YAC5B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,CAAC,cAAc,EAAE;oBAAE,SAAS,CAAC,yBAAyB;gBAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAChF,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,GAAW;YACpB,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { FileSource, FileEntry } from '../shared/contracts.js';\n// FileSource over node:fs. Paths crossing the boundary are repo-relative\n// POSIX style; this adapter is the only place that knows about the real\n// filesystem layout.\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { resolveInside } from './paths.js';\n\nexport function nodeFileSource(rootAbs: string): FileSource {\n const toAbs = (rel: string): string => {\n // `list('')` is how the scanner asks for the top of the repo. Everywhere\n // else an empty path is a caller mistake, which is why resolveInside\n // refuses it rather than quietly meaning \"the root\".\n if (rel === '' || rel == null) return rootAbs;\n // The scanner only ever produces paths from directory listings, so this\n // should never fire — but it's the same check the HTTP routes use, and a\n // free one.\n const abs = resolveInside(rootAbs, String(rel));\n if (!abs) throw new Error('Path escapes the repository root');\n return abs;\n };\n\n return {\n kind: 'node',\n root: rootAbs,\n name: path.basename(rootAbs),\n\n async list(dir: string): Promise<FileEntry[]> {\n const entries = await fs.readdir(toAbs(dir), { withFileTypes: true });\n const out: FileEntry[] = [];\n for (const e of entries) {\n if (e.isSymbolicLink()) continue; // no loops, no surprises\n const rel = dir ? dir + '/' + e.name : e.name;\n out.push({ name: e.name, path: rel, type: e.isDirectory() ? 'dir' : 'file' });\n }\n return out;\n },\n\n async read(rel: string): Promise<string> {\n return fs.readFile(toAbs(rel), 'utf8');\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function isCloneDir(dir: any): boolean;
|
|
2
|
+
export declare function assertGitUrl(url: any): string;
|
|
3
|
+
export declare function repoNameFromUrl(url: any): string;
|
|
4
|
+
export declare function cloneRepo(url: any): Promise<{
|
|
5
|
+
id: string;
|
|
6
|
+
dir: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function removeClone(dir: any): Promise<void>;
|
|
9
|
+
export declare function removeCloneSync(dir: any): void;
|
package/server/gitClone.js
CHANGED
|
@@ -1,95 +1,93 @@
|
|
|
1
1
|
// Shallow-cloning for the git-URL flow. The URL is validated, then passed to
|
|
2
2
|
// git as an argument array — never through a shell — so nothing in it gets
|
|
3
3
|
// executed. Clones land in the OS temp dir and are removed on request.
|
|
4
|
-
|
|
5
4
|
import { spawn } from 'node:child_process';
|
|
6
5
|
import { promises as fs, rmSync } from 'node:fs';
|
|
7
6
|
import os from 'node:os';
|
|
8
7
|
import path from 'node:path';
|
|
9
8
|
import crypto from 'node:crypto';
|
|
10
|
-
|
|
11
9
|
const GIT_URL_RE = /^(https?:\/\/[^\s]+|git@[^\s:]+:[^\s]+|ssh:\/\/[^\s]+)$/;
|
|
12
10
|
const CLONE_TIMEOUT_MS = 180_000;
|
|
13
|
-
|
|
14
11
|
// Where clones go, and the only shape of path the removals below will touch.
|
|
15
12
|
// One definition: a delete that walks a directory tree should not be guarded by
|
|
16
13
|
// a condition restated at each call site.
|
|
17
14
|
const CLONE_PREFIX = path.join(os.tmpdir(), 'onboarder-');
|
|
18
|
-
|
|
19
15
|
export function isCloneDir(dir) {
|
|
20
|
-
|
|
16
|
+
return typeof dir === 'string' && dir.startsWith(CLONE_PREFIX) && dir !== CLONE_PREFIX;
|
|
21
17
|
}
|
|
22
|
-
|
|
23
18
|
export function assertGitUrl(url) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
if (typeof url !== 'string' || !GIT_URL_RE.test(url.trim())) {
|
|
20
|
+
throw new Error('That doesn’t look like a git URL. https://, ssh:// or git@ forms work.');
|
|
21
|
+
}
|
|
22
|
+
return url.trim();
|
|
28
23
|
}
|
|
29
|
-
|
|
30
24
|
// The name people recognize, from any URL shape we accept:
|
|
31
25
|
// https://github.com/org/repo.git, ssh://host/org/repo, git@host:org/repo
|
|
32
26
|
export function repoNameFromUrl(url) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const clean = String(url).trim().replace(/\.git$/, '').replace(/\/+$/, '');
|
|
28
|
+
const tail = !clean.includes('://') && clean.includes(':') ? clean.split(':').pop() : clean;
|
|
29
|
+
return tail.split('/').pop() || 'repo';
|
|
36
30
|
}
|
|
37
|
-
|
|
38
31
|
export async function cloneRepo(url) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
32
|
+
const id = crypto.randomBytes(6).toString('hex');
|
|
33
|
+
const dir = CLONE_PREFIX + id;
|
|
34
|
+
try {
|
|
35
|
+
// Blobless rather than shallow: `--depth 1` threw the history away, and
|
|
36
|
+
// history is half of what this tool has to say (churn × complexity).
|
|
37
|
+
// `--filter=blob:none` fetches every commit and tree but no file versions —
|
|
38
|
+
// full history at a fraction of a full clone's weight; blobs arrive on
|
|
39
|
+
// demand if anything ever checks one out. `--single-branch` keeps it to the
|
|
40
|
+
// default branch: the newcomer cares about main, not every fork line.
|
|
41
|
+
await run('git', ['clone', '--filter=blob:none', '--single-branch', '--quiet', url, dir]);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
await fs.rm(dir, { recursive: true, force: true }).catch((..._args) => { });
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
return { id, dir };
|
|
54
48
|
}
|
|
55
|
-
|
|
56
49
|
export async function removeClone(dir) {
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
if (!isCloneDir(dir))
|
|
51
|
+
return;
|
|
52
|
+
await fs.rm(dir, { recursive: true, force: true }).catch((..._args) => { });
|
|
59
53
|
}
|
|
60
|
-
|
|
61
54
|
// The version an exit handler can actually finish. A process on its way out will
|
|
62
55
|
// not come back to resolve a promise, so the sweep on shutdown has to be
|
|
63
56
|
// synchronous or it does not happen at all.
|
|
64
57
|
export function removeCloneSync(dir) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
58
|
+
if (!isCloneDir(dir))
|
|
59
|
+
return;
|
|
60
|
+
try {
|
|
61
|
+
rmSync(dir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* going down anyway; a leftover temp dir is not worth a crash on exit */
|
|
65
|
+
}
|
|
71
66
|
}
|
|
72
|
-
|
|
73
67
|
function run(cmd, args) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const child = spawn(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
70
|
+
let stderr = '';
|
|
71
|
+
child.stderr.on('data', (chunk) => {
|
|
72
|
+
stderr += chunk;
|
|
73
|
+
if (stderr.length > 4000)
|
|
74
|
+
stderr = stderr.slice(-4000);
|
|
75
|
+
});
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
child.kill('SIGKILL');
|
|
78
|
+
reject(new Error('git clone took too long and was stopped.'));
|
|
79
|
+
}, CLONE_TIMEOUT_MS);
|
|
80
|
+
child.on('error', (err) => {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
reject(new Error('Could not run git: ' + err.message));
|
|
83
|
+
});
|
|
84
|
+
child.on('close', (code) => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
if (code === 0)
|
|
87
|
+
resolve();
|
|
88
|
+
else
|
|
89
|
+
reject(new Error('git clone failed. ' + (stderr.trim().split('\n').pop() || '')));
|
|
90
|
+
});
|
|
80
91
|
});
|
|
81
|
-
const timer = setTimeout(() => {
|
|
82
|
-
child.kill('SIGKILL');
|
|
83
|
-
reject(new Error('git clone took too long and was stopped.'));
|
|
84
|
-
}, CLONE_TIMEOUT_MS);
|
|
85
|
-
child.on('error', (err) => {
|
|
86
|
-
clearTimeout(timer);
|
|
87
|
-
reject(new Error('Could not run git: ' + err.message));
|
|
88
|
-
});
|
|
89
|
-
child.on('close', (code) => {
|
|
90
|
-
clearTimeout(timer);
|
|
91
|
-
if (code === 0) resolve();
|
|
92
|
-
else reject(new Error('git clone failed. ' + (stderr.trim().split('\n').pop() || '')));
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
92
|
}
|
|
93
|
+
//# sourceMappingURL=gitClone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitClone.js","sourceRoot":"","sources":["../src/server/gitClone.ts"],"names":[],"mappings":"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","sourcesContent":["// Shallow-cloning for the git-URL flow. The URL is validated, then passed to\n// git as an argument array — never through a shell — so nothing in it gets\n// executed. Clones land in the OS temp dir and are removed on request.\n\nimport { spawn } from 'node:child_process';\nimport { promises as fs, rmSync } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\n\nconst GIT_URL_RE = /^(https?:\\/\\/[^\\s]+|git@[^\\s:]+:[^\\s]+|ssh:\\/\\/[^\\s]+)$/;\nconst CLONE_TIMEOUT_MS = 180_000;\n\n// Where clones go, and the only shape of path the removals below will touch.\n// One definition: a delete that walks a directory tree should not be guarded by\n// a condition restated at each call site.\nconst CLONE_PREFIX = path.join(os.tmpdir(), 'onboarder-');\n\nexport function isCloneDir(dir) {\n return typeof dir === 'string' && dir.startsWith(CLONE_PREFIX) && dir !== CLONE_PREFIX;\n}\n\nexport function assertGitUrl(url) {\n if (typeof url !== 'string' || !GIT_URL_RE.test(url.trim())) {\n throw new Error('That doesn’t look like a git URL. https://, ssh:// or git@ forms work.');\n }\n return url.trim();\n}\n\n// The name people recognize, from any URL shape we accept:\n// https://github.com/org/repo.git, ssh://host/org/repo, git@host:org/repo\nexport function repoNameFromUrl(url) {\n const clean = String(url).trim().replace(/\\.git$/, '').replace(/\\/+$/, '');\n const tail = !clean.includes('://') && clean.includes(':') ? clean.split(':').pop() : clean;\n return tail.split('/').pop() || 'repo';\n}\n\nexport async function cloneRepo(url) {\n const id = crypto.randomBytes(6).toString('hex');\n const dir = CLONE_PREFIX + id;\n try {\n // Blobless rather than shallow: `--depth 1` threw the history away, and\n // history is half of what this tool has to say (churn × complexity).\n // `--filter=blob:none` fetches every commit and tree but no file versions —\n // full history at a fraction of a full clone's weight; blobs arrive on\n // demand if anything ever checks one out. `--single-branch` keeps it to the\n // default branch: the newcomer cares about main, not every fork line.\n await run('git', ['clone', '--filter=blob:none', '--single-branch', '--quiet', url, dir]);\n } catch (err) {\n await fs.rm(dir, { recursive: true, force: true }).catch((..._args: unknown[]) => {});\n throw err;\n }\n return { id, dir };\n}\n\nexport async function removeClone(dir) {\n if (!isCloneDir(dir)) return;\n await fs.rm(dir, { recursive: true, force: true }).catch((..._args: unknown[]) => {});\n}\n\n// The version an exit handler can actually finish. A process on its way out will\n// not come back to resolve a promise, so the sweep on shutdown has to be\n// synchronous or it does not happen at all.\nexport function removeCloneSync(dir) {\n if (!isCloneDir(dir)) return;\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n /* going down anyway; a leftover temp dir is not worth a crash on exit */\n }\n}\n\nfunction run(cmd, args) {\n return new Promise<void>((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });\n let stderr = '';\n child.stderr.on('data', (chunk) => {\n stderr += chunk;\n if (stderr.length > 4000) stderr = stderr.slice(-4000);\n });\n const timer = setTimeout(() => {\n child.kill('SIGKILL');\n reject(new Error('git clone took too long and was stopped.'));\n }, CLONE_TIMEOUT_MS);\n child.on('error', (err) => {\n clearTimeout(timer);\n reject(new Error('Could not run git: ' + err.message));\n });\n child.on('close', (code) => {\n clearTimeout(timer);\n if (code === 0) resolve();\n else reject(new Error('git clone failed. ' + (stderr.trim().split('\\n').pop() || '')));\n });\n });\n}\n"]}
|