java-dependency-analyzer 1.0.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.
Files changed (31) hide show
  1. java_dependency_analyzer/__init__.py +11 -0
  2. java_dependency_analyzer/cache/__init__.py +11 -0
  3. java_dependency_analyzer/cache/db.py +101 -0
  4. java_dependency_analyzer/cache/vulnerability_cache.py +156 -0
  5. java_dependency_analyzer/cli.py +394 -0
  6. java_dependency_analyzer/models/__init__.py +11 -0
  7. java_dependency_analyzer/models/dependency.py +80 -0
  8. java_dependency_analyzer/models/report.py +108 -0
  9. java_dependency_analyzer/parsers/__init__.py +11 -0
  10. java_dependency_analyzer/parsers/base.py +150 -0
  11. java_dependency_analyzer/parsers/gradle_dep_tree_parser.py +125 -0
  12. java_dependency_analyzer/parsers/gradle_parser.py +206 -0
  13. java_dependency_analyzer/parsers/maven_dep_tree_parser.py +123 -0
  14. java_dependency_analyzer/parsers/maven_parser.py +182 -0
  15. java_dependency_analyzer/reporters/__init__.py +11 -0
  16. java_dependency_analyzer/reporters/base.py +33 -0
  17. java_dependency_analyzer/reporters/html_reporter.py +82 -0
  18. java_dependency_analyzer/reporters/json_reporter.py +52 -0
  19. java_dependency_analyzer/reporters/templates/report.html +406 -0
  20. java_dependency_analyzer/resolvers/__init__.py +11 -0
  21. java_dependency_analyzer/resolvers/transitive.py +276 -0
  22. java_dependency_analyzer/scanners/__init__.py +11 -0
  23. java_dependency_analyzer/scanners/base.py +102 -0
  24. java_dependency_analyzer/scanners/ghsa_scanner.py +204 -0
  25. java_dependency_analyzer/scanners/osv_scanner.py +167 -0
  26. java_dependency_analyzer/util/__init__.py +11 -0
  27. java_dependency_analyzer/util/logger.py +48 -0
  28. java_dependency_analyzer-1.0.0.dist-info/METADATA +193 -0
  29. java_dependency_analyzer-1.0.0.dist-info/RECORD +31 -0
  30. java_dependency_analyzer-1.0.0.dist-info/WHEEL +4 -0
  31. java_dependency_analyzer-1.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,11 @@
