Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 1x 1x 1x 1x 20x 18x 16x 15x 4x 3x 2x 1x 11x 11x 1x 10x 10x 10x 10x 10x 9x 9x 9x 9x 9x 9x 9x 9x 10x 10x 9x 9x 9x 6x 6x 5x 5x 5x 5x 2x 5x 10x 10x 10x 10x 10x 10x 10x 10x 10x | // Copyright 2026 ForgeKit Contributors
// SPDX-License-Identifier: Apache-2.0
// https://github.com/SubhanshuMG/ForgeKit
import * as fs from 'fs';
import * as path from 'path';
import { spawnSync } from 'child_process';
import { AuditResult, OutdatedPackage } from '../types';
export function detectPackageManager(projectPath: string): string | null {
if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';
if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) return 'yarn';
if (fs.existsSync(path.join(projectPath, 'package-lock.json'))) return 'npm';
if (fs.existsSync(path.join(projectPath, 'package.json'))) return 'npm';
if (fs.existsSync(path.join(projectPath, 'Pipfile'))) return 'pip';
if (fs.existsSync(path.join(projectPath, 'requirements.txt'))) return 'pip';
return null;
}
export async function runAudit(projectPath: string): Promise<AuditResult> {
const pm = detectPackageManager(projectPath);
if (!pm) {
throw new Error('No recognized package manager found. Ensure package.json, Pipfile, or requirements.txt exists.');
}
const vulnerabilities = { critical: 0, high: 0, moderate: 0, low: 0, total: 0 };
const outdated: OutdatedPackage[] = [];
if (pm === 'npm' || pm === 'yarn' || pm === 'pnpm') {
// Run npm audit
try {
const auditResult = spawnSync('npm', ['audit', '--json'], {
cwd: projectPath,
encoding: 'utf-8',
timeout: 30000,
});
if (auditResult.stdout) {
const data = JSON.parse(auditResult.stdout);
const vulns = data.metadata?.vulnerabilities || {};
vulnerabilities.critical = vulns.critical || 0;
vulnerabilities.high = vulns.high || 0;
vulnerabilities.moderate = vulns.moderate || 0;
vulnerabilities.low = vulns.low || 0;
vulnerabilities.total = vulns.total || (vulnerabilities.critical + vulnerabilities.high + vulnerabilities.moderate + vulnerabilities.low);
}
} catch {
// npm audit failed - continue with zero vulns
}
// Run npm outdated
try {
const outdatedResult = spawnSync('npm', ['outdated', '--json'], {
cwd: projectPath,
encoding: 'utf-8',
timeout: 30000,
});
if (outdatedResult.stdout) {
const data = JSON.parse(outdatedResult.stdout);
for (const [name, info] of Object.entries(data)) {
const pkg = info as { current: string; wanted: string; latest: string };
if (!pkg.current || !pkg.latest) continue;
const currentParts = pkg.current.split('.');
const latestParts = pkg.latest.split('.');
let updateType: 'major' | 'minor' | 'patch' = 'patch';
if (currentParts[0] !== latestParts[0]) updateType = 'major';
else if (currentParts[1] !== latestParts[1]) updateType = 'minor';
outdated.push({
name,
current: pkg.current,
wanted: pkg.wanted || pkg.current,
latest: pkg.latest,
type: updateType,
});
}
}
} catch {
// npm outdated failed - continue with empty list
}
}
// Calculate score: start at 100, deduct for issues
let score = 100;
score -= vulnerabilities.critical * 20;
score -= vulnerabilities.high * 10;
score -= vulnerabilities.moderate * 3;
score -= vulnerabilities.low * 1;
score -= outdated.filter(p => p.type === 'major').length * 5;
score -= outdated.filter(p => p.type === 'minor').length * 2;
score = Math.max(0, Math.min(100, score));
return {
vulnerabilities,
outdated,
score,
packageManager: pm,
};
}
|