Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ For complete details on each command, refer to the following documents:
- [`config`](./docs/cli/config.md)
- [`cache`](./docs/cli/cache.md)
- [`extract integrity`](./docs/cli/extract-integrity.md)
- [`stats`](./docs/cli/stats.md)

Each link provides access to the full documentation for the command, including additional details, options, and usage examples.

Expand Down
6 changes: 6 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ prog
.example("nsecure extract integrity lodash@^4.1.2")
.action(commands.extractIntegrity.main);

prog
.command("stats")
.describe(i18n.getTokenSync("cli.commands.stats.desc"))
.example("nsecure stats")
.action(commands.stats.main);

prog.parse(process.argv);

function defaultScannerCommand(name, options = {}) {
Expand Down
Binary file added docs/cli/images/stats.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions docs/cli/stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## 📝 Command `stats`

The `stats` displays the statistics of the last performed scan such as :

- The total execution time of the scan
- The total number of API calls made
- every stats about the API calls made
- The total number of errors encountered
- every error encountered

<p align="center">
<img src="./images/stats.PNG">
</p>

## 📜 Syntax

```bash
$ nsecure stats
```
9 changes: 8 additions & 1 deletion i18n/arabic.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const cli = {
successfully_written_json: tS`تم كتابة ملف النتائج بنجاح في: ${0}`,
http_server_started: "تم تشغيل خادم HTTP على:",
missingEnv: tS`متغير البيئة ${0} مفقود!`,
stat: tS`${0} ${1} في ${2}`,
stat: tS`${0}${1} في ${2}`,
error: {
name: tS`اسم ${0}: ${1}`,
message: tS`الرسالة: ${0}`,
Expand Down Expand Up @@ -88,6 +88,13 @@ const cli = {
missingSpecVersion: tS`يجب تحديد إصدار لحزمة '${0}'.`,
invalidSpec: tS`مواصفات الحزمة '${0}' غير صالحة.`,
specNotFound: tS`لم يتم العثور على مواصفات الحزمة '${0}' في سجل npm.`
},
stats: {
desc: "عرض إحصائيات المسح.",
elapsed: tS`مدة المسح: ${0}`,
stats: tS`عدد استدعاءات API: ${0}`,
error: "يجب إجراء مسح قبل عرض الإحصائيات.",
errors: tS`عدد الأخطاء: ${0}`
}
},
startHttp: {
Expand Down
9 changes: 8 additions & 1 deletion i18n/english.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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}`,
stat: tS`${0}${1} in ${2}`,
error: {
name: tS`${0} name: ${1}`,
message: tS`Message: ${0}`,
Expand Down Expand Up @@ -90,6 +90,13 @@ const cli = {
missingSpecVersion: tS`You must specify a version for '${0}' package.`,
invalidSpec: tS`The package spec '${0}' is invalid.`,
specNotFound: tS`The package spec '${0}' could not be found from the npm registry.`
},
stats: {
desc: "Display the stats of a scan.",
elapsed: tS`Scan duration: ${0}`,
stats: tS`API calls count: ${0}`,
error: "A scan must be performed before displaying stats.",
errors: tS`Error count: ${0}`
}
},
startHttp: {
Expand Down
9 changes: 8 additions & 1 deletion i18n/french.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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}`,
stat: tS`${0}${1} en ${2}`,
error: {
name: tS`Nom ${0}: ${1}`,
message: tS`Message: ${0}`,
Expand Down Expand Up @@ -90,6 +90,13 @@ const cli = {
missingSpecVersion: tS`Vous devez spécifier une version pour le package '${0}'.`,
invalidSpec: tS`La spécification '${0}' est invalide.`,
specNotFound: tS`La spécification '${0}' n'a pas pu être trouvée dans le registre npm.`
},
stats: {
desc: "Afficher les statistiques d'un scan.",
elapsed: tS`Durée du scan: ${0}`,
stats: tS`Nombre d'appels API: ${0}`,
error: "Un scan doit être effectué avant d'afficher les statistiques.",
errors: tS`Nombre d'erreurs: ${0}`
}
},
startHttp: {
Expand Down
9 changes: 8 additions & 1 deletion i18n/turkish.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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}`,
stat: tS`${0}${1} içinde ${2}`,
error: {
name: tS`${0} adı: ${1}`,
message: tS`Mesaj: ${0}`,
Expand Down Expand Up @@ -90,6 +90,13 @@ const cli = {
missingSpecVersion: tS`'${0}' paketi için bir sürüm belirtmelisiniz.`,
invalidSpec: tS`'${0}' paket özelliği geçersiz.`,
specNotFound: tS`'${0}' paket özelliği npm kayıt defterinde bulunamadı.`
},
stats: {
desc: "Bir taramanın istatistiklerini görüntüle.",
elapsed: tS`Tarama süresi: ${0}`,
stats: tS`API çağrı sayısı: ${0}`,
error: "İstatistikleri görüntülemeden önce bir tarama yapılmalıdır.",
errors: tS`Hata sayısı: ${0}`
}
},
startHttp: {
Expand Down
1 change: 1 addition & 0 deletions src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * as scorecard from "./scorecard.js";
export * as report from "./report.js";
export * as cache from "./cache.js";
export * as extractIntegrity from "./extract-integrity.js";
export * as stats from "./stats.js";
78 changes: 78 additions & 0 deletions src/commands/loggers/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Import Third-party Dependencies
import * as i18n from "@nodesecure/i18n";
import ms from "ms";

// Import Internal Dependencies
import kleur from "../../utils/styleText.js";

export function log(token, ...args) {
console.log(kleur.white().bold(i18n.getTokenSync(token, ...args)));
}

export function logError(token, ...args) {
console.log(kleur.red().bold(i18n.getTokenSync(token, ...args)));
}

export function logScannerStat(stat, isVerbose = true) {
console.log(kleur.bold.white(
i18n.getTokenSync("cli.stat",
isVerbose ? kleur.blue().bold("verbose ") : "",
stat.name,
colorExecutionTime(stat.executionTime)
)));
}

export function logScannerError(error, phase) {
console.log(kleur.bold.white(
i18n.getTokenSync("cli.error.name",
kleur.red().bold("error"),
error.name
)));

if (error.message) {
console.log(i18n.getTokenSync("cli.error.message",
error.message
));
}

if (phase) {
console.log(i18n.getTokenSync("cli.error.phase",
phase
));
}

if (error.statusCode) {
console.log(i18n.getTokenSync("cli.error.statusCode",
error.statusCode
));
}

console.log(i18n.getTokenSync("cli.error.executionTime",
colorExecutionTime(error.executionTime)
));

if (error.stack) {
console.log(i18n.getTokenSync("cli.error.stack",
error.stack
));
}
}

export function formatMs(time) {
return ms(Number(time.toFixed(2)));
}

function colorExecutionTime(timeMs) {
const formatted = formatMs(timeMs);
if (timeMs <= 1_000) {
return kleur.green().bold(formatted);
}
else if (timeMs <= 5_000) {
return kleur.cyan().bold(formatted);
}
else if (timeMs <= 30_000) {
return kleur.yellow().bold(formatted);
}

return kleur.red().bold(formatted);
}
64 changes: 3 additions & 61 deletions src/commands/scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import events from "node:events";
import semver from "semver";
import filenamify from "filenamify";
import { Spinner } from "@topcli/spinner";
import ms from "ms";
import * as i18n from "@nodesecure/i18n";
import * as scanner from "@nodesecure/scanner";

// Import Internal Dependencies
import kleur from "../utils/styleText.js";
import * as http from "./http.js";
import { logScannerStat, logScannerError, formatMs } from "./loggers/logger.js";
import { parseContacts } from "./parsers/contacts.js";

export async function auto(spec, options) {
Expand Down Expand Up @@ -192,77 +192,19 @@ function initLogger(spec, verbose = true) {

logger.on("stat", (stat) => {
stopSpinners();
console.log(kleur.bold.white(
i18n.getTokenSync("cli.stat",
kleur.blue().bold("verbose"),
stat.name,
colorExecutionTime(stat.executionTime)
)));
logScannerStat(stat);
startSpinners();
});

logger.on("error", (error, phase) => {
stopSpinners();

console.log(kleur.bold.white(
i18n.getTokenSync("cli.error.name",
kleur.red().bold("error"),
error.name
)));

if (error.message) {
console.log(i18n.getTokenSync("cli.error.message",
error.message
));
}

if (phase) {
console.log(i18n.getTokenSync("cli.error.phase",
phase
));
}

if (error.statusCode) {
console.log(i18n.getTokenSync("cli.error.statusCode",
error.statusCode
));
}

console.log(i18n.getTokenSync("cli.error.executionTime",
kleur.cyan().bold(formatMs(error.executionTime))
));

if (error.stack) {
console.log(i18n.getTokenSync("cli.error.stack",
error.stack
));
}

logScannerError(error, phase);
startSpinners();
});

return logger;
}

function formatMs(time) {
return ms(Number(time.toFixed(2)));
}

function colorExecutionTime(timeMs) {
const formatted = formatMs(timeMs);
if (timeMs <= 1_000) {
return kleur.green().bold(formatted);
}
else if (timeMs <= 5_000) {
return kleur.cyan().bold(formatted);
}
else if (timeMs <= 30_000) {
return kleur.yellow().bold(formatted);
}

return kleur.red().bold(formatted);
}

function stopSpinners() {
spinners.forEach((spinner) => {
if (!spinner.succeeded) {
Expand Down
42 changes: 42 additions & 0 deletions src/commands/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Import Node.js Dependencies
import { readFile } from "node:fs/promises";
import path from "node:path";

// Import Internal Dependencies
import { logScannerStat, logScannerError, log, logError, formatMs } from "./loggers/logger.js";

export async function main(options) {
const { getScanResult = getScanFromFile, logger = {
logScannerStat,
logScannerError,
log,
logError
} } = options;
try {
const scanResult = await getScanResult();
const { metadata } = scanResult;

logger.log("cli.commands.stats.elapsed", formatMs(metadata.executionTime));
logger.log("cli.commands.stats.stats", metadata.apiCallsCount);
metadata.apiCalls.forEach((call) => {
logger.logScannerStat(call, false);
});
if (metadata.errorCount === 0) {
return;
}
logger.log("cli.commands.stats.errors", metadata.errorCount);
metadata.errors.forEach((error) => {
logger.logScannerError(error);
});
}
catch {
logger.logError("cli.commands.stats.error");
}
}

async function getScanFromFile() {
const projectRootDir = path.join(import.meta.dirname, "..", "..");
const filePath = path.join(projectRootDir, "nsecure-result.json");

return JSON.parse(await readFile(filePath, "utf8"));
}
Loading
Loading