qleaner 1.0.10 → 1.0.12

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
@@ -1,14 +1,15 @@
1
1
  # Qleaner
2
2
 
3
- A powerful CLI tool to analyze and clean up your React codebase by finding unused files and listing all imports. Built with Node.js and Babel AST parsing.
3
+ A powerful CLI tool to analyze and clean up your React codebase by finding unused files and listing all imports.
4
4
 
5
5
  ## Features
6
6
 
7
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
8
+ - 📋 **List all imports**: Get a complete list of all import statements in your codebase with file locations
9
9
  - 🎯 **File listing**: List all files in your project
10
+ - 📊 **Table output**: Display results in formatted tables for better readability
10
11
  - ⚙️ **Flexible configuration**: Exclude directories and files from scanning
11
- - 🚀 **Fast performance**: Uses fast-glob for efficient file scanning
12
+ - 🚀 **Fast performance**: Efficient file scanning across large codebases
12
13
  - 💪 **TypeScript support**: Works with TypeScript, JavaScript, JSX, and TSX files
13
14
 
14
15
  ## Installation
@@ -40,6 +41,7 @@ qleaner qlean-list <path> [options]
40
41
  - `-e, --exclude-dir <dir...>` - Exclude directories from the scan
41
42
  - `-f, --exclude-file <file...>` - Exclude files from the scan
42
43
  - `-F, --exclude-file-print <file...>` - Do not print the excluded files
44
+ - `-t, --table` - Display results in a formatted table
43
45
 
44
46
  **Examples:**
45
47
 
@@ -50,11 +52,17 @@ qleaner qlean-list src --list-files
50
52
  # List all imports in src directory
51
53
  qleaner qlean-list src --list-imports
52
54
 
55
+ # List both files and imports in table format
56
+ qleaner qlean-list src --list-files --list-imports --table
57
+
53
58
  # List files excluding node_modules and dist
54
59
  qleaner qlean-list src --list-files -e node_modules dist
55
60
 
56
61
  # List imports excluding specific files
57
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
58
66
  ```
59
67
 
60
68
  ### Scan for Unused Files
@@ -69,6 +77,7 @@ qleaner qlean-scan <path> [options]
69
77
  - `-e, --exclude-dir <dir...>` - Exclude directories from the scan
70
78
  - `-f, --exclude-file <file...>` - Exclude files from the scan
71
79
  - `-F, --exclude-file-print <files...>` - Do not print the excluded files
80
+ - `-t, --table` - Display results in a formatted table
72
81
 
73
82
  **Examples:**
74
83
 
@@ -76,6 +85,9 @@ qleaner qlean-scan <path> [options]
76
85
  # Scan src directory for unused files
77
86
  qleaner qlean-scan src
78
87
 
88
+ # Display unused files in a table format
89
+ qleaner qlean-scan src --table
90
+
79
91
  # Scan excluding test directories
80
92
  qleaner qlean-scan src -e __tests__ __mocks__ test
81
93
 
@@ -84,14 +96,31 @@ qleaner qlean-scan src -f "**/*.test.js" "**/*.stories.js"
84
96
 
85
97
  # Scan with multiple exclusions
86
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"
87
102
  ```
88
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
+
89
118
  ## How It Works
90
119
 
91
- 1. **File Discovery**: Uses `fast-glob` to recursively find all `.tsx`, `.ts`, `.js`, and `.jsx` files in the specified directory
92
- 2. **AST Parsing**: Uses Babel parser to parse files and extract import statements
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
93
122
  3. **Analysis**: Compares file paths with import paths to identify unused files
94
- 4. **Reporting**: Outputs the results based on the selected command and options
123
+ 4. **Reporting**: Outputs the results in standard or table format based on your preferences
95
124
 
96
125
  ## Supported File Types
97
126
 
