|
4 | 4 |
|
5 | 5 | import click |
6 | 6 | from rich.console import Console |
| 7 | +from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn |
7 | 8 | from rich.table import Table |
8 | 9 |
|
9 | 10 | from ...core.api.packages import list_packages |
@@ -180,39 +181,59 @@ def _print_repo_summary_table(package_rows, severity_filter=None): |
180 | 181 |
|
181 | 182 |
|
182 | 183 | def _collect_repo_scan_data(opts, owner, repo, slugs, severity_filter, fixable): |
183 | | - """Silently collect scan data for all packages. Returns list of (label, counts) tuples.""" |
184 | | - rows = [] |
| 184 | + """Silently collect scan data for all packages with a progress bar. |
185 | 185 |
|
186 | | - for slug in slugs: |
187 | | - try: |
188 | | - data = get_package_scan_result( |
189 | | - opts=opts, |
190 | | - owner=owner, |
191 | | - repo=repo, |
192 | | - package=slug, |
193 | | - show_assessment=False, |
194 | | - severity_filter=severity_filter, |
195 | | - fixable=fixable, |
196 | | - ) |
197 | | - except Exception: # pylint: disable=broad-exception-caught |
198 | | - continue |
| 186 | + Returns list of (label, counts) tuples sorted by total desc. |
| 187 | + """ |
| 188 | + rows = [] |
| 189 | + console = Console(stderr=True) |
| 190 | + |
| 191 | + with Progress( |
| 192 | + SpinnerColumn(), |
| 193 | + TextColumn("[bold blue]{task.description}"), |
| 194 | + BarColumn(bar_width=40), |
| 195 | + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), |
| 196 | + TextColumn("({task.completed}/{task.total})"), |
| 197 | + console=console, |
| 198 | + transient=True, # remove progress bar when done |
| 199 | + ) as progress: |
| 200 | + task = progress.add_task("Scanning packages...", total=len(slugs)) |
| 201 | + |
| 202 | + for slug in slugs: |
| 203 | + progress.update(task, description=f"Processing {slug}...") |
| 204 | + |
| 205 | + try: |
| 206 | + data = get_package_scan_result( |
| 207 | + opts=opts, |
| 208 | + owner=owner, |
| 209 | + repo=repo, |
| 210 | + package=slug, |
| 211 | + show_assessment=False, |
| 212 | + severity_filter=severity_filter, |
| 213 | + fixable=fixable, |
| 214 | + ) |
| 215 | + except Exception: # pylint: disable=broad-exception-caught |
| 216 | + progress.advance(task) |
| 217 | + continue |
199 | 218 |
|
200 | | - # Skip packages with no scan data |
201 | | - if not data or not _has_scan_results(data): |
202 | | - continue |
| 219 | + # Skip packages with no scan data |
| 220 | + if not data or not _has_scan_results(data): |
| 221 | + progress.advance(task) |
| 222 | + continue |
203 | 223 |
|
204 | | - # Apply filters if active |
205 | | - if severity_filter or fixable is not None: |
206 | | - _apply_filters(data, severity_filter, fixable) |
| 224 | + # Apply filters if active |
| 225 | + if severity_filter or fixable is not None: |
| 226 | + _apply_filters(data, severity_filter, fixable) |
207 | 227 |
|
208 | | - # Build label from package metadata |
209 | | - pkg_data = getattr(data, "package", None) |
210 | | - pkg_name = getattr(pkg_data, "name", slug) |
211 | | - pkg_version = getattr(pkg_data, "version", "") |
212 | | - label = f"{pkg_name}:{pkg_version}" if pkg_version else pkg_name |
| 228 | + # Build label from package metadata |
| 229 | + pkg_data = getattr(data, "package", None) |
| 230 | + pkg_name = getattr(pkg_data, "name", slug) |
| 231 | + pkg_version = getattr(pkg_data, "version", "") |
| 232 | + label = f"{pkg_name}:{pkg_version}" if pkg_version else pkg_name |
213 | 233 |
|
214 | | - counts = _aggregate_severity_counts(data, severity_filter) |
215 | | - rows.append((label, counts)) |
| 234 | + counts = _aggregate_severity_counts(data, severity_filter) |
| 235 | + rows.append((label, counts)) |
| 236 | + progress.advance(task) |
216 | 237 |
|
217 | 238 | # Sort by total vulnerability count descending |
218 | 239 | rows.sort(key=lambda row: sum(row[1].values()), reverse=True) |
@@ -303,14 +324,13 @@ def vulnerabilities( |
303 | 324 | ) |
304 | 325 | return |
305 | 326 |
|
306 | | - # Repo summary mode: collect everything silently, then output once |
| 327 | + # Repo summary mode: collect with progress bar, then output once |
307 | 328 | if repo_summary: |
308 | 329 | slugs = get_packages_in_repo(opts, owner, repo) |
309 | 330 |
|
310 | | - with utils.maybe_spinner(opts): |
311 | | - repo_summary_rows = _collect_repo_scan_data( |
312 | | - opts, owner, repo, slugs, severity_filter, fixable |
313 | | - ) |
| 331 | + repo_summary_rows = _collect_repo_scan_data( |
| 332 | + opts, owner, repo, slugs, severity_filter, fixable |
| 333 | + ) |
314 | 334 |
|
315 | 335 | if not repo_summary_rows: |
316 | 336 | click.secho( |
|
0 commit comments