assets-tracker 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # ๐Ÿš€ Assets Tracker
2
+
3
+ [![npm version](https://img.shields.io/npm/v/assets-tracker.svg)](https://www.npmjs.com/package/assets-tracker)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **Clean up your codebase and shrink your bundle size with the ultimate unused assets detector.**
7
+
8
+ `assets-tracker` is a modern, high-performance CLI tool designed to identify and remove unreferenced assets from your project. Whether it's a forgotten hero image, an unused font file, or a legacy video asset, this tool helps you keep your repository lean and efficient.
9
+
10
+ ---
11
+
12
+ ## โœจ Features
13
+
14
+ - ๐Ÿ” **Deep Scan**: Identifies unused images, videos, fonts, and more across your entire project.
15
+ - ๐Ÿ› ๏ธ **Smart Analysis**: Scans your source files (JS, TS, HTML, CSS, etc.) to detect asset references.
16
+ - ๐Ÿข **Modern CLI**: Beautiful, interactive terminal interface with progress indicators and formatted reports.
17
+ - ๐Ÿงน **Interactive Cleanup**: Choose to delete all unused assets at once or select specific files to remove safely.
18
+ - ๐Ÿ“ฆ **Dev-Friendly**: Zero configuration needed. Works out of the box with standard project structures.
19
+ - ๐Ÿ“‰ **Size Reports**: View exactly how much space you can save.
20
+
21
+ ---
22
+
23
+ ## ๐Ÿš€ Installation
24
+
25
+ Install it as a dev dependency in your project:
26
+
27
+ ```bash
28
+ npm install --save-dev assets-tracker
29
+ # or
30
+ yarn add -D assets-tracker
31
+ # or
32
+ pnpm add -D assets-tracker
33
+ ```
34
+
35
+ ---
36
+
37
+ ## ๐Ÿ“– Usage
38
+
39
+ ### ๐Ÿ” Scan for Unused Assets
40
+ Run a full scan to see what can be removed:
41
+
42
+ ```bash
43
+ npx assets-tracker scan
44
+ ```
45
+
46
+ ### ๐Ÿงน Clean Up Unused Assets
47
+ Start an interactive session to select and delete unused files:
48
+
49
+ ```bash
50
+ npx assets-tracker clean
51
+ ```
52
+
53
+ ---
54
+
55
+ ## ๐Ÿ“‚ Supported Asset Types
56
+
57
+ - **Images**: `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`, `.ico`
58
+ - **Video/Audio**: `.mp4`, `.webm`, `.ogg`, `.mp3`, `.wav`, `.flac`, `.aac`
59
+ - **Fonts**: `.woff`, `.woff2`, `.eot`, `.ttf`, `.otf`
60
+
61
+ ---
62
+
63
+ ## ๐Ÿง  How It Works
64
+
65
+ 1. **Discovery**: The tool builds a list of all asset files in your project (excluding `node_modules`, `dist`, `.git`, etc.).
66
+ 2. **Analysis**: It then parses your source code files to search for any mention of the asset's filename or relative path.
67
+ 3. **Reporting**: Assets that aren't mentioned anywhere are marked as "unused".
68
+ 4. **Action**: You get a detailed report and the option to delete them securely.
69
+
70
+ ---
71
+
72
+ ## ๐Ÿ› ๏ธ Configuration (Coming Soon)
73
+
74
+ We are working on adding a `.assettracker.json` config file to allow:
75
+ - Custom asset extensions.
76
+ - Custom source directories.
77
+ - Exclusion patterns.
78
+
79
+ ---
80
+
81
+ ---
82
+
83
+ ## ๐Ÿ‘ฅ Authors
84
+
85
+ - **Dheeraj & Vaibhav**
86
+
87
+ ## ๐Ÿค Contributors
88
+
89
+ - **Dheeraj Khush** - [GitHub](https://github.com/DheerajKhush)
90
+ - **Vaibhav Khushalani** - [GitHub](https://github.com/VaibhavKhushalani)
91
+
92
+ Feel free to check the [issues page](https://github.com/yourusername/assets-tracker/issues).
93
+
94
+ ## ๐Ÿ“„ License
95
+
96
+ This project is [MIT](https://opensource.org/licenses/MIT) licensed.
97
+
98
+ ---
99
+
100
+ <p align="center">
101
+ Built with โค๏ธ for the developer community.
102
+ </p>
@@ -0,0 +1,6 @@
1
+ import { AssetFile } from './scanner.js';
2
+ export interface ScanResult {
3
+ unusedAssets: AssetFile[];
4
+ totalUnusedSize: number;
5
+ }
6
+ export declare function analyzeUnusedAssets(assets: AssetFile[], sourceFiles: string[]): Promise<ScanResult>;
@@ -0,0 +1,32 @@
1
+ import fs from 'fs-extra';
2
+ export async function analyzeUnusedAssets(assets, sourceFiles) {
3
+ const unusedAssets = [...assets];
4
+ // We'll read each source file once and check all assets against it.
5
+ // This is more efficient than reading every source file for each asset.
6
+ for (const sourceFile of sourceFiles) {
7
+ if (unusedAssets.length === 0)
8
+ break;
9
+ const content = await fs.readFile(sourceFile, 'utf8');
10
+ // Check which assets are referenced in this file
11
+ for (let i = unusedAssets.length - 1; i >= 0; i--) {
12
+ const asset = unusedAssets[i];
13
+ // Simple heuristic: check if filename or relative path is in content
14
+ // We use word boundaries for filename to avoid false positives
15
+ // (like 'icon.png' matching 'large-icon.png')
16
+ // Note: This matches common patterns like:
17
+ // - src="path/to/asset.png"
18
+ // - url('path/to/asset.png')
19
+ // - import asset from './path/to/asset.png'
20
+ const fileNameEscaped = asset.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
21
+ const fileNameRegex = new RegExp(`\\b${fileNameEscaped}\\b`, 'i');
22
+ if (content.includes(asset.path) || fileNameRegex.test(content)) {
23
+ unusedAssets.splice(i, 1);
24
+ }
25
+ }
26
+ }
27
+ const totalUnusedSize = unusedAssets.reduce((sum, asset) => sum + asset.size, 0);
28
+ return {
29
+ unusedAssets,
30
+ totalUnusedSize
31
+ };
32
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import fs from 'fs-extra';
4
+ import inquirer from 'inquirer';
5
+ import { findAssets, findSourceFiles } from './scanner.js';
6
+ import { analyzeUnusedAssets } from './analyzer.js';
7
+ import { logger, createSpinner, formatAssetTable } from './ui.js';
8
+ import chalk from 'chalk';
9
+ const program = new Command();
10
+ program
11
+ .name('assets-tracker')
12
+ .description('Modern CLI tool to track and cleanup unused assets')
13
+ .version('1.0.0');
14
+ async function runScan(cwd) {
15
+ logger.banner();
16
+ const spinner = createSpinner('Scanning files...').start();
17
+ try {
18
+ const assets = await findAssets(cwd);
19
+ const sourceFiles = await findSourceFiles(cwd);
20
+ spinner.text = 'Analyzing references...';
21
+ const result = await analyzeUnusedAssets(assets, sourceFiles);
22
+ spinner.stop();
23
+ formatAssetTable(result.unusedAssets);
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ spinner.fail('Error during scan: ' + error.message);
28
+ process.exit(1);
29
+ }
30
+ }
31
+ program
32
+ .command('scan')
33
+ .description('Scan the codebase for unused assets and report them')
34
+ .action(async () => {
35
+ await runScan(process.cwd());
36
+ });
37
+ program
38
+ .command('clean')
39
+ .description('Scan and provide an interactive way to delete unused assets')
40
+ .action(async () => {
41
+ const result = await runScan(process.cwd());
42
+ if (!result || result.unusedAssets.length === 0)
43
+ return;
44
+ const { action } = await inquirer.prompt([
45
+ {
46
+ type: 'list',
47
+ name: 'action',
48
+ message: 'How would you like to proceed?',
49
+ choices: [
50
+ { name: chalk.red('Delete all unused assets'), value: 'all' },
51
+ { name: chalk.yellow('Select assets to delete'), value: 'select' },
52
+ { name: 'Cancel', value: 'cancel' }
53
+ ]
54
+ }
55
+ ]);
56
+ if (action === 'cancel') {
57
+ logger.info('Clean aborted.');
58
+ return;
59
+ }
60
+ let toDelete = result.unusedAssets;
61
+ if (action === 'select') {
62
+ const { selected } = await inquirer.prompt([
63
+ {
64
+ type: 'checkbox',
65
+ name: 'selected',
66
+ message: 'Select assets to remove:',
67
+ choices: result.unusedAssets.map(a => ({
68
+ name: `${a.name} (${a.path})`,
69
+ value: a
70
+ }))
71
+ }
72
+ ]);
73
+ toDelete = selected;
74
+ }
75
+ if (toDelete.length === 0) {
76
+ logger.info('No assets selected for deletion.');
77
+ return;
78
+ }
79
+ const { confirm } = await inquirer.prompt([
80
+ {
81
+ type: 'confirm',
82
+ name: 'confirm',
83
+ message: chalk.red.bold(`Are you sure you want to delete ${toDelete.length} assets?`),
84
+ default: false
85
+ }
86
+ ]);
87
+ if (confirm) {
88
+ const deleteSpinner = createSpinner('Deleting assets...').start();
89
+ try {
90
+ for (const asset of toDelete) {
91
+ await fs.remove(asset.absolutePath);
92
+ }
93
+ deleteSpinner.succeed(`Successfully deleted ${toDelete.length} assets.`);
94
+ }
95
+ catch (error) {
96
+ deleteSpinner.fail('Error during deletion: ' + error.message);
97
+ }
98
+ }
99
+ else {
100
+ logger.info('Deletion cancelled.');
101
+ }
102
+ });
103
+ program.parse(process.argv);
104
+ // If no arguments, show help
105
+ if (!process.argv.slice(2).length) {
106
+ program.outputHelp();
107
+ }
@@ -0,0 +1,12 @@
1
+ export declare const DEFAULT_ASSET_EXTENSIONS: string[];
2
+ export declare const DEFAULT_SOURCE_EXTENSIONS: string[];
3
+ export declare const DEFAULT_IGNORE: string[];
4
+ export interface AssetFile {
5
+ name: string;
6
+ path: string;
7
+ absolutePath: string;
8
+ size: number;
9
+ extension: string;
10
+ }
11
+ export declare function findAssets(cwd: string, extensions?: string[]): Promise<AssetFile[]>;
12
+ export declare function findSourceFiles(cwd: string, extensions?: string[]): Promise<string[]>;
@@ -0,0 +1,49 @@
1
+ import { globby } from 'globby';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ export const DEFAULT_ASSET_EXTENSIONS = [
5
+ 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico',
6
+ 'mp4', 'webm', 'ogg', 'mp3', 'wav', 'flac', 'aac',
7
+ 'woff', 'woff2', 'eot', 'ttf', 'otf'
8
+ ];
9
+ export const DEFAULT_SOURCE_EXTENSIONS = [
10
+ 'js', 'ts', 'jsx', 'tsx', 'html', 'css', 'scss', 'sass', 'less', 'vue', 'svelte', 'json'
11
+ ];
12
+ export const DEFAULT_IGNORE = [
13
+ '**/node_modules/**',
14
+ '**/.git/**',
15
+ '**/dist/**',
16
+ '**/build/**',
17
+ '**/.next/**',
18
+ '**/coverage/**',
19
+ '**/package.json',
20
+ '**/package-lock.json',
21
+ '**/tsconfig.json'
22
+ ];
23
+ export async function findAssets(cwd, extensions = DEFAULT_ASSET_EXTENSIONS) {
24
+ const patterns = extensions.map(ext => `**/*.${ext}`);
25
+ const files = await globby(patterns, {
26
+ cwd,
27
+ ignore: DEFAULT_IGNORE,
28
+ absolute: true
29
+ });
30
+ return Promise.all(files.map(async (file) => {
31
+ const stats = await fs.stat(file);
32
+ const relPath = path.relative(cwd, file);
33
+ return {
34
+ name: path.basename(file),
35
+ path: relPath,
36
+ absolutePath: file,
37
+ size: stats.size,
38
+ extension: path.extname(file).slice(1)
39
+ };
40
+ }));
41
+ }
42
+ export async function findSourceFiles(cwd, extensions = DEFAULT_SOURCE_EXTENSIONS) {
43
+ const patterns = extensions.map(ext => `**/*.${ext}`);
44
+ return globby(patterns, {
45
+ cwd,
46
+ ignore: DEFAULT_IGNORE,
47
+ absolute: true
48
+ });
49
+ }
package/dist/ui.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { AssetFile } from './scanner.js';
2
+ export declare const logger: {
3
+ info: (msg: string) => void;
4
+ success: (msg: string) => void;
5
+ warn: (msg: string) => void;
6
+ error: (msg: string) => void;
7
+ dim: (msg: string) => void;
8
+ bold: (msg: string) => void;
9
+ banner: () => void;
10
+ };
11
+ export declare function createSpinner(text: string): import("ora").Ora;
12
+ export declare function formatAssetTable(assets: AssetFile[]): void;
package/dist/ui.js ADDED
@@ -0,0 +1,44 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { filesize } from 'filesize';
4
+ export const logger = {
5
+ info: (msg) => console.log(chalk.blue('โ„น ') + msg),
6
+ success: (msg) => console.log(chalk.green('โœ” ') + msg),
7
+ warn: (msg) => console.log(chalk.yellow('โš  ') + msg),
8
+ error: (msg) => console.log(chalk.red('โœ– ') + msg),
9
+ dim: (msg) => console.log(chalk.dim(msg)),
10
+ bold: (msg) => console.log(chalk.bold(msg)),
11
+ banner: () => {
12
+ console.log('\n' + chalk.cyan.bold(' ๐Ÿš€ Assets Tracker') + chalk.dim(' - Cleanup unused assets') + '\n');
13
+ }
14
+ };
15
+ export function createSpinner(text) {
16
+ return ora({
17
+ text,
18
+ color: 'cyan',
19
+ spinner: 'dots'
20
+ });
21
+ }
22
+ export function formatAssetTable(assets) {
23
+ if (assets.length === 0) {
24
+ logger.success('No unused assets found! Your project is clean. โœจ');
25
+ return;
26
+ }
27
+ logger.bold(`\nFound ${assets.length} unused assets:\n`);
28
+ // Simple table formatting
29
+ const headers = ['NAME', 'TYPE', 'SIZE', 'PATH'];
30
+ const columnWidths = [20, 8, 12, 40];
31
+ const headerRow = headers.map((h, i) => chalk.bold.underline(h.padEnd(columnWidths[i]))).join(' ');
32
+ console.log(headerRow);
33
+ assets.forEach(asset => {
34
+ const row = [
35
+ chalk.cyan(asset.name.substring(0, columnWidths[0] - 3).padEnd(columnWidths[0])),
36
+ chalk.dim(asset.extension.toUpperCase().padEnd(columnWidths[1])),
37
+ chalk.yellow(filesize(asset.size).toString().padEnd(columnWidths[2])),
38
+ chalk.white(asset.path.substring(0, columnWidths[3] - 3).padEnd(columnWidths[3]))
39
+ ].join(' ');
40
+ console.log(row);
41
+ });
42
+ const totalSize = assets.reduce((sum, a) => sum + a.size, 0);
43
+ console.log('\n' + chalk.bgCyan.black(` Potential Savings: ${filesize(totalSize)} `) + '\n');
44
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "assets-tracker",
3
+ "version": "1.0.0",
4
+ "description": "A modern, high-performance CLI tool to identify and remove unused assets in your codebase. Automatically scan for unreferenced images, fonts, and media files to optimize your project size.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "assets-tracker": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts",
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "prepublishOnly": "npm run build",
17
+ "deploy": "npm run build && npm publish --access public"
18
+ },
19
+ "keywords": [
20
+ "assets",
21
+ "unused-assets",
22
+ "cleanup",
23
+ "cli",
24
+ "optimization",
25
+ "bundle-size",
26
+ "images",
27
+ "media-cleanup",
28
+ "web-performance",
29
+ "developer-tools"
30
+ ],
31
+ "author": "Dheeraj & Vaibhav",
32
+ "contributors": [
33
+ {
34
+ "name": "Dheeraj Khush",
35
+ "url": "https://github.com/DheerajKhush"
36
+ },
37
+ {
38
+ "name": "Vaibhav Khushalani",
39
+ "url": "https://github.com/VaibhavKhushalani"
40
+ }
41
+ ],
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "chalk": "^5.3.0",
45
+ "commander": "^12.1.0",
46
+ "filesize": "^10.1.2",
47
+ "fs-extra": "^11.2.0",
48
+ "globby": "^14.0.1",
49
+ "inquirer": "^9.2.22",
50
+ "ora": "^8.0.1"
51
+ },
52
+ "devDependencies": {
53
+ "@types/fs-extra": "^11.0.4",
54
+ "@types/inquirer": "^9.0.7",
55
+ "@types/node": "^20.14.2",
56
+ "nodemon": "^3.1.3",
57
+ "ts-node": "^10.9.2",
58
+ "typescript": "^5.4.5"
59
+ },
60
+ "type": "module"
61
+ }