@@ -106,6 +135,7 @@ qleaner qlean-scan src -e node_modules dist -f "**/*.config.js"
106
135
  - 📊 **Code analysis**: Understand import patterns and dependencies in your codebase
107
136
  - 🔍 **Project audit**: Identify orphaned files that may have been forgotten
108
137
  - 📦 **Bundle optimization**: Find files that can be removed to reduce bundle size
138
+ - 🎯 **Maintenance**: Keep your codebase clean and maintainable
109
139
 
110
140
  ## Configuration
111
141
 
@@ -115,6 +145,31 @@ You can exclude directories and files from scanning using the command-line optio
115
145
  - Excluding third-party libraries
116
146
  - Excluding configuration files
117
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
+
118
173
  ## Requirements
119
174
 
120
175
  - 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
- const { getFiles, unUsedFiles } = require("../command");
4
- const Table = require('cli-table3')
3
+ const { list, scan } = require("../controllers/list");
4
+
5
5
 
6
6
  async function loadChalk() {
7
7
  return (await import("chalk")).default;
@@ -31,13 +31,7 @@ async function loadChalk() {
31
31
  )
32
32
  .option("-t, --table", "Print the results in a table")
33
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
- }
34
+ await list(chalk, path, options);
41
35
  });
42
36
 
43
37
  program
@@ -48,27 +42,27 @@ async function loadChalk() {
48
42
  .option("-f, --exclude-file <file...>", "Exclude files from the scan")
49
43
  .option(
50
44
  "-F, --exclude-file-print <files...>",
51
- "Do not Print the excluded files"
45
+ "Scan but don't print the excluded files"
52
46
  )
53
47
  .option("-t, --table", "Print the results in a table")
48
+ .option("-d, --dry-run", "Show what would be deleted without actually deleting (skips prompt)")
54
49
  .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
- }
50
+ await scan(chalk, path, options);
72
51
  });
52
+
53
+ program
54
+ .command('qlean-image')
55
+ .description("Scan the project for unused images")
56
+ .argument("<path>", "The path to the directory to scan for unused images")
57
+ .option("-e, --exclude-dir <dir...>", "Exclude directories from the scan")
58
+ .option("-f, --exclude-file <file...>", "Exclude files from the scan")
59
+ .option(
60
+ "-F, --exclude-file-print <files...>",
61
+ "Scan but don't print the excluded files"
62
+ )
63
+ .option("-t, --table", "Print the results in a table")
64
+ .action(async (path, options) => {
65
+
66
+ });
73
67
  program.parse(process.argv);
74
68
  })();
package/command.js CHANGED
@@ -1,25 +1,26 @@
1
1
  const fg = require("fast-glob");
2
2
  const fs = require("fs");
3
- const Table = require('cli-table3');
3
+ const Table = require("cli-table3");
4
4
  const parser = require("@babel/parser");
5
5
  const traverse = require("@babel/traverse").default;
6
-
7
-
8
-
6
+ const path = require("path");
7
+ const { createResolver } = require("./utils/resolver");
8
+ const { loadCache, getFileHash, needsRebuild, saveCache } = require("./utils/cache");
9
9
 
