Skip to content

Commit 62b5f89

Browse files
KadirBalkuclaude
andcommitted
feat(check): show progress spinner during dependency audits
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e4475a6 commit 62b5f89

1 file changed

Lines changed: 59 additions & 15 deletions

File tree

src/viur_cli/local.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import contextlib
2+
import itertools
13
import json
4+
import sys
5+
import threading
6+
import time
27

38
import click
49
import requests
@@ -186,6 +191,41 @@ def check(dev):
186191
utils.echo_info("\U00002714 No vulnerabilities found.")
187192

188193

194+
@contextlib.contextmanager
195+
def _spinner(message):
196+
"""Show an animated spinner next to ``message`` until the block exits.
197+
198+
No-op (prints the message once) when stdout is not a TTY so CI logs
199+
stay clean.
200+
"""
201+
styled = click.style(message, fg="cyan")
202+
if not sys.stdout.isatty():
203+
click.echo(styled)
204+
yield
205+
return
206+
207+
done = threading.Event()
208+
209+
def spin():
210+
for ch in itertools.cycle("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"):
211+
if done.is_set():
212+
break
213+
sys.stdout.write(f"\r{styled} {click.style(ch, fg='cyan')}")
214+
sys.stdout.flush()
215+
time.sleep(0.1)
216+
217+
t = threading.Thread(target=spin, daemon=True)
218+
t.start()
219+
try:
220+
yield
221+
finally:
222+
done.set()
223+
t.join()
224+
# Leave the message in place; swap the spinner glyph for a check mark.
225+
sys.stdout.write(f"\r{styled} {click.style('✓', fg='cyan')}\n")
226+
sys.stdout.flush()
227+
228+
189229
def do_checks(dev=True):
190230
"""Run Python (pip-audit) and npm (npm audit) vulnerability scans.
191231
@@ -204,11 +244,12 @@ def do_checks(dev=True):
204244

205245
# --- Python vulnerability check via pip-audit (PyPA tool, OSV data) ---
206246
try:
207-
result = subprocess.run(
208-
["uvx", "pip-audit", "--format", "json", "--vulnerability-service", "osv"],
209-
capture_output=True,
210-
text=True,
211-
)
247+
with _spinner("Scanning Python dependencies (pip-audit)"):
248+
result = subprocess.run(
249+
["uvx", "pip-audit", "--format", "json", "--vulnerability-service", "osv"],
250+
capture_output=True,
251+
text=True,
252+
)
212253
except FileNotFoundError:
213254
echo_warning("uvx not found; skipping Python vulnerability check.")
214255
else:
@@ -229,7 +270,7 @@ def do_checks(dev=True):
229270
vuln_deps = [d for d in deps if d.get("vulns")]
230271
total_vulns = sum(len(d["vulns"]) for d in vuln_deps)
231272

232-
click.echo("\n" + "=" * 60)
273+
click.echo("\n\n" + "=" * 60)
233274
click.echo("Python Security Scan Results (pip-audit, OSV)")
234275
click.echo("=" * 60)
235276
click.echo(f"Total Packages: {len(deps)}")
@@ -240,6 +281,7 @@ def do_checks(dev=True):
240281
if vuln_deps:
241282
has_vulnerabilities = True
242283
click.echo("Run `uvx pip-audit --vulnerability-service osv` for per-package detail.")
284+
click.echo()
243285

244286
# --- npm vulnerability check ---
245287
if shutil.which("npm"):
@@ -258,7 +300,7 @@ def do_checks(dev=True):
258300
npm_builds.append({"name": build_name, "path": audit_path})
259301

260302
if not npm_builds:
261-
click.echo("\n" + "=" * 60)
303+
click.echo("\n\n" + "=" * 60)
262304
click.echo("No npm builds found in config - skipping npm audit")
263305
click.echo("=" * 60)
264306
else:
@@ -267,12 +309,13 @@ def do_checks(dev=True):
267309
try:
268310
npm_audit_dir = build["path"]
269311

270-
npm_result = subprocess.run(
271-
["npm", "audit", "--json"],
272-
capture_output=True,
273-
text=True,
274-
cwd=npm_audit_dir,
275-
)
312+
with _spinner(f"Scanning npm dependencies ({build['name']})"):
313+
npm_result = subprocess.run(
314+
["npm", "audit", "--json"],
315+
capture_output=True,
316+
text=True,
317+
cwd=npm_audit_dir,
318+
)
276319

277320
npm_data = json.loads(npm_result.stdout, strict=False)
278321

@@ -288,7 +331,7 @@ def do_checks(dev=True):
288331
has_vulnerabilities = True
289332

290333
# Display npm scan summary
291-
click.echo("\n" + "=" * 60)
334+
click.echo("\n\n" + "=" * 60)
292335
click.echo(f"npm Security Scan Results - {build['name']}")
293336
click.echo("=" * 60)
294337
click.echo(f"Build Path: {npm_audit_dir}")
@@ -309,6 +352,7 @@ def do_checks(dev=True):
309352

310353
if total_npm_vulns > 0:
311354
click.echo(f"Run `npm audit` in {build['path']} for per-package detail.")
355+
click.echo()
312356

313357
except FileNotFoundError:
314358
echo_warning(f"npm audit directory not found: {build['path']}")
@@ -321,7 +365,7 @@ def do_checks(dev=True):
321365
f"Unexpected error during npm security check for {build['name']}: {e}"
322366
)
323367
else:
324-
click.echo("\n" + "=" * 60)
368+
click.echo("\n\n" + "=" * 60)
325369
click.echo("npm not found - skipping npm audit")
326370
click.echo("=" * 60)
327371

0 commit comments

Comments
 (0)