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,431 @@
|
|
|
1
|
+
"""Configuration file loading and merging.
|
|
2
|
+
|
|
3
|
+
Handles loading configuration from YAML files with:
|
|
4
|
+
- Project-level config (.lucidscan.yml)
|
|
5
|
+
- Global config (~/.lucidscan/config/config.yml)
|
|
6
|
+
- Environment variable expansion (${VAR})
|
|
7
|
+
- Config merging with proper precedence
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from lucidscan.config.models import (
|
|
20
|
+
CoveragePipelineConfig,
|
|
21
|
+
DomainPipelineConfig,
|
|
22
|
+
FailOnConfig,
|
|
23
|
+
LucidScanConfig,
|
|
24
|
+
OutputConfig,
|
|
25
|
+
PipelineConfig,
|
|
26
|
+
ProjectConfig,
|
|
27
|
+
ScannerDomainConfig,
|
|
28
|
+
ToolConfig,
|
|
29
|
+
)
|
|
30
|
+
from lucidscan.config.validation import validate_config
|
|
31
|
+
from lucidscan.core.logging import get_logger
|
|
32
|
+
from lucidscan.bootstrap.paths import get_lucidscan_home
|
|
33
|
+
|
|
34
|
+
LOGGER = get_logger(__name__)
|
|
35
|
+
|
|
36
|
+
# Config file names
|
|
37
|
+
PROJECT_CONFIG_NAMES = [".lucidscan.yml", ".lucidscan.yaml", "lucidscan.yml", "lucidscan.yaml"]
|
|
38
|
+
GLOBAL_CONFIG_NAME = "config.yml"
|
|
39
|
+
|
|
40
|
+
# Environment variable pattern: ${VAR} or ${VAR:-default}
|
|
41
|
+
ENV_VAR_PATTERN = re.compile(r"\$\{([^}:]+)(?::-([^}]*))?\}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConfigError(Exception):
|
|
45
|
+
"""Configuration loading or parsing error."""
|
|
46
|
+
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_config(
|
|
51
|
+
project_root: Path,
|
|
52
|
+
cli_config_path: Optional[Path] = None,
|
|
53
|
+
cli_overrides: Optional[Dict[str, Any]] = None,
|
|
54
|
+
) -> LucidScanConfig:
|
|
55
|
+
"""Load configuration with proper precedence.
|
|
56
|
+
|
|
57
|
+
Precedence (highest to lowest):
|
|
58
|
+
1. CLI flags (cli_overrides)
|
|
59
|
+
2. Custom config file (cli_config_path) OR project config (.lucidscan.yml)
|
|
60
|
+
3. Global config (~/.lucidscan/config/config.yml)
|
|
61
|
+
4. Built-in defaults
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
project_root: Project root directory for finding .lucidscan.yml.
|
|
65
|
+
cli_config_path: Optional path to custom config file (--config flag).
|
|
66
|
+
cli_overrides: Dict of CLI flag overrides.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Merged LucidScanConfig instance.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ConfigError: If specified config file doesn't exist or has parse errors.
|
|
73
|
+
"""
|
|
74
|
+
sources: List[str] = []
|
|
75
|
+
merged: Dict[str, Any] = {}
|
|
76
|
+
|
|
77
|
+
# Layer 1: Global config
|
|
78
|
+
global_path = find_global_config()
|
|
79
|
+
if global_path and global_path.exists():
|
|
80
|
+
try:
|
|
81
|
+
global_dict = load_yaml_file(global_path)
|
|
82
|
+
validate_config(global_dict, source=str(global_path))
|
|
83
|
+
merged = merge_configs(merged, global_dict)
|
|
84
|
+
sources.append(f"global:{global_path}")
|
|
85
|
+
LOGGER.debug(f"Loaded global config from {global_path}")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
LOGGER.warning(f"Failed to load global config: {e}")
|
|
88
|
+
|
|
89
|
+
# Layer 2: Project or custom config
|
|
90
|
+
if cli_config_path:
|
|
91
|
+
if not cli_config_path.exists():
|
|
92
|
+
raise ConfigError(f"Config file not found: {cli_config_path}")
|
|
93
|
+
try:
|
|
94
|
+
project_dict = load_yaml_file(cli_config_path)
|
|
95
|
+
validate_config(project_dict, source=str(cli_config_path))
|
|
96
|
+
merged = merge_configs(merged, project_dict)
|
|
97
|
+
sources.append(f"custom:{cli_config_path}")
|
|
98
|
+
LOGGER.debug(f"Loaded custom config from {cli_config_path}")
|
|
99
|
+
except yaml.YAMLError as e:
|
|
100
|
+
raise ConfigError(f"Invalid YAML in {cli_config_path}: {e}") from e
|
|
101
|
+
else:
|
|
102
|
+
project_path = find_project_config(project_root)
|
|
103
|
+
if project_path and project_path.exists():
|
|
104
|
+
try:
|
|
105
|
+
project_dict = load_yaml_file(project_path)
|
|
106
|
+
validate_config(project_dict, source=str(project_path))
|
|
107
|
+
merged = merge_configs(merged, project_dict)
|
|
108
|
+
sources.append(f"project:{project_path}")
|
|
109
|
+
LOGGER.debug(f"Loaded project config from {project_path}")
|
|
110
|
+
except yaml.YAMLError as e:
|
|
111
|
+
raise ConfigError(f"Invalid YAML in {project_path}: {e}") from e
|
|
112
|
+
|
|
113
|
+
# Layer 3: CLI overrides
|
|
114
|
+
if cli_overrides:
|
|
115
|
+
merged = merge_configs(merged, cli_overrides)
|
|
116
|
+
sources.append("cli")
|
|
117
|
+
LOGGER.debug("Applied CLI overrides")
|
|
118
|
+
|
|
119
|
+
# Convert to typed config
|
|
120
|
+
config = dict_to_config(merged)
|
|
121
|
+
config._config_sources = sources
|
|
122
|
+
|
|
123
|
+
LOGGER.debug(f"Config loaded from sources: {sources}")
|
|
124
|
+
return config
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def find_project_config(project_root: Path) -> Optional[Path]:
|
|
128
|
+
"""Find config file in project root.
|
|
129
|
+
|
|
130
|
+
Searches for .lucidscan.yml, .lucidscan.yaml, lucidscan.yml, lucidscan.yaml
|
|
131
|
+
in the project root directory.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
project_root: Directory to search in.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Path to config file if found, None otherwise.
|
|
138
|
+
"""
|
|
139
|
+
for name in PROJECT_CONFIG_NAMES:
|
|
140
|
+
config_path = project_root / name
|
|
141
|
+
if config_path.exists():
|
|
142
|
+
return config_path
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def find_global_config() -> Optional[Path]:
|
|
147
|
+
"""Find global config at ~/.lucidscan/config/config.yml.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Path to global config if it exists, None otherwise.
|
|
151
|
+
"""
|
|
152
|
+
home = get_lucidscan_home()
|
|
153
|
+
config_path = home / "config" / GLOBAL_CONFIG_NAME
|
|
154
|
+
if config_path.exists():
|
|
155
|
+
return config_path
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def load_yaml_file(path: Path) -> Dict[str, Any]:
|
|
160
|
+
"""Load and parse a YAML config file.
|
|
161
|
+
|
|
162
|
+
Performs environment variable expansion on string values.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
path: Path to YAML file.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Parsed dictionary.
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
yaml.YAMLError: If YAML parsing fails.
|
|
172
|
+
FileNotFoundError: If file doesn't exist.
|
|
173
|
+
"""
|
|
174
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
175
|
+
content = f.read()
|
|
176
|
+
|
|
177
|
+
data = yaml.safe_load(content)
|
|
178
|
+
|
|
179
|
+
if data is None:
|
|
180
|
+
return {}
|
|
181
|
+
|
|
182
|
+
if not isinstance(data, dict):
|
|
183
|
+
raise ConfigError(f"Config file must be a YAML mapping, got {type(data).__name__}")
|
|
184
|
+
|
|
185
|
+
# Expand environment variables
|
|
186
|
+
return expand_env_vars(data)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def expand_env_vars(data: Any) -> Any:
|
|
190
|
+
"""Recursively expand environment variables in config values.
|
|
191
|
+
|
|
192
|
+
Supports ${VAR} and ${VAR:-default} syntax.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
data: Config data (dict, list, or scalar).
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Data with environment variables expanded.
|
|
199
|
+
"""
|
|
200
|
+
if isinstance(data, dict):
|
|
201
|
+
return {k: expand_env_vars(v) for k, v in data.items()}
|
|
202
|
+
elif isinstance(data, list):
|
|
203
|
+
return [expand_env_vars(item) for item in data]
|
|
204
|
+
elif isinstance(data, str):
|
|
205
|
+
return ENV_VAR_PATTERN.sub(_env_var_replacer, data)
|
|
206
|
+
else:
|
|
207
|
+
return data
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _env_var_replacer(match: re.Match[str]) -> str:
|
|
211
|
+
"""Replace environment variable reference with its value."""
|
|
212
|
+
var_name = match.group(1)
|
|
213
|
+
default_value = match.group(2)
|
|
214
|
+
|
|
215
|
+
value = os.environ.get(var_name)
|
|
216
|
+
if value is not None:
|
|
217
|
+
return value
|
|
218
|
+
if default_value is not None:
|
|
219
|
+
return default_value
|
|
220
|
+
|
|
221
|
+
LOGGER.warning(f"Environment variable ${var_name} is not set and has no default")
|
|
222
|
+
return ""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def merge_configs(base: Dict[str, Any], overlay: Dict[str, Any]) -> Dict[str, Any]:
|
|
226
|
+
"""Deep merge two config dicts, with overlay taking precedence.
|
|
227
|
+
|
|
228
|
+
Rules:
|
|
229
|
+
- Scalar values: overlay replaces base
|
|
230
|
+
- Lists: overlay replaces base (no merging)
|
|
231
|
+
- Dicts: recursive merge
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
base: Base configuration dictionary.
|
|
235
|
+
overlay: Overlay configuration to merge on top.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Merged configuration dictionary.
|
|
239
|
+
"""
|
|
240
|
+
result = base.copy()
|
|
241
|
+
|
|
242
|
+
for key, overlay_value in overlay.items():
|
|
243
|
+
if key in result and isinstance(result[key], dict) and isinstance(overlay_value, dict):
|
|
244
|
+
result[key] = merge_configs(result[key], overlay_value)
|
|
245
|
+
else:
|
|
246
|
+
result[key] = overlay_value
|
|
247
|
+
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _parse_tool_config(tool_data: Dict[str, Any]) -> ToolConfig:
|
|
252
|
+
"""Parse a single tool configuration.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
tool_data: Tool configuration dictionary.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
ToolConfig instance.
|
|
259
|
+
"""
|
|
260
|
+
name = tool_data.get("name", "")
|
|
261
|
+
config_path = tool_data.get("config")
|
|
262
|
+
strict = tool_data.get("strict", False)
|
|
263
|
+
domains = tool_data.get("domains", [])
|
|
264
|
+
|
|
265
|
+
# Everything else is tool-specific options
|
|
266
|
+
options = {
|
|
267
|
+
k: v for k, v in tool_data.items()
|
|
268
|
+
if k not in ("name", "config", "strict", "domains")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return ToolConfig(
|
|
272
|
+
name=name,
|
|
273
|
+
config=config_path,
|
|
274
|
+
strict=strict,
|
|
275
|
+
domains=domains,
|
|
276
|
+
options=options,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _parse_domain_pipeline_config(
|
|
281
|
+
domain_data: Optional[Dict[str, Any]]
|
|
282
|
+
) -> Optional[DomainPipelineConfig]:
|
|
283
|
+
"""Parse a domain pipeline configuration (linting, type_checking, etc.).
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
domain_data: Domain configuration dictionary or None.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
DomainPipelineConfig instance or None if not configured.
|
|
290
|
+
"""
|
|
291
|
+
if domain_data is None:
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
enabled = domain_data.get("enabled", True)
|
|
295
|
+
tools_data = domain_data.get("tools", [])
|
|
296
|
+
|
|
297
|
+
tools = []
|
|
298
|
+
for tool_data in tools_data:
|
|
299
|
+
if isinstance(tool_data, dict):
|
|
300
|
+
tools.append(_parse_tool_config(tool_data))
|
|
301
|
+
elif isinstance(tool_data, str):
|
|
302
|
+
# Simple string format: just the tool name
|
|
303
|
+
tools.append(ToolConfig(name=tool_data))
|
|
304
|
+
|
|
305
|
+
return DomainPipelineConfig(enabled=enabled, tools=tools)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _parse_coverage_pipeline_config(
|
|
309
|
+
coverage_data: Optional[Dict[str, Any]]
|
|
310
|
+
) -> Optional[CoveragePipelineConfig]:
|
|
311
|
+
"""Parse coverage pipeline configuration.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
coverage_data: Coverage configuration dictionary or None.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
CoveragePipelineConfig instance or None if not configured.
|
|
318
|
+
"""
|
|
319
|
+
if coverage_data is None:
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
# Parse tools the same way as _parse_domain_pipeline_config
|
|
323
|
+
tools_data = coverage_data.get("tools", [])
|
|
324
|
+
tools = []
|
|
325
|
+
for tool_data in tools_data:
|
|
326
|
+
if isinstance(tool_data, dict):
|
|
327
|
+
tools.append(_parse_tool_config(tool_data))
|
|
328
|
+
elif isinstance(tool_data, str):
|
|
329
|
+
# Simple string format: just the tool name
|
|
330
|
+
tools.append(ToolConfig(name=tool_data))
|
|
331
|
+
|
|
332
|
+
return CoveragePipelineConfig(
|
|
333
|
+
enabled=coverage_data.get("enabled", False),
|
|
334
|
+
threshold=coverage_data.get("threshold", 80),
|
|
335
|
+
tools=tools,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def dict_to_config(data: Dict[str, Any]) -> LucidScanConfig:
|
|
340
|
+
"""Convert validated dict to typed LucidScanConfig.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
data: Configuration dictionary.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Typed LucidScanConfig instance.
|
|
347
|
+
"""
|
|
348
|
+
# Parse output config
|
|
349
|
+
output_data = data.get("output", {})
|
|
350
|
+
output = OutputConfig(
|
|
351
|
+
format=output_data.get("format", "json"),
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Parse scanner configs
|
|
355
|
+
scanners: Dict[str, ScannerDomainConfig] = {}
|
|
356
|
+
scanners_data = data.get("scanners", {})
|
|
357
|
+
|
|
358
|
+
for domain, domain_data in scanners_data.items():
|
|
359
|
+
if not isinstance(domain_data, dict):
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
# Extract framework-level keys
|
|
363
|
+
enabled = domain_data.get("enabled", True)
|
|
364
|
+
plugin = domain_data.get("plugin", "")
|
|
365
|
+
|
|
366
|
+
# Everything else is plugin-specific options
|
|
367
|
+
options = {k: v for k, v in domain_data.items() if k not in ("enabled", "plugin")}
|
|
368
|
+
|
|
369
|
+
scanners[domain] = ScannerDomainConfig(
|
|
370
|
+
enabled=enabled,
|
|
371
|
+
plugin=plugin,
|
|
372
|
+
options=options,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Parse enrichers (passthrough for now)
|
|
376
|
+
enrichers = data.get("enrichers", {})
|
|
377
|
+
|
|
378
|
+
# Parse pipeline config
|
|
379
|
+
pipeline_data = data.get("pipeline", {})
|
|
380
|
+
pipeline = PipelineConfig(
|
|
381
|
+
enrichers=pipeline_data.get("enrichers", []),
|
|
382
|
+
max_workers=pipeline_data.get("max_workers", 4),
|
|
383
|
+
linting=_parse_domain_pipeline_config(pipeline_data.get("linting")),
|
|
384
|
+
type_checking=_parse_domain_pipeline_config(pipeline_data.get("type_checking")),
|
|
385
|
+
testing=_parse_domain_pipeline_config(pipeline_data.get("testing")),
|
|
386
|
+
coverage=_parse_coverage_pipeline_config(pipeline_data.get("coverage")),
|
|
387
|
+
security=_parse_domain_pipeline_config(pipeline_data.get("security")),
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Parse project config
|
|
391
|
+
project_data = data.get("project", {})
|
|
392
|
+
project = ProjectConfig(
|
|
393
|
+
name=project_data.get("name", ""),
|
|
394
|
+
languages=project_data.get("languages", []),
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Parse fail_on (string or dict format)
|
|
398
|
+
fail_on_data = data.get("fail_on")
|
|
399
|
+
fail_on: str | FailOnConfig | None = None
|
|
400
|
+
if fail_on_data is not None:
|
|
401
|
+
if isinstance(fail_on_data, str):
|
|
402
|
+
# Legacy string format - keep as string
|
|
403
|
+
fail_on = fail_on_data
|
|
404
|
+
elif isinstance(fail_on_data, dict):
|
|
405
|
+
# Dict format - convert to FailOnConfig
|
|
406
|
+
fail_on = FailOnConfig(
|
|
407
|
+
linting=fail_on_data.get("linting"),
|
|
408
|
+
type_checking=fail_on_data.get("type_checking"),
|
|
409
|
+
security=fail_on_data.get("security"),
|
|
410
|
+
testing=fail_on_data.get("testing"),
|
|
411
|
+
coverage=fail_on_data.get("coverage"),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
return LucidScanConfig(
|
|
415
|
+
project=project,
|
|
416
|
+
fail_on=fail_on,
|
|
417
|
+
ignore=data.get("ignore", []),
|
|
418
|
+
output=output,
|
|
419
|
+
scanners=scanners,
|
|
420
|
+
enrichers=enrichers,
|
|
421
|
+
pipeline=pipeline,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def get_default_config() -> LucidScanConfig:
|
|
426
|
+
"""Get default configuration with no scanners enabled.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Default LucidScanConfig instance.
|
|
430
|
+
"""
|
|
431
|
+
return LucidScanConfig()
|