10
10
  async function getFiles(directory = "src", options, chalk) {
11
- const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
12
- if (options.excludeDir && options.excludeDir.length > 0) {
13
- options.excludeDir.forEach(dir => {
14
- contentPaths.push(`!${dir}/**`);
15
- });
16
- }
17
- if (options.excludeFile && options.excludeFile.length > 0) {
18
- options.excludeFile.forEach(file => {
19
- contentPaths.push(`!${directory}/**/${file}`);
20
- });
21
- }
22
- const files = await fg(contentPaths);
11
+ const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
12
+ if (options.excludeDir && options.excludeDir.length > 0) {
13
+ options.excludeDir.forEach((dir) => {
14
+ contentPaths.push(`!${dir}/**`);
15
+ });
16
+ }
17
+ if (options.excludeFile && options.excludeFile.length > 0) {
18
+ options.excludeFile.forEach((file) => {
19
+ contentPaths.push(`!${directory}/**/${file}`);
20
+ });
21
+ }
22
+
23
+ const files = await fg(contentPaths);
23
24
  const imports = [];
24
25
  for (const file of files) {
25
26
  const code = fs.readFileSync(file, "utf8");
@@ -30,13 +31,12 @@ async function getFiles(directory = "src", options, chalk) {
30
31
  traverse(ast, {
31
32
  ImportDeclaration: ({ node }) => {
32
33
  imports.push({
33
- from: node.source.value,
34
- file: file,
35
- line: node.loc.start.line,
36
- column: node.loc.start.column,
34
+ from: node.source.value,
35
+ file: file,
36
+ line: node.loc.start.line,
37
+ column: node.loc.start.column,
37
38
  });
38
39
  },
39
-
40
40
  });
41
41
  // ExportNamedDeclaration: ({ node }) => {
42
42
  // if (node.declaration.declarations && node.declaration.declarations[0].id && node.declaration.declarations[0].id.name) {
@@ -51,123 +51,166 @@ async function getFiles(directory = "src", options, chalk) {
51
51
  // },
52
52
  // });
53
53
  }
54
-
54
+
55
55
  if (options.table) {
56
56
  const tableImports = new Table({
57
- head: ['File', 'Line', 'Column', 'Import'],
58
- colWidths: [20, 10, 10, 20],
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]);
59
69
  });
60
-
61
- const tableFiles = new Table({
62
- head: ['File'],
63
- colWidths: [20],
70
+ }
71
+ if (options.listImports) {
72
+ imports.forEach((importStatement) => {
73
+ tableImports.push([
74
+ importStatement.file,
75
+ importStatement.line,
76
+ importStatement.column,
77
+ importStatement.from,
78
+ ]);
64
79
  });
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{
80
+ }
81
+ return { tableImports, tableFiles };
82
+ } else {
78
83
  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
- }
84
+ console.log(chalk.green("***************** Files *****************"));
85
+ files.forEach((file) => {
86
+ console.log(chalk.green(file));
87
+ });
88
+ }
89
+ if (options.listImports) {
90
+ console.log(chalk.yellow("***************** Imports *****************"));
91
+ imports.forEach((importStatement) => {
92
+ console.log(
93
+ chalk.yellow(
94
+ `${importStatement.file}:${importStatement.line}:${importStatement.column} ${importStatement.from}`
95
+ )
96
+ );
97
+ });
98
+ }
99
+ return { tableImports: imports, tableFiles: files };
90
100
  }
91
101
  }
92
102
 
