From 856dff715dd1cf5f543ca36a674482f884a9742b Mon Sep 17 00:00:00 2001 From: cgombauld Date: Tue, 24 Feb 2026 11:54:05 +0100 Subject: [PATCH] feat(cli): add stat log in verbose mod --- bin/index.js | 3 +- docs/cli/auto.md | 2 +- docs/cli/cwd.md | 2 +- docs/cli/from.md | 2 +- i18n/arabic.js | 4 +- i18n/english.js | 2 + i18n/french.js | 2 + i18n/turkish.js | 2 + package.json | 6 +-- src/commands/scanner.js | 70 ++++++++++++++++++++++++----- workspaces/cache/package.json | 2 +- workspaces/server/package.json | 1 + workspaces/vis-network/package.json | 2 +- 13 files changed, 78 insertions(+), 22 deletions(-) diff --git a/bin/index.js b/bin/index.js index b4bbcbc9..13e372d4 100755 --- a/bin/index.js +++ b/bin/index.js @@ -148,7 +148,8 @@ function defaultScannerCommand(name, options = {}) { const cmd = prog.command(name) .option("-d, --depth", i18n.getTokenSync("cli.commands.option_depth"), Infinity) .option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false) - .option("-c, --contacts", i18n.getTokenSync("cli.commands.option_contacts"), []); + .option("-c, --contacts", i18n.getTokenSync("cli.commands.option_contacts"), []) + .option("--verbose", i18n.getTokenSync("cli.commands.option_verbose"), false); if (includeOutput) { cmd.option("-o, --output", i18n.getTokenSync("cli.commands.option_output"), "nsecure-result"); diff --git a/docs/cli/auto.md b/docs/cli/auto.md index 6764da77..d5484f7a 100644 --- a/docs/cli/auto.md +++ b/docs/cli/auto.md @@ -28,4 +28,4 @@ $ nsecure auto --keep | `--vulnerabilityStrategy` | `-s` | github-advisory | Strategy used to fetch package vulnerabilities (see Vulnera [available strategy](https://github.com/NodeSecure/vulnera?tab=readme-ov-file#available-strategy)). | | `--keep` | `-k` | `false` | Preserve JSON payload after execution. | | `--developer` | `-d` | `false` | Launch the server in developer mode, enabling automatic HTML component refresh. | -| `--contacts` | `-c` | `[]` | List of contacts to highlight. | +| `--contacts` | `-c` | `[]` | List of contacts to highlight. | `--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. | diff --git a/docs/cli/cwd.md b/docs/cli/cwd.md index 32be1110..5d4888de 100644 --- a/docs/cli/cwd.md +++ b/docs/cli/cwd.md @@ -18,4 +18,4 @@ $ nsecure cwd [options] | `--silent` | | `false` | Suppress console output, making execution silent. | | `--output` | `-o` | `nsecure-result` | Specify the output file for the results. | | `--vulnerabilityStrategy` | `-s` | github-advisory | Strategy used to fetch package vulnerabilities (see Vulnera [available strategy](https://github.com/NodeSecure/vulnera?tab=readme-ov-file#available-strategy)). | -| `--contacts` | `-c` | `[]` | List of contacts to highlight. | +| `--contacts` | `-c` | `[]` | List of contacts to highlight. | `--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. | diff --git a/docs/cli/from.md b/docs/cli/from.md index d05d95f0..7613cd14 100644 --- a/docs/cli/from.md +++ b/docs/cli/from.md @@ -24,4 +24,4 @@ $ nsecure from express@3.0.0 -o express-report | `--silent` | | `false` | Suppress console output, making execution silent. | | `--output` | `-o` | `nsecure-result` | Specify the output file for the results. | | `--vulnerabilityStrategy` | `-s` | github-advisory | Strategy used to fetch package vulnerabilities (see Vulnera [available strategy](https://github.com/NodeSecure/vulnera?tab=readme-ov-file#available-strategy)). | -| `--contacts` | `-c` | `[]` | List of contacts to highlight. | +| `--contacts` | `-c` | `[]` | List of contacts to highlight. | `--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. | diff --git a/i18n/arabic.js b/i18n/arabic.js index 532e70ac..f994fedf 100644 --- a/i18n/arabic.js +++ b/i18n/arabic.js @@ -1,5 +1,3 @@ -/* eslint-disable @stylistic/max-len */ - // Import Third-party Dependencies import { taggedString as tS } from "@nodesecure/i18n"; @@ -10,11 +8,13 @@ const cli = { successfully_written_json: tS`تم كتابة ملف النتائج بنجاح في: ${0}`, http_server_started: "تم تشغيل خادم HTTP على:", missingEnv: tS`متغير البيئة ${0} مفقود!`, + stat: tS`${0} ${1} في ${2}`, commands: { option_depth: "أقصى عمق للتبعيات لجلبه", option_output: "اسم ملف JSON الناتج", option_silent: "تفعيل الوضع الصامت الذي يعطل مؤشرات CLI", option_contacts: "قائمة جهات الاتصال للتمييز", + option_verbose: "ضبط مستوى الـ log الخاص بالـ CLI على verbose، مما يجعل الـ CLI يولّد logs أكثر تفصيلاً.", strategy: "مصدر الثغرات للاستخدام", cwd: { desc: "تشغيل تحليل الأمان على دليل العمل الحالي", diff --git a/i18n/english.js b/i18n/english.js index d3d0a391..1e4f986a 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -10,11 +10,13 @@ const cli = { successfully_written_json: tS`Successfully written results file at: ${0}`, http_server_started: "HTTP Server started on:", missingEnv: tS`Environment variable ${0} is missing!`, + stat: tS`${0} ${1} in ${2}`, commands: { option_depth: "Maximum dependencies depth to fetch", option_output: "Json file output name", option_silent: "enable silent mode which disable CLI spinners", option_contacts: "List of contacts to hightlight", + option_verbose: "Sets cli log level to verbose, causing the CLI to output more detailed logs.", strategy: "Vulnerabilities source to use", cwd: { desc: "Run security analysis on the current working dir", diff --git a/i18n/french.js b/i18n/french.js index c2ee0dd4..93e25185 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -10,11 +10,13 @@ const cli = { successfully_written_json: tS`Ecriture du fichier de résultats réalisée avec succès ici : ${0}`, http_server_started: "Serveur HTTP démarré sur :", missingEnv: tS`La variable d'environnement ${0} est manquante!`, + stat: tS`${0} ${1} en ${2}`, commands: { option_depth: "Niveau de profondeur de dépendances maximum à aller chercher", option_output: "Nom de sortie du fichier json", option_silent: "Activer le mode silencieux qui désactive les spinners du CLI", option_contacts: "Liste des contacts à mettre en évidence", + option_verbose: "Définir le niveau de log CLI à verbeux, ce qui amènera la CLI à générer des logs plus détaillés.", strategy: "Source de vulnérabilités à utiliser", cwd: { desc: "Démarre une analyse de sécurité sur le dossier courant", diff --git a/i18n/turkish.js b/i18n/turkish.js index fadb9aaa..a9f0303e 100644 --- a/i18n/turkish.js +++ b/i18n/turkish.js @@ -10,11 +10,13 @@ const cli = { successfully_written_json: tS`Sonuç dosyası başarıyla yazıldı: ${0}`, http_server_started: "HTTP Sunucusu başlatıldı:", missingEnv: tS`${0} ortam değişkeni eksik!`, + stat: tS`${0} ${1} içinde ${2}`, commands: { option_depth: "Getirilecek maksimum bağımlılık derinliği", option_output: "JSON dosyası çıktı adı", option_silent: "CLI döndürücülerini devre dışı bırakan sessiz modu etkinleştir", option_contacts: "Vurgulanacak kişilerin listesi", + option_verbose: "CLI'nin log seviyesini verbose olarak ayarlar, bu da CLI'nin daha ayrıntılı loglar üretmesine neden olur.", strategy: "Kullanılacak güvenlik açığı kaynağı", cwd: { desc: "Geçerli çalışma dizininde güvenlik analizi çalıştır", diff --git a/package.json b/package.json index 5f8d844f..1468b097 100644 --- a/package.json +++ b/package.json @@ -95,12 +95,12 @@ "@nodesecure/i18n": "^4.0.2", "@nodesecure/js-x-ray": "^11.0.1", "@nodesecure/licenses-conformance": "^2.1.0", + "@nodesecure/mama": "2.0.2", "@nodesecure/npm-registry-sdk": "^4.4.0", "@nodesecure/ossf-scorecard-sdk": "^3.2.1", "@nodesecure/rc": "^5.0.0", "@nodesecure/report": "4.1.1", - "@nodesecure/scanner": "9.0.0", - "@nodesecure/mama": "2.0.2", + "@nodesecure/scanner": "10.2.0", "@nodesecure/server": "1.0.0", "@nodesecure/utils": "^2.2.0", "@nodesecure/vulnera": "^2.0.1", @@ -108,7 +108,7 @@ "@topcli/cliui": "^1.1.0", "@topcli/pretty-json": "^1.0.0", "@topcli/prompts": "^2.0.0", - "@topcli/spinner": "^4.0.0", + "@topcli/spinner": "4.2.1", "filenamify": "^7.0.0", "highlightjs-line-numbers.js": "^2.8.0", "ini": "^6.0.0", diff --git a/src/commands/scanner.js b/src/commands/scanner.js index 46147f2d..c908b6bd 100644 --- a/src/commands/scanner.js +++ b/src/commands/scanner.js @@ -65,13 +65,16 @@ export async function cwd(options) { full, vulnerabilityStrategy, silent, - contacts + contacts, + verbose } = options; - const payload = await scanner.cwd( + const payload = await scanner.workingDir( process.cwd(), - { maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy, highlight: - { contacts: parseContacts(contacts) } }, + { + maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy, highlight: + { contacts: parseContacts(contacts) }, isVerbose: verbose + }, initLogger(void 0, !silent) ); @@ -79,7 +82,7 @@ export async function cwd(options) { } export async function from(spec, options) { - const { depth: maxDepth = Infinity, output, silent, contacts, vulnerabilityStrategy } = options; + const { depth: maxDepth = Infinity, output, silent, contacts, vulnerabilityStrategy, verbose } = options; const payload = await scanner.from( spec, @@ -88,7 +91,8 @@ export async function from(spec, options) { vulnerabilityStrategy, highlight: { contacts: parseContacts(contacts) - } + }, + isVerbose: verbose }, initLogger(spec, !silent) ); @@ -96,12 +100,14 @@ export async function from(spec, options) { return await logAndWrite(payload, output); } +const spinners = []; + function initLogger(spec, verbose = true) { const spinner = { - walkTree: new Spinner({ verbose }), - tarball: new Spinner({ verbose }), - registry: new Spinner({ verbose }), - fetchManifest: new Spinner({ verbose }), + walkTree: buildSpinner(verbose), + tarball: buildSpinner(verbose), + registry: buildSpinner(verbose), + fetchManifest: buildSpinner(verbose), i18n: { start: { fetchManifest: "cli.commands.from.searching", @@ -122,6 +128,13 @@ function initLogger(spec, verbose = true) { } }; + function buildSpinner(verbose) { + const spinner = new Spinner({ verbose }); + spinners.push(spinner); + + return spinner; + } + const logger = new scanner.Logger(); logger.on("start", (eventName) => { if (!(eventName in spinner)) { @@ -154,27 +167,62 @@ function initLogger(spec, verbose = true) { const spin = spinner[eventName]; const tokenName = spinner.i18n.end[eventName]; - const execTime = kleur.cyan().bold(ms(Number(spin.elapsedTime.toFixed(2)))); + const execTime = kleur.cyan().bold(formatMs(spin.elapsedTime)); if (eventName === "walkTree") { spin.succeed(kleur.white().bold( i18n.getTokenSync(tokenName, kleur.yellow().bold(i18n.getTokenSync("depWalker.dep_tree")), execTime))); + spin.succeeded = true; } else if (eventName === "registry") { spin.succeed(kleur.white().bold(i18n.getTokenSync(tokenName))); + spin.succeeded = true; } else if (eventName === "tarball") { spin.succeed(kleur.white().bold(i18n.getTokenSync(tokenName, kleur.green().bold(logger.count("walkTree")), execTime))); + spin.succeeded = true; } else if (eventName === "fetchManifest") { spin.succeed(kleur.white().bold(i18n.getTokenSync(tokenName, kleur.green().bold(spec), execTime))); + spin.succeeded = true; console.log(""); } }); + logger.on("stat", (stat) => { + stopSpinners(); + console.log(kleur.bold.white( + i18n.getTokenSync("cli.stat", + kleur.blue().bold("verbose"), + stat.name, + kleur.cyan().bold(formatMs(stat.executionTime)) + ))); + startSpinners(); + }); + return logger; } +function formatMs(time) { + return ms(Number(time.toFixed(2))); +} + +function stopSpinners() { + spinners.forEach((spinner) => { + if (!spinner.succeeded) { + spinner.stop(); + } + }); +} + +function startSpinners() { + spinners.forEach((spinner) => { + if (!spinner.succeeded) { + spinner.start(); + } + }); +} + async function logAndWrite( /** @type {import("@nodesecure/scanner").Payload} */ payload, diff --git a/workspaces/cache/package.json b/workspaces/cache/package.json index 01918028..ac599594 100644 --- a/workspaces/cache/package.json +++ b/workspaces/cache/package.json @@ -23,7 +23,7 @@ "dependencies": { "@nodesecure/flags": "3.0.3", "@nodesecure/js-x-ray": "11.1.0", - "@nodesecure/scanner": "9.0.0", + "@nodesecure/scanner": "10.2.0", "cacache": "20.0.3" } } diff --git a/workspaces/server/package.json b/workspaces/server/package.json index c6f1b4ea..53e02ea0 100644 --- a/workspaces/server/package.json +++ b/workspaces/server/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@nodesecure/cache": "1.0.0", + "@nodesecure/scanner": "10.2.0", "cacache": "20.0.3", "chokidar": "5.0.0", "find-my-way": "9.3.0", diff --git a/workspaces/vis-network/package.json b/workspaces/vis-network/package.json index c921cbcf..3a82ea64 100644 --- a/workspaces/vis-network/package.json +++ b/workspaces/vis-network/package.json @@ -30,6 +30,6 @@ }, "devDependencies": { "@nodesecure/flags": "^3.0.3", - "@nodesecure/scanner": "9.0.0" + "@nodesecure/scanner": "10.2.0" } }