import { promisify } from 'util'; import { Command } from 'commander'; import * as licenseChecker from 'license-checker'; export interface BomAgentOptions {} export interface BomPackage { name: string; version: string; license: string; risk: 'Low' | 'Medium' | 'High' | 'Unknown'; } export interface BomAgentResult { packages: BomPackage[]; } /** * Génère le Bill Of Materials (liste des packages et licences). */ /** * Exécute license-checker pour récupérer la liste des dépendances et licences, * et évalue le risque associé à chaque licence. */ export async function bomAgent( _opts?: BomAgentOptions, ): Promise { // Programmatic retrieval via license-checker const initChecker = promisify(licenseChecker.init); const data = (await initChecker({ start: process.cwd(), production: true, json: true, })) as Record; const results: BomPackage[] = []; // Risk evaluation function const evaluateRisk = (lic: string): BomPackage['risk'] => { const l = lic.toLowerCase(); // Copyleft strong licenses if (l.includes('agpl') || (l.includes('gpl') && !l.includes('lgpl'))) return 'High'; // Lesser copyleft if (l.includes('lgpl')) return 'Medium'; // Permissive licenses if ( l.includes('mit') || l.includes('bsd') || l.includes('apache') || l.includes('isc') ) { return 'Low'; } return 'Unknown'; }; for (const key of Object.keys(data)) { const info = data[key] as { licenses?: string | string[] }; // key format: 'name@version' const at = key.lastIndexOf('@'); const name = key.substring(0, at); const version = key.substring(at + 1); let license = ''; if (Array.isArray(info.licenses)) { license = info.licenses.join(', '); } else if (typeof info.licenses === 'string') { license = info.licenses; } else { license = String(info.licenses || 'Unknown'); } const risk = evaluateRisk(license); results.push({ name, version, license, risk }); } return { packages: results }; } export const cli = { command: 'qualimetrie:bom', description: 'Génère le Bill Of Materials des licences', builder: (cmd: Command) => cmd, handler: async (_opts: unknown) => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { bomAgent: run } = require('./bomAgent'); const res: BomAgentResult = await run(); // Report header const date = new Date().toISOString().split('T')[0]; console.log('# 📜 Bill Of Materials Report'); console.log(`Date: ${date}`); // Executive summary const total = res.packages.length; const counts = res.packages.reduce( (acc, pkg) => { acc[pkg.risk] = (acc[pkg.risk] || 0) + 1; return acc; }, {} as Record, ); console.log('## 🔎 Executive Summary'); console.log(`- Total packages: ${total}`); console.log(`- High risk: ${counts['High'] || 0}`); console.log(`- Medium risk: ${counts['Medium'] || 0}`); console.log(`- Low risk: ${counts['Low'] || 0}`); console.log(`- Unknown risk: ${counts['Unknown'] || 0}\n`); // Detailed table console.log('## 📜 Details'); console.log('| Package | Version | License | Risk |'); console.log('|---|---|---|---|'); res.packages.forEach((pkg) => { console.log(`| ${pkg.name} | ${pkg.version} | ${pkg.license} | ${pkg.risk} |`); }); }, };