qleaner 1.0.14 โ†’ 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 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. **Import Extraction**: Parses files using Babel AST and extracts all import statements
131
- 3. **Module Resolution**: Uses enhanced-resolve to properly resolve import paths (supports path aliases like `@/` and `~/`)
132
- 4. **Analysis**: Compares file paths with resolved import paths to identify unused files
133
- 5. **Reporting**: Outputs the results in standard or table format based on your preferences
134
- 6. **Safe Deletion**: In dry run mode, shows what would be deleted without making changes. In normal mode, prompts for confirmation before deletion
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. **Use exclusions**: Exclude test files and build outputs when scanning for unused files
172
- 4. **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)
173
- 5. **Use table format**: The table format is easier to read for large results
174
- 6. **Combine options**: Use multiple flags together for comprehensive analysis
175
- 7. **Check dynamic imports**: Files imported using dynamic imports (`import()`) may appear as unused but are actually needed
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 { loadCache, getFileHash, needsRebuild, saveCache } = require("./utils/cache");
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
- const imports = [];
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
- // console.log('imports', imports);
163
- // console.log('files', files);
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('debug hit', debugCount);
201
+ // console.log("debug hit", debugCount);
198
202
  }
199
203
  }
200
- console.timeEnd('unUsedFiles');
204
+ cache[directory] = {
205
+ imports: imports,
206
+ }
201
207
  saveCache(process.cwd(), cache);
202
208
  return unusedFiles;
203
209
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qleaner",
3
3
  "packageManager": "yarn@4.6.0",
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "main": "command.js",
6
6
  "bin": "./bin/cli.js",
7
7
  "scripts": {
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
  }
@@ -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
- }