93
- async function unUsedFiles(directory = "src", options) {
94
- const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
95
- if (options.excludeDir && options.excludeDir.length > 0) {
96
- options.excludeDir.forEach(dir => {
97
- contentPaths.push(`!${dir}/**`);
98
- });
99
- }
100
- if (options.excludeFile && options.excludeFile.length > 0) {
101
- options.excludeFile.forEach(file => {
102
- contentPaths.push(`!${directory}/**/${file}`);
103
- });
104
- }
105
-
106
- const files = await fg(contentPaths);
107
- const imports = [];
108
- const unusedFiles = [];
109
- for (const file of files) {
110
- const code = fs.readFileSync(file, "utf8");
103
+ async function unUsedFiles(chalk, directory = "src", options) {
104
+ console.time('unUsedFiles');
105
+ const resolver = createResolver(directory);
106
+ const cache = loadCache(process.cwd());
107
+ const contentPaths = [`${directory}/**/*.{tsx,ts,js,jsx}`];
108
+ if (options.excludeDir && options.excludeDir.length > 0) {
109
+ options.excludeDir.forEach((dir) => {
110
+ contentPaths.push(`!${dir}/**`);
111
+ });
112
+ }
113
+ if (options.excludeFile && options.excludeFile.length > 0) {
114
+ options.excludeFile.forEach((file) => {
115
+ contentPaths.push(`!${directory}/**/${file}`);
116
+ });
117
+ }
118
+
119
+ const files = await fg(contentPaths);
120
+ const imports = [];
121
+ const unusedFiles = [];
122
+
123
+ // debug log
124
+ // let debugCount = 0;
125
+ // console.log('total files---', files.length);
126
+ for (const file of files) {
127
+ const code = fs.readFileSync(file, "utf8");
128
+ if(needsRebuild(file, code, cache)) {
111
129
  const ast = parser.parse(code, {
112
130
  sourceType: "module",
113
- plugins: [
114
- "jsx",
115
- "typescript",
116
- ],
131
+ plugins: ["jsx", "typescript"],
117
132
  });
118
-
133
+ cache[file] = {
134
+ hash: getFileHash(code),
135
+ imports: [],
136
+ isImported: false,
137
+ lastModified: fs.statSync(file).mtime.getTime(),
138
+ };
119
139
  traverse(ast, {
120
140
  ImportDeclaration: ({ node }) => {
121
141
  imports.push({
122
- from: node.source.value,
123
- file: file,
124
- line: node.loc.start.line,
125
- column: node.loc.start.column,
142
+ from: node.source.value,
143
+ file: file,
144
+ line: node.loc.start.line,
145
+ column: node.loc.start.column,
146
+ });
147
+ cache[file].imports.push({
148
+ from: node.source.value,
149
+ file: file,
150
+ line: node.loc.start.line,
151
+ column: node.loc.start.column,
152
+ lastModified: fs.statSync(file).mtime.getTime(),
126
153
  });
127
154
  },
128
155
  });
156
+ }else {
157
+ // debugCount++;
158
+ // console.log('cache hit', debugCount);
129
159
  }
160
+ }
130
161
 
131
- // console.log('imports', imports);
132
- // console.log('files', files);
133
- for (const file of files) {
134
- let i = 0;
135
- // console.log('Checking', file);
136
- let isFound = false;
137
- while (!isFound && i < imports.length) {
138
-
139
- if (compareFiles(file, imports[i].from)) {
140
- isFound = true;
141
- break;
142
- }else if(i === imports.length - 1) {
143
- if(options.excludeFilePrint && options.excludeFilePrint.length > 0) {
144
- if(!isExcludedFile(file, options.excludeFilePrint)){
145
- unusedFiles.push(file);
146
- }
147
- }else {
148
- unusedFiles.push(file);
149
- }
150
- break;
162
+ // console.log('imports', imports);
163
+ // console.log('files', files);
164
+ // debugCount = 0;
165
+ // console.log('total files', files.length);
166
+ for (const file of files) {
167
+ const code = fs.readFileSync(file, "utf8");
168
+ if(!cache[file].isImported || needsRebuild(file, code, cache)) {
169
+ let i = 0;
170
+ // console.log('Checking', file);
171
+ let isFound = false;
172
+ while (!isFound && i < imports.length) {
173
+ const importFilePath = await resolver(
174
+ chalk,
175
+ imports[i].file,
176
+ imports[i].from
177
+ );
178
+ // console.log(chalk.blue('importFilePath'), importFilePath);
179
+ if (compareFiles(path.resolve(file), importFilePath)) {
180
+ isFound = true;
181
+ cache[file].isImported = true;
182
+ break;
183
+ } else if (i === imports.length - 1) {
184
+ if (options.excludeFilePrint && options.excludeFilePrint.length > 0) {
185
+ if (!isExcludedFile(file, options.excludeFilePrint)) {
186
+ unusedFiles.push(file);
151
187
  }
152
- i++;
188
+ } else {
189
+ unusedFiles.push(file);
190
+ }
191
+ break;
153
192
  }
193
+ i++;
194
+ }
195
+ }else {
196
+ // debugCount++;
197
+ // console.log('debug hit', debugCount);
154
198
  }
155
- return unusedFiles;
199
+ }
200
+ console.timeEnd('unUsedFiles');
201
+ saveCache(process.cwd(), cache);
202
+ return unusedFiles;
156
203
  }
157
204
 
158
205
  function isExcludedFile(file, excludeFiles) {
159
- return excludeFiles.some(exclude => file.includes(exclude));
206
+ return excludeFiles.some((exclude) => file.includes(exclude));
160
207
  }
161
208
 
162
- function compareFiles(filePath, importPath) {
163
- const importNoExt = importPath.replace(/\.[^/.]+$/, "");
164
- const prefixImportPath = importNoExt.replace(/^(?:\.{1,2}\/|[@~]+\/)+/, "");
165
- // console.log('importNoExt', importNoExt, ' prefixImportPath', prefixImportPath, ' filePath', filePath, ' ', filePath.includes(prefixImportPath));
166
-
167
- return filePath.includes(prefixImportPath);
209
+ function compareFiles(filePath, importPath) {
210
+ return filePath === importPath;
168
211
  }
169
212
 
170
213
  module.exports = {
171
214
  getFiles,
172
215
  unUsedFiles,
173
- };
216
+ };
@@ -0,0 +1,60 @@
1
+ const { getFiles, unUsedFiles } = require("../command");
2
+ const Table = require('cli-table3');
3
+ const askDeleteFiles = require("../utils/utils");
4
+
5
+
6
+ async function list(chalk, path, options) {
7
+ const { tableImports, tableFiles } = await getFiles(path, options, chalk);
8
+ if(options.table){
9
+ console.log(chalk.yellow('***************** Imported Files *****************'));
10
+ console.log(tableImports.toString());
11
+ console.log(chalk.green('***************** List Files *****************'));
12
+ console.log(tableFiles.toString());
13
+ }
14
+ }
15
+
16
+ async function scan(chalk, path, options) {
17
+ const unusedFiles = await unUsedFiles(chalk,path, options);
18
+ // console.clear()
19
+
20
+ if (options.dryRun) {
21
+ console.log(chalk.cyan('\n[DRY RUN MODE] No files will be deleted\n'));
22
+ }
23
+
24
+ if(options.table){
25
+ const table = new Table({
26
+ head: options.dryRun ? ['Unused Files (Would Delete)'] : ['Unused Files'],
27
+ colWidths: [50]
28
+ })
29
+ unusedFiles.forEach(file => {
30
+ table.push([file])
31
+ })
32
+ console.log(table.toString())
33
+
34
+ }else {
35
+ unusedFiles.forEach((file) => {
36
+ console.log(chalk.red(file));
37
+ });
38
+ }
39
+
40
+ if (options.dryRun && unusedFiles.length > 0) {
41
+ console.log(chalk.cyan(`\n[DRY RUN] Would delete ${unusedFiles.length} file(s)`));
42
+ console.log(chalk.cyan('Run without --dry-run to actually delete files\n'));
43
+ } else if (!options.dryRun && unusedFiles.length > 0) {
44
+ askDeleteFiles(unusedFiles);
45
+ }
46
+ }
47
+
48
+ async function image(chalk, path, options) {
49
+ const unusedImages = await unUsedImages(chalk,path, options);
50
+ if(options.table){
51
+ console.log(chalk.yellow('***************** Unused Images *****************'));
52
+ console.log(unusedImages.toString());
53
+ }
54
+ }
55
+
56
+
57
+ module.exports = {
58
+ list,
59
+ scan
60
+ };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "qleaner",
3
3
  "packageManager": "yarn@4.6.0",
4
- "version": "1.0.10",
4
+ "version": "1.0.12",
5
5
  "main": "command.js",
6
6
  "bin": "./bin/cli.js",
7
7
  "scripts": {
8
- "start": "node ./bin/cli.js"
8
+ "start": "node ./bin/cli.js",
9
+ "resolve": "node ./utils/resolver.js"
9
10
  },
10
11
  "devDependencies": {
11
12
  "@types/node": "^20.10.5",
@@ -17,6 +18,8 @@
17
18
  "chalk": "^5.6.2",
18
19
  "cli-table3": "^0.6.5",
19
20
  "commander": "^14.0.2",
20
- "fast-glob": "^3.3.3"
21
+ "enhanced-resolve": "^5.18.3",
22
+ "fast-glob": "^3.3.3",
23
+ "prompts": "^2.4.2"
21
24
  }
22
25
  }
@@ -0,0 +1,180 @@
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
+ }
package/utils/cache.js ADDED
@@ -0,0 +1,38 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+
5
+
6
+ function loadCache(rootPath) {
7
+ const file = path.join(rootPath, "unused-check-cache.json");
8
+ if(!fs.existsSync(file)) return {};
9
+
10
+ try {
11
+ return JSON.parse(fs.readFileSync(file, 'utf8'))
12
+ } catch{
13
+ return {};
14
+ }
15
+ }
16
+
17
+ function getFileHash(content) {
18
+ return crypto.createHash('md5').update(content).digest('hex');
19
+ }
20
+
21
+
22
+ function needsRebuild(file, content, cache) {
23
+ const hash = getFileHash(content);
24
+ return !cache[file] || cache[file].hash !== hash
25
+ }
26
+
27
+
28
+ function saveCache(rootPath, cache) {
29
+ const file = path.join(rootPath, "unused-check-cache.json");
30
+ fs.writeFileSync(file, JSON.stringify(cache, null, 2))
31
+ }
32
+
33
+ module.exports = {
34
+ loadCache,
35
+ getFileHash,
36
+ needsRebuild,
37
+ saveCache
38
+ }
@@ -0,0 +1,32 @@
1
+ const path = require('path');
2
+ const {create} = require('enhanced-resolve');
3
+
4
+
5
+ function createResolver(directory) {
6
+ const resolver = create({
7
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
8
+ alias: {
9
+ "@": path.resolve(directory),
10
+ "~": path.resolve(directory)
11
+ },
12
+ mainFiles: ["index"],
13
+ // Where resolution begins
14
+ modules: [
15
+ path.resolve(directory)
16
+ ]
17
+ });
18
+
19
+ return function resolveImport(chalk,sourceFile, importPath) {
20
+ // console.log(chalk.yellow('sourceFile--resolver'), sourceFile, chalk.yellow('importPath--resolver'), importPath);
21
+ return new Promise((resolve, reject) => {
22
+ resolver(path.dirname(sourceFile), importPath, (err, result) => {
23
+ if (err) return resolve(null);
24
+ resolve(path.resolve(result));
25
+ });
26
+ });
27
+ }
28
+ }
29
+
30
+ module.exports = {
31
+ createResolver
32
+ };
package/utils/utils.js ADDED
@@ -0,0 +1,56 @@
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
+ }
32
+
33
+ async function moveToTrash(files) {
34
+ const trashDir = path.join(process.cwd(), '.trash');
35
+
36
+ if(!fs.existsSync(trashDir)) {
37
+ fs.mkdirSync(trashDir);
38
+ }
39
+
40
+ for(const file of files) {
41
+ const fileName = path.basename(file);
42
+ const destination = path.join(trashDir, fileName);
43
+ fs.renameSync(file, destination);
44
+ }
45
+
46
+ console.log(`Moved ${files.length} files to .trash directory`);
47
+ }
48
+
49
+ async function deleteFiles(files) {
50
+ for(const file of files) {
51
+ fs.unlinkSync(file);
52
+ }
53
+ console.log(`Deleted ${files.length} files`);
54
+ }
55
+
56
+ module.exports = askDeleteFiles;