lucidscan 0.5.12__py3-none-any.whl
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.
- lucidscan/__init__.py +12 -0
- lucidscan/bootstrap/__init__.py +26 -0
- lucidscan/bootstrap/paths.py +160 -0
- lucidscan/bootstrap/platform.py +111 -0
- lucidscan/bootstrap/validation.py +76 -0
- lucidscan/bootstrap/versions.py +119 -0
- lucidscan/cli/__init__.py +50 -0
- lucidscan/cli/__main__.py +8 -0
- lucidscan/cli/arguments.py +405 -0
- lucidscan/cli/commands/__init__.py +64 -0
- lucidscan/cli/commands/autoconfigure.py +294 -0
- lucidscan/cli/commands/help.py +69 -0
- lucidscan/cli/commands/init.py +656 -0
- lucidscan/cli/commands/list_scanners.py +59 -0
- lucidscan/cli/commands/scan.py +307 -0
- lucidscan/cli/commands/serve.py +142 -0
- lucidscan/cli/commands/status.py +84 -0
- lucidscan/cli/commands/validate.py +105 -0
- lucidscan/cli/config_bridge.py +152 -0
- lucidscan/cli/exit_codes.py +17 -0
- lucidscan/cli/runner.py +284 -0
- lucidscan/config/__init__.py +29 -0
- lucidscan/config/ignore.py +178 -0
- lucidscan/config/loader.py +431 -0
- lucidscan/config/models.py +316 -0
- lucidscan/config/validation.py +645 -0
- lucidscan/core/__init__.py +3 -0
- lucidscan/core/domain_runner.py +463 -0
- lucidscan/core/git.py +174 -0
- lucidscan/core/logging.py +34 -0
- lucidscan/core/models.py +207 -0
- lucidscan/core/streaming.py +340 -0
- lucidscan/core/subprocess_runner.py +164 -0
- lucidscan/detection/__init__.py +21 -0
- lucidscan/detection/detector.py +154 -0
- lucidscan/detection/frameworks.py +270 -0
- lucidscan/detection/languages.py +328 -0
- lucidscan/detection/tools.py +229 -0
- lucidscan/generation/__init__.py +15 -0
- lucidscan/generation/config_generator.py +275 -0
- lucidscan/generation/package_installer.py +330 -0
- lucidscan/mcp/__init__.py +20 -0
- lucidscan/mcp/formatter.py +510 -0
- lucidscan/mcp/server.py +297 -0
- lucidscan/mcp/tools.py +1049 -0
- lucidscan/mcp/watcher.py +237 -0
- lucidscan/pipeline/__init__.py +17 -0
- lucidscan/pipeline/executor.py +187 -0
- lucidscan/pipeline/parallel.py +181 -0
- lucidscan/plugins/__init__.py +40 -0
- lucidscan/plugins/coverage/__init__.py +28 -0
- lucidscan/plugins/coverage/base.py +160 -0
- lucidscan/plugins/coverage/coverage_py.py +454 -0
- lucidscan/plugins/coverage/istanbul.py +411 -0
- lucidscan/plugins/discovery.py +107 -0
- lucidscan/plugins/enrichers/__init__.py +61 -0
- lucidscan/plugins/enrichers/base.py +63 -0
- lucidscan/plugins/linters/__init__.py +26 -0
- lucidscan/plugins/linters/base.py +125 -0
- lucidscan/plugins/linters/biome.py +448 -0
- lucidscan/plugins/linters/checkstyle.py +393 -0
- lucidscan/plugins/linters/eslint.py +368 -0
- lucidscan/plugins/linters/ruff.py +498 -0
- lucidscan/plugins/reporters/__init__.py +45 -0
- lucidscan/plugins/reporters/base.py +30 -0
- lucidscan/plugins/reporters/json_reporter.py +79 -0
- lucidscan/plugins/reporters/sarif_reporter.py +303 -0
- lucidscan/plugins/reporters/summary_reporter.py +61 -0
- lucidscan/plugins/reporters/table_reporter.py +81 -0
- lucidscan/plugins/scanners/__init__.py +57 -0
- lucidscan/plugins/scanners/base.py +60 -0
- lucidscan/plugins/scanners/checkov.py +484 -0
- lucidscan/plugins/scanners/opengrep.py +464 -0
- lucidscan/plugins/scanners/trivy.py +492 -0
- lucidscan/plugins/test_runners/__init__.py +27 -0
- lucidscan/plugins/test_runners/base.py +111 -0
- lucidscan/plugins/test_runners/jest.py +381 -0
- lucidscan/plugins/test_runners/karma.py +481 -0
- lucidscan/plugins/test_runners/playwright.py +434 -0
- lucidscan/plugins/test_runners/pytest.py +598 -0
- lucidscan/plugins/type_checkers/__init__.py +27 -0
- lucidscan/plugins/type_checkers/base.py +106 -0
- lucidscan/plugins/type_checkers/mypy.py +355 -0
- lucidscan/plugins/type_checkers/pyright.py +313 -0
- lucidscan/plugins/type_checkers/typescript.py +280 -0
- lucidscan-0.5.12.dist-info/METADATA +242 -0
- lucidscan-0.5.12.dist-info/RECORD +91 -0
- lucidscan-0.5.12.dist-info/WHEEL +5 -0
- lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
- lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
- lucidscan-0.5.12.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Codebase detection module.
|
|
2
|
+
|
|
3
|
+
This module provides automatic detection of:
|
|
4
|
+
- Programming languages and their versions
|
|
5
|
+
- Frameworks and libraries
|
|
6
|
+
- Existing tool configurations (linters, type checkers, etc.)
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from lucidscan.detection import CodebaseDetector, ProjectContext
|
|
10
|
+
|
|
11
|
+
detector = CodebaseDetector()
|
|
12
|
+
context = detector.detect(Path("."))
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from lucidscan.detection.detector import CodebaseDetector, ProjectContext, LanguageInfo
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"CodebaseDetector",
|
|
19
|
+
"ProjectContext",
|
|
20
|
+
"LanguageInfo",
|
|
21
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Main codebase detector orchestrating all detection modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from lucidscan.detection.languages import detect_languages, LanguageInfo
|
|
10
|
+
from lucidscan.detection.frameworks import detect_frameworks
|
|
11
|
+
from lucidscan.detection.tools import detect_tools, ToolConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ProjectContext:
|
|
16
|
+
"""Detected project characteristics.
|
|
17
|
+
|
|
18
|
+
This dataclass holds all information detected about a project,
|
|
19
|
+
including languages, frameworks, existing tools, and CI systems.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
root: Path
|
|
23
|
+
"""Project root directory."""
|
|
24
|
+
|
|
25
|
+
languages: list[LanguageInfo] = field(default_factory=list)
|
|
26
|
+
"""Detected programming languages with metadata."""
|
|
27
|
+
|
|
28
|
+
package_managers: list[str] = field(default_factory=list)
|
|
29
|
+
"""Detected package managers (pip, npm, cargo, etc.)."""
|
|
30
|
+
|
|
31
|
+
frameworks: list[str] = field(default_factory=list)
|
|
32
|
+
"""Detected frameworks (fastapi, react, django, etc.)."""
|
|
33
|
+
|
|
34
|
+
existing_tools: dict[str, ToolConfig] = field(default_factory=dict)
|
|
35
|
+
"""Existing tool configurations found in the project."""
|
|
36
|
+
|
|
37
|
+
test_frameworks: list[str] = field(default_factory=list)
|
|
38
|
+
"""Detected test frameworks (pytest, jest, etc.)."""
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def primary_language(self) -> Optional[str]:
|
|
42
|
+
"""Get the primary language (by file count).
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Primary language name or None if no languages detected.
|
|
46
|
+
"""
|
|
47
|
+
if not self.languages:
|
|
48
|
+
return None
|
|
49
|
+
return max(self.languages, key=lambda lang: lang.file_count).name
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def has_python(self) -> bool:
|
|
53
|
+
"""Check if project has Python code."""
|
|
54
|
+
return any(lang.name == "python" for lang in self.languages)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def has_javascript(self) -> bool:
|
|
58
|
+
"""Check if project has JavaScript/TypeScript code."""
|
|
59
|
+
return any(lang.name in ("javascript", "typescript") for lang in self.languages)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def has_go(self) -> bool:
|
|
63
|
+
"""Check if project has Go code."""
|
|
64
|
+
return any(lang.name == "go" for lang in self.languages)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CodebaseDetector:
|
|
68
|
+
"""Orchestrates codebase detection.
|
|
69
|
+
|
|
70
|
+
This class coordinates all detection modules to build a complete
|
|
71
|
+
ProjectContext for a given project directory.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def detect(self, project_root: Path) -> ProjectContext:
|
|
75
|
+
"""Detect project characteristics.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
project_root: Path to the project root directory.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
ProjectContext with detected information.
|
|
82
|
+
"""
|
|
83
|
+
project_root = project_root.resolve()
|
|
84
|
+
|
|
85
|
+
# Detect languages first as other detectors may use this info
|
|
86
|
+
languages = detect_languages(project_root)
|
|
87
|
+
|
|
88
|
+
# Extract package managers from language detection
|
|
89
|
+
package_managers = self._extract_package_managers(languages, project_root)
|
|
90
|
+
|
|
91
|
+
# Detect frameworks based on dependencies
|
|
92
|
+
frameworks, test_frameworks = detect_frameworks(project_root)
|
|
93
|
+
|
|
94
|
+
# Detect existing tool configurations
|
|
95
|
+
existing_tools = detect_tools(project_root)
|
|
96
|
+
|
|
97
|
+
return ProjectContext(
|
|
98
|
+
root=project_root,
|
|
99
|
+
languages=languages,
|
|
100
|
+
package_managers=package_managers,
|
|
101
|
+
frameworks=frameworks,
|
|
102
|
+
existing_tools=existing_tools,
|
|
103
|
+
test_frameworks=test_frameworks,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _extract_package_managers(
|
|
107
|
+
self,
|
|
108
|
+
languages: list[LanguageInfo],
|
|
109
|
+
project_root: Path,
|
|
110
|
+
) -> list[str]:
|
|
111
|
+
"""Extract package managers from detected languages and files.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
languages: Detected languages.
|
|
115
|
+
project_root: Project root directory.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of detected package manager names.
|
|
119
|
+
"""
|
|
120
|
+
managers = []
|
|
121
|
+
|
|
122
|
+
# Python package managers
|
|
123
|
+
if any(lang.name == "python" for lang in languages):
|
|
124
|
+
if (project_root / "pyproject.toml").exists():
|
|
125
|
+
managers.append("pip")
|
|
126
|
+
elif (project_root / "requirements.txt").exists():
|
|
127
|
+
managers.append("pip")
|
|
128
|
+
elif (project_root / "Pipfile").exists():
|
|
129
|
+
managers.append("pipenv")
|
|
130
|
+
elif (project_root / "poetry.lock").exists():
|
|
131
|
+
managers.append("poetry")
|
|
132
|
+
|
|
133
|
+
# JavaScript/TypeScript package managers
|
|
134
|
+
if any(lang.name in ("javascript", "typescript") for lang in languages):
|
|
135
|
+
if (project_root / "package-lock.json").exists():
|
|
136
|
+
managers.append("npm")
|
|
137
|
+
elif (project_root / "yarn.lock").exists():
|
|
138
|
+
managers.append("yarn")
|
|
139
|
+
elif (project_root / "pnpm-lock.yaml").exists():
|
|
140
|
+
managers.append("pnpm")
|
|
141
|
+
elif (project_root / "package.json").exists():
|
|
142
|
+
managers.append("npm") # Default
|
|
143
|
+
|
|
144
|
+
# Go
|
|
145
|
+
if any(lang.name == "go" for lang in languages):
|
|
146
|
+
if (project_root / "go.mod").exists():
|
|
147
|
+
managers.append("go")
|
|
148
|
+
|
|
149
|
+
# Rust
|
|
150
|
+
if any(lang.name == "rust" for lang in languages):
|
|
151
|
+
if (project_root / "Cargo.toml").exists():
|
|
152
|
+
managers.append("cargo")
|
|
153
|
+
|
|
154
|
+
return managers
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Framework detection module.
|
|
2
|
+
|
|
3
|
+
Detects web frameworks, testing frameworks, and libraries by analyzing:
|
|
4
|
+
- Dependencies in package manifests (pyproject.toml, package.json)
|
|
5
|
+
- Import statements (optional, for deeper analysis)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, Optional
|
|
14
|
+
|
|
15
|
+
# Python frameworks and their package names
|
|
16
|
+
PYTHON_FRAMEWORKS: Dict[str, str] = {
|
|
17
|
+
"fastapi": "fastapi",
|
|
18
|
+
"django": "django",
|
|
19
|
+
"flask": "flask",
|
|
20
|
+
"starlette": "starlette",
|
|
21
|
+
"tornado": "tornado",
|
|
22
|
+
"aiohttp": "aiohttp",
|
|
23
|
+
"sanic": "sanic",
|
|
24
|
+
"quart": "quart",
|
|
25
|
+
"falcon": "falcon",
|
|
26
|
+
"bottle": "bottle",
|
|
27
|
+
"pyramid": "pyramid",
|
|
28
|
+
"cherrypy": "cherrypy",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Python test frameworks
|
|
32
|
+
PYTHON_TEST_FRAMEWORKS: Dict[str, Optional[str]] = {
|
|
33
|
+
"pytest": "pytest",
|
|
34
|
+
"unittest": None, # Built-in, detected by imports
|
|
35
|
+
"nose": "nose",
|
|
36
|
+
"nose2": "nose2",
|
|
37
|
+
"hypothesis": "hypothesis",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# JavaScript/TypeScript frameworks
|
|
41
|
+
JS_FRAMEWORKS = {
|
|
42
|
+
"react": "react",
|
|
43
|
+
"vue": "vue",
|
|
44
|
+
"angular": "@angular/core",
|
|
45
|
+
"svelte": "svelte",
|
|
46
|
+
"next": "next",
|
|
47
|
+
"nuxt": "nuxt",
|
|
48
|
+
"express": "express",
|
|
49
|
+
"fastify": "fastify",
|
|
50
|
+
"nest": "@nestjs/core",
|
|
51
|
+
"koa": "koa",
|
|
52
|
+
"hapi": "@hapi/hapi",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# JavaScript test frameworks
|
|
56
|
+
JS_TEST_FRAMEWORKS = {
|
|
57
|
+
"jest": "jest",
|
|
58
|
+
"mocha": "mocha",
|
|
59
|
+
"vitest": "vitest",
|
|
60
|
+
"jasmine": "jasmine",
|
|
61
|
+
"cypress": "cypress",
|
|
62
|
+
"playwright": "@playwright/test",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def detect_frameworks(project_root: Path) -> tuple[list[str], list[str]]:
|
|
67
|
+
"""Detect frameworks and test frameworks in a project.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
project_root: Path to the project root directory.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Tuple of (frameworks, test_frameworks).
|
|
74
|
+
"""
|
|
75
|
+
frameworks = []
|
|
76
|
+
test_frameworks = []
|
|
77
|
+
|
|
78
|
+
# Check Python dependencies
|
|
79
|
+
python_deps = _get_python_dependencies(project_root)
|
|
80
|
+
for framework, package in PYTHON_FRAMEWORKS.items():
|
|
81
|
+
if package in python_deps:
|
|
82
|
+
frameworks.append(framework)
|
|
83
|
+
|
|
84
|
+
for framework, pkg in PYTHON_TEST_FRAMEWORKS.items():
|
|
85
|
+
if pkg and pkg in python_deps:
|
|
86
|
+
test_frameworks.append(framework)
|
|
87
|
+
|
|
88
|
+
# Check JavaScript/TypeScript dependencies
|
|
89
|
+
js_deps = _get_js_dependencies(project_root)
|
|
90
|
+
for framework, package in JS_FRAMEWORKS.items():
|
|
91
|
+
if package in js_deps:
|
|
92
|
+
frameworks.append(framework)
|
|
93
|
+
|
|
94
|
+
for framework, package in JS_TEST_FRAMEWORKS.items():
|
|
95
|
+
if package in js_deps:
|
|
96
|
+
test_frameworks.append(framework)
|
|
97
|
+
|
|
98
|
+
# Check for pytest.ini or conftest.py as indicators
|
|
99
|
+
if (project_root / "pytest.ini").exists() or (project_root / "conftest.py").exists():
|
|
100
|
+
if "pytest" not in test_frameworks:
|
|
101
|
+
test_frameworks.append("pytest")
|
|
102
|
+
|
|
103
|
+
# Check for jest.config.js
|
|
104
|
+
jest_configs = ["jest.config.js", "jest.config.ts", "jest.config.mjs"]
|
|
105
|
+
if any((project_root / cfg).exists() for cfg in jest_configs):
|
|
106
|
+
if "jest" not in test_frameworks:
|
|
107
|
+
test_frameworks.append("jest")
|
|
108
|
+
|
|
109
|
+
return frameworks, test_frameworks
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _get_python_dependencies(project_root: Path) -> set[str]:
|
|
113
|
+
"""Extract Python dependencies from pyproject.toml or requirements.txt.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
project_root: Project root directory.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Set of package names (lowercase).
|
|
120
|
+
"""
|
|
121
|
+
deps = set()
|
|
122
|
+
|
|
123
|
+
# Check pyproject.toml
|
|
124
|
+
pyproject = project_root / "pyproject.toml"
|
|
125
|
+
if pyproject.exists():
|
|
126
|
+
try:
|
|
127
|
+
content = pyproject.read_text()
|
|
128
|
+
deps.update(_parse_pyproject_deps(content))
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# Check requirements.txt
|
|
133
|
+
requirements = project_root / "requirements.txt"
|
|
134
|
+
if requirements.exists():
|
|
135
|
+
try:
|
|
136
|
+
deps.update(_parse_requirements_txt(requirements.read_text()))
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
# Check requirements-dev.txt or requirements_dev.txt
|
|
141
|
+
for dev_req in ["requirements-dev.txt", "requirements_dev.txt", "dev-requirements.txt"]:
|
|
142
|
+
dev_requirements = project_root / dev_req
|
|
143
|
+
if dev_requirements.exists():
|
|
144
|
+
try:
|
|
145
|
+
deps.update(_parse_requirements_txt(dev_requirements.read_text()))
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
return deps
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _parse_pyproject_deps(content: str) -> set[str]:
|
|
153
|
+
"""Parse dependencies from pyproject.toml content.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
content: pyproject.toml file content.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Set of package names.
|
|
160
|
+
"""
|
|
161
|
+
deps = set()
|
|
162
|
+
|
|
163
|
+
# Simple regex-based parsing for dependencies
|
|
164
|
+
# Matches: "package-name>=1.0" or "package-name[extra]>=1.0" etc.
|
|
165
|
+
# In dependencies array or optional-dependencies
|
|
166
|
+
|
|
167
|
+
# Find dependencies section
|
|
168
|
+
dep_section = re.search(
|
|
169
|
+
r'dependencies\s*=\s*\[(.*?)\]',
|
|
170
|
+
content,
|
|
171
|
+
re.DOTALL
|
|
172
|
+
)
|
|
173
|
+
if dep_section:
|
|
174
|
+
deps.update(_extract_package_names(dep_section.group(1)))
|
|
175
|
+
|
|
176
|
+
# Find optional-dependencies (all groups)
|
|
177
|
+
opt_deps = re.findall(
|
|
178
|
+
r'\[project\.optional-dependencies\.[^\]]+\]\s*\n([^\[]+)',
|
|
179
|
+
content
|
|
180
|
+
)
|
|
181
|
+
for section in opt_deps:
|
|
182
|
+
deps.update(_extract_package_names(section))
|
|
183
|
+
|
|
184
|
+
# Also check [tool.poetry.dependencies] for Poetry projects
|
|
185
|
+
poetry_deps = re.search(
|
|
186
|
+
r'\[tool\.poetry\.dependencies\](.*?)(?=\[|$)',
|
|
187
|
+
content,
|
|
188
|
+
re.DOTALL
|
|
189
|
+
)
|
|
190
|
+
if poetry_deps:
|
|
191
|
+
# Poetry uses package = "version" format
|
|
192
|
+
package_matches = re.findall(r'^(\w[\w-]*)\s*=', poetry_deps.group(1), re.MULTILINE)
|
|
193
|
+
deps.update(p.lower().replace("-", "_").replace("_", "-") for p in package_matches)
|
|
194
|
+
|
|
195
|
+
return deps
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _extract_package_names(text: str) -> set[str]:
|
|
199
|
+
"""Extract package names from a dependencies list.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
text: Text containing package specifications.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Set of normalized package names.
|
|
206
|
+
"""
|
|
207
|
+
deps = set()
|
|
208
|
+
|
|
209
|
+
# Match quoted strings like "fastapi>=0.100" or 'django[async]'
|
|
210
|
+
matches = re.findall(r'["\']([a-zA-Z][\w.-]*)', text)
|
|
211
|
+
for match in matches:
|
|
212
|
+
# Normalize: remove extras, convert to lowercase
|
|
213
|
+
package = re.sub(r'\[.*?\]', '', match).lower()
|
|
214
|
+
# Normalize underscores and hyphens
|
|
215
|
+
package = package.replace("_", "-")
|
|
216
|
+
deps.add(package)
|
|
217
|
+
|
|
218
|
+
return deps
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _parse_requirements_txt(content: str) -> set[str]:
|
|
222
|
+
"""Parse package names from requirements.txt content.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
content: requirements.txt file content.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Set of package names.
|
|
229
|
+
"""
|
|
230
|
+
deps = set()
|
|
231
|
+
|
|
232
|
+
for line in content.splitlines():
|
|
233
|
+
line = line.strip()
|
|
234
|
+
# Skip comments and empty lines
|
|
235
|
+
if not line or line.startswith("#") or line.startswith("-"):
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
# Extract package name (before any version specifier)
|
|
239
|
+
match = re.match(r'^([a-zA-Z][\w.-]*)', line)
|
|
240
|
+
if match:
|
|
241
|
+
package = match.group(1).lower().replace("_", "-")
|
|
242
|
+
deps.add(package)
|
|
243
|
+
|
|
244
|
+
return deps
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _get_js_dependencies(project_root: Path) -> set[str]:
|
|
248
|
+
"""Extract JavaScript/TypeScript dependencies from package.json.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
project_root: Project root directory.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Set of package names.
|
|
255
|
+
"""
|
|
256
|
+
deps = set()
|
|
257
|
+
|
|
258
|
+
package_json = project_root / "package.json"
|
|
259
|
+
if package_json.exists():
|
|
260
|
+
try:
|
|
261
|
+
data = json.loads(package_json.read_text())
|
|
262
|
+
|
|
263
|
+
# Collect from all dependency types
|
|
264
|
+
for dep_type in ["dependencies", "devDependencies", "peerDependencies"]:
|
|
265
|
+
if dep_type in data:
|
|
266
|
+
deps.update(data[dep_type].keys())
|
|
267
|
+
except Exception:
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
return deps
|