assetdrain
Version:
๐งน A blazing-fast CLI to detect and clean unused assets from your codebase with interactive UX.
153 lines (151 loc) โข 5.5 kB
JavaScript
// Use built-in `createRequire` for safely loading package.json
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const pkg = require("../../package.json");
// Handle CLI flags before anything else
if (process.argv.includes("--version") || process.argv.includes("-v")) {
console.log(`๐งน assetdrain v${pkg.version}`);
process.exit(0);
}
if (process.argv.includes("--help")) {
console.log(`
Usage: npx assetdrain [asset-folder]
Options:
--version, -v Show version
--help Show help
`);
process.exit(0);
}
// Built-ins
import path from "path";
// Third-party
import chalk from "chalk";
import ora from "ora";
// Local modules
import { scanForImages } from "../core/findImages.js";
import { scanForUsages } from "../core/findUsages.js";
import { generateReport } from "../core/report.js";
import { defaultAssetExts, imageExts, videoExts, gifExts, defaultCodeExts, } from "../core/fileTypes.js";
import { askAssetTypes, askCodeFileTypes, askAction, askExportFormat, askIfShouldDelete, } from "./prompts.js";
async function main() {
console.log(chalk.cyanBright.bold("\n๐งน Welcome to assetdrain!\n"));
// Ask for asset types
const assetChoice = await askAssetTypes();
let assetExts = [];
if (assetChoice === "default")
assetExts = defaultAssetExts;
else if (assetChoice === "images")
assetExts = imageExts;
else if (assetChoice === "videos")
assetExts = videoExts;
else if (assetChoice === "gifs")
assetExts = gifExts;
else if (Array.isArray(assetChoice))
assetExts = assetChoice;
// Ask for code file types
const codeChoice = await askCodeFileTypes();
const codeExts = codeChoice === "default" ? defaultCodeExts : codeChoice;
// Ask what action to perform
const action = await askAction();
// Directories
const assetScanDir = path.resolve(process.cwd(), process.argv[2] || ".");
const projectRoot = process.cwd(); // code scan will always be done from root
console.log(chalk.gray(`\n๐ Scanning assets in: ${assetScanDir}`));
console.log(chalk.gray(`๐ Analyzing code usage in: ${projectRoot}\n`));
// Spinner: scanning assets
const assetSpinner = ora("๐ Scanning for asset files...").start();
let allAssets = [];
try {
allAssets = await scanForImages(assetScanDir, assetExts, [
"**/node_modules/**",
]);
assetSpinner.succeed(`๐ฆ Found ${allAssets.length} asset files.`);
}
catch (err) {
assetSpinner.fail("โ Failed to scan asset files.");
throw err;
}
// Spinner: scanning usages
const usageSpinner = ora("๐ Analyzing code usage...").start();
let usedAssets;
try {
usedAssets = await scanForUsages(projectRoot, allAssets, codeExts, 20 // concurrency
);
usageSpinner.succeed("โ
Usage analysis complete.");
}
catch (err) {
usageSpinner.fail("โ Failed to analyze usage.");
throw err;
}
// Spinner: generate report (terminal view)
const reportSpinner = ora("๐งพ Generating report...").start();
try {
await generateReport(allAssets, usedAssets, {
mode: action,
export: undefined,
});
reportSpinner.succeed("๐ Report complete.");
}
catch (err) {
reportSpinner.fail("โ Failed to generate report.");
throw err;
}
let deleted = false;
if (action === "review") {
const shouldDelete = await askIfShouldDelete();
if (shouldDelete) {
const deleteSpinner = ora("๐งน Deleting unused assets...").start();
try {
await generateReport(allAssets, usedAssets, {
mode: "delete",
export: undefined,
});
deleteSpinner.succeed("โ
Unused assets deleted.");
deleted = true;
}
catch (err) {
deleteSpinner.fail("โ Failed to delete unused assets.");
throw err;
}
}
}
else if (action === "delete") {
const deleteSpinner = ora(chalk.red("๐งน Deleting unused assets...")).start();
try {
await generateReport(allAssets, usedAssets, {
mode: "delete",
export: undefined,
});
deleteSpinner.succeed(chalk.redBright("๐จ Unused assets deleted automatically."));
deleted = true;
}
catch (err) {
deleteSpinner.fail("โ Failed to delete unused assets.");
throw err;
}
}
else {
deleted = false; // Scan Only
}
const exportFormat = await askExportFormat();
if (exportFormat !== "no") {
const exportSpinner = ora(`๐พ Saving report as ${exportFormat.toUpperCase()}...`).start();
try {
await generateReport(allAssets, usedAssets, {
mode: "dry", // safe for export
export: exportFormat,
deleted, // โ
now correct and accurate
});
exportSpinner.succeed(`โ
Report saved to assetdrain-report.${exportFormat}`);
}
catch (err) {
exportSpinner.fail("โ Failed to save report.");
throw err;
}
}
}
main().catch((err) => {
console.error(chalk.red("โ Error running assetdrain:"), err);
process.exit(1);
});