qleaner 1.0.9 → 1.0.11
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 +188 -1
- package/bin/cli.js +66 -30
- package/command.js +41 -14
- package/package.json +4 -2
- package/utils/utils.js +57 -0
- package/.pnp.cjs +0 -7954
- package/.pnp.loader.mjs +0 -2126
- package/.yarn/install-state.gz +0 -0
package/README.md
CHANGED
|
@@ -1 +1,188 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Qleaner
|
|
2
|
+
|
|
3
|
+
A powerful CLI tool to analyze and clean up your React codebase by finding unused files and listing all imports.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **Scan for unused files**: Identify files that are not imported anywhere in your project
|
|
8
|
+
- 📋 **List all imports**: Get a complete list of all import statements in your codebase with file locations
|
|
9
|
+
- 🎯 **File listing**: List all files in your project
|
|
10
|
+
- 📊 **Table output**: Display results in formatted tables for better readability
|
|
11
|
+
- ⚙️ **Flexible configuration**: Exclude directories and files from scanning
|
|
12
|
+
- 🚀 **Fast performance**: Efficient file scanning across large codebases
|
|
13
|
+
- 💪 **TypeScript support**: Works with TypeScript, JavaScript, JSX, and TSX files
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install globally
|
|
19
|
+
npm install -g qleaner
|
|
20
|
+
|
|
21
|
+
# Or use with npx
|
|
22
|
+
npx qleaner
|
|
23
|
+
|
|
24
|
+
# Or install locally
|
|
25
|
+
yarn add qleaner
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### List Imports and Files
|
|
31
|
+
|
|
32
|
+
List all imports and files in your project:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
qleaner qlean-list <path> [options]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Options:**
|
|
39
|
+
- `-l, --list-files` - List all the files in the project
|
|
40
|
+
- `-i, --list-imports` - List all the imports in the project
|
|
41
|
+
- `-e, --exclude-dir <dir...>` - Exclude directories from the scan
|
|
42
|
+
- `-f, --exclude-file <file...>` - Exclude files from the scan
|
|
43
|
+
- `-F, --exclude-file-print <file...>` - Do not print the excluded files
|
|
44
|
+
- `-t, --table` - Display results in a formatted table
|
|
45
|
+
|
|
46
|
+
**Examples:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# List all files in src directory
|
|
50
|
+
qleaner qlean-list src --list-files
|
|
51
|
+
|
|
52
|
+
# List all imports in src directory
|
|
53
|
+
qleaner qlean-list src --list-imports
|
|
54
|
+
|
|
55
|
+
# List both files and imports in table format
|
|
56
|
+
qleaner qlean-list src --list-files --list-imports --table
|
|
57
|
+
|
|
58
|
+
# List files excluding node_modules and dist
|
|
59
|
+
qleaner qlean-list src --list-files -e node_modules dist
|
|
60
|
+
|
|
61
|
+
# List imports excluding specific files
|
|
62
|
+
qleaner qlean-list src --list-imports -f "**/*.test.js" "**/*.spec.js"
|
|
63
|
+
|
|
64
|
+
# List with table output
|
|
65
|
+
qleaner qlean-list src --list-files --list-imports --table
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Scan for Unused Files
|
|
69
|
+
|
|
70
|
+
Find files that are not imported anywhere in your project:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
qleaner qlean-scan <path> [options]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Options:**
|
|
77
|
+
- `-e, --exclude-dir <dir...>` - Exclude directories from the scan
|
|
78
|
+
- `-f, --exclude-file <file...>` - Exclude files from the scan
|
|
79
|
+
- `-F, --exclude-file-print <files...>` - Do not print the excluded files
|
|
80
|
+
- `-t, --table` - Display results in a formatted table
|
|
81
|
+
|
|
82
|
+
**Examples:**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Scan src directory for unused files
|
|
86
|
+
qleaner qlean-scan src
|
|
87
|
+
|
|
88
|
+
# Display unused files in a table format
|
|
89
|
+
qleaner qlean-scan src --table
|
|
90
|
+
|
|
91
|
+
# Scan excluding test directories
|
|
92
|
+
qleaner qlean-scan src -e __tests__ __mocks__ test
|
|
93
|
+
|
|
94
|
+
# Scan excluding specific file patterns
|
|
95
|
+
qleaner qlean-scan src -f "**/*.test.js" "**/*.stories.js"
|
|
96
|
+
|
|
97
|
+
# Scan with multiple exclusions
|
|
98
|
+
qleaner qlean-scan src -e node_modules dist -f "**/*.config.js"
|
|
99
|
+
|
|
100
|
+
# Scan with table output and exclusions
|
|
101
|
+
qleaner qlean-scan src --table -e __tests__ dist -f "**/*.config.js"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Output Formats
|
|
105
|
+
|
|
106
|
+
Qleaner provides two output formats:
|
|
107
|
+
|
|
108
|
+
1. **Standard output**: Color-coded text output
|
|
109
|
+
- Green for files
|
|
110
|
+
- Yellow for imports
|
|
111
|
+
- Red for unused files
|
|
112
|
+
|
|
113
|
+
2. **Table output**: Formatted tables with organized columns (use `--table` flag)
|
|
114
|
+
- Import tables show: File, Line, Column, and Import path
|
|
115
|
+
- File tables show: File path
|
|
116
|
+
- Unused files table shows: Unused file paths
|
|
117
|
+
|
|
118
|
+
## How It Works
|
|
119
|
+
|
|
120
|
+
1. **File Discovery**: Recursively finds all `.tsx`, `.ts`, `.js`, and `.jsx` files in the specified directory
|
|
121
|
+
2. **Import Extraction**: Parses files and extracts all import statements
|
|
122
|
+
3. **Analysis**: Compares file paths with import paths to identify unused files
|
|
123
|
+
4. **Reporting**: Outputs the results in standard or table format based on your preferences
|
|
124
|
+
|
|
125
|
+
## Supported File Types
|
|
126
|
+
|
|
127
|
+
- `.js` - JavaScript files
|
|
128
|
+
- `.jsx` - JavaScript React files
|
|
129
|
+
- `.ts` - TypeScript files
|
|
130
|
+
- `.tsx` - TypeScript React files
|
|
131
|
+
|
|
132
|
+
## Use Cases
|
|
133
|
+
|
|
134
|
+
- 🧹 **Code cleanup**: Remove dead code and unused files from your React projects
|
|
135
|
+
- 📊 **Code analysis**: Understand import patterns and dependencies in your codebase
|
|
136
|
+
- 🔍 **Project audit**: Identify orphaned files that may have been forgotten
|
|
137
|
+
- 📦 **Bundle optimization**: Find files that can be removed to reduce bundle size
|
|
138
|
+
- 🎯 **Maintenance**: Keep your codebase clean and maintainable
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
You can exclude directories and files from scanning using the command-line options. This is useful for:
|
|
143
|
+
- Excluding test files
|
|
144
|
+
- Excluding build outputs
|
|
145
|
+
- Excluding third-party libraries
|
|
146
|
+
- Excluding configuration files
|
|
147
|
+
|
|
148
|
+
**Common exclusions:**
|
|
149
|
+
- Test files: `-f "**/*.test.*" "**/*.spec.*"`
|
|
150
|
+
- Storybook files: `-f "**/*.stories.*"`
|
|
151
|
+
- Test directories: `-e __tests__ __mocks__ test`
|
|
152
|
+
- Build outputs: `-e dist build .next`
|
|
153
|
+
- Configuration files: `-f "**/*.config.*"`
|
|
154
|
+
- Third-party code: `-e node_modules vendor`
|
|
155
|
+
|
|
156
|
+
## Tips and Best Practices
|
|
157
|
+
|
|
158
|
+
1. **Start with a small scope**: Begin by scanning a specific directory before scanning the entire project
|
|
159
|
+
2. **Use exclusions**: Exclude test files and build outputs when scanning for unused files
|
|
160
|
+
3. **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)
|
|
161
|
+
4. **Use table format**: The table format is easier to read for large results
|
|
162
|
+
5. **Combine options**: Use multiple flags together for comprehensive analysis
|
|
163
|
+
6. **Check dynamic imports**: Files imported using dynamic imports (`import()`) may appear as unused but are actually needed
|
|
164
|
+
|
|
165
|
+
## Important Notes
|
|
166
|
+
|
|
167
|
+
⚠️ **Warning**: Always review files before deletion. Some files might be:
|
|
168
|
+
- Used dynamically (dynamic imports)
|
|
169
|
+
- Referenced in configuration files
|
|
170
|
+
- Required for build processes
|
|
171
|
+
- Used as entry points that aren't directly imported
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
- Node.js 14+
|
|
176
|
+
- Yarn or npm
|
|
177
|
+
|
|
178
|
+
## Contributing
|
|
179
|
+
|
|
180
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
|
185
|
+
|
|
186
|
+
## Version
|
|
187
|
+
|
|
188
|
+
Current version: 1.0.9
|
package/bin/cli.js
CHANGED
|
@@ -1,38 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { Command } = require("commander");
|
|
3
3
|
const { getFiles, unUsedFiles } = require("../command");
|
|
4
|
+
const Table = require('cli-table3')
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
async function loadChalk() {
|
|
7
|
+
return (await import("chalk")).default;
|
|
8
|
+
}
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
.description("A tool to clean up your React code")
|
|
10
|
-
.version("1.0.0");
|
|
10
|
+
(async () => {
|
|
11
|
+
const chalk = await loadChalk();
|
|
11
12
|
|
|
12
|
-
program
|
|
13
|
-
.command("qlean-list")
|
|
14
|
-
.description("List all the imports in the project")
|
|
15
|
-
.argument("<path>", "The path to the directory to scan for imports")
|
|
16
|
-
.option("-l, --list-files", "List all the files in the project")
|
|
17
|
-
.option("-i, --list-imports", "List all the imports in the project")
|
|
18
|
-
.option("-e, --exclude-dir <dir...>", "Exclude directories from the scan")
|
|
19
|
-
.option("-f, --exclude-file <file...>", "Exclude files from the scan")
|
|
20
|
-
.option("-F, --exclude-file-print <file...>", "Do not Print the excluded files")
|
|
21
|
-
.action(async (path, options) => {
|
|
22
|
-
const imports = await getFiles(path, options);
|
|
23
|
-
});
|
|
13
|
+
const program = new Command();
|
|
24
14
|
|
|
25
|
-
program
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
program
|
|
16
|
+
.name("qleaner")
|
|
17
|
+
.description("A tool to clean up your React code")
|
|
18
|
+
.version("1.0.0");
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command("qlean-list")
|
|
22
|
+
.description("List all the imports in the project")
|
|
23
|
+
.argument("<path>", "The path to the directory to scan for imports")
|
|
24
|
+
.option("-l, --list-files", "List all the files in the project")
|
|
25
|
+
.option("-i, --list-imports", "List all the imports in the project")
|
|
26
|
+
.option("-e, --exclude-dir <dir...>", "Exclude directories from the scan")
|
|
27
|
+
.option("-f, --exclude-file <file...>", "Exclude files from the scan")
|
|
28
|
+
.option(
|
|
29
|
+
"-F, --exclude-file-print <file...>",
|
|
30
|
+
"Do not Print the excluded files"
|
|
31
|
+
)
|
|
32
|
+
.option("-t, --table", "Print the results in a table")
|
|
33
|
+
.action(async (path, options) => {
|
|
34
|
+
const { tableImports, tableFiles } = await getFiles(path, options, chalk);
|
|
35
|
+
if(options.table){
|
|
36
|
+
console.log(chalk.yellow('***************** Imported Files *****************'));
|
|
37
|
+
console.log(tableImports.toString());
|
|
38
|
+
console.log(chalk.green('***************** List Files *****************'));
|
|
39
|
+
console.log(tableFiles.toString());
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command("qlean-scan")
|
|
45
|
+
.description("Scan the project for unused files")
|
|
46
|
+
.argument("<path>", "The path to the directory to scan for unused files")
|
|
47
|
+
.option("-e, --exclude-dir <dir...>", "Exclude directories from the scan")
|
|
48
|
+
.option("-f, --exclude-file <file...>", "Exclude files from the scan")
|
|
49
|
+
.option(
|
|
50
|
+
"-F, --exclude-file-print <files...>",
|
|
51
|
+
"Scan but don't print the excluded files"
|
|
52
|
+
)
|
|
53
|
+
.option("-t, --table", "Print the results in a table")
|
|
54
|
+
.action(async (path, options) => {
|
|
55
|
+
const unusedFiles = await unUsedFiles(path, options);
|
|
56
|
+
// console.clear()
|
|
57
|
+
if(options.table){
|
|
58
|
+
const table = new Table({
|
|
59
|
+
head: ['Unused Files'],
|
|
60
|
+
colWidths: [50]
|
|
61
|
+
})
|
|
62
|
+
unusedFiles.forEach(file => {
|
|
63
|
+
table.push([file])
|
|
64
|
+
})
|
|
65
|
+
console.log(table.toString())
|
|
66
|
+
|
|
67
|
+
}else {
|
|
68
|
+
unusedFiles.forEach((file) => {
|
|
69
|
+
console.log(chalk.red(file));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
36
72
|
});
|
|
37
|
-
|
|
38
|
-
|
|
73
|
+
program.parse(process.argv);
|
|
74
|
+
})();
|
package/command.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const fg = require("fast-glob");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
-
const
|
|
3
|
+
const Table = require('cli-table3');
|
|
4
4
|
const parser = require("@babel/parser");
|
|
5
5
|
const traverse = require("@babel/traverse").default;
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async function getFiles(directory = "src", options, chalk) {
|
|
9
11
|
const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
|
|
10
12
|
if (options.excludeDir && options.excludeDir.length > 0) {
|
|
11
13
|
options.excludeDir.forEach(dir => {
|
|
@@ -49,18 +51,42 @@ async function getFiles(directory = "src", options) {
|
|
|
49
51
|
// },
|
|
50
52
|
// });
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
if (options.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
|
|
55
|
+
if (options.table) {
|
|
56
|
+
const tableImports = new Table({
|
|
57
|
+
head: ['File', 'Line', 'Column', 'Import'],
|
|
58
|
+
colWidths: [20, 10, 10, 20],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const tableFiles = new Table({
|
|
62
|
+
head: ['File'],
|
|
63
|
+
colWidths: [20],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (options.listFiles) {
|
|
67
|
+
files.forEach((file) => {
|
|
68
|
+
tableFiles.push([file]);
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
if (options.listImports) {
|
|
72
|
+
imports.forEach((importStatement) => {
|
|
73
|
+
tableImports.push([importStatement.file, importStatement.line, importStatement.column, importStatement.from]);
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
return { tableImports, tableFiles };
|
|
77
|
+
}else{
|
|
78
|
+
if (options.listFiles) {
|
|
79
|
+
console.log(chalk.green('***************** Files *****************'));
|
|
80
|
+
files.forEach((file) => {
|
|
81
|
+
console.log(chalk.green(file));
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
if (options.listImports) {
|
|
85
|
+
console.log(chalk.yellow('***************** Imports *****************'));
|
|
86
|
+
imports.forEach((importStatement) => {
|
|
87
|
+
console.log(chalk.yellow(`${importStatement.file}:${importStatement.line}:${importStatement.column} ${importStatement.from}`));
|
|
88
|
+
})
|
|
89
|
+
}
|
|
64
90
|
}
|
|
65
91
|
}
|
|
66
92
|
|
|
@@ -76,6 +102,7 @@ async function unUsedFiles(directory = "src", options) {
|
|
|
76
102
|
contentPaths.push(`!${directory}/**/${file}`);
|
|
77
103
|
});
|
|
78
104
|
}
|
|
105
|
+
|
|
79
106
|
const files = await fg(contentPaths);
|
|
80
107
|
const imports = [];
|
|
81
108
|
const unusedFiles = [];
|
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.
|
|
4
|
+
"version": "1.0.11",
|
|
5
5
|
"main": "command.js",
|
|
6
6
|
"bin": "./bin/cli.js",
|
|
7
7
|
"scripts": {
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"@babel/parser": "^7.28.5",
|
|
16
16
|
"@babel/traverse": "^7.28.5",
|
|
17
17
|
"chalk": "^5.6.2",
|
|
18
|
+
"cli-table3": "^0.6.5",
|
|
18
19
|
"commander": "^14.0.2",
|
|
19
|
-
"fast-glob": "^3.3.3"
|
|
20
|
+
"fast-glob": "^3.3.3",
|
|
21
|
+
"prompts": "^2.4.2"
|
|
20
22
|
}
|
|
21
23
|
}
|
package/utils/utils.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const prompts = require('prompts');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
async function askDeleteFiles(files) {
|
|
6
|
+
const response = await prompts({
|
|
7
|
+
type: 'multiselect',
|
|
8
|
+
name: 'toDelete',
|
|
9
|
+
message: 'Select unused files to delete or move to .trash directory',
|
|
10
|
+
choices: files.map(file => ({
|
|
11
|
+
title: file,
|
|
12
|
+
value: file,
|
|
13
|
+
}))
|
|
14
|
+
})
|
|
15
|
+
if(response && response.toDelete && response.toDelete.length > 0) {
|
|
16
|
+
const method = await prompts({
|
|
17
|
+
type: 'select',
|
|
18
|
+
name: 'method',
|
|
19
|
+
message: 'Select a method to delete the files',
|
|
20
|
+
choices: [
|
|
21
|
+
{ title: 'Move to .trash directory', value: 'moveToTrash' },
|
|
22
|
+
{ title: 'Delete files', value: 'deleteFiles' },
|
|
23
|
+
]
|
|
24
|
+
})
|
|
25
|
+
if(method.method === 'moveToTrash') {
|
|
26
|
+
return await moveToTrash(response.toDelete);
|
|
27
|
+
} else if(method.method === 'deleteFiles') {
|
|
28
|
+
return await deleteFiles(response.toDelete);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return response && response.toDelete ? response.toDelete : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function moveToTrash(files) {
|
|
35
|
+
const trashDir = path.join(process.cwd(), '.trash');
|
|
36
|
+
|
|
37
|
+
if(!fs.existsSync(trashDir)) {
|
|
38
|
+
fs.mkdirSync(trashDir);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for(const file of files) {
|
|
42
|
+
const fileName = path.basename(file);
|
|
43
|
+
const destination = path.join(trashDir, fileName);
|
|
44
|
+
fs.renameSync(file, destination);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Moved ${files.length} files to .trash directory`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function deleteFiles(files) {
|
|
51
|
+
for(const file of files) {
|
|
52
|
+
fs.unlinkSync(file);
|
|
53
|
+
}
|
|
54
|
+
console.log(`Deleted ${files.length} files`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = askDeleteFiles;
|