UNPKG

depsweep

Version:

🌱 Automated intelligent dependency cleanup with environmental impact reporting

869 lines (868 loc) • 34.1 kB
import { execSync, spawn } from "node:child_process"; import { readdirSync, statSync } from "node:fs"; import * as fs from "node:fs/promises"; import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import v8 from "node:v8"; import { parse } from "@babel/parser"; import traverse from "@babel/traverse"; import chalk from "chalk"; import CliTable from "cli-table3"; import { isBinaryFileSync } from "isbinaryfile"; import micromatch from "micromatch"; import fetch from "node-fetch"; import shellEscape from "shell-escape"; import { FILE_PATTERNS, DEPENDENCY_PATTERNS, PACKAGE_MANAGERS, RAW_CONTENT_PATTERNS, ENVIRONMENTAL_CONSTANTS, } from "./constants.js"; import { calculateComprehensiveEnvironmentalImpact, validateEnvironmentalCalculations, } from "./enhanced-environmental-calculations.js"; import { customSort } from "./index.js"; export function isConfigFile(filePath) { if (!filePath || typeof filePath !== "string") { return false; } const filename = path.basename(filePath).toLowerCase(); return (filename.includes("config") || filename.startsWith(".") || filename === FILE_PATTERNS.PACKAGE_JSON || FILE_PATTERNS.CONFIG_REGEX.test(filename)); } export async function parseConfigFile(filePath) { const extension = path.extname(filePath).toLowerCase(); const content = await fs.readFile(filePath, "utf8"); try { switch (extension) { case ".json": { return JSON.parse(content); } case ".yaml": case ".yml": { const yaml = await import("yaml").catch(() => null); return yaml ? yaml.parse(content) : content; } case ".js": case ".cjs": case ".mjs": { return content; } default: { try { return JSON.parse(content); } catch { return content; } } } } catch { return content; } } const traverseFunction = (traverse.default || traverse); export async function isTypePackageUsed(dependency, installedPackages, unusedDependencies, context, sourceFiles) { if (!dependency.startsWith(DEPENDENCY_PATTERNS.TYPES_PREFIX)) { return { isUsed: false }; } const correspondingPackage = dependency .replace(/^@types\//, "") .replaceAll("__", "/"); const normalizedPackage = correspondingPackage.includes("/") ? `@${correspondingPackage}` : correspondingPackage; const supportedPackage = installedPackages.find((package_) => package_ === normalizedPackage); if (supportedPackage) { for (const file of sourceFiles) { if (await isDependencyUsedInFile(supportedPackage, file, context)) { return { isUsed: true, supportedPackage }; } } } for (const package_ of installedPackages) { try { const packageJsonPath = require.resolve(`${package_}/package.json`, { paths: [process.cwd()], }); const packageJsonBuffer = await fs.readFile(packageJsonPath); const packageJson = JSON.parse(packageJsonBuffer.toString("utf8")); if (packageJson.peerDependencies?.[dependency]) { return { isUsed: true, supportedPackage: package_ }; } } catch { } } return { isUsed: false }; } export function scanForDependency(object, dependency) { if (typeof object === "string") { const matchers = generatePatternMatcher(dependency); return matchers.some((pattern) => pattern.test(object)); } if (Array.isArray(object)) { return object.some((item) => scanForDependency(item, dependency)); } if (object && typeof object === "object") { return Object.values(object).some((value) => scanForDependency(value, dependency)); } return false; } export async function isDependencyUsedInFile(dependency, filePath, context) { if (path.basename(filePath) === FILE_PATTERNS.PACKAGE_JSON && context.configs?.["package.json"] && scanForDependency(context.configs["package.json"], dependency)) { return true; } const configKey = path.relative(path.dirname(filePath), filePath); const config = context.configs?.[configKey]; if (config) { if (typeof config === "string") { if (config.includes(dependency)) { return true; } } else if (scanForDependency(config, dependency)) { return true; } } if (context.scripts) { for (const script of Object.values(context.scripts)) { const scriptParts = script.split(" "); if (scriptParts.includes(dependency)) { return true; } } } try { if (isBinaryFileSync(filePath)) { return false; } const content = await fs.readFile(filePath, "utf8"); const dynamicImportRegex = new RegExp(`${DEPENDENCY_PATTERNS.DYNAMIC_IMPORT_BASE}${dependency.replaceAll(/[/@-]/g, "[/@-]")}${DEPENDENCY_PATTERNS.DYNAMIC_IMPORT_END}`, "i"); if (dynamicImportRegex.test(content)) { return true; } try { const ast = parse(content, { sourceType: "unambiguous", plugins: [ "typescript", "jsx", "decorators-legacy", "classProperties", "dynamicImport", "exportDefaultFrom", "exportNamespaceFrom", "importMeta", ], }); let isUsed = false; traverseFunction(ast, { ImportDeclaration(importPath) { const importSource = importPath.node.source.value; if (matchesDependency(importSource, dependency)) { isUsed = true; importPath.stop(); } }, CallExpression(importPath) { if (importPath.node.callee.type === "Identifier" && importPath.node.callee.name === "require" && importPath.node.arguments[0]?.type === "StringLiteral" && matchesDependency(importPath.node.arguments[0].value, dependency)) { isUsed = true; importPath.stop(); } }, TSImportType(importPath) { const importSource = importPath.node.argument.value; if (matchesDependency(importSource, dependency)) { isUsed = true; importPath.stop(); } }, TSExternalModuleReference(importPath) { const importSource = importPath.node.expression.value; if (matchesDependency(importSource, dependency)) { isUsed = true; importPath.stop(); } }, }); if (isUsed) return true; for (const [base, patterns] of RAW_CONTENT_PATTERNS.entries()) { if (dependency.startsWith(base) && patterns.some((pattern) => micromatch.isMatch(dependency, pattern))) { const searchPattern = new RegExp(`\\b${dependency.replaceAll(/[/@-]/g, "[/@-]")}\\b`, "i"); if (searchPattern.test(content)) { return true; } } } } catch { } for (const [base, patterns] of RAW_CONTENT_PATTERNS.entries()) { if (dependency.startsWith(base) && patterns.some((pattern) => micromatch.isMatch(dependency, pattern))) { const searchPattern = new RegExp(`\\b${dependency.replaceAll(/[/@-]/g, "[/@-]")}\\b`, "i"); if (searchPattern.test(content)) { return true; } } } } catch { } return false; } export function getMemoryUsage() { const heapStats = v8.getHeapStatistics(); return { used: heapStats.used_heap_size, total: heapStats.heap_size_limit, }; } const COMMON_PATTERNS = [ { type: "exact", match: "" }, { type: "prefix", match: "@" }, { type: "prefix", match: "@types/" }, { type: "prefix", match: "@storybook/" }, { type: "prefix", match: "@testing-library/" }, { type: "suffix", match: "config", variations: ["rc", "settings", "configuration", "setup", "options"], }, { type: "suffix", match: "plugin", variations: ["plugins", "extension", "extensions", "addon", "addons"], }, { type: "suffix", match: "preset", variations: ["presets", "recommended", "standard", "defaults"], }, { type: "combined", match: "", variations: ["cli", "core", "utils", "tools", "helper", "helpers"], }, { type: "regex", match: /[/-](react|vue|svelte|angular|node)$/i, }, { type: "regex", match: /[/-](loader|parser|transformer|formatter|linter|compiler)s?$/i, }, ]; export function generatePatternMatcher(dependency) { const patterns = []; const escapedDep = dependency.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`); for (const pattern of COMMON_PATTERNS) { switch (pattern.type) { case "exact": { patterns.push(new RegExp(`^${escapedDep}$`)); break; } case "prefix": { patterns.push(new RegExp(`^${pattern.match}${escapedDep}(/.*)?$`)); break; } case "suffix": { const suffixes = [pattern.match, ...(pattern.variations || [])]; for (const suffix of suffixes) { patterns.push(new RegExp(`^${escapedDep}[-./]${suffix}$`), new RegExp(`^${escapedDep}[-./]${suffix}s$`)); } break; } case "combined": { const parts = [pattern.match, ...(pattern.variations || [])]; for (const part of parts) { patterns.push(new RegExp(`^${escapedDep}[-./]${part}$`), new RegExp(`^${part}[-./]${escapedDep}$`)); } break; } case "regex": { if (pattern.match instanceof RegExp) { patterns.push(new RegExp(`^${escapedDep}${pattern.match.source}`, pattern.match.flags)); } break; } } } return patterns; } export function matchesDependency(importSource, dependency) { const depWithoutScope = dependency.startsWith("@") ? dependency.split("/")[1] : dependency; const sourceWithoutScope = importSource.startsWith("@") ? importSource.split("/")[1] : importSource; return (importSource === dependency || importSource.startsWith(`${dependency}/`) || sourceWithoutScope === depWithoutScope || sourceWithoutScope.startsWith(`${depWithoutScope}/`) || (dependency.startsWith("@types/") && (importSource === dependency.replace(/^@types\//, "") || importSource.startsWith(`${dependency.replace(/^@types\//, "")}/`)))); } export function processResults(batchResults) { const validResults = []; let errors = 0; for (const result of batchResults) { if (result.status === "fulfilled") { if (result.value.hasError) { errors++; } else if (result.value.result) { validResults.push(result.value.result); } } } return { validResults, errors }; } function getDirectorySize(directory) { let total = 0; const files = readdirSync(directory, { withFileTypes: true }); for (const f of files) { const fullPath = path.join(directory, f.name); total += f.isDirectory() ? getDirectorySize(fullPath) : statSync(fullPath).size; } return total; } export function formatSize(bytes) { if (bytes >= 1e12) { return `${(bytes / 1e12).toFixed(2)} ${chalk.blue("TB")}`; } else if (bytes >= 1e9) { return `${(bytes / 1e9).toFixed(2)} ${chalk.blue("GB")}`; } else if (bytes >= 1e6) { return `${(bytes / 1e6).toFixed(2)} ${chalk.blue("MB")}`; } else if (bytes >= 1e3) { return `${(bytes / 1e3).toFixed(2)} ${chalk.blue("KB")}`; } return `${bytes} ${chalk.blue("Bytes")}`; } export function formatTime(seconds) { if (seconds >= 86_400) { return `${(seconds / 86_400).toFixed(2)} ${chalk.blue("Days")}`; } else if (seconds >= 3600) { return `${(seconds / 3600).toFixed(2)} ${chalk.blue("Hours")}`; } else if (seconds >= 60) { return `${(seconds / 60).toFixed(2)} ${chalk.blue("Minutes")}`; } return `${seconds.toFixed(2)} ${chalk.blue("Seconds")}`; } export function formatNumber(n) { return n.toLocaleString(); } export function safeExecSync(command, options) { if (!Array.isArray(command) || command.length === 0) { throw new Error("Invalid command array"); } const [packageManager, ...arguments_] = command; if (!Object.values(PACKAGE_MANAGERS).includes(packageManager)) { throw new Error(`Invalid package manager: ${packageManager}`); } if (!arguments_.every((argument) => typeof argument === "string" && argument.length > 0)) { throw new Error("Invalid command arguments"); } try { execSync(shellEscape(command), { stdio: options.stdio || "inherit", cwd: options.cwd, timeout: options.timeout ?? 300_000, encoding: "utf8", }); } catch (error) { throw new Error(`Command execution failed: ${error.message}`); } } export async function detectPackageManager(projectDirectory) { if (await fs .access(path.join(projectDirectory, FILE_PATTERNS.YARN_LOCK)) .then(() => true) .catch(() => false)) { return PACKAGE_MANAGERS.YARN; } else if (await fs .access(path.join(projectDirectory, FILE_PATTERNS.PNPM_LOCK)) .then(() => true) .catch(() => false)) { return PACKAGE_MANAGERS.PNPM; } return PACKAGE_MANAGERS.NPM; } export async function createTemporaryPackageJson(package_) { const minimalPackageJson = { name: "depsweep-temp", version: "1.0.0", private: true, dependencies: { [package_]: "*" }, }; const temporaryDirectory = await mkdtemp(path.join(tmpdir(), "depsweep-")); const packageJsonPath = path.join(temporaryDirectory, "package.json"); await writeFile(packageJsonPath, JSON.stringify(minimalPackageJson, null, 2)); return temporaryDirectory; } export async function measurePackageInstallation(packageName) { const metrics = { installTime: 0, diskSpace: 0, errors: [], }; try { const temporaryDirectory = await createTemporaryPackageJson(packageName); const startTime = Date.now(); try { await new Promise((resolve, reject) => { const install = spawn("npm", ["install", "--no-package-lock"], { cwd: temporaryDirectory, stdio: "ignore", }); install.on("close", (code) => { if (code === 0) resolve(); else reject(new Error(`npm install failed with code ${code}`)); }); install.on("error", reject); }); } catch (error) { metrics.errors?.push(`Install error: ${error.message}`); } metrics.installTime = (Date.now() - startTime) / 1000; const nodeModulesPath = path.join(temporaryDirectory, "node_modules"); metrics.diskSpace = getDirectorySize(nodeModulesPath); await fs.rm(temporaryDirectory, { recursive: true, force: true }); } catch (error) { metrics.errors?.push(`Measurement error: ${error.message}`); } return metrics; } export async function getDownloadStatsFromNpm(packageName) { try { const response = await fetch(`https://api.npmjs.org/downloads/point/last-month/${packageName}`); if (!response.ok) { return null; } const data = await response.json(); const downloadData = data; return downloadData.downloads || null; } catch { return null; } } export async function getParentPackageDownloads(packageJsonPath) { try { const packageJsonString = (await fs.readFile(packageJsonPath, "utf8")) || "{}"; const packageJson = JSON.parse(packageJsonString); const { name, repository, homepage } = packageJson; if (!name) return null; const downloads = await getDownloadStatsFromNpm(name); if (!downloads) { console.log(chalk.yellow(`\nUnable to find download stats for '${name}'`)); return null; } return { name, downloads, repository, homepage }; } catch { return null; } } export async function getYearlyDownloads(packageName, months = 12) { const monthlyDownloads = []; const currentDate = new Date(); let startDate = ""; let monthsFetched = 0; for (let index = 0; index < months; index++) { const start = new Date(currentDate.getFullYear(), currentDate.getMonth() - index, 1); const end = new Date(currentDate.getFullYear(), currentDate.getMonth() - index + 1, 0); const [startString] = start.toISOString().split("T"); const [endString] = end.toISOString().split("T"); try { const response = await fetch(`https://api.npmjs.org/downloads/range/${startString}:${endString}/${packageName}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = (await response.json()); if (data.downloads && Array.isArray(data.downloads)) { const monthTotal = data.downloads.reduce((accumulator, dayItem) => accumulator + (dayItem.downloads || 0), 0); monthlyDownloads.push(monthTotal); if (monthTotal > 0 && !startDate) { startDate = startString; } monthsFetched++; } } catch (error) { console.error(`Failed to fetch downloads for ${startString} to ${endString}:`, error); break; } } let lastNonZeroIndex = -1; for (let index = monthlyDownloads.length - 1; index >= 0; index--) { if (monthlyDownloads[index] > 0) { lastNonZeroIndex = index; break; } } if (lastNonZeroIndex === -1) { return null; } monthlyDownloads.splice(lastNonZeroIndex + 1); monthsFetched = monthlyDownloads.length; if (!startDate) { const validMonthsAgo = monthsFetched - 1; const trimmedStart = new Date(currentDate.getFullYear(), currentDate.getMonth() - validMonthsAgo, 1); [startDate] = trimmedStart.toISOString().split("T"); } const totalDownloads = monthlyDownloads.reduce((a, b) => a + b, 0); return { total: totalDownloads, monthsFetched, startDate }; } export function calculateImpactStats(diskSpace, installTime, monthlyDownloads, yearlyData) { const stats = {}; if (!yearlyData) { return stats; } const { total, monthsFetched } = yearlyData; const daysCount = monthsFetched * 30; if (daysCount === 0 || total === 0) { return stats; } const dailyAvg = total / daysCount; const relevantDays = Math.min(30, daysCount); const daySum = dailyAvg * relevantDays; const dayAverage = daySum / relevantDays; stats.day = { downloads: Math.round(dayAverage), diskSpace: diskSpace * dayAverage, installTime: installTime * dayAverage, }; stats.monthly = { downloads: Math.round(dailyAvg * 30), diskSpace: diskSpace * dailyAvg * 30, installTime: installTime * dailyAvg * 30, }; stats[`last_${monthsFetched}_months`] = { downloads: Math.round(dailyAvg * daysCount), diskSpace: diskSpace * dailyAvg * daysCount, installTime: installTime * dailyAvg * daysCount, }; if (monthsFetched >= 12) { const yearlyDays = 12 * 30; stats.yearly = { downloads: Math.round(dailyAvg * yearlyDays), diskSpace: diskSpace * dailyAvg * yearlyDays, installTime: installTime * dailyAvg * yearlyDays, }; } return stats; } export function displayImpactTable(impactData, totalInstallTime, totalDiskSpace) { const table = new CliTable({ head: ["Package", "Install Time", "Disk Space"], colWidths: [29, 25, 25], wordWrap: true, style: { head: ["cyan"], border: ["grey"], }, }); const sortedImpactData = Object.entries(impactData).sort(([a], [b]) => customSort(a, b)); for (const [package_, data] of sortedImpactData) { const numericTime = Number.parseFloat(data.installTime); table.push([package_, formatTime(numericTime), data.diskSpace]); } table.push([ chalk.bold("Total"), chalk.bold(formatTime(totalInstallTime)), chalk.bold(formatSize(totalDiskSpace)), ]); console.log(table.toString()); } export function calculateEnvironmentalImpact(diskSpace, installTime, monthlyDownloads, options = {}) { try { validateInputs(diskSpace, installTime, monthlyDownloads); if (diskSpace === 0 && installTime === 0) { return createZeroEnvironmentalImpact(); } const enhancedImpact = calculateComprehensiveEnvironmentalImpact(diskSpace, installTime, monthlyDownloads, options); const validation = validateEnvironmentalCalculations(enhancedImpact); if (!validation.isValid) { console.warn("Environmental impact validation failed:", validation.errors); } if (validation.warnings.length > 0) { console.warn("Environmental impact warnings:", validation.warnings); } return enhancedImpact; } catch (error) { if (error instanceof Error && error.message.includes("cannot be negative")) { throw error; } if (error instanceof Error && error.message.includes("must be")) { throw error; } if (error instanceof Error && error.message.includes("exceeds maximum")) { throw error; } console.error("Error calculating environmental impact:", error); return createZeroEnvironmentalImpact(); } } export function validateInputs(diskSpace, installTime, monthlyDownloads) { if (typeof diskSpace !== "number" || isNaN(diskSpace)) { throw new Error("Disk space must be a valid number"); } if (typeof installTime !== "number" || isNaN(installTime)) { throw new Error("Install time must be a valid number"); } if (diskSpace < 0) { throw new Error("Disk space cannot be negative"); } if (installTime < 0) { throw new Error("Install time cannot be negative"); } if (diskSpace > Number.MAX_SAFE_INTEGER) { throw new Error("Disk space exceeds maximum safe integer"); } if (installTime > Number.MAX_SAFE_INTEGER) { throw new Error("Install time exceeds maximum safe integer"); } if (monthlyDownloads !== null && (typeof monthlyDownloads !== "number" || monthlyDownloads < 0)) { throw new Error("Monthly downloads must be null or a non-negative number"); } } export function calculateTransferEnergy(diskSpaceGB) { const energy = diskSpaceGB * ENVIRONMENTAL_CONSTANTS.ENERGY_PER_GB; return Math.max(0, Math.min(energy, 1000)); } export function calculateNetworkEnergy(diskSpaceMB) { const energy = diskSpaceMB * ENVIRONMENTAL_CONSTANTS.NETWORK_ENERGY_PER_MB; return Math.max(0, Math.min(energy, 100)); } export function calculateStorageEnergy(diskSpaceGB) { const energy = (diskSpaceGB * ENVIRONMENTAL_CONSTANTS.STORAGE_ENERGY_PER_GB_YEAR) / 12; return Math.max(0, Math.min(energy, 500)); } export function calculateEwasteEnergy(diskSpaceGB) { const energy = diskSpaceGB * ENVIRONMENTAL_CONSTANTS.EWASTE_IMPACT_PER_GB; return Math.max(0, Math.min(energy, 50)); } export function calculateEfficiencyEnergy(installTimeHours) { const energy = (installTimeHours * ENVIRONMENTAL_CONSTANTS.EFFICIENCY_IMPROVEMENT) / 100; return Math.max(0, Math.min(energy, 100)); } export function calculateServerEfficiencyEnergy(diskSpaceGB) { const energy = (diskSpaceGB * ENVIRONMENTAL_CONSTANTS.SERVER_UTILIZATION_IMPROVEMENT) / 100; return Math.max(0, Math.min(energy, 200)); } export function aggregateEnergySavings(energies) { const total = energies.reduce((sum, energy) => sum + energy, 0); if (total > 10000) { console.warn("Total energy savings exceed typical ranges, capping at 10,000 kWh"); return 10000; } return Math.max(0, total); } export function createZeroEnvironmentalImpact() { return { carbonSavings: 0, energySavings: 0, waterSavings: 0, treesEquivalent: 0, carMilesEquivalent: 0, efficiencyGain: 0, networkSavings: 0, storageSavings: 0, transferEnergy: 0, cpuEnergy: 0, memoryEnergy: 0, latencyEnergy: 0, buildEnergy: 0, ciCdEnergy: 0, registryEnergy: 0, lifecycleEnergy: 0, carbonOffsetValue: 0, waterTreatmentValue: 0, totalFinancialValue: 0, carbonIntensityUsed: ENVIRONMENTAL_CONSTANTS.CARBON_INTENSITY, regionalMultiplier: 1.0, peakEnergySavings: 0, offPeakEnergySavings: 0, timeOfDayMultiplier: 1.0, renewableEnergySavings: 0, fossilFuelSavings: 0, renewablePercentage: 0, ewasteReduction: 0, serverUtilizationImprovement: 0, developerProductivityGain: 0, buildTimeReduction: 0, }; } function validateEnvironmentalImpact(impact) { const warnings = []; const isValid = true; if (impact.carbonSavings < 0) { warnings.push("Carbon savings cannot be negative."); } if (impact.energySavings < 0) { warnings.push("Energy savings cannot be negative."); } if (impact.waterSavings < 0) { warnings.push("Water savings cannot be negative."); } if (impact.treesEquivalent < 0) { warnings.push("Trees equivalent cannot be negative."); } if (impact.carMilesEquivalent < 0) { warnings.push("Car miles equivalent cannot be negative."); } if (impact.efficiencyGain < 0) { warnings.push("Efficiency gain cannot be negative."); } if (impact.networkSavings < 0) { warnings.push("Network savings cannot be negative."); } if (impact.storageSavings < 0) { warnings.push("Storage savings cannot be negative."); } return { isValid, warnings }; } export function calculateCumulativeEnvironmentalImpact(impacts) { return impacts.reduce((total, impact) => ({ carbonSavings: total.carbonSavings + impact.carbonSavings, energySavings: total.energySavings + impact.energySavings, waterSavings: total.waterSavings + impact.waterSavings, treesEquivalent: total.treesEquivalent + impact.treesEquivalent, carMilesEquivalent: total.carMilesEquivalent + impact.carMilesEquivalent, efficiencyGain: Math.max(total.efficiencyGain, impact.efficiencyGain), networkSavings: total.networkSavings + impact.networkSavings, storageSavings: total.storageSavings + impact.storageSavings, transferEnergy: total.transferEnergy + impact.transferEnergy, cpuEnergy: total.cpuEnergy + impact.cpuEnergy, memoryEnergy: total.memoryEnergy + impact.memoryEnergy, latencyEnergy: total.latencyEnergy + impact.latencyEnergy, buildEnergy: total.buildEnergy + impact.buildEnergy, ciCdEnergy: total.ciCdEnergy + impact.ciCdEnergy, registryEnergy: total.registryEnergy + impact.registryEnergy, lifecycleEnergy: total.lifecycleEnergy + impact.lifecycleEnergy, carbonOffsetValue: total.carbonOffsetValue + impact.carbonOffsetValue, waterTreatmentValue: total.waterTreatmentValue + impact.waterTreatmentValue, totalFinancialValue: total.totalFinancialValue + impact.totalFinancialValue, carbonIntensityUsed: total.carbonIntensityUsed, regionalMultiplier: total.regionalMultiplier, peakEnergySavings: total.peakEnergySavings + impact.peakEnergySavings, offPeakEnergySavings: total.offPeakEnergySavings + impact.offPeakEnergySavings, timeOfDayMultiplier: total.timeOfDayMultiplier, renewableEnergySavings: total.renewableEnergySavings + impact.renewableEnergySavings, fossilFuelSavings: total.fossilFuelSavings + impact.fossilFuelSavings, renewablePercentage: total.renewablePercentage, ewasteReduction: total.ewasteReduction + impact.ewasteReduction, serverUtilizationImprovement: total.serverUtilizationImprovement + impact.serverUtilizationImprovement, developerProductivityGain: total.developerProductivityGain + impact.developerProductivityGain, buildTimeReduction: total.buildTimeReduction + impact.buildTimeReduction, }), createZeroEnvironmentalImpact()); } export function formatEnvironmentalImpact(impact) { return { carbonSavings: `${impact.carbonSavings.toFixed(3)} kg CO2e`, energySavings: `${impact.energySavings.toFixed(3)} kWh`, waterSavings: `${impact.waterSavings.toFixed(1)} L`, treesEquivalent: `${impact.treesEquivalent.toFixed(2)} trees/year`, carMilesEquivalent: `${impact.carMilesEquivalent.toFixed(1)} miles`, efficiencyGain: `${impact.efficiencyGain}%`, networkSavings: `${impact.networkSavings.toFixed(4)} kWh`, storageSavings: `${impact.storageSavings.toFixed(4)} kWh`, }; } export function displayEnvironmentalImpactTable(impact, title = "Environmental Impact") { const formatted = formatEnvironmentalImpact(impact); const table = new CliTable({ head: ["Metric", "Value", "Impact"], colWidths: [25, 20, 35], wordWrap: true, style: { head: ["green"], border: ["grey"], }, }); table.push([ "🌱 Carbon Savings", formatted.carbonSavings, `Equivalent to ${formatted.treesEquivalent} trees planted`, ], [ "⚔ Energy Savings", formatted.energySavings, "Reduced data center energy consumption", ], [ "šŸ’§ Water Savings", formatted.waterSavings, "Reduced data center cooling needs", ], [ "šŸš— Car Miles Equivalent", formatted.carMilesEquivalent, "CO2 savings equivalent to driving", ], [ "šŸš€ Efficiency Gain", formatted.efficiencyGain, "Improved build and runtime performance", ]); console.log(chalk.green(`\n${title}`)); console.log(table.toString()); } export function generateEnvironmentalRecommendations(impact, packageCount) { const recommendations = []; if (impact.carbonSavings > 0.1) { recommendations.push(`šŸŒ You're saving ${impact.carbonSavings.toFixed(3)} kg CO2e - equivalent to ${impact.treesEquivalent.toFixed(2)} trees planted annually!`); } if (impact.energySavings > 0.01) { recommendations.push(`⚔ Energy savings of ${impact.energySavings.toFixed(3)} kWh - enough to power a laptop for ${(impact.energySavings * 10).toFixed(1)} hours!`); } if (impact.waterSavings > 1) { recommendations.push(`šŸ’§ Water savings of ${impact.waterSavings.toFixed(1)}L - equivalent to ${(impact.waterSavings / 2).toFixed(1)} water bottles!`); } if (packageCount > 5) { recommendations.push(`šŸŽÆ Removing ${packageCount} unused dependencies significantly reduces your project's environmental footprint!`); } if (impact.carMilesEquivalent > 0.1) { recommendations.push(`šŸš— Your CO2 savings equal driving ${impact.carMilesEquivalent.toFixed(1)} fewer miles - every bit helps!`); } recommendations.push(`🌟 You're making a real difference! Share your environmental impact with your team to inspire others.`); return recommendations; } export function displayEnvironmentalHeroMessage(impact) { const totalSavings = impact.carbonSavings + impact.energySavings + impact.waterSavings; if (totalSavings > 1) { console.log(chalk.green.bold("\nšŸ† Environmental Hero Award! šŸ†")); console.log(chalk.green("You're making a significant positive impact on the environment!")); } else if (totalSavings > 0.1) { console.log(chalk.yellow.bold("\n🌱 Green Developer! 🌱")); console.log(chalk.yellow("Every small action counts toward a sustainable future!")); } else { console.log(chalk.blue.bold("\nšŸ’š Eco-Conscious Developer! šŸ’š")); console.log(chalk.blue("You're contributing to a cleaner, more efficient codebase!")); } }