qgis-plugin-analyzer 1.4.0__py3-none-any.whl → 1.6.0__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.
- analyzer/__init__.py +2 -1
- analyzer/cli/__init__.py +14 -0
- analyzer/cli/app.py +147 -0
- analyzer/cli/base.py +93 -0
- analyzer/cli/commands/__init__.py +19 -0
- analyzer/cli/commands/analyze.py +47 -0
- analyzer/cli/commands/fix.py +58 -0
- analyzer/cli/commands/init.py +41 -0
- analyzer/cli/commands/list_rules.py +41 -0
- analyzer/cli/commands/security.py +46 -0
- analyzer/cli/commands/summary.py +52 -0
- analyzer/cli/commands/version.py +41 -0
- analyzer/cli.py +4 -281
- analyzer/commands.py +163 -0
- analyzer/engine.py +491 -245
- analyzer/fixer.py +206 -130
- analyzer/reporters/markdown_reporter.py +88 -14
- analyzer/reporters/summary_reporter.py +226 -49
- analyzer/rules/qgis_rules.py +3 -1
- analyzer/scanner.py +219 -711
- analyzer/secrets.py +84 -0
- analyzer/security_checker.py +85 -0
- analyzer/security_rules.py +127 -0
- analyzer/transformers.py +29 -8
- analyzer/utils/__init__.py +2 -0
- analyzer/utils/path_utils.py +53 -1
- analyzer/validators.py +90 -55
- analyzer/visitors/__init__.py +19 -0
- analyzer/visitors/base.py +75 -0
- analyzer/visitors/composite_visitor.py +73 -0
- analyzer/visitors/imports_visitor.py +85 -0
- analyzer/visitors/metrics_visitor.py +158 -0
- analyzer/visitors/security_visitor.py +52 -0
- analyzer/visitors/standards_visitor.py +284 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/METADATA +32 -10
- qgis_plugin_analyzer-1.6.0.dist-info/RECORD +52 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/WHEEL +1 -1
- qgis_plugin_analyzer-1.4.0.dist-info/RECORD +0 -30
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/entry_points.txt +0 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {qgis_plugin_analyzer-1.4.0.dist-info → qgis_plugin_analyzer-1.6.0.dist-info}/top_level.txt +0 -0
analyzer/cli.py
CHANGED
|
@@ -19,292 +19,15 @@
|
|
|
19
19
|
# ***************************************************************************/
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
import argparse
|
|
23
|
-
import pathlib
|
|
24
22
|
import sys
|
|
25
23
|
|
|
26
|
-
from .
|
|
27
|
-
from .utils import logger, setup_logger
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _setup_argument_parser() -> argparse.ArgumentParser:
|
|
31
|
-
"""Sets up and returns the argument parser with all subcommands.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
A configured ArgumentParser instance.
|
|
35
|
-
"""
|
|
36
|
-
parser = argparse.ArgumentParser(
|
|
37
|
-
description="QGIS Plugin Analyzer - A guardian for your PyQGIS code"
|
|
38
|
-
)
|
|
39
|
-
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
40
|
-
|
|
41
|
-
# Analyze Command
|
|
42
|
-
analyze_parser = subparsers.add_parser("analyze", help="Analyze an existing QGIS plugin")
|
|
43
|
-
analyze_parser.add_argument("project_path", help="Path to the QGIS project to analyze")
|
|
44
|
-
analyze_parser.add_argument(
|
|
45
|
-
"-o",
|
|
46
|
-
"--output",
|
|
47
|
-
help="Output directory for reports",
|
|
48
|
-
default="./analysis_results",
|
|
49
|
-
)
|
|
50
|
-
analyze_parser.add_argument(
|
|
51
|
-
"-r",
|
|
52
|
-
"--report",
|
|
53
|
-
action="store_true",
|
|
54
|
-
help="Generate detailed HTML/Markdown reports",
|
|
55
|
-
)
|
|
56
|
-
analyze_parser.add_argument(
|
|
57
|
-
"-p",
|
|
58
|
-
"--profile",
|
|
59
|
-
help="Configuration profile from pyproject.toml",
|
|
60
|
-
default="default",
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# Fix Command
|
|
64
|
-
fix_parser = subparsers.add_parser("fix", help="Auto-fix common QGIS plugin issues")
|
|
65
|
-
fix_parser.add_argument("path", type=str, help="Path to the QGIS plugin directory")
|
|
66
|
-
fix_parser.add_argument(
|
|
67
|
-
"--dry-run",
|
|
68
|
-
action="store_true",
|
|
69
|
-
default=True,
|
|
70
|
-
help="Show proposed changes without applying (default: True)",
|
|
71
|
-
)
|
|
72
|
-
fix_parser.add_argument("--apply", action="store_true", help="Apply fixes (disables dry-run)")
|
|
73
|
-
fix_parser.add_argument(
|
|
74
|
-
"--auto-approve",
|
|
75
|
-
action="store_true",
|
|
76
|
-
help="Apply all fixes without confirmation",
|
|
77
|
-
)
|
|
78
|
-
fix_parser.add_argument(
|
|
79
|
-
"-p",
|
|
80
|
-
"--profile",
|
|
81
|
-
help="Configuration profile from pyproject.toml",
|
|
82
|
-
default="default",
|
|
83
|
-
)
|
|
84
|
-
fix_parser.add_argument(
|
|
85
|
-
"--rules",
|
|
86
|
-
type=str,
|
|
87
|
-
help="Comma-separated list of rule IDs to fix",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# List Rules Command
|
|
91
|
-
subparsers.add_parser("list-rules", help="List all available QGIS audit rules")
|
|
92
|
-
|
|
93
|
-
# Init Command
|
|
94
|
-
subparsers.add_parser("init", help="Initialize a new .analyzerignore with defaults")
|
|
95
|
-
|
|
96
|
-
# Summary Command
|
|
97
|
-
summary_parser = subparsers.add_parser(
|
|
98
|
-
"summary", help="Show a quick terminal summary of analysis results"
|
|
99
|
-
)
|
|
100
|
-
summary_parser.add_argument(
|
|
101
|
-
"-i",
|
|
102
|
-
"--input",
|
|
103
|
-
help="Path to the research JSON file",
|
|
104
|
-
default="analysis_results/project_context.json",
|
|
105
|
-
)
|
|
106
|
-
summary_parser.add_argument(
|
|
107
|
-
"-b",
|
|
108
|
-
"--by",
|
|
109
|
-
choices=["total", "modules", "functions", "classes"],
|
|
110
|
-
default="total",
|
|
111
|
-
help="Granularity of the summary (default: total)",
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
return parser
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _handle_fix_command(args: argparse.Namespace) -> bool:
|
|
118
|
-
"""Handles the execution of the 'fix' command.
|
|
119
|
-
|
|
120
|
-
Args:
|
|
121
|
-
args: Parsed command line arguments.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
True if the fix process completed successfully, False otherwise.
|
|
125
|
-
"""
|
|
126
|
-
import json
|
|
127
|
-
|
|
128
|
-
from .fixer import AutoFixer
|
|
129
|
-
|
|
130
|
-
project_path = pathlib.Path(args.path).resolve()
|
|
131
|
-
if not project_path.exists():
|
|
132
|
-
print(f"❌ Path not found: {project_path}")
|
|
133
|
-
return False
|
|
134
|
-
|
|
135
|
-
# Run analysis first
|
|
136
|
-
print("🔍 Analyzing project for fixable issues...")
|
|
137
|
-
analyzer = ProjectAnalyzer(
|
|
138
|
-
str(project_path),
|
|
139
|
-
args.output if hasattr(args, "output") else "./analysis_results",
|
|
140
|
-
args.profile if hasattr(args, "profile") else "default",
|
|
141
|
-
)
|
|
142
|
-
analyzer.run()
|
|
143
|
-
|
|
144
|
-
# Load issues
|
|
145
|
-
context_file = analyzer.output_dir / "project_context.json"
|
|
146
|
-
with open(context_file) as f:
|
|
147
|
-
context = json.load(f)
|
|
148
|
-
|
|
149
|
-
all_issues = []
|
|
150
|
-
for module in context.get("modules", []):
|
|
151
|
-
all_issues.extend(module.get("ast_issues", []))
|
|
152
|
-
|
|
153
|
-
if args.rules:
|
|
154
|
-
rule_ids = [r.strip() for r in args.rules.split(",")]
|
|
155
|
-
all_issues = [i for i in all_issues if i.get("type") in rule_ids]
|
|
156
|
-
|
|
157
|
-
fixer = AutoFixer(project_path, dry_run=not args.apply)
|
|
158
|
-
fixable = fixer.get_fixable_issues(all_issues)
|
|
159
|
-
|
|
160
|
-
if not fixable:
|
|
161
|
-
print("✅ No fixable issues found!")
|
|
162
|
-
return True
|
|
163
|
-
|
|
164
|
-
print(f"\n📋 Found {len(fixable)} fixable issue(s)")
|
|
165
|
-
if not args.apply:
|
|
166
|
-
print("\n⚠️ DRY RUN MODE (use --apply to execute changes)\n")
|
|
167
|
-
|
|
168
|
-
stats = fixer.apply_fixes(fixable, interactive=not args.auto_approve)
|
|
169
|
-
print(
|
|
170
|
-
f"\n📊 Summary: Applied: {stats['applied']}, Skipped: {stats['skipped']}, Failed: {stats['failed']}"
|
|
171
|
-
)
|
|
172
|
-
return True
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def _handle_analyze_command(args: argparse.Namespace) -> None:
|
|
176
|
-
"""Handles the execution of the 'analyze' command.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
args: Parsed command line arguments.
|
|
180
|
-
"""
|
|
181
|
-
# Force generate_html based on flag, overriding profile if necessary for CLI usage
|
|
182
|
-
# We pass it via a temporary config override or modify the analyzer init
|
|
183
|
-
# For now, let's pass it to the analyzer constructor or modify config after init
|
|
184
|
-
|
|
185
|
-
analyzer = ProjectAnalyzer(args.project_path, args.output, args.profile)
|
|
186
|
-
|
|
187
|
-
# Override config based on CLI flag
|
|
188
|
-
if hasattr(args, "report") and args.report:
|
|
189
|
-
analyzer.config["generate_html"] = True
|
|
190
|
-
else:
|
|
191
|
-
analyzer.config["generate_html"] = False
|
|
192
|
-
|
|
193
|
-
success = analyzer.run()
|
|
194
|
-
|
|
195
|
-
# Always show terminal summary
|
|
196
|
-
from .reporters.summary_reporter import report_summary
|
|
197
|
-
|
|
198
|
-
# If we didn't generate reports, we might still want to show the summary
|
|
199
|
-
# using the in-memory data or the context file if it was saved.
|
|
200
|
-
# Engine saves json context by default? Let's check engine.py.
|
|
201
|
-
# Assuming engine saves project_context.json always or we need to access results directly.
|
|
202
|
-
# To keep it simple, we depend on the engine saving the context or returning it.
|
|
203
|
-
# Current engine.run retuns bool.
|
|
204
|
-
|
|
205
|
-
context_path = analyzer.output_dir / "project_context.json"
|
|
206
|
-
if context_path.exists():
|
|
207
|
-
report_summary(context_path)
|
|
208
|
-
|
|
209
|
-
if not success:
|
|
210
|
-
sys.exit(1)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def _handle_list_rules_command() -> None:
|
|
214
|
-
"""Handles the 'list-rules' command by displaying available audit rules."""
|
|
215
|
-
from .rules import get_qgis_audit_rules
|
|
216
|
-
|
|
217
|
-
rules = get_qgis_audit_rules()
|
|
218
|
-
print("\n📋 QGIS Audit Rules Catalog:")
|
|
219
|
-
print("=" * 30)
|
|
220
|
-
for r in rules:
|
|
221
|
-
print(f"- [{r['severity'].upper()}] {r['id']}: {r['message']}")
|
|
222
|
-
print(f"\nTotal: {len(rules)} rules.\n")
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def _handle_init_command() -> None:
|
|
226
|
-
"""Handles the 'init' command by creating a default .analyzerignore file."""
|
|
227
|
-
from .utils import DEFAULT_EXCLUDE
|
|
228
|
-
|
|
229
|
-
ignore_file = pathlib.Path(".analyzerignore")
|
|
230
|
-
if ignore_file.exists():
|
|
231
|
-
print("⚠️ .analyzerignore already exists. Skipping.")
|
|
232
|
-
else:
|
|
233
|
-
with open(ignore_file, "w") as f:
|
|
234
|
-
f.write("# QGIS Plugin Analyzer Ignore File\n")
|
|
235
|
-
for p in DEFAULT_EXCLUDE:
|
|
236
|
-
f.write(f"{p}\n")
|
|
237
|
-
print("✅ Created .analyzerignore with default excludes.")
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _handle_summary_command(args: argparse.Namespace) -> None:
|
|
241
|
-
"""Handles the 'summary' command by displaying a terminal report.
|
|
242
|
-
|
|
243
|
-
Args:
|
|
244
|
-
args: Parsed command line arguments.
|
|
245
|
-
"""
|
|
246
|
-
from .reporters.summary_reporter import report_summary
|
|
247
|
-
|
|
248
|
-
input_path = pathlib.Path(args.input).resolve()
|
|
249
|
-
report_summary(input_path, by=args.by)
|
|
24
|
+
from .cli import CLIApp
|
|
250
25
|
|
|
251
26
|
|
|
252
27
|
def main() -> None:
|
|
253
|
-
"""Main entry point for the QGIS Plugin Analyzer CLI.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
sets up the global logging environment.
|
|
257
|
-
"""
|
|
258
|
-
parser = _setup_argument_parser()
|
|
259
|
-
|
|
260
|
-
# Legacy support / default to analyze if no command provided
|
|
261
|
-
if len(sys.argv) > 1 and sys.argv[1] not in [
|
|
262
|
-
"analyze",
|
|
263
|
-
"fix",
|
|
264
|
-
"list-rules",
|
|
265
|
-
"init",
|
|
266
|
-
"summary",
|
|
267
|
-
"-h",
|
|
268
|
-
"--help",
|
|
269
|
-
]:
|
|
270
|
-
# If the first argument is a path (doesn't start with -), assume 'analyze'
|
|
271
|
-
if not sys.argv[1].startswith("-"):
|
|
272
|
-
sys.argv.insert(1, "analyze")
|
|
273
|
-
|
|
274
|
-
args = parser.parse_args()
|
|
275
|
-
|
|
276
|
-
# Initialize logger (default to analysis_results if not specified)
|
|
277
|
-
output_dir = pathlib.Path(getattr(args, "output", "./analysis_results")).resolve()
|
|
278
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
279
|
-
setup_logger(output_dir)
|
|
280
|
-
|
|
281
|
-
try:
|
|
282
|
-
if args.command == "fix":
|
|
283
|
-
_handle_fix_command(args)
|
|
284
|
-
elif args.command == "analyze":
|
|
285
|
-
_handle_analyze_command(args)
|
|
286
|
-
elif args.command == "list-rules":
|
|
287
|
-
_handle_list_rules_command()
|
|
288
|
-
elif args.command == "init":
|
|
289
|
-
_handle_init_command()
|
|
290
|
-
elif args.command == "summary":
|
|
291
|
-
_handle_summary_command(args)
|
|
292
|
-
else:
|
|
293
|
-
parser.print_help()
|
|
294
|
-
|
|
295
|
-
except KeyboardInterrupt:
|
|
296
|
-
logger.info("\n⏹️ Analysis interrupted.")
|
|
297
|
-
sys.exit(1)
|
|
298
|
-
except FileNotFoundError as e:
|
|
299
|
-
logger.error(f"Error: File not found: {e}")
|
|
300
|
-
sys.exit(1)
|
|
301
|
-
except ValueError as e:
|
|
302
|
-
# This handles path traversal or other validation errors
|
|
303
|
-
logger.error(f"Error: {e}")
|
|
304
|
-
sys.exit(1)
|
|
305
|
-
except Exception as e:
|
|
306
|
-
logger.critical(f"Critical Error: {e}", exc_info=True)
|
|
307
|
-
sys.exit(1)
|
|
28
|
+
"""Main entry point for the QGIS Plugin Analyzer CLI."""
|
|
29
|
+
app = CLIApp()
|
|
30
|
+
sys.exit(app.run())
|
|
308
31
|
|
|
309
32
|
|
|
310
33
|
if __name__ == "__main__":
|
analyzer/commands.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Command handlers for the QGIS Plugin Analyzer CLI.
|
|
2
|
+
|
|
3
|
+
This module contains the implementation of individual CLI commands to separate
|
|
4
|
+
interface definition (cli.py) from execution logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import dataclasses
|
|
9
|
+
import json
|
|
10
|
+
import pathlib
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from .engine import ProjectAnalyzer
|
|
14
|
+
from .fixer import AutoFixer
|
|
15
|
+
from .reporters.summary_reporter import report_summary
|
|
16
|
+
from .rules import get_qgis_audit_rules
|
|
17
|
+
from .utils import DEFAULT_EXCLUDE
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def handle_fix(args: argparse.Namespace) -> bool:
|
|
21
|
+
"""Handles the execution of the 'fix' command.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
args: Parsed command line arguments.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
True if the fix process completed successfully, False otherwise.
|
|
28
|
+
"""
|
|
29
|
+
project_path = pathlib.Path(args.path).resolve()
|
|
30
|
+
if not project_path.exists():
|
|
31
|
+
print(f"❌ Path not found: {project_path}")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# Run analysis first
|
|
35
|
+
print("🔍 Analyzing project for fixable issues...")
|
|
36
|
+
analyzer = ProjectAnalyzer(
|
|
37
|
+
str(project_path),
|
|
38
|
+
args.output if hasattr(args, "output") else "./analysis_results",
|
|
39
|
+
args.profile if hasattr(args, "profile") else "default",
|
|
40
|
+
)
|
|
41
|
+
analyzer.run()
|
|
42
|
+
|
|
43
|
+
# Load issues
|
|
44
|
+
context_file = analyzer.output_dir / "project_context.json"
|
|
45
|
+
if not context_file.exists():
|
|
46
|
+
print("❌ Analysis failed to generate context file.")
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
with open(context_file) as f:
|
|
50
|
+
context = json.load(f)
|
|
51
|
+
|
|
52
|
+
all_issues = []
|
|
53
|
+
for module in context.get("modules", []):
|
|
54
|
+
all_issues.extend(module.get("ast_issues", []))
|
|
55
|
+
|
|
56
|
+
if args.rules:
|
|
57
|
+
rule_ids = [r.strip() for r in args.rules.split(",")]
|
|
58
|
+
all_issues = [i for i in all_issues if i.get("type") in rule_ids]
|
|
59
|
+
|
|
60
|
+
fixer = AutoFixer(project_path, dry_run=not args.apply)
|
|
61
|
+
fixable = fixer.get_fixable_issues(all_issues)
|
|
62
|
+
|
|
63
|
+
if not fixable:
|
|
64
|
+
print("✅ No fixable issues found!")
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
print(f"\n📋 Found {len(fixable)} fixable issue(s)")
|
|
68
|
+
if not args.apply:
|
|
69
|
+
print("\n⚠️ DRY RUN MODE (use --apply to execute changes)\n")
|
|
70
|
+
|
|
71
|
+
stats = fixer.apply_fixes(fixable, interactive=not args.auto_approve)
|
|
72
|
+
print(
|
|
73
|
+
f"\n📊 Summary: Applied: {stats['applied']}, Skipped: {stats['skipped']}, Failed: {stats['failed']}"
|
|
74
|
+
)
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def handle_analyze(args: argparse.Namespace) -> None:
|
|
79
|
+
"""Handles the execution of the 'analyze' command.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
args: Parsed command line arguments.
|
|
83
|
+
"""
|
|
84
|
+
analyzer = ProjectAnalyzer(args.project_path, args.output, args.profile)
|
|
85
|
+
|
|
86
|
+
# Override config based on CLI flag using dataclasses.replace since it's frozen
|
|
87
|
+
report_enabled = bool(hasattr(args, "report") and args.report)
|
|
88
|
+
analyzer.config = dataclasses.replace(analyzer.config, generate_html=report_enabled)
|
|
89
|
+
|
|
90
|
+
success = analyzer.run()
|
|
91
|
+
|
|
92
|
+
context_path = analyzer.output_dir / "project_context.json"
|
|
93
|
+
if context_path.exists():
|
|
94
|
+
report_summary(context_path)
|
|
95
|
+
|
|
96
|
+
if not success:
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def handle_list_rules() -> None:
|
|
101
|
+
"""Handles the 'list-rules' command by displaying available audit rules."""
|
|
102
|
+
rules = get_qgis_audit_rules()
|
|
103
|
+
print("\n📋 QGIS Audit Rules Catalog:")
|
|
104
|
+
print("=" * 30)
|
|
105
|
+
for r in rules:
|
|
106
|
+
print(f"- [{r['severity'].upper()}] {r['id']}: {r['message']}")
|
|
107
|
+
print(f"\nTotal: {len(rules)} rules.\n")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def handle_init() -> None:
|
|
111
|
+
"""Handles the 'init' command by creating a default .analyzerignore file."""
|
|
112
|
+
ignore_file = pathlib.Path(".analyzerignore")
|
|
113
|
+
if ignore_file.exists():
|
|
114
|
+
print("⚠️ .analyzerignore already exists. Skipping.")
|
|
115
|
+
else:
|
|
116
|
+
with open(ignore_file, "w") as f:
|
|
117
|
+
f.write("# QGIS Plugin Analyzer Ignore File\n")
|
|
118
|
+
for p in DEFAULT_EXCLUDE:
|
|
119
|
+
f.write(f"{p}\n")
|
|
120
|
+
print("✅ Created .analyzerignore with default excludes.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def handle_summary(args: argparse.Namespace) -> None:
|
|
124
|
+
"""Handles the 'summary' command by displaying a terminal report.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
args: Parsed command line arguments.
|
|
128
|
+
"""
|
|
129
|
+
input_path = pathlib.Path(args.input).resolve()
|
|
130
|
+
report_summary(input_path, by=args.by)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def handle_security(args: argparse.Namespace) -> None:
|
|
134
|
+
"""Handles the execution of the 'security' command.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
args: Parsed command line arguments.
|
|
138
|
+
"""
|
|
139
|
+
project_path = pathlib.Path(args.project_path).resolve()
|
|
140
|
+
if not project_path.exists():
|
|
141
|
+
print(f"❌ Path not found: {project_path}")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
print(f"🛡️ Starting focused security scan for: {project_path.name}...")
|
|
145
|
+
|
|
146
|
+
# Run analyzer with current profile
|
|
147
|
+
analyzer = ProjectAnalyzer(str(project_path), args.output, args.profile)
|
|
148
|
+
|
|
149
|
+
# We could potentially add high-level flags here
|
|
150
|
+
if args.deep:
|
|
151
|
+
# Note: ProjectConfig currently doesn't have security_deep_scan,
|
|
152
|
+
# but if it did, we would use dataclasses.replace here too.
|
|
153
|
+
print("🔍 Deep scan enabled (Entropy analysis and full secret detection)")
|
|
154
|
+
|
|
155
|
+
success = analyzer.run()
|
|
156
|
+
|
|
157
|
+
context_path = analyzer.output_dir / "project_context.json"
|
|
158
|
+
if context_path.exists():
|
|
159
|
+
# Use the specialized security reporter
|
|
160
|
+
report_summary(context_path, by="security")
|
|
161
|
+
|
|
162
|
+
if not success:
|
|
163
|
+
sys.exit(1)
|