qleaner 1.0.15 โ 1.0.16
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 +28 -11
- package/bin/cli.js +6 -1
- package/command.js +21 -15
- package/package.json +1 -1
- package/utils/cache.js +9 -1
- package/unused-check-cache.json +0 -180
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ A powerful CLI tool to analyze and clean up your React codebase by finding unuse
|
|
|
9
9
|
- ๐ฏ **File listing**: List all files in your project
|
|
10
10
|
- ๐ **Table output**: Display results in formatted tables for better readability
|
|
11
11
|
- โ๏ธ **Flexible configuration**: Exclude directories and files from scanning
|
|
12
|
-
- ๐ **Fast performance**: Efficient file scanning across large codebases
|
|
12
|
+
- ๐ **Fast performance**: Efficient file scanning across large codebases with intelligent caching
|
|
13
13
|
- ๐ช **TypeScript support**: Works with TypeScript, JavaScript, JSX, and TSX files
|
|
14
14
|
- ๐งช **Dry run mode**: Preview what would be deleted without actually deleting files
|
|
15
|
+
- ๐พ **Smart caching**: Caches scan results for faster subsequent runs (automatically invalidates on file changes)
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
@@ -80,6 +81,7 @@ qleaner qlean-scan <path> [options]
|
|
|
80
81
|
- `-F, --exclude-file-print <files...>` - Do not print the excluded files
|
|
81
82
|
- `-t, --table` - Display results in a formatted table
|
|
82
83
|
- `-d, --dry-run` - Show what would be deleted without actually deleting (skips prompt)
|
|
84
|
+
- `-C, --clear-cache` - Clear the cache before scanning (use when code changes have been made)
|
|
83
85
|
|
|
84
86
|
**Examples:**
|
|
85
87
|
|
|
@@ -107,6 +109,12 @@ qleaner qlean-scan src --dry-run
|
|
|
107
109
|
|
|
108
110
|
# Dry run with table output
|
|
109
111
|
qleaner qlean-scan src --dry-run --table
|
|
112
|
+
|
|
113
|
+
# Clear cache and scan (recommended after making code changes)
|
|
114
|
+
qleaner qlean-scan src --clear-cache
|
|
115
|
+
|
|
116
|
+
# Clear cache with dry run
|
|
117
|
+
qleaner qlean-scan src --clear-cache --dry-run
|
|
110
118
|
```
|
|
111
119
|
|
|
112
120
|
## Output Formats
|
|
@@ -127,11 +135,13 @@ Qleaner provides two output formats:
|
|
|
127
135
|
## How It Works
|
|
128
136
|
|
|
129
137
|
1. **File Discovery**: Recursively finds all `.tsx`, `.ts`, `.js`, and `.jsx` files in the specified directory
|
|
130
|
-
2. **
|
|
131
|
-
3. **
|
|
132
|
-
4. **
|
|
133
|
-
5. **
|
|
134
|
-
6. **
|
|
138
|
+
2. **Caching**: Checks cache for previously scanned files. Files that haven't changed are skipped for faster performance
|
|
139
|
+
3. **Import Extraction**: Parses files using Babel AST and extracts all import statements (only for changed files or cache misses)
|
|
140
|
+
4. **Module Resolution**: Uses enhanced-resolve to properly resolve import paths (supports path aliases like `@/` and `~/`)
|
|
141
|
+
5. **Analysis**: Compares file paths with resolved import paths to identify unused files
|
|
142
|
+
6. **Cache Update**: Saves scan results to cache for faster subsequent runs
|
|
143
|
+
7. **Reporting**: Outputs the results in standard or table format based on your preferences
|
|
144
|
+
8. **Safe Deletion**: In dry run mode, shows what would be deleted without making changes. In normal mode, prompts for confirmation before deletion
|
|
135
145
|
|
|
136
146
|
## Supported File Types
|
|
137
147
|
|
|
@@ -168,11 +178,12 @@ You can exclude directories and files from scanning using the command-line optio
|
|
|
168
178
|
|
|
169
179
|
1. **Start with a small scope**: Begin by scanning a specific directory before scanning the entire project
|
|
170
180
|
2. **Use dry run first**: Always run with `--dry-run` first to preview what would be deleted before actually deleting files
|
|
171
|
-
3. **
|
|
172
|
-
4. **
|
|
173
|
-
5. **
|
|
174
|
-
6. **
|
|
175
|
-
7. **
|
|
181
|
+
3. **Clear cache after code changes**: If you've made changes to your codebase (added/removed files, changed imports), use `--clear-cache` to ensure accurate results. The cache automatically invalidates when files change, but clearing it manually ensures a fresh scan
|
|
182
|
+
4. **Use exclusions**: Exclude test files and build outputs when scanning for unused files
|
|
183
|
+
5. **Review before deleting**: Always review the unused files list before removing them - some files might be used dynamically (e.g., through dynamic imports, configuration files, or asset references)
|
|
184
|
+
6. **Use table format**: The table format is easier to read for large results
|
|
185
|
+
7. **Combine options**: Use multiple flags together for comprehensive analysis
|
|
186
|
+
8. **Check dynamic imports**: Files imported using dynamic imports (`import()`) may appear as unused but are actually needed
|
|
176
187
|
|
|
177
188
|
## Important Notes
|
|
178
189
|
|
|
@@ -184,6 +195,12 @@ You can exclude directories and files from scanning using the command-line optio
|
|
|
184
195
|
|
|
185
196
|
๐ก **Tip**: Always use `--dry-run` first to preview what would be deleted. This is especially important in CI/CD pipelines or when scanning large codebases.
|
|
186
197
|
|
|
198
|
+
๐พ **Cache Management**: Qleaner uses intelligent caching to speed up scans. The cache automatically detects file changes via content hashing. However, if you've made significant changes to your codebase structure or want to ensure a completely fresh scan, use `--clear-cache` before scanning. This is particularly useful:
|
|
199
|
+
- After major refactoring
|
|
200
|
+
- When files have been moved or renamed
|
|
201
|
+
- When you want to ensure the most up-to-date results
|
|
202
|
+
- In CI/CD pipelines where you want consistent, fresh scans
|
|
203
|
+
|
|
187
204
|
## Requirements
|
|
188
205
|
|
|
189
206
|
- Node.js 14+
|
package/bin/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { Command } = require("commander");
|
|
3
3
|
const { list, scan } = require("../controllers/list");
|
|
4
|
-
|
|
4
|
+
const { clearCache } = require("../utils/cache");
|
|
5
5
|
|
|
6
6
|
async function loadChalk() {
|
|
7
7
|
return (await import("chalk")).default;
|
|
@@ -46,7 +46,12 @@ async function loadChalk() {
|
|
|
46
46
|
)
|
|
47
47
|
.option("-t, --table", "Print the results in a table")
|
|
48
48
|
.option("-d, --dry-run", "Show what would be deleted without actually deleting (skips prompt)")
|
|
49
|
+
.option("-C, --clear-cache", "Clear the cache")
|
|
49
50
|
.action(async (path, options) => {
|
|
51
|
+
if(options.clearCache) {
|
|
52
|
+
clearCache(process.cwd());
|
|
53
|
+
console.log(chalk.green('โ Cache cleared successfully'));
|
|
54
|
+
}
|
|
50
55
|
await scan(chalk, path, options);
|
|
51
56
|
});
|
|
52
57
|
|
package/command.js
CHANGED
|
@@ -5,7 +5,12 @@ const parser = require("@babel/parser");
|
|
|
5
5
|
const traverse = require("@babel/traverse").default;
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const { createResolver } = require("./utils/resolver");
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
loadCache,
|
|
10
|
+
getFileHash,
|
|
11
|
+
needsRebuild,
|
|
12
|
+
saveCache,
|
|
13
|
+
} = require("./utils/cache");
|
|
9
14
|
|
|
10
15
|
async function getFiles(directory = "src", options, chalk) {
|
|
11
16
|
const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
|
|
@@ -101,7 +106,6 @@ async function getFiles(directory = "src", options, chalk) {
|
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
async function unUsedFiles(chalk, directory = "src", options) {
|
|
104
|
-
console.time('unUsedFiles');
|
|
105
109
|
const resolver = createResolver(directory);
|
|
106
110
|
const cache = loadCache(process.cwd());
|
|
107
111
|
const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
|
|
@@ -117,7 +121,7 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
const files = await fg(contentPaths);
|
|
120
|
-
|
|
124
|
+
let imports = [];
|
|
121
125
|
const unusedFiles = [];
|
|
122
126
|
|
|
123
127
|
// debug log
|
|
@@ -125,7 +129,7 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
125
129
|
// console.log('total files---', files.length);
|
|
126
130
|
for (const file of files) {
|
|
127
131
|
const code = fs.readFileSync(file, "utf8");
|
|
128
|
-
if(needsRebuild(file, code, cache)) {
|
|
132
|
+
if (needsRebuild(file, code, cache)) {
|
|
129
133
|
const ast = parser.parse(code, {
|
|
130
134
|
sourceType: "module",
|
|
131
135
|
plugins: ["jsx", "typescript"],
|
|
@@ -149,25 +153,26 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
149
153
|
file: file,
|
|
150
154
|
line: node.loc.start.line,
|
|
151
155
|
column: node.loc.start.column,
|
|
152
|
-
lastModified: fs.statSync(file).mtime.getTime(),
|
|
156
|
+
lastModified: fs.statSync(path.resolve(file)).mtime.getTime(),
|
|
153
157
|
});
|
|
154
158
|
},
|
|
155
159
|
});
|
|
156
|
-
}else {
|
|
160
|
+
} else {
|
|
157
161
|
// debugCount++;
|
|
158
162
|
// console.log('cache hit', debugCount);
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
|
|
162
|
-
//
|
|
163
|
-
|
|
166
|
+
// imports beings empty shows all the files were cached
|
|
167
|
+
if(imports.length === 0) {
|
|
168
|
+
imports = cache[directory]? cache[directory].imports: [];
|
|
169
|
+
}
|
|
170
|
+
|
|
164
171
|
// debugCount = 0;
|
|
165
|
-
// console.log('total files', files.length);
|
|
166
172
|
for (const file of files) {
|
|
167
173
|
const code = fs.readFileSync(file, "utf8");
|
|
168
|
-
if(!cache[file].isImported || needsRebuild(file, code, cache)) {
|
|
174
|
+
if (!cache[file].isImported || needsRebuild(file, code, cache)) {
|
|
169
175
|
let i = 0;
|
|
170
|
-
// console.log('Checking', file);
|
|
171
176
|
let isFound = false;
|
|
172
177
|
while (!isFound && i < imports.length) {
|
|
173
178
|
const importFilePath = await resolver(
|
|
@@ -175,7 +180,6 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
175
180
|
imports[i].file,
|
|
176
181
|
imports[i].from
|
|
177
182
|
);
|
|
178
|
-
// console.log(chalk.blue('importFilePath'), importFilePath);
|
|
179
183
|
if (compareFiles(path.resolve(file), importFilePath)) {
|
|
180
184
|
isFound = true;
|
|
181
185
|
cache[file].isImported = true;
|
|
@@ -192,12 +196,14 @@ async function unUsedFiles(chalk, directory = "src", options) {
|
|
|
192
196
|
}
|
|
193
197
|
i++;
|
|
194
198
|
}
|
|
195
|
-
}else {
|
|
199
|
+
} else {
|
|
196
200
|
// debugCount++;
|
|
197
|
-
// console.log(
|
|
201
|
+
// console.log("debug hit", debugCount);
|
|
198
202
|
}
|
|
199
203
|
}
|
|
200
|
-
|
|
204
|
+
cache[directory] = {
|
|
205
|
+
imports: imports,
|
|
206
|
+
}
|
|
201
207
|
saveCache(process.cwd(), cache);
|
|
202
208
|
return unusedFiles;
|
|
203
209
|
}
|
package/package.json
CHANGED
package/utils/cache.js
CHANGED
|
@@ -30,9 +30,17 @@ function saveCache(rootPath, cache) {
|
|
|
30
30
|
fs.writeFileSync(file, JSON.stringify(cache, null, 2))
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
function clearCache(rootPath) {
|
|
35
|
+
const file = path.join(rootPath, "unused-check-cache.json");
|
|
36
|
+
if (fs.existsSync(file)) {
|
|
37
|
+
fs.unlinkSync(file);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
33
40
|
module.exports = {
|
|
34
41
|
loadCache,
|
|
35
42
|
getFileHash,
|
|
36
43
|
needsRebuild,
|
|
37
|
-
saveCache
|
|
44
|
+
saveCache,
|
|
45
|
+
clearCache
|
|
38
46
|
}
|
package/unused-check-cache.json
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"src/hello.find.tsx": {
|
|
3
|
-
"hash": "13088ef1169a758d26b7fde99ca67d5a",
|
|
4
|
-
"imports": [
|
|
5
|
-
{
|
|
6
|
-
"from": "./demo/functions",
|
|
7
|
-
"file": "src/hello.find.tsx",
|
|
8
|
-
"line": 1,
|
|
9
|
-
"column": 0,
|
|
10
|
-
"lastModified": 1764776380790
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"from": "~/demo/book/hidden",
|
|
14
|
-
"file": "src/hello.find.tsx",
|
|
15
|
-
"line": 2,
|
|
16
|
-
"column": 0,
|
|
17
|
-
"lastModified": 1764776380790
|
|
18
|
-
}
|
|
19
|
-
],
|
|
20
|
-
"isImported": true,
|
|
21
|
-
"lastModified": 1764776380790
|
|
22
|
-
},
|
|
23
|
-
"src/jambo.tsx": {
|
|
24
|
-
"hash": "6b9a7f99ad2bd68e567368a35bc2831c",
|
|
25
|
-
"imports": [
|
|
26
|
-
{
|
|
27
|
-
"from": "./demo/functions",
|
|
28
|
-
"file": "src/jambo.tsx",
|
|
29
|
-
"line": 1,
|
|
30
|
-
"column": 0,
|
|
31
|
-
"lastModified": 1764776380792
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"from": "hello.find",
|
|
35
|
-
"file": "src/jambo.tsx",
|
|
36
|
-
"line": 2,
|
|
37
|
-
"column": 0,
|
|
38
|
-
"lastModified": 1764776380792
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"isImported": true,
|
|
42
|
-
"lastModified": 1764776380792
|
|
43
|
-
},
|
|
44
|
-
"src/main.js": {
|
|
45
|
-
"hash": "961f4425237e1551bb8ec8a136f12a66",
|
|
46
|
-
"imports": [
|
|
47
|
-
{
|
|
48
|
-
"from": "jambo",
|
|
49
|
-
"file": "src/main.js",
|
|
50
|
-
"line": 1,
|
|
51
|
-
"column": 0,
|
|
52
|
-
"lastModified": 1764776380796
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"from": "poa",
|
|
56
|
-
"file": "src/main.js",
|
|
57
|
-
"line": 2,
|
|
58
|
-
"column": 0,
|
|
59
|
-
"lastModified": 1764776380796
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"from": "./style.css",
|
|
63
|
-
"file": "src/main.js",
|
|
64
|
-
"line": 3,
|
|
65
|
-
"column": 0,
|
|
66
|
-
"lastModified": 1764776380796
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"from": "./demo/demo",
|
|
70
|
-
"file": "src/main.js",
|
|
71
|
-
"line": 4,
|
|
72
|
-
"column": 0,
|
|
73
|
-
"lastModified": 1764776380796
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"from": "./jambo",
|
|
77
|
-
"file": "src/main.js",
|
|
78
|
-
"line": 5,
|
|
79
|
-
"column": 0,
|
|
80
|
-
"lastModified": 1764776380796
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"from": "@/demo/book/hidden",
|
|
84
|
-
"file": "src/main.js",
|
|
85
|
-
"line": 6,
|
|
86
|
-
"column": 0,
|
|
87
|
-
"lastModified": 1764776380796
|
|
88
|
-
}
|
|
89
|
-
],
|
|
90
|
-
"isImported": true,
|
|
91
|
-
"lastModified": 1764776380796
|
|
92
|
-
},
|
|
93
|
-
"src/poa.tsx": {
|
|
94
|
-
"hash": "09d1c8072d500780c9d6b223f5609cc5",
|
|
95
|
-
"imports": [
|
|
96
|
-
{
|
|
97
|
-
"from": "one.png",
|
|
98
|
-
"file": "src/poa.tsx",
|
|
99
|
-
"line": 1,
|
|
100
|
-
"column": 0,
|
|
101
|
-
"lastModified": 1764776380809
|
|
102
|
-
}
|
|
103
|
-
],
|
|
104
|
-
"isImported": true,
|
|
105
|
-
"lastModified": 1764776380809
|
|
106
|
-
},
|
|
107
|
-
"src/demo/demo.tsx": {
|
|
108
|
-
"hash": "fe20d628c85aad8b491b3e45e6559ce4",
|
|
109
|
-
"imports": [
|
|
110
|
-
{
|
|
111
|
-
"from": "../hello.find",
|
|
112
|
-
"file": "src/demo/demo.tsx",
|
|
113
|
-
"line": 1,
|
|
114
|
-
"column": 0,
|
|
115
|
-
"lastModified": 1764828590237
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"from": "../demo/book/hidden",
|
|
119
|
-
"file": "src/demo/demo.tsx",
|
|
120
|
-
"line": 2,
|
|
121
|
-
"column": 0,
|
|
122
|
-
"lastModified": 1764828590237
|
|
123
|
-
}
|
|
124
|
-
],
|
|
125
|
-
"isImported": true,
|
|
126
|
-
"lastModified": 1764828590237
|
|
127
|
-
},
|
|
128
|
-
"src/demo/functions.js": {
|
|
129
|
-
"hash": "091a10616a5be2b7c7803a3f7513453e",
|
|
130
|
-
"imports": [],
|
|
131
|
-
"isImported": true,
|
|
132
|
-
"lastModified": 1764776380788
|
|
133
|
-
},
|
|
134
|
-
"src/demo/public.tsx": {
|
|
135
|
-
"hash": "d41d8cd98f00b204e9800998ecf8427e",
|
|
136
|
-
"imports": [],
|
|
137
|
-
"isImported": false,
|
|
138
|
-
"lastModified": 1764690373726
|
|
139
|
-
},
|
|
140
|
-
"src/shell/main.js": {
|
|
141
|
-
"hash": "d41d8cd98f00b204e9800998ecf8427e",
|
|
142
|
-
"imports": [],
|
|
143
|
-
"isImported": false,
|
|
144
|
-
"lastModified": 1764822800241
|
|
145
|
-
},
|
|
146
|
-
"src/demo/book/hidden.tsx": {
|
|
147
|
-
"hash": "6c3fb783f6f246ad67252489ba9367f4",
|
|
148
|
-
"imports": [
|
|
149
|
-
{
|
|
150
|
-
"from": "../../main",
|
|
151
|
-
"file": "src/demo/book/hidden.tsx",
|
|
152
|
-
"line": 1,
|
|
153
|
-
"column": 0,
|
|
154
|
-
"lastModified": 1764776380785
|
|
155
|
-
}
|
|
156
|
-
],
|
|
157
|
-
"isImported": true,
|
|
158
|
-
"lastModified": 1764776380785
|
|
159
|
-
},
|
|
160
|
-
"src/demo/book/public.tsx": {
|
|
161
|
-
"hash": "d41d8cd98f00b204e9800998ecf8427e",
|
|
162
|
-
"imports": [],
|
|
163
|
-
"isImported": false,
|
|
164
|
-
"lastModified": 1764690329997
|
|
165
|
-
},
|
|
166
|
-
"src/layer/shell/shell.tsx": {
|
|
167
|
-
"hash": "659029bd48113a93f0ce44bc1e8005d3",
|
|
168
|
-
"imports": [
|
|
169
|
-
{
|
|
170
|
-
"from": "../../demo/book/hidden",
|
|
171
|
-
"file": "src/layer/shell/shell.tsx",
|
|
172
|
-
"line": 1,
|
|
173
|
-
"column": 0,
|
|
174
|
-
"lastModified": 1764776380795
|
|
175
|
-
}
|
|
176
|
-
],
|
|
177
|
-
"isImported": false,
|
|
178
|
-
"lastModified": 1764776380795
|
|
179
|
-
}
|
|
180
|
-
}
|