1
+ """
2
+ java_dependency_analyzer package.
3
+
4
+ Java Dependency Analyzer is a tool that inspects dependencies.
5
+
6
+ :author: Ron Webb
7
+ :since: 1.0.0
8
+ """
9
+
10
+ __author__ = "Ron Webb"
11
+ __since__ = "1.0.0"
@@ -0,0 +1,11 @@
1
+ """
2
+ cache package.
3
+
4
+ Provides SQLite-backed persistence for vulnerability scan results.
5
+
6
+ :author: Ron Webb
7
+ :since: 1.0.0
8
+ """
9
+
10
+ __author__ = "Ron Webb"
11
+ __since__ = "1.0.0"
@@ -0,0 +1,101 @@
1
+ """
2
+ db module.
3
+
4
+ Manages the SQLite database lifecycle: path resolution, connection,
5
+ schema creation, and deletion.
6
+
7
+ :author: Ron Webb
8
+ :since: 1.0.0
9
+ """
10
+
11
+ import sqlite3
12
+ from pathlib import Path
13
+
14
+ from ..util.logger import setup_logger
15
+
16
+ __author__ = "Ron Webb"
17
+ __since__ = "1.0.0"
18
+
19
+ _logger = setup_logger(__name__)
20
+
21
+ _DB_DIR = Path.home() / ".jda"
22
+ _DB_FILE = "cache.db"
23
+
24
+ _CREATE_TABLE_SQL = """
25
+ CREATE TABLE IF NOT EXISTS vulnerability_cache (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ source TEXT NOT NULL,
28
+ group_id TEXT NOT NULL,
29
+ artifact_id TEXT NOT NULL,
30
+ version TEXT NOT NULL,
31
+ payload TEXT NOT NULL,
32
+ cached_at TEXT NOT NULL,
33
+ UNIQUE(source, group_id, artifact_id, version)
34
+ )
35
+ """
36
+
37
+ _CREATE_INDEX_SQL = """
38
+ CREATE INDEX IF NOT EXISTS idx_cache_lookup
39
+ ON vulnerability_cache(group_id, artifact_id, version, source)
40
+ """
41
+
42
+
43
+ def get_db_path() -> Path:
44
+ """
45
+ Return the absolute path to the SQLite cache database file.
46
+
47
+ The default location is ``~/.jda/cache.db``.
48
+
49
+ :author: Ron Webb
50
+ :since: 1.0.0
51
+ """
52
+ return _DB_DIR / _DB_FILE
53
+
54
+
55
+ def get_connection() -> sqlite3.Connection:
56
+ """
57
+ Open (and initialise) the SQLite database, creating the parent directory
58
+ and schema if they do not yet exist.
59
+
60
+ Returns a ``sqlite3.Connection`` with ``check_same_thread=False`` so the
61
+ connection can be shared within a single CLI invocation.
62
+
63
+ :author: Ron Webb
64
+ :since: 1.0.0
65
+ """
66
+ db_path = get_db_path()
67
+ db_path.parent.mkdir(parents=True, exist_ok=True)
68
+ _logger.debug("Opening cache database at %s", db_path)
69
+ conn = sqlite3.connect(str(db_path), check_same_thread=False)
70
+ _initialise_schema(conn)
71
+ return conn
72
+
73
+
74
+ def _initialise_schema(conn: sqlite3.Connection) -> None:
75
+ """
76
+ Create the cache table and lookup index when they do not already exist.
77
+
78
+ :author: Ron Webb
79
+ :since: 1.0.0
80
+ """
81
+ with conn:
82
+ conn.execute(_CREATE_TABLE_SQL)
83
+ conn.execute(_CREATE_INDEX_SQL)
84
+
85
+
86
+ def delete_database() -> bool:
87
+ """
88
+ Delete the SQLite cache database file.
89
+
90
+ Returns ``True`` if the file was deleted, ``False`` if it did not exist.
91
+
92
+ :author: Ron Webb
93
+ :since: 1.0.0
94
+ """
95
+ db_path = get_db_path()
96
+ if db_path.exists():
97
+ db_path.unlink()
98
+ _logger.info("Deleted cache database at %s", db_path)
99
+ return True
100
+ _logger.debug("Cache database not found at %s, nothing to delete", db_path)
101
+ return False
@@ -0,0 +1,156 @@
1
+ """
2
+ vulnerability_cache module.
3
+
4
+ Provides a SQLite-backed cache for vulnerability scan API responses.
5
+ Cache entries expire after a configurable number of days (default: 7).
6
+
7
+ :author: Ron Webb
8
+ :since: 1.0.0
9
+ """
10
+
11
+ import sqlite3
12
+ from datetime import datetime, timedelta
13
+
14
+ from ..util.logger import setup_logger
15
+ from .db import get_connection
16
+
17
+ __author__ = "Ron Webb"
18
+ __since__ = "1.0.0"
19
+
20
+ _logger = setup_logger(__name__)
21
+
22
+ _DEFAULT_TTL_DAYS = 7
23
+
24
+ _SELECT_SQL = """
25
+ SELECT payload, cached_at FROM vulnerability_cache
26
+ WHERE source = ? AND group_id = ? AND artifact_id = ? AND version = ?
27
+ """
28
+
29
+ _UPSERT_SQL = """
30
+ INSERT INTO vulnerability_cache(source, group_id, artifact_id, version, payload, cached_at)
31
+ VALUES (?, ?, ?, ?, ?, ?)
32
+ ON CONFLICT(source, group_id, artifact_id, version)
33
+ DO UPDATE SET payload = excluded.payload, cached_at = excluded.cached_at
34
+ """
35
+
36
+ _DELETE_SQL = """
37
+ DELETE FROM vulnerability_cache
38
+ WHERE source = ? AND group_id = ? AND artifact_id = ? AND version = ?
39
+ """
40
+
41
+
42
+ class VulnerabilityCache:
43
+ """
44
+ SQLite-backed cache for raw vulnerability API payloads.
45
+
46
+ Each entry is keyed by ``(source, group_id, artifact_id, version)`` and stores
47
+ the raw JSON response payload string. Entries older than ``ttl_days`` are
48
+ treated as expired and will not be returned.
49
+
50
+ :author: Ron Webb
51
+ :since: 1.0.0
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ connection: sqlite3.Connection | None = None,
57
+ ttl_days: int = _DEFAULT_TTL_DAYS,
58
+ ) -> None:
59
+ """
60
+ Initialise the cache with an optional SQLite connection and TTL.
61
+
62
+ When *connection* is ``None`` the default on-disk database is used.
63
+
64
+ :author: Ron Webb
65
+ :since: 1.0.0
66
+ """
67
+ self._conn = connection or get_connection()
68
+ self._ttl_days = ttl_days
69
+
70
+ def get(
71
+ self,
72
+ source: str,
73
+ group_id: str,
74
+ artifact_id: str,
75
+ version: str,
76
+ ) -> str | None:
77
+ """
78
+ Return the cached JSON payload for the given key, or ``None`` when there
79
+ is no valid (non-expired) entry.
80
+
81
+ :author: Ron Webb
82
+ :since: 1.0.0
83
+ """
84
+ cursor = self._conn.execute(_SELECT_SQL, (source, group_id, artifact_id, version))
85
+ row = cursor.fetchone()
86
+ if row is None:
87
+ return None
88
+ payload, cached_at = row
89
+ if self._is_expired(cached_at):
90
+ with self._conn:
91
+ self._conn.execute(_DELETE_SQL, (source, group_id, artifact_id, version))
92
+ _logger.debug(
93
+ "Cache entry expired and deleted for %s/%s:%s@%s",
94
+ source,
95
+ group_id,
96
+ artifact_id,
97
+ version,
98
+ )
99
+ return None
100
+ _logger.debug(
101
+ "Cache hit for %s/%s:%s@%s", source, group_id, artifact_id, version
102
+ )
103
+ return payload
104
+
105
+ def put( # pylint: disable=too-many-arguments,too-many-positional-arguments
106
+ self,
107
+ source: str,
108
+ group_id: str,
109
+ artifact_id: str,
110
+ version: str,
111
+ payload: str,
112
+ ) -> None:
113
+ """
114
+ Insert or replace the cache entry for the given key.
115
+
116
+ *payload* must be a JSON string representing the raw API response.
117
+
118
+ :author: Ron Webb
119
+ :since: 1.0.0
120
+ """
121
+ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
122
+ with self._conn:
123
+ self._conn.execute(
124
+ _UPSERT_SQL,
125
+ (source, group_id, artifact_id, version, payload, now),
126
+ )
127
+ _logger.debug(
128
+ "Cached payload for %s/%s:%s@%s", source, group_id, artifact_id, version
129
+ )
130
+
131
+ def close(self) -> None:
132
+ """
133
+ Close the underlying SQLite connection.
134
+
135
+ Call this when the cache is no longer needed to release the database
136
+ file handle immediately. Callers that passed their own *connection* to
137
+ the constructor are responsible for not using the connection after calling
138
+ this method.
139
+
140
+ :author: Ron Webb
141
+ :since: 1.0.0
142
+ """
143
+ self._conn.close()
144
+
145
+ def _is_expired(self, cached_at: str) -> bool:
146
+ """
147
+ Return ``True`` when *cached_at* is older than the configured TTL.
148
+
149
+ :author: Ron Webb
150
+ :since: 1.0.0
151
+ """
152
+ try:
153
+ entry_time = datetime.strptime(cached_at, "%Y-%m-%d %H:%M:%S")
154
+ except ValueError:
155
+ return True
156
+ return datetime.now() - entry_time > timedelta(days=self._ttl_days)
@@ -0,0 +1,394 @@
1
+ """
2
+ cli module.
3
+
4
+ Command-line interface entry point for the Java Dependency Analyzer.
5
+
6
+ :author: Ron Webb
7
+ :since: 1.0.0
8
+ """
9
+
10
+ from pathlib import Path
11
+
12
+ import click
13
+
14
+ from .cache.db import delete_database
15
+ from .cache.vulnerability_cache import VulnerabilityCache
16
+ from .models.dependency import Dependency
17
+ from .models.report import ScanResult
18
+ from .parsers.gradle_dep_tree_parser import GradleDepTreeParser
19
+ from .parsers.gradle_parser import GradleParser
20
+ from .parsers.maven_dep_tree_parser import MavenDepTreeParser
21
+ from .parsers.maven_parser import MavenParser
22
+ from .reporters.html_reporter import HtmlReporter
23
+ from .reporters.json_reporter import JsonReporter
24
+ from .resolvers.transitive import TransitiveResolver
25
+ from .scanners.ghsa_scanner import GhsaScanner
26
+ from .scanners.osv_scanner import OsvScanner
27
+ from .util.logger import setup_logger
28
+
29
+ __author__ = "Ron Webb"
30
+ __since__ = "1.0.0"
31
+
32
+ _logger = setup_logger(__name__)
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Shared CLI options applied to both subcommands
36
+ # ---------------------------------------------------------------------------
37
+
38
+ _COMMON_OPTIONS = [
39
+ click.option(
40
+ "--output-format",
41
+ "-f",
42
+ type=click.Choice(["json", "html", "all"], case_sensitive=False),
43
+ default="all",
44
+ show_default=True,
45
+ help="Output format for the vulnerability report.",
46
+ ),
47
+ click.option(
48
+ "--output-dir",
49
+ "-o",
50
+ default="./reports",
51
+ show_default=True,
52
+ type=click.Path(file_okay=False),
53
+ help="Directory to write the report file(s) into.",
54
+ ),
55
+ click.option(
56
+ "--no-transitive",
57
+ is_flag=True,
58
+ default=False,
59
+ help="Skip transitive dependency resolution (direct dependencies only).",
60
+ ),
61
+ click.option(
62
+ "--verbose",
63
+ "-v",
64
+ is_flag=True,
65
+ default=False,
66
+ help="Enable verbose progress output.",
67
+ ),
68
+ click.option(
69
+ "--rebuild-cache",
70
+ is_flag=True,
71
+ default=False,
72
+ help="Delete the vulnerability cache database before scanning.",
73
+ ),
74
+ click.option(
75
+ "--cache-ttl",
76
+ default=7,
77
+ show_default=True,
78
+ type=int,
79
+ help="Cache TTL in days. Set to 0 to disable caching.",
80
+ ),
81
+ ]
82
+
83
+
84
+ def _common_options(func):
85
+ """Apply all shared options to a Click command."""
86
+ for option in reversed(_COMMON_OPTIONS):
87
+ func = option(func)
88
+ return func
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Click group
93
+ # ---------------------------------------------------------------------------
94
+
95
+
96
+ @click.group()
97
+ def main() -> None:
98
+ """Java Dependency Analyzer -- inspect Java dependency trees for known vulnerabilities."""
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # gradle subcommand
103
+ # ---------------------------------------------------------------------------
104
+
105
+
106
+ @main.command()
107
+ @click.argument(
108
+ "file",
109
+ required=False,
110
+ default=None,
111
+ type=click.Path(exists=True, dir_okay=False, readable=True),
112
+ )
113
+ @click.option(
114
+ "--dependencies",
115
+ "-d",
116
+ default=None,
117
+ type=click.Path(exists=True, dir_okay=False, readable=True),
118
+ help=(
119
+ "Path to a pre-resolved Gradle dependency tree text file "
120
+ "(output of ``gradle dependencies``). When supplied, transitive "
121
+ "resolution is skipped."
122
+ ),
123
+ )
124
+ @_common_options
125
+ def gradle( # pylint: disable=too-many-arguments,too-many-positional-arguments
126
+ file: str | None,
127
+ dependencies: str | None,
128
+ output_format: str,
129
+ output_dir: str,
130
+ no_transitive: bool,
131
+ verbose: bool,
132
+ rebuild_cache: bool,
133
+ cache_ttl: int,
134
+ ) -> None:
135
+ """
136
+ Analyse a Gradle build file (build.gradle or build.gradle.kts) for known
137
+ dependency vulnerabilities.
138
+
139
+ FILE is the path to a build.gradle or build.gradle.kts file. Alternatively,
140
+ supply a pre-resolved dependency tree via --dependencies to skip both parsing
141
+ and transitive resolution.
142
+
143
+ :author: Ron Webb
144
+ :since: 1.0.0
145
+ """
146
+ if file is None and dependencies is None:
147
+ raise click.UsageError("Provide FILE or --dependencies (or both).")
148
+
149
+ if file is not None:
150
+ file_path = Path(file)
151
+ name = file_path.name
152
+ if not (name.endswith("build.gradle.kts") or name.endswith("build.gradle")):
153
+ raise click.UsageError(
154
+ f"Unsupported file: {name}. Expected build.gradle or build.gradle.kts."
155
+ )
156
+
157
+ cache = _init_cache(rebuild_cache, cache_ttl, verbose)
158
+
159
+ try:
160
+ if dependencies is not None:
161
+ if verbose:
162
+ click.echo(f"Loading dependency tree from {dependencies}...")
163
+ parsed_deps = GradleDepTreeParser().parse(dependencies)
164
+ source = file if file is not None else dependencies
165
+ _run_analysis(
166
+ parsed_deps,
167
+ source_file=source,
168
+ output_format=output_format,
169
+ output_dir=output_dir,
170
+ no_transitive=True,
171
+ verbose=verbose,
172
+ cache=cache,
173
+ )
174
+ else:
175
+ if verbose:
176
+ click.echo(f"Parsing {Path(file).name}...") # type: ignore[arg-type]
177
+ parsed_deps = GradleParser().parse(file) # type: ignore[arg-type]
178
+ _run_analysis(
179
+ parsed_deps,
180
+ source_file=file, # type: ignore[arg-type]
181
+ output_format=output_format,
182
+ output_dir=output_dir,
183
+ no_transitive=no_transitive,
184
+ verbose=verbose,
185
+ cache=cache,
186
+ )
187
+ finally:
188
+ if cache is not None:
189
+ cache.close()
190
+
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # maven subcommand
194
+ # ---------------------------------------------------------------------------
195
+
196
+
197
+ @main.command()
198
+ @click.argument(
199
+ "file",
200
+ required=False,
201
+ default=None,
202
+ type=click.Path(exists=True, dir_okay=False, readable=True),
203
+ )
204
+ @click.option(
205
+ "--dependencies",
206
+ "-d",
207
+ default=None,
208
+ type=click.Path(exists=True, dir_okay=False, readable=True),
209
+ help=(
210
+ "Path to a pre-resolved Maven dependency tree text file "
211
+ "(output of ``mvn dependency:tree``). When supplied, transitive "
212
+ "resolution is skipped."
213
+ ),
214
+ )
215
+ @_common_options
216
+ def maven( # pylint: disable=too-many-arguments,too-many-positional-arguments
217
+ file: str | None,
218
+ dependencies: str | None,
219
+ output_format: str,
220
+ output_dir: str,
221
+ no_transitive: bool,
222
+ verbose: bool,
223
+ rebuild_cache: bool,
224
+ cache_ttl: int,
225
+ ) -> None:
226
+ """
227
+ Analyse a Maven POM file (pom.xml) for known dependency vulnerabilities.
228
+
229
+ FILE is the path to a pom.xml file. Alternatively, supply a pre-resolved
230
+ dependency tree via --dependencies to skip both parsing and transitive
231
+ resolution.
232
+
233
+ :author: Ron Webb
234
+ :since: 1.0.0
235
+ """
236
+ if file is None and dependencies is None:
237
+ raise click.UsageError("Provide FILE or --dependencies (or both).")
238
+
239
+ if file is not None:
240
+ file_path = Path(file)
241
+ if not file_path.name.endswith("pom.xml"):
242
+ raise click.UsageError(
243
+ f"Unsupported file: {file_path.name}. Expected pom.xml."
244
+ )
245
+
246
+ cache = _init_cache(rebuild_cache, cache_ttl, verbose)
247
+
248
+ try:
249
+ if dependencies is not None:
250
+ if verbose:
251
+ click.echo(f"Loading dependency tree from {dependencies}...")
252
+ parsed_deps = MavenDepTreeParser().parse(dependencies)
253
+ source = file if file is not None else dependencies
254
+ _run_analysis(
255
+ parsed_deps,
256
+ source_file=source,
257
+ output_format=output_format,
258
+ output_dir=output_dir,
259
+ no_transitive=True,
260
+ verbose=verbose,
261
+ cache=cache,
262
+ )
263
+ else:
264
+ if verbose:
265
+ click.echo(f"Parsing {Path(file).name}...") # type: ignore[arg-type]
266
+ parsed_deps = MavenParser().parse(file) # type: ignore[arg-type]
267
+ _run_analysis(
268
+ parsed_deps,
269
+ source_file=file, # type: ignore[arg-type]
270
+ output_format=output_format,
271
+ output_dir=output_dir,
272
+ no_transitive=no_transitive,
273
+ verbose=verbose,
274
+ cache=cache,
275
+ )
276
+ finally:
277
+ if cache is not None:
278
+ cache.close()
279
+
280
+
281
+ # ---------------------------------------------------------------------------
282
+ # Private helpers
283
+ # ---------------------------------------------------------------------------
284
+
285
+
286
+ def _init_cache(
287
+ rebuild_cache: bool, cache_ttl: int, verbose: bool
288
+ ) -> VulnerabilityCache | None:
289
+ """
290
+ Optionally clear and then create the vulnerability cache.
291
+
292
+ :author: Ron Webb
293
+ :since: 1.0.0
294
+ """
295
+ if rebuild_cache:
296
+ delete_database()
297
+ if verbose:
298
+ click.echo("Vulnerability cache cleared.")
299
+
300
+ return VulnerabilityCache(ttl_days=cache_ttl) if cache_ttl > 0 else None
301
+
302
+
303
+ def _run_analysis( # pylint: disable=too-many-arguments,too-many-positional-arguments
304
+ dependencies: list[Dependency],
305
+ source_file: str,
306
+ output_format: str,
307
+ output_dir: str,
308
+ no_transitive: bool,
309
+ verbose: bool,
310
+ cache: VulnerabilityCache | None,
311
+ ) -> None:
312
+ """
313
+ Resolve transitive dependencies (unless skipped), scan for vulnerabilities,
314
+ and write the requested reports.
315
+
316
+ :author: Ron Webb
317
+ :since: 1.0.0
318
+ """
319
+ if not dependencies:
320
+ click.echo("No runtime dependencies found.", err=True)
321
+
322
+ if verbose:
323
+ click.echo(f"Found {len(dependencies)} direct dependencies.")
324
+
325
+ if not no_transitive:
326
+ if verbose:
327
+ click.echo("Resolving transitive dependencies from Maven Central...")
328
+ TransitiveResolver().resolve_all(dependencies)
329
+
330
+ if verbose:
331
+ click.echo("Scanning for vulnerabilities...")
332
+
333
+ osv = OsvScanner(cache=cache)
334
+ ghsa = GhsaScanner(cache=cache)
335
+ _scan_all(dependencies, osv, ghsa, verbose)
336
+
337
+ result = ScanResult(source_file=source_file, dependencies=dependencies)
338
+
339
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
340
+ _write_reports(result, Path(output_dir), output_format, verbose)
341
+
342
+ click.echo(
343
+ f"\nScan complete. "
344
+ f"{result.total_dependencies} dependencies, "
345
+ f"{result.total_vulnerabilities} vulnerabilities found."
346
+ )
347
+
348
+
349
+ def _scan_all(
350
+ dependencies: list[Dependency],
351
+ osv: OsvScanner,
352
+ ghsa: GhsaScanner,
353
+ verbose: bool,
354
+ ) -> None:
355
+ """
356
+ Recursively scan all dependencies (direct + transitive) for vulnerabilities.
357
+
358
+ Uses the GitHub Advisory Database (GHSA) as the primary source. When GHSA
359
+ returns no results -- either because the API failed or no advisories were
360
+ found -- the OSV.dev scanner is used as a fallback.
361
+
362
+ :author: Ron Webb
363
+ :since: 1.0.0
364
+ """
365
+ for dep in dependencies:
366
+ if verbose:
367
+ click.echo(f" Scanning {dep.coordinates}...")
368
+ ghsa_vulns = ghsa.scan(dep)
369
+ dep.vulnerabilities = ghsa_vulns if ghsa_vulns else osv.scan(dep)
370
+ _scan_all(dep.transitive_dependencies, osv, ghsa, verbose)
371
+
372
+
373
+ def _write_reports(
374
+ result: ScanResult, output_dir: Path, output_format: str, verbose: bool
375
+ ) -> None:
376
+ """
377
+ Write one or both report formats based on the --output-format flag.
378
+
379
+ :author: Ron Webb
380
+ :since: 1.0.0
381
+ """
382
+ stem = Path(result.source_file).stem
383
+
384
+ if output_format in ("json", "all"):
385
+ json_path = output_dir / f"{stem}-report.json"
386
+ JsonReporter().report(result, str(json_path))
387
+ if verbose:
388
+ click.echo(f"JSON report: {json_path}")
389
+
390
+ if output_format in ("html", "all"):
391
+ html_path = output_dir / f"{stem}-report.html"
392
+ HtmlReporter().report(result, str(html_path))
393
+ if verbose:
394
+ click.echo(f"HTML report: {html_path}")
@@ -0,0 +1,11 @@
1
+ """
2
+ models package.
3
+
4
+ Data models for the Java Dependency Analyzer.
5
+
6
+ :author: Ron Webb
7
+ :since: 1.0.0
8
+ """
9
+
10
+ __author__ = "Ron Webb"
11
+ __since__ = "1.0.0"