codeboarding 0.9.4__tar.gz → 0.9.5__tar.gz
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.
- {codeboarding-0.9.4/codeboarding.egg-info → codeboarding-0.9.5}/PKG-INFO +1 -1
- codeboarding-0.9.5/README.md +149 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5/codeboarding.egg-info}/PKG-INFO +1 -1
- {codeboarding-0.9.4 → codeboarding-0.9.5}/codeboarding.egg-info/SOURCES.txt +2 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/install.py +86 -45
- {codeboarding-0.9.4 → codeboarding-0.9.5}/pyproject.toml +1 -1
- codeboarding-0.9.5/tests/test_install.py +120 -0
- codeboarding-0.9.5/tests/test_tool_registry.py +94 -0
- codeboarding-0.9.5/tests/test_user_config.py +65 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tool_registry.py +97 -14
- codeboarding-0.9.4/README.md +0 -281
- codeboarding-0.9.4/tests/test_install.py +0 -51
- {codeboarding-0.9.4 → codeboarding-0.9.5}/LICENSE +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/PYPI.md +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/abstraction_agent.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/agent.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/agent_responses.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/cluster_methods_mixin.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/constants.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/details_agent.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/llm_config.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/meta_agent.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/planner_agent.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/abstract_prompt_factory.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/claude_prompts.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/deepseek_prompts.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/gemini_flash_prompts.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/glm_prompts.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/gpt_prompts.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/kimi_prompts.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/prompts/prompt_factory.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/base.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_file.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_file_structure.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_git_diff.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_source.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/tools/toolkit.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/agents/validation.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/caching/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/caching/cache.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/caching/meta_cache.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/codeboarding.egg-info/requires.txt +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/core/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/core/plugin_loader.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/core/protocols.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/core/registry.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/analysis_json.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/diagram_generator.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/component_checker.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/file_manager.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/impact_analyzer.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/io_utils.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/models.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/path_patching.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/reexpansion.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/scoped_analysis.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/updater.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/incremental/validation.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/manifest.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/diagram_analysis/version.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/duckdb_crud.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/github_action.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/cohesion.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/coupling.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/function_size.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/god_class.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/inheritance.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/instability.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/config.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/constants.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/models.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health/runner.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/health_main.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/logging_config.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/main.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/callbacks.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/context.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/mixin.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/paths.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/stats.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/monitoring/writers.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/output_generators/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/output_generators/html.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/output_generators/html_template.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/output_generators/markdown.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/output_generators/mdx.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/output_generators/sphinx.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/repo_utils/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/repo_utils/change_detector.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/repo_utils/errors.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/repo_utils/git_diff.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/repo_utils/ignore.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/setup.cfg +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/analysis_cache.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/analysis_result.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/cluster_change_analyzer.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/cluster_helpers.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/constants.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/git_diff_analyzer.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/graph.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/incremental_orchestrator.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/lsp_client/client.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/lsp_client/java_client.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/lsp_client/language_settings.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/lsp_client/typescript_client.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/reference_resolve_mixin.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_github_action.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_incremental_analyzer.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_logging_config.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_main.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/tests/test_windows_encoding.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/user_config.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/utils.py +0 -0
- {codeboarding-0.9.4 → codeboarding-0.9.5}/vscode_constants.py +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# CodeBoarding
|
|
2
|
+
|
|
3
|
+
See what your AI is building before it breaks.
|
|
4
|
+
|
|
5
|
+
CodeBoarding gives developers and coding agents a visual map of a codebase. It combines static analysis with LLM reasoning to generate architecture diagrams, component-level documentation, and navigable outputs you can use in your IDE, CI, and docs.
|
|
6
|
+
|
|
7
|
+
[Website](https://codeboarding.org) · [Open VSX extension](https://open-vsx.org/extension/CodeBoarding/codeboarding) · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) · [GitHub Action](https://github.com/marketplace/actions/codeboarding-diagram-first-documentation) ·[Discord](https://discord.gg/T5zHTJYFuy)
|
|
8
|
+
|
|
9
|
+
[](https://open-vsx.org/extension/CodeBoarding/codeboarding)
|
|
10
|
+
|
|
11
|
+
Install the extension from Open VSX.
|
|
12
|
+
|
|
13
|
+
[](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
|
14
|
+
[](https://www.typescriptlang.org/)
|
|
15
|
+
[](https://www.java.com/)
|
|
16
|
+
[](https://www.python.org/)
|
|
17
|
+
[](https://go.dev/)
|
|
18
|
+
[](https://www.php.net/)
|
|
19
|
+
|
|
20
|
+
## Few use cases:
|
|
21
|
+
|
|
22
|
+
- Keep architecture visible while agents code.
|
|
23
|
+
- Review AI-generated changes with system context before they turn into hidden debt.
|
|
24
|
+
- Understand large repositories faster with layered diagrams and component breakdowns.
|
|
25
|
+
- Share the same visual model across local workflows, IDEs, pull requests, and docs.
|
|
26
|
+
|
|
27
|
+
## What CodeBoarding generates
|
|
28
|
+
|
|
29
|
+
- High-level system architecture diagrams.
|
|
30
|
+
- Deeper component diagrams for important subsystems.
|
|
31
|
+
- Markdown documentation in `.codeboarding/`.
|
|
32
|
+
- Mermaid output that is easy to embed in docs and PRs.
|
|
33
|
+
- Incremental updates when only part of the codebase changes.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
graph LR
|
|
39
|
+
Application_Orchestrator_Repository_Manager["Application Orchestrator & Repository Manager"]
|
|
40
|
+
LLM_Agent_Core["LLM Agent Core"]
|
|
41
|
+
Static_Code_Analyzer["Static Code Analyzer"]
|
|
42
|
+
Agent_Tooling_Interface["Agent Tooling Interface"]
|
|
43
|
+
Incremental_Analysis_Engine["Incremental Analysis Engine"]
|
|
44
|
+
Documentation_Diagram_Generator["Documentation & Diagram Generator"]
|
|
45
|
+
Application_Orchestrator_Repository_Manager -- "Orchestrator initiates analysis workflow, leveraging incremental updates based on detected code changes." --> Incremental_Analysis_Engine
|
|
46
|
+
Application_Orchestrator_Repository_Manager -- "Orchestrator passes project context and triggers the main analysis workflow for the LLM Agent." --> LLM_Agent_Core
|
|
47
|
+
Incremental_Analysis_Engine -- "Incremental engine requests static analysis for specific code segments (new or changed)." --> Static_Code_Analyzer
|
|
48
|
+
Static_Code_Analyzer -- "Static analyzer provides analysis results to the incremental engine for caching." --> Incremental_Analysis_Engine
|
|
49
|
+
LLM_Agent_Core -- "LLM Agent invokes specialized tools to interact with the codebase and analysis data." --> Agent_Tooling_Interface
|
|
50
|
+
Agent_Tooling_Interface -- "Agent tools query the static analysis engine for detailed code insights." --> Static_Code_Analyzer
|
|
51
|
+
Static_Code_Analyzer -- "Static analysis engine provides requested data to the agent tools." --> Agent_Tooling_Interface
|
|
52
|
+
LLM_Agent_Core -- "LLM Agent delivers structured analysis insights for documentation and diagram generation." --> Documentation_Diagram_Generator
|
|
53
|
+
click Application_Orchestrator_Repository_Manager href "https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/Application_Orchestrator_Repository_Manager.md" "Details"
|
|
54
|
+
click LLM_Agent_Core href "https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/LLM_Agent_Core.md" "Details"
|
|
55
|
+
click Static_Code_Analyzer href "https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/Static_Code_Analyzer.md" "Details"
|
|
56
|
+
click Agent_Tooling_Interface href "https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/Agent_Tooling_Interface.md" "Details"
|
|
57
|
+
click Incremental_Analysis_Engine href "https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/Incremental_Analysis_Engine.md" "Details"
|
|
58
|
+
click Documentation_Diagram_Generator href "https://github.com/CodeBoarding/CodeBoarding/blob/main/.codeboarding/Documentation_Diagram_Generator.md" "Details"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For a deeper architecture walkthrough, see [`.codeboarding/overview.md`](.codeboarding/overview.md).
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
### Run from source
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv sync --frozen
|
|
69
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
70
|
+
python install.py
|
|
71
|
+
python main.py --local /path/to/repo
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Use the packaged CLI
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install codeboarding
|
|
78
|
+
codeboarding-setup
|
|
79
|
+
codeboarding --local /path/to/repo
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Output is written to `/path/to/repo/.codeboarding/`.
|
|
83
|
+
|
|
84
|
+
`python install.py` and `codeboarding-setup` download language server binaries to `~/.codeboarding/servers/`, shared across projects. `npm` is required for Python, TypeScript, JavaScript, and PHP language servers; if it is missing, setup can install it via `nodeenv`.
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
On first run, CodeBoarding creates `~/.codeboarding/config.toml`. Set one provider there or use environment variables.
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[provider]
|
|
92
|
+
# openai_api_key = "sk-..."
|
|
93
|
+
# anthropic_api_key = "sk-ant-..."
|
|
94
|
+
# google_api_key = "AIza..."
|
|
95
|
+
# vercel_api_key = "vck_..."
|
|
96
|
+
# aws_bearer_token_bedrock = "..."
|
|
97
|
+
# ollama_base_url = "http://localhost:11434"
|
|
98
|
+
# openrouter_api_key = "sk-..."
|
|
99
|
+
|
|
100
|
+
[llm]
|
|
101
|
+
# agent_model = "gemini-3-flash"
|
|
102
|
+
# parsing_model = "gemini-3-flash"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Shell environment variables such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, and `OLLAMA_BASE_URL` take precedence over the config file. For private repositories, set `GITHUB_TOKEN` in your environment.
|
|
106
|
+
|
|
107
|
+
## Common commands
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Analyze a local repository
|
|
111
|
+
python main.py --local ./my-project
|
|
112
|
+
|
|
113
|
+
# Increase diagram depth
|
|
114
|
+
python main.py --local ./my-project --depth-level 2
|
|
115
|
+
|
|
116
|
+
# Re-analyze only changed parts when possible
|
|
117
|
+
python main.py --local ./my-project --incremental
|
|
118
|
+
|
|
119
|
+
# Update a single component by ID
|
|
120
|
+
python main.py --local ./my-project --partial-component-id "a3f2b1c4d5e6f789"
|
|
121
|
+
|
|
122
|
+
# Analyze a remote GitHub repository
|
|
123
|
+
python main.py https://github.com/pytorch/pytorch
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Where to use it
|
|
127
|
+
|
|
128
|
+
- [CLI](https://github.com/CodeBoarding/CodeBoarding) for local analysis, automation, and CI workflows.
|
|
129
|
+
- [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) for in-editor visual architecture.
|
|
130
|
+
- [GitHub Action](https://github.com/marketplace/actions/codeboarding-diagram-first-documentation) to keep diagrams updated in CI.
|
|
131
|
+
|
|
132
|
+
## Supported stack
|
|
133
|
+
|
|
134
|
+
- Languages: Python, TypeScript, JavaScript, Java, Go, PHP.
|
|
135
|
+
- LLM providers: OpenAI, Anthropic, Google, Vercel AI Gateway, AWS Bedrock, Ollama, OpenRouter, and more.
|
|
136
|
+
|
|
137
|
+
## Examples
|
|
138
|
+
|
|
139
|
+
- Visualized 800+ open-source repositories.
|
|
140
|
+
- Browse generated examples in [GeneratedOnBoardings](https://github.com/CodeBoarding/GeneratedOnBoardings).
|
|
141
|
+
- Try the hosted explorer at [codeboarding.org/diagrams](https://codeboarding.org/diagrams).
|
|
142
|
+
|
|
143
|
+
## Contributing
|
|
144
|
+
|
|
145
|
+
If you want to improve CodeBoarding, open an [issue](https://github.com/CodeBoarding/CodeBoarding/issues) or send a pull request. We welcome improvements to analysis quality, output generators, integrations, and developer experience.
|
|
146
|
+
|
|
147
|
+
## Vision
|
|
148
|
+
|
|
149
|
+
CodeBoarding is building an open standard for code understanding: a visual, accurate, high-level representation of a codebase that both humans and agents can use.
|
|
@@ -134,6 +134,8 @@ tests/test_incremental_analyzer.py
|
|
|
134
134
|
tests/test_install.py
|
|
135
135
|
tests/test_logging_config.py
|
|
136
136
|
tests/test_main.py
|
|
137
|
+
tests/test_tool_registry.py
|
|
138
|
+
tests/test_user_config.py
|
|
137
139
|
tests/test_vscode_constants.py
|
|
138
140
|
tests/test_windows_compatibility.py
|
|
139
141
|
tests/test_windows_encoding.py
|
|
@@ -5,6 +5,7 @@ import platform
|
|
|
5
5
|
import shutil
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
|
+
import tarfile
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
@@ -18,7 +19,10 @@ from tool_registry import (
|
|
|
18
19
|
install_archive_tool,
|
|
19
20
|
install_native_tools,
|
|
20
21
|
install_node_tools,
|
|
22
|
+
npm_subprocess_env,
|
|
21
23
|
platform_bin_dir,
|
|
24
|
+
preferred_node_path,
|
|
25
|
+
preferred_npm_command,
|
|
22
26
|
)
|
|
23
27
|
from user_config import ensure_config_template
|
|
24
28
|
|
|
@@ -43,15 +47,17 @@ class LanguageSupportCheck:
|
|
|
43
47
|
return False, reason
|
|
44
48
|
|
|
45
49
|
|
|
46
|
-
def check_npm():
|
|
47
|
-
"""Check if npm is
|
|
50
|
+
def check_npm(target_dir: Path | None = None) -> bool:
|
|
51
|
+
"""Check if npm is available via the configured Node.js runtime or PATH."""
|
|
48
52
|
print("Step: npm check started")
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
target = (target_dir or get_servers_dir()).resolve()
|
|
55
|
+
npm_command = preferred_npm_command(target)
|
|
51
56
|
|
|
52
|
-
if
|
|
57
|
+
if npm_command:
|
|
53
58
|
try:
|
|
54
|
-
|
|
59
|
+
env = npm_subprocess_env(target)
|
|
60
|
+
result = subprocess.run([*npm_command, "--version"], capture_output=True, text=True, check=True, env=env)
|
|
55
61
|
print(f"Step: npm check finished: success (version {result.stdout.strip()})")
|
|
56
62
|
return True
|
|
57
63
|
except Exception as e:
|
|
@@ -59,31 +65,71 @@ def check_npm():
|
|
|
59
65
|
f"Step: npm check finished: failure - npm command failed ({e}). Skipping Language Servers installation."
|
|
60
66
|
)
|
|
61
67
|
return False
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
|
|
69
|
+
print("Step: npm check finished: failure - npm not found")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def bootstrapped_npm_cli_path(target_dir: Path) -> Path:
|
|
74
|
+
"""Return the npm CLI entrypoint inside the target directory."""
|
|
75
|
+
return target_dir / "npm" / "package" / "bin" / "npm-cli.js"
|
|
65
76
|
|
|
66
77
|
|
|
67
|
-
def
|
|
68
|
-
"""
|
|
69
|
-
|
|
78
|
+
def extract_tarball_safely(fileobj: io.BytesIO, destination: Path) -> None:
|
|
79
|
+
"""Extract a tarball while rejecting path traversal entries."""
|
|
80
|
+
destination = destination.resolve()
|
|
81
|
+
with tarfile.open(fileobj=fileobj, mode="r:gz") as tar:
|
|
82
|
+
for member in tar.getmembers():
|
|
83
|
+
member_path = (destination / member.name).resolve()
|
|
84
|
+
if not member_path.is_relative_to(destination):
|
|
85
|
+
raise ValueError(f"Unsafe tar entry: {member.name}")
|
|
86
|
+
tar.extractall(destination)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def bootstrap_npm(target_dir: Path | None = None) -> bool:
|
|
90
|
+
"""Download npm from the registry and invoke it through the configured Node.js runtime."""
|
|
91
|
+
target = (target_dir or get_servers_dir()).resolve()
|
|
92
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
node_path = preferred_node_path(target)
|
|
94
|
+
|
|
70
95
|
print("Step: npm remediation started")
|
|
71
|
-
print(f"
|
|
72
|
-
print(" impact: installs
|
|
96
|
+
print(f" target: {target}")
|
|
97
|
+
print(" impact: installs npm into the CodeBoarding tools directory and invokes it via Node.js")
|
|
73
98
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
print(f"Step: npm remediation finished: failure - {e}")
|
|
78
|
-
if e.stderr:
|
|
79
|
-
print(f" {e.stderr.strip()}")
|
|
80
|
-
print(" You can install Node.js manually from: https://nodejs.org/en/download")
|
|
81
|
-
print(" Then verify with: npm --version")
|
|
99
|
+
if not node_path:
|
|
100
|
+
print("Step: npm remediation finished: failure - no Node.js runtime available")
|
|
101
|
+
print(" Set CODEBOARDING_NODE_PATH or install Node.js from: https://nodejs.org/en/download")
|
|
82
102
|
return False
|
|
83
103
|
|
|
84
|
-
|
|
104
|
+
os.environ.setdefault("CODEBOARDING_NODE_PATH", node_path)
|
|
105
|
+
print(f" node: {os.environ['CODEBOARDING_NODE_PATH']}")
|
|
106
|
+
|
|
107
|
+
npm_cli = bootstrapped_npm_cli_path(target)
|
|
108
|
+
if not npm_cli.exists():
|
|
109
|
+
npm_root = npm_cli.parent.parent.parent
|
|
110
|
+
metadata_url = "https://registry.npmjs.org/npm/latest"
|
|
111
|
+
try:
|
|
112
|
+
metadata_response = requests.get(metadata_url, timeout=30)
|
|
113
|
+
metadata_response.raise_for_status()
|
|
114
|
+
metadata = metadata_response.json()
|
|
115
|
+
tarball_url = metadata["dist"]["tarball"]
|
|
116
|
+
print(f" source: {tarball_url}")
|
|
117
|
+
|
|
118
|
+
tarball_response = requests.get(tarball_url, timeout=60)
|
|
119
|
+
tarball_response.raise_for_status()
|
|
120
|
+
|
|
121
|
+
shutil.rmtree(npm_root, ignore_errors=True)
|
|
122
|
+
npm_root.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
extract_tarball_safely(io.BytesIO(tarball_response.content), npm_root)
|
|
124
|
+
except (requests.RequestException, KeyError, OSError, ValueError, tarfile.TarError) as e:
|
|
125
|
+
print(f"Step: npm remediation finished: failure - {e}")
|
|
126
|
+
print(" Install Node.js manually from: https://nodejs.org/en/download")
|
|
127
|
+
print(" Then verify with: npm --version")
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
npm_available = check_npm(target)
|
|
85
131
|
if not npm_available:
|
|
86
|
-
print("Step: npm remediation finished: failure - npm still not found after
|
|
132
|
+
print("Step: npm remediation finished: failure - npm still not found after bootstrap")
|
|
87
133
|
print(" You can install Node.js manually from: https://nodejs.org/en/download")
|
|
88
134
|
print(" Then verify with: npm --version")
|
|
89
135
|
return False
|
|
@@ -97,30 +143,25 @@ def is_non_interactive_mode() -> bool:
|
|
|
97
143
|
return bool(os.getenv("CI")) or not sys.stdin.isatty()
|
|
98
144
|
|
|
99
145
|
|
|
100
|
-
def resolve_missing_npm(auto_install_npm: bool = False) -> bool:
|
|
101
|
-
"""
|
|
146
|
+
def resolve_missing_npm(auto_install_npm: bool = False, target_dir: Path | None = None) -> bool:
|
|
147
|
+
"""Try to bootstrap npm; fall back gracefully if it cannot be obtained.
|
|
102
148
|
|
|
103
|
-
Returns True
|
|
104
|
-
|
|
149
|
+
Returns True when npm becomes available, False otherwise.
|
|
150
|
+
In non-interactive mode (VS Code extension / CI) the function never raises —
|
|
151
|
+
a missing npm just means Node-based language servers will be unavailable.
|
|
105
152
|
"""
|
|
106
153
|
print("Step: npm required for TypeScript/JavaScript/PHP/Python language servers")
|
|
107
154
|
|
|
108
|
-
if auto_install_npm:
|
|
109
|
-
installed =
|
|
155
|
+
if auto_install_npm or is_non_interactive_mode():
|
|
156
|
+
installed = bootstrap_npm(target_dir=target_dir)
|
|
110
157
|
if not installed:
|
|
111
|
-
print("
|
|
112
|
-
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
if is_non_interactive_mode():
|
|
116
|
-
print("Error: npm is required but not found and cannot be installed non-interactively.")
|
|
117
|
-
print(" Re-run with --auto-install-npm to install npm in this virtual environment,")
|
|
118
|
-
print(" or install Node.js manually from: https://nodejs.org/en/download")
|
|
119
|
-
raise SystemExit(1)
|
|
158
|
+
print("Warning: npm bootstrap failed. Node-based language servers will be unavailable.")
|
|
159
|
+
print(" Install Node.js from https://nodejs.org/en/download to enable them.")
|
|
160
|
+
return installed
|
|
120
161
|
|
|
121
|
-
choice = input("npm is missing. Install it now using
|
|
162
|
+
choice = input("npm is missing. Install it now using the configured Node.js runtime? [y/N]: ").strip().lower()
|
|
122
163
|
if choice in {"y", "yes"}:
|
|
123
|
-
installed =
|
|
164
|
+
installed = bootstrap_npm(target_dir=target_dir)
|
|
124
165
|
if not installed:
|
|
125
166
|
print("Error: npm installation failed. Install Node.js from https://nodejs.org/en/download and retry.")
|
|
126
167
|
raise SystemExit(1)
|
|
@@ -130,12 +171,12 @@ def resolve_missing_npm(auto_install_npm: bool = False) -> bool:
|
|
|
130
171
|
raise SystemExit(1)
|
|
131
172
|
|
|
132
173
|
|
|
133
|
-
def resolve_npm_availability(auto_install_npm: bool = False) -> bool:
|
|
174
|
+
def resolve_npm_availability(auto_install_npm: bool = False, target_dir: Path | None = None) -> bool:
|
|
134
175
|
"""Determine npm availability and run remediation when needed."""
|
|
135
|
-
npm_available = check_npm()
|
|
176
|
+
npm_available = check_npm(target_dir)
|
|
136
177
|
|
|
137
178
|
if not npm_available:
|
|
138
|
-
npm_available = resolve_missing_npm(auto_install_npm=auto_install_npm)
|
|
179
|
+
npm_available = resolve_missing_npm(auto_install_npm=auto_install_npm, target_dir=target_dir)
|
|
139
180
|
|
|
140
181
|
return npm_available
|
|
141
182
|
|
|
@@ -146,7 +187,7 @@ def parse_args() -> argparse.Namespace:
|
|
|
146
187
|
parser.add_argument(
|
|
147
188
|
"--auto-install-npm",
|
|
148
189
|
action="store_true",
|
|
149
|
-
help="Automatically
|
|
190
|
+
help="Automatically bootstrap npm when missing (downloads from registry, runs via Node.js)",
|
|
150
191
|
)
|
|
151
192
|
parser.add_argument(
|
|
152
193
|
"--auto-install-vcpp",
|
|
@@ -483,7 +524,7 @@ def run_install(
|
|
|
483
524
|
|
|
484
525
|
ensure_config_template()
|
|
485
526
|
|
|
486
|
-
npm_available = resolve_npm_availability(auto_install_npm=auto_install_npm)
|
|
527
|
+
npm_available = resolve_npm_availability(auto_install_npm=auto_install_npm, target_dir=target)
|
|
487
528
|
if npm_available:
|
|
488
529
|
install_node_servers(target)
|
|
489
530
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import tempfile
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import Mock, patch
|
|
7
|
+
|
|
8
|
+
import install
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestParseArgs(unittest.TestCase):
|
|
12
|
+
def test_parse_args_defaults(self):
|
|
13
|
+
with patch.object(sys, "argv", ["install.py"]):
|
|
14
|
+
args = install.parse_args()
|
|
15
|
+
self.assertFalse(args.auto_install_npm)
|
|
16
|
+
|
|
17
|
+
def test_parse_args_auto_install_npm(self):
|
|
18
|
+
with patch.object(sys, "argv", ["install.py", "--auto-install-npm"]):
|
|
19
|
+
args = install.parse_args()
|
|
20
|
+
self.assertTrue(args.auto_install_npm)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestResolveMissingNpm(unittest.TestCase):
|
|
24
|
+
@patch("install.bootstrap_npm", return_value=True)
|
|
25
|
+
def test_auto_install_mode_bootstraps_npm(self, mock_bootstrap):
|
|
26
|
+
target_dir = Path("/tmp/codeboarding-servers")
|
|
27
|
+
result = install.resolve_missing_npm(auto_install_npm=True, target_dir=target_dir)
|
|
28
|
+
self.assertTrue(result)
|
|
29
|
+
mock_bootstrap.assert_called_once_with(target_dir=target_dir)
|
|
30
|
+
|
|
31
|
+
@patch("install.bootstrap_npm", return_value=False)
|
|
32
|
+
@patch("install.is_non_interactive_mode", return_value=True)
|
|
33
|
+
def test_non_interactive_falls_back_gracefully(self, mock_non_interactive, mock_bootstrap):
|
|
34
|
+
"""Non-interactive mode should not raise SystemExit — just return False."""
|
|
35
|
+
result = install.resolve_missing_npm(auto_install_npm=False)
|
|
36
|
+
self.assertFalse(result)
|
|
37
|
+
mock_bootstrap.assert_called_once()
|
|
38
|
+
|
|
39
|
+
@patch("install.bootstrap_npm", return_value=False)
|
|
40
|
+
def test_auto_install_failure_degrades_gracefully(self, mock_bootstrap):
|
|
41
|
+
"""When auto_install is True but bootstrap fails, return False (don't crash server)."""
|
|
42
|
+
result = install.resolve_missing_npm(auto_install_npm=True, target_dir=Path("/tmp/test"))
|
|
43
|
+
self.assertFalse(result)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestResolveNpmAvailability(unittest.TestCase):
|
|
47
|
+
@patch("install.resolve_missing_npm")
|
|
48
|
+
@patch("install.check_npm", return_value=True)
|
|
49
|
+
def test_npm_present_does_not_attempt_remediation(self, mock_check_npm, mock_resolve_missing_npm):
|
|
50
|
+
target_dir = Path("/tmp/codeboarding-servers")
|
|
51
|
+
result = install.resolve_npm_availability(auto_install_npm=False, target_dir=target_dir)
|
|
52
|
+
self.assertTrue(result)
|
|
53
|
+
mock_check_npm.assert_called_once_with(target_dir)
|
|
54
|
+
mock_resolve_missing_npm.assert_not_called()
|
|
55
|
+
|
|
56
|
+
@patch("install.resolve_missing_npm", return_value=True)
|
|
57
|
+
@patch("install.check_npm", return_value=False)
|
|
58
|
+
def test_missing_npm_attempts_remediation(self, mock_check_npm, mock_resolve_missing_npm):
|
|
59
|
+
target_dir = Path("/tmp/codeboarding-servers")
|
|
60
|
+
result = install.resolve_npm_availability(auto_install_npm=True, target_dir=target_dir)
|
|
61
|
+
self.assertTrue(result)
|
|
62
|
+
mock_check_npm.assert_called_once_with(target_dir)
|
|
63
|
+
mock_resolve_missing_npm.assert_called_once_with(auto_install_npm=True, target_dir=target_dir)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestBootstrapNpm(unittest.TestCase):
|
|
67
|
+
@patch("install.check_npm", return_value=True)
|
|
68
|
+
@patch("install.requests.get")
|
|
69
|
+
@patch("install.preferred_node_path", return_value="/custom/node")
|
|
70
|
+
def test_bootstraps_npm_cli_when_missing(self, mock_preferred_node_path, mock_requests_get, mock_check_npm):
|
|
71
|
+
with tempfile.TemporaryDirectory() as temp_dir, patch.dict(os.environ, {}, clear=True):
|
|
72
|
+
target_dir = Path(temp_dir)
|
|
73
|
+
|
|
74
|
+
metadata_response = Mock()
|
|
75
|
+
metadata_response.raise_for_status.return_value = None
|
|
76
|
+
metadata_response.json.return_value = {
|
|
77
|
+
"dist": {"tarball": "https://registry.npmjs.org/npm/-/npm-1.0.0.tgz"}
|
|
78
|
+
}
|
|
79
|
+
tarball_response = Mock()
|
|
80
|
+
tarball_response.raise_for_status.return_value = None
|
|
81
|
+
tarball_response.content = b"fake-tarball"
|
|
82
|
+
mock_requests_get.side_effect = [metadata_response, tarball_response]
|
|
83
|
+
|
|
84
|
+
with patch("install.extract_tarball_safely") as mock_extract, patch("install.shutil.rmtree") as mock_rmtree:
|
|
85
|
+
result = install.bootstrap_npm(target_dir=target_dir)
|
|
86
|
+
self.assertEqual(os.environ["CODEBOARDING_NODE_PATH"], "/custom/node")
|
|
87
|
+
|
|
88
|
+
self.assertTrue(result)
|
|
89
|
+
mock_preferred_node_path.assert_called_once_with(target_dir.resolve())
|
|
90
|
+
mock_requests_get.assert_any_call("https://registry.npmjs.org/npm/latest", timeout=30)
|
|
91
|
+
mock_requests_get.assert_any_call("https://registry.npmjs.org/npm/-/npm-1.0.0.tgz", timeout=60)
|
|
92
|
+
mock_rmtree.assert_called_once_with(target_dir.resolve() / "npm", ignore_errors=True)
|
|
93
|
+
mock_extract.assert_called_once()
|
|
94
|
+
mock_check_npm.assert_called_once_with(target_dir.resolve())
|
|
95
|
+
|
|
96
|
+
@patch("install.check_npm", return_value=True)
|
|
97
|
+
@patch("install.requests.get")
|
|
98
|
+
@patch("install.preferred_node_path", return_value="/custom/node")
|
|
99
|
+
def test_uses_existing_bootstrapped_npm_cli(self, mock_preferred_node_path, mock_requests_get, mock_check_npm):
|
|
100
|
+
with tempfile.TemporaryDirectory() as temp_dir, patch.dict(os.environ, {}, clear=True):
|
|
101
|
+
target_dir = Path(temp_dir)
|
|
102
|
+
npm_cli = install.bootstrapped_npm_cli_path(target_dir)
|
|
103
|
+
npm_cli.parent.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
npm_cli.write_text("", encoding="utf-8")
|
|
105
|
+
|
|
106
|
+
result = install.bootstrap_npm(target_dir=target_dir)
|
|
107
|
+
|
|
108
|
+
self.assertTrue(result)
|
|
109
|
+
mock_preferred_node_path.assert_called_once_with(target_dir.resolve())
|
|
110
|
+
mock_requests_get.assert_not_called()
|
|
111
|
+
mock_check_npm.assert_called_once_with(target_dir.resolve())
|
|
112
|
+
|
|
113
|
+
@patch("install.preferred_node_path", return_value=None)
|
|
114
|
+
def test_returns_false_when_no_node_runtime_is_available(self, mock_preferred_node_path):
|
|
115
|
+
with tempfile.TemporaryDirectory() as temp_dir, patch.dict(os.environ, {}, clear=True):
|
|
116
|
+
target_dir = Path(temp_dir)
|
|
117
|
+
result = install.bootstrap_npm(target_dir=target_dir)
|
|
118
|
+
|
|
119
|
+
self.assertFalse(result)
|
|
120
|
+
mock_preferred_node_path.assert_called_once_with(target_dir.resolve())
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from tool_registry import TOOL_REGISTRY, ToolKind, install_node_tools, resolve_config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestToolRegistry(unittest.TestCase):
|
|
11
|
+
@patch("platform.system", return_value="Linux")
|
|
12
|
+
@patch.dict(os.environ, {"CODEBOARDING_NODE_PATH": "/vscode/node"}, clear=False)
|
|
13
|
+
def test_resolve_config_uses_explicit_node_path_for_node_servers(self, mock_system):
|
|
14
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
15
|
+
base_dir = Path(temp_dir)
|
|
16
|
+
(base_dir / "node_modules" / ".bin").mkdir(parents=True)
|
|
17
|
+
(base_dir / "node_modules" / ".bin" / "typescript-language-server").write_text("")
|
|
18
|
+
ts_dir = base_dir / "node_modules" / "typescript-language-server"
|
|
19
|
+
ts_dir.mkdir(parents=True)
|
|
20
|
+
(ts_dir / "cli.mjs").write_text("")
|
|
21
|
+
|
|
22
|
+
config = resolve_config(base_dir)
|
|
23
|
+
command = config["lsp_servers"]["typescript"]["command"]
|
|
24
|
+
|
|
25
|
+
self.assertEqual(command[0], "/vscode/node")
|
|
26
|
+
self.assertTrue(command[1].endswith("cli.mjs"))
|
|
27
|
+
self.assertEqual(command[2:], ["--stdio", "--log-level=2"])
|
|
28
|
+
|
|
29
|
+
@patch("platform.system", return_value="Linux")
|
|
30
|
+
def test_resolve_config_falls_back_to_embedded_nodeenv_node(self, mock_system):
|
|
31
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
32
|
+
base_dir = Path(temp_dir)
|
|
33
|
+
nodeenv_bin = base_dir / "nodeenv" / "bin"
|
|
34
|
+
nodeenv_bin.mkdir(parents=True)
|
|
35
|
+
(nodeenv_bin / "node").write_text("")
|
|
36
|
+
(nodeenv_bin / "npm").write_text("")
|
|
37
|
+
|
|
38
|
+
(base_dir / "node_modules" / ".bin").mkdir(parents=True)
|
|
39
|
+
(base_dir / "node_modules" / ".bin" / "pyright-langserver").write_text("")
|
|
40
|
+
pyright_dir = base_dir / "node_modules" / "pyright" / "dist"
|
|
41
|
+
pyright_dir.mkdir(parents=True)
|
|
42
|
+
(pyright_dir / "langserver.index.js").write_text("")
|
|
43
|
+
|
|
44
|
+
config = resolve_config(base_dir)
|
|
45
|
+
command = config["lsp_servers"]["python"]["command"]
|
|
46
|
+
|
|
47
|
+
self.assertEqual(command[0], str(nodeenv_bin / "node"))
|
|
48
|
+
self.assertTrue(command[1].endswith("langserver.index.js"))
|
|
49
|
+
self.assertEqual(command[2:], ["--stdio"])
|
|
50
|
+
|
|
51
|
+
@patch("tool_registry.subprocess.run")
|
|
52
|
+
@patch("platform.system", return_value="Linux")
|
|
53
|
+
def test_install_node_tools_prefers_embedded_npm(self, mock_system, mock_run):
|
|
54
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
55
|
+
base_dir = Path(temp_dir)
|
|
56
|
+
nodeenv_bin = base_dir / "nodeenv" / "bin"
|
|
57
|
+
nodeenv_bin.mkdir(parents=True)
|
|
58
|
+
(nodeenv_bin / "npm").write_text("")
|
|
59
|
+
|
|
60
|
+
node_deps = [dep for dep in TOOL_REGISTRY if dep.kind is ToolKind.NODE]
|
|
61
|
+
install_node_tools(base_dir, node_deps)
|
|
62
|
+
|
|
63
|
+
self.assertGreaterEqual(mock_run.call_count, 2)
|
|
64
|
+
first_command = mock_run.call_args_list[0].args[0]
|
|
65
|
+
second_command = mock_run.call_args_list[1].args[0]
|
|
66
|
+
self.assertEqual(first_command[0], str(nodeenv_bin / "npm"))
|
|
67
|
+
self.assertEqual(second_command[0], str(nodeenv_bin / "npm"))
|
|
68
|
+
# Verify env is passed with ELECTRON_RUN_AS_NODE
|
|
69
|
+
for call in mock_run.call_args_list:
|
|
70
|
+
env = call.kwargs.get("env", {})
|
|
71
|
+
self.assertEqual(env.get("ELECTRON_RUN_AS_NODE"), "1")
|
|
72
|
+
|
|
73
|
+
@patch("tool_registry.subprocess.run")
|
|
74
|
+
@patch("platform.system", return_value="Linux")
|
|
75
|
+
@patch.dict(os.environ, {"CODEBOARDING_NODE_PATH": "/vscode/node"}, clear=False)
|
|
76
|
+
def test_install_node_tools_uses_bootstrapped_npm_cli(self, mock_system, mock_run):
|
|
77
|
+
"""When only a bootstrapped npm-cli.js exists, use [node, npm-cli.js, ...]."""
|
|
78
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
79
|
+
base_dir = Path(temp_dir)
|
|
80
|
+
npm_cli = base_dir / "npm" / "package" / "bin" / "npm-cli.js"
|
|
81
|
+
npm_cli.parent.mkdir(parents=True)
|
|
82
|
+
npm_cli.write_text("")
|
|
83
|
+
|
|
84
|
+
node_deps = [dep for dep in TOOL_REGISTRY if dep.kind is ToolKind.NODE]
|
|
85
|
+
install_node_tools(base_dir, node_deps)
|
|
86
|
+
|
|
87
|
+
self.assertGreaterEqual(mock_run.call_count, 2)
|
|
88
|
+
first_command = mock_run.call_args_list[0].args[0]
|
|
89
|
+
self.assertEqual(first_command[0], "/vscode/node")
|
|
90
|
+
self.assertEqual(first_command[1], str(npm_cli))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
unittest.main()
|