UNPKG

am-i-secure

Version:

A CLI tool to detect malicious npm packages in your project dependencies

201 lines (164 loc) 5.86 kB
const chalk = require('chalk'); const Table = require('cli-table3'); const path = require('path'); class Logger { constructor() { this.verboseMode = false; this.jsonOutput = false; this.projectDir = null; this.fullPaths = false; } setVerbose(verbose) { this.verboseMode = verbose; } setJsonOutput(jsonOutput) { this.jsonOutput = jsonOutput; } setProjectDir(projectDir) { this.projectDir = projectDir; } setFullPaths(fullPaths) { this.fullPaths = fullPaths; } info(message) { if (!this.jsonOutput) { console.log(chalk.blue('ℹ'), message); } } success(message) { if (!this.jsonOutput) { console.log(chalk.green(message)); } } warn(message) { if (!this.jsonOutput) { console.log(chalk.yellow('⚠'), message); } } error(message) { if (!this.jsonOutput) { console.error(chalk.red(message)); } } verbose(message) { if (this.verboseMode && !this.jsonOutput) { console.log(chalk.gray('→'), message); } } displayResults(results) { if (this.jsonOutput) { return; // JSON output is handled in the main CLI } console.log(); this.displayScanSummary(results.summary); if (results.findings.length > 0) { console.log(); this.displayFindings(results.findings); } } displayScanSummary(summary) { console.log(chalk.bold('📊 Scan Summary:')); console.log(); const summaryTable = new Table({ head: ['Metric', 'Count'], colWidths: [30, 10] }); summaryTable.push( ['Lock files scanned', summary.lockFilesScanned], ['node_modules scanned', summary.nodeModulesScanned ? 'Yes' : 'No'], ['Total packages checked', summary.totalPackagesChecked], ['Malicious packages found', chalk.red(summary.maliciousPackagesFound)] ); console.log(summaryTable.toString()); } displayFindings(findings) { console.log(chalk.bold.red('🚨 Malicious Packages Found:')); console.log(); const findingsTable = new Table({ head: ['Package', 'Version', 'Source', 'Introduced By', 'File Path'], colWidths: this.fullPaths ? [25, 12, 15, 20, 120] : [25, 12, 15, 20, 60] }); findings.forEach(finding => { findingsTable.push([ chalk.red(finding.packageName), chalk.red(finding.version), this.formatSource(finding.source), finding.introducedBy || 'Direct dependency', this.formatFilePath(finding.filePath) ]); }); console.log(findingsTable.toString()); // Add helpful tip about clickable paths if (!this.fullPaths) { console.log(); console.log(chalk.gray('💡 Tip: File paths should be clickable in most terminals. Use --full-paths for complete paths.')); } } formatSource(source) { const sourceColors = { 'package-lock.json': chalk.blue, 'yarn.lock': chalk.cyan, 'pnpm-lock.yaml': chalk.magenta, 'node_modules': chalk.yellow }; const color = sourceColors[source] || chalk.white; return color(source); } formatFilePath(filePath, maxLength = 55) { if (!filePath) { return 'Unknown'; } // Get the absolute path for clickability const absolutePath = path.resolve(filePath); // If full paths are requested, return the complete absolute path if (this.fullPaths) { return absolutePath; } // For clickability, show the absolute path but truncate it intelligently // Most terminals support clicking on absolute paths if (absolutePath.length <= maxLength) { return absolutePath; } // Smart truncation for absolute paths that preserves clickability const segments = absolutePath.split('/'); if (segments.length > 4) { const fileName = segments[segments.length - 1]; const parentDir = segments[segments.length - 2]; const start = segments.slice(0, 3).join('/'); // Keep /Users/username const truncated = `${start}/.../${parentDir}/${fileName}`; if (truncated.length <= maxLength) { return truncated; } } // Fallback: create a relative path display version let displayPath = filePath; if (this.projectDir) { const relativePath = path.relative(this.projectDir, filePath); // Add ./ prefix for clarity if it's a relative path within the project if (!relativePath.startsWith('..')) { displayPath = `./${relativePath}`; } else { displayPath = relativePath; } } if (displayPath.length <= maxLength) { return displayPath; } // Final fallback: truncate the relative path const start = displayPath.substring(0, 15); const end = displayPath.substring(displayPath.length - (maxLength - 18)); return `${start}...${end}`; } displayProgress(message) { if (this.verboseMode && !this.jsonOutput) { process.stdout.write(chalk.gray(`→ ${message}...\r`)); } } clearProgress() { if (this.verboseMode && !this.jsonOutput) { process.stdout.clearLine(); process.stdout.cursorTo(0); } } } module.exports = { Logger };