depsweep
Version:
š± Automated intelligent dependency cleanup with environmental impact reporting
869 lines (868 loc) ⢠34.1 kB
JavaScript
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!"));
}
}