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 +102 -0
- package/dist/analyzer.d.ts +6 -0
- package/dist/analyzer.js +32 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +107 -0
- package/dist/scanner.d.ts +12 -0
- package/dist/scanner.js +49 -0
- package/dist/ui.d.ts +12 -0
- package/dist/ui.js +44 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# ๐ Assets Tracker
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/assets-tracker)
|
|
4
|
+
[](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>
|
package/dist/analyzer.js
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
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[]>;
|
package/dist/scanner.js
ADDED
|
@@ -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
|
+
}
|