react-prune 1.0.0 → 1.1.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 +22 -4
- package/dist/cli.js +1 -247
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -224
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +1 -183
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -146
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -11
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# react-prune ✂️
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/react-prune)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.npmjs.com/package/react-prune)
|
|
6
|
+
|
|
3
7
|
> **Monitor usage of packages and component imports across your React, Next.js, and React Native apps.**
|
|
4
8
|
|
|
5
9
|
`react-prune` is a powerful CLI tool designed to help you maintain a healthy codebase by identifying unused files, analyzing package usage, and estimating dependency sizes.
|
|
@@ -14,16 +18,30 @@
|
|
|
14
18
|
|
|
15
19
|
## 📦 Installation
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
To save `react-prune` to your `package.json` (recommended as a Dev Dependency):
|
|
22
|
+
|
|
23
|
+
### Using npm
|
|
18
24
|
|
|
19
25
|
```bash
|
|
20
|
-
|
|
26
|
+
npm install -D react-prune
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Using yarn
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add -D react-prune
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Using pnpm
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add -D react-prune
|
|
21
39
|
```
|
|
22
40
|
|
|
23
|
-
|
|
41
|
+
You can also run it one-off using `npx`:
|
|
24
42
|
|
|
25
43
|
```bash
|
|
26
|
-
|
|
44
|
+
npx react-prune analyze
|
|
27
45
|
```
|
|
28
46
|
|
|
29
47
|
## 🛠 Usage
|
package/dist/cli.js
CHANGED
|
@@ -1,249 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
-
mod
|
|
24
|
-
));
|
|
25
|
-
|
|
26
|
-
// src/cli.ts
|
|
27
|
-
var import_commander = require("commander");
|
|
28
|
-
var import_chalk2 = __toESM(require("chalk"));
|
|
29
|
-
|
|
30
|
-
// src/analyzer.ts
|
|
31
|
-
var import_ts_morph = require("ts-morph");
|
|
32
|
-
var import_chalk = __toESM(require("chalk"));
|
|
33
|
-
var import_glob = require("glob");
|
|
34
|
-
var import_path = __toESM(require("path"));
|
|
35
|
-
var import_fs = __toESM(require("fs"));
|
|
36
|
-
function getFolderSize(dirPath) {
|
|
37
|
-
let size = 0;
|
|
38
|
-
try {
|
|
39
|
-
const files = import_fs.default.readdirSync(dirPath);
|
|
40
|
-
for (const file of files) {
|
|
41
|
-
const filePath = import_path.default.join(dirPath, file);
|
|
42
|
-
const stats = import_fs.default.statSync(filePath);
|
|
43
|
-
if (stats.isDirectory()) {
|
|
44
|
-
size += getFolderSize(filePath);
|
|
45
|
-
} else {
|
|
46
|
-
size += stats.size;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
} catch (e) {
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
return size;
|
|
53
|
-
}
|
|
54
|
-
function formatBytes(bytes, decimals = 2) {
|
|
55
|
-
if (bytes === 0) return "0 Bytes";
|
|
56
|
-
const k = 1024;
|
|
57
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
58
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
59
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
60
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
61
|
-
}
|
|
62
|
-
function getPackageSize(rootPath, packageName) {
|
|
63
|
-
const pkgPath = import_path.default.join(rootPath, "node_modules", packageName);
|
|
64
|
-
if (import_fs.default.existsSync(pkgPath)) {
|
|
65
|
-
const size = getFolderSize(pkgPath);
|
|
66
|
-
return formatBytes(size);
|
|
67
|
-
}
|
|
68
|
-
return "N/A";
|
|
69
|
-
}
|
|
70
|
-
async function analyzeProject(rootPath) {
|
|
71
|
-
console.log(import_chalk.default.green(`Analyzing project at ${rootPath}`));
|
|
72
|
-
const files = await (0, import_glob.glob)("**/*.{js,jsx,ts,tsx}", {
|
|
73
|
-
cwd: rootPath,
|
|
74
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"],
|
|
75
|
-
absolute: true
|
|
76
|
-
});
|
|
77
|
-
console.log(import_chalk.default.blue(`Found ${files.length} files to analyze.`));
|
|
78
|
-
const project = new import_ts_morph.Project({
|
|
79
|
-
skipAddingFilesFromTsConfig: true
|
|
80
|
-
});
|
|
81
|
-
files.forEach((file) => {
|
|
82
|
-
try {
|
|
83
|
-
project.addSourceFileAtPath(file);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
console.warn(import_chalk.default.yellow(`Skipping file ${file} due to load error:`), e);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
const packageUsage = {};
|
|
89
|
-
const localUsage = {};
|
|
90
|
-
files.forEach((f) => {
|
|
91
|
-
const relative = import_path.default.relative(rootPath, f);
|
|
92
|
-
localUsage[relative] = 0;
|
|
93
|
-
});
|
|
94
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
95
|
-
const imports = sourceFile.getImportDeclarations();
|
|
96
|
-
for (const imp of imports) {
|
|
97
|
-
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
98
|
-
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
99
|
-
try {
|
|
100
|
-
const sourceFilePath = sourceFile.getFilePath();
|
|
101
|
-
const sourceDir = import_path.default.dirname(sourceFilePath);
|
|
102
|
-
const resolvedPath = import_path.default.resolve(sourceDir, moduleSpecifier);
|
|
103
|
-
const extensions = [
|
|
104
|
-
"",
|
|
105
|
-
".ts",
|
|
106
|
-
".tsx",
|
|
107
|
-
".js",
|
|
108
|
-
".jsx",
|
|
109
|
-
"/index.ts",
|
|
110
|
-
"/index.tsx",
|
|
111
|
-
"/index.js",
|
|
112
|
-
"/index.jsx"
|
|
113
|
-
];
|
|
114
|
-
for (const ext of extensions) {
|
|
115
|
-
const tryPath = resolvedPath + ext;
|
|
116
|
-
const relativeTry = import_path.default.relative(rootPath, tryPath);
|
|
117
|
-
if (localUsage.hasOwnProperty(relativeTry)) {
|
|
118
|
-
localUsage[relativeTry]++;
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} catch (e) {
|
|
123
|
-
}
|
|
124
|
-
} else {
|
|
125
|
-
let packageName = moduleSpecifier;
|
|
126
|
-
if (moduleSpecifier.startsWith("@")) {
|
|
127
|
-
const parts = moduleSpecifier.split("/");
|
|
128
|
-
if (parts.length >= 2) {
|
|
129
|
-
packageName = `${parts[0]}/${parts[1]}`;
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
const parts = moduleSpecifier.split("/");
|
|
133
|
-
if (parts.length >= 1) {
|
|
134
|
-
packageName = parts[0];
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
141
|
-
import_ts_morph.SyntaxKind.CallExpression
|
|
142
|
-
);
|
|
143
|
-
for (const call of callExpressions) {
|
|
144
|
-
const expression = call.getExpression();
|
|
145
|
-
if (expression.getText() === "require") {
|
|
146
|
-
const args = call.getArguments();
|
|
147
|
-
if (args.length > 0 && args[0].getKind() === import_ts_morph.SyntaxKind.StringLiteral) {
|
|
148
|
-
const rawArg = args[0].getText().replace(/['"`]/g, "");
|
|
149
|
-
if (!rawArg.startsWith(".") && !rawArg.startsWith("/")) {
|
|
150
|
-
let pkg = rawArg.startsWith("@") ? rawArg.split("/").slice(0, 2).join("/") : rawArg.split("/")[0];
|
|
151
|
-
packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const reportPackages = {};
|
|
158
|
-
for (const [pkg, count] of Object.entries(packageUsage)) {
|
|
159
|
-
const size = getPackageSize(rootPath, pkg);
|
|
160
|
-
reportPackages[pkg] = { count, size };
|
|
161
|
-
}
|
|
162
|
-
const unused = Object.entries(localUsage).filter(([file, count]) => {
|
|
163
|
-
if (file.includes("pages/") || file.includes("app/") || file.includes("main.tsx") || file.includes("index.tsx"))
|
|
164
|
-
return false;
|
|
165
|
-
return count === 0;
|
|
166
|
-
}).map(([file]) => file);
|
|
167
|
-
return {
|
|
168
|
-
packages: reportPackages,
|
|
169
|
-
components: localUsage,
|
|
170
|
-
unusedFiles: unused
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// src/cli.ts
|
|
175
|
-
var import_cli_table3 = __toESM(require("cli-table3"));
|
|
176
|
-
var import_boxen = __toESM(require("boxen"));
|
|
177
|
-
var program = new import_commander.Command();
|
|
178
|
-
program.name("react-prune").description(
|
|
179
|
-
"Monitor usage of packages and component imports across your React/Next.js/React Native app"
|
|
180
|
-
).version("1.0.0");
|
|
181
|
-
program.command("analyze").description("Analyze the current project for package and component usage").action(async () => {
|
|
182
|
-
console.log(import_chalk2.default.blue("Starting analysis..."));
|
|
183
|
-
try {
|
|
184
|
-
const report = await analyzeProject(process.cwd());
|
|
185
|
-
const packageTable = new import_cli_table3.default({
|
|
186
|
-
head: [
|
|
187
|
-
import_chalk2.default.cyan("Package Name"),
|
|
188
|
-
import_chalk2.default.cyan("Usage Count"),
|
|
189
|
-
import_chalk2.default.cyan("Est. Size")
|
|
190
|
-
],
|
|
191
|
-
colWidths: [40, 15, 15]
|
|
192
|
-
});
|
|
193
|
-
const sortedPackages = Object.entries(report.packages).sort(
|
|
194
|
-
(a, b) => b[1].count - a[1].count
|
|
195
|
-
);
|
|
196
|
-
sortedPackages.slice(0, 50).forEach(([pkg, data]) => {
|
|
197
|
-
packageTable.push([pkg, data.count, data.size]);
|
|
198
|
-
});
|
|
199
|
-
console.log(
|
|
200
|
-
(0, import_boxen.default)(import_chalk2.default.bold("\u{1F4E6} Package Usage Report"), {
|
|
201
|
-
padding: 1,
|
|
202
|
-
margin: 1,
|
|
203
|
-
borderStyle: "round",
|
|
204
|
-
borderColor: "green"
|
|
205
|
-
})
|
|
206
|
-
);
|
|
207
|
-
console.log(packageTable.toString());
|
|
208
|
-
if (sortedPackages.length > 50) {
|
|
209
|
-
console.log(
|
|
210
|
-
import_chalk2.default.gray(`...and ${sortedPackages.length - 50} more packages.`)
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
if (report.unusedFiles.length > 0) {
|
|
214
|
-
console.log(
|
|
215
|
-
(0, import_boxen.default)(import_chalk2.default.bold("\u26A0\uFE0F Potential Unused Files"), {
|
|
216
|
-
padding: 1,
|
|
217
|
-
margin: 1,
|
|
218
|
-
borderStyle: "round",
|
|
219
|
-
borderColor: "yellow"
|
|
220
|
-
})
|
|
221
|
-
);
|
|
222
|
-
const unusedTable = new import_cli_table3.default({
|
|
223
|
-
head: [import_chalk2.default.yellow("File Path")],
|
|
224
|
-
colWidths: [80]
|
|
225
|
-
});
|
|
226
|
-
report.unusedFiles.slice(0, 50).forEach((file) => unusedTable.push([file]));
|
|
227
|
-
console.log(unusedTable.toString());
|
|
228
|
-
if (report.unusedFiles.length > 50) {
|
|
229
|
-
console.log(
|
|
230
|
-
import_chalk2.default.gray(`...and ${report.unusedFiles.length - 50} more files.`)
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
console.log(
|
|
235
|
-
(0, import_boxen.default)(import_chalk2.default.bold("\u2705 No unused files detected!"), {
|
|
236
|
-
padding: 1,
|
|
237
|
-
margin: 1,
|
|
238
|
-
borderStyle: "round",
|
|
239
|
-
borderColor: "green"
|
|
240
|
-
})
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error(import_chalk2.default.red("Analysis failed:"), error);
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
program.parse(process.argv);
|
|
2
|
+
'use strict';var commander=require('commander'),a=require('picocolors'),tsMorph=require('ts-morph'),R=require('fast-glob'),p=require('path'),y=require('fs'),z=require('cli-table3'),x=require('boxen');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var a__default=/*#__PURE__*/_interopDefault(a);var R__default=/*#__PURE__*/_interopDefault(R);var p__default=/*#__PURE__*/_interopDefault(p);var y__default=/*#__PURE__*/_interopDefault(y);var z__default=/*#__PURE__*/_interopDefault(z);var x__default=/*#__PURE__*/_interopDefault(x);function S(e){let n=0;try{let r=y__default.default.readdirSync(e);for(let s of r){let o=p__default.default.join(e,s),g=y__default.default.statSync(o);g.isDirectory()?n+=S(o):n+=g.size;}}catch{return 0}return n}function E(e,n=2){if(e===0)return "0 Bytes";let r=1024,s=n<0?0:n,o=["Bytes","KB","MB","GB","TB"],g=Math.floor(Math.log(e)/Math.log(r));return parseFloat((e/Math.pow(r,g)).toFixed(s))+" "+o[g]}function T(e,n){let r=p__default.default.join(e,"node_modules",n);if(y__default.default.existsSync(r)){let s=S(r);return E(s)}return "N/A"}async function F(e){console.log(a__default.default.green(`Analyzing project at ${e}`));let n=await R__default.default("**/*.{js,jsx,ts,tsx}",{cwd:e,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(a__default.default.blue(`Found ${n.length} files to analyze.`));let r=new tsMorph.Project({skipAddingFilesFromTsConfig:true});n.forEach(t=>{try{r.addSourceFileAtPath(t);}catch(l){console.warn(a__default.default.yellow(`Skipping file ${t} due to load error:`),l);}});let s={},o={};n.forEach(t=>{let l=p__default.default.relative(e,t);o[l]=0;});for(let t of r.getSourceFiles()){let l=t.getImportDeclarations();for(let f of l){let d=f.getModuleSpecifierValue();if(d.startsWith(".")||d.startsWith("/"))try{let c=t.getFilePath(),i=p__default.default.dirname(c),m=p__default.default.resolve(i,d),v=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let P of v){let W=m+P,j=p__default.default.relative(e,W);if(o.hasOwnProperty(j)){o[j]++;break}}}catch{}else {let c=d;if(d.startsWith("@")){let i=d.split("/");i.length>=2&&(c=`${i[0]}/${i[1]}`);}else {let i=d.split("/");i.length>=1&&(c=i[0]);}s[c]=(s[c]||0)+1;}}let u=t.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression);for(let f of u)if(f.getExpression().getText()==="require"){let c=f.getArguments();if(c.length>0&&c[0].getKind()===tsMorph.SyntaxKind.StringLiteral){let i=c[0].getText().replace(/['"`]/g,"");if(!i.startsWith(".")&&!i.startsWith("/")){let m=i.startsWith("@")?i.split("/").slice(0,2).join("/"):i.split("/")[0];s[m]=(s[m]||0)+1;}}}}let g={};for(let[t,l]of Object.entries(s)){let u=T(e,t);g[t]={count:l,size:u};}let w=Object.entries(o).filter(([t,l])=>t.includes("pages/")||t.includes("app/")||t.endsWith("main.tsx")||t.endsWith("index.tsx")||t.endsWith("index.js")||t.endsWith("App.tsx")||t.endsWith("App.js")||!p__default.default.relative(e,t).includes(p__default.default.sep)?false:l===0).map(([t])=>t);return {packages:g,components:o,unusedFiles:w}}var b=new commander.Command;b.name("react-prune").description("Monitor usage of packages and component imports across your React/Next.js/React Native app").version("1.0.0");b.command("analyze").description("Analyze the current project for package and component usage").action(async()=>{console.log(a__default.default.blue("Starting analysis..."));try{let e=await F(process.cwd()),n=new z__default.default({head:[a__default.default.cyan("Package Name"),a__default.default.cyan("Usage Count"),a__default.default.cyan("Est. Size")],colWidths:[40,15,15]}),r=Object.entries(e.packages).sort((s,o)=>o[1].count-s[1].count);if(r.slice(0,50).forEach(([s,o])=>{n.push([s,o.count,o.size]);}),console.log(x__default.default(a__default.default.bold("\u{1F4E6} Package Usage Report"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"})),console.log(n.toString()),r.length>50&&console.log(a__default.default.gray(`...and ${r.length-50} more packages.`)),e.unusedFiles.length>0){let s=new z__default.default({head:[a__default.default.yellow("File Path")],colWidths:[80]});console.log(x__default.default(a__default.default.bold(`\u26A0\uFE0F Potential Unused Files (${e.unusedFiles.length})`),{padding:1,margin:1,borderStyle:"round",borderColor:"yellow"})),e.unusedFiles.forEach(o=>s.push([o])),console.log(s.toString());}else console.log(x__default.default(a__default.default.bold("\u2705 No unused files detected!"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"}));}catch(e){console.error(a__default.default.red("Analysis failed:"),e),process.exit(1);}});b.parse(process.argv);//# sourceMappingURL=cli.js.map
|
|
249
3
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/analyzer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async () => {\n console.log(chalk.blue(\"Starting analysis...\"));\n try {\n const report = await analyzeProject(process.cwd());\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n chalk.cyan(\"Package Name\"),\n chalk.cyan(\"Usage Count\"),\n chalk.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(chalk.bold(\"📦 Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n chalk.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n console.log(\n boxen(chalk.bold(\"⚠️ Potential Unused Files\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n })\n );\n const unusedTable = new Table({\n head: [chalk.yellow(\"File Path\")],\n colWidths: [80]\n });\n report.unusedFiles\n .slice(0, 50)\n .forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n if (report.unusedFiles.length > 50) {\n console.log(\n chalk.gray(`...and ${report.unusedFiles.length - 50} more files.`)\n );\n }\n } else {\n console.log(\n boxen(chalk.bold(\"✅ No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n } catch (error) {\n console.error(chalk.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n","import { Project, SyntaxKind } from \"ts-morph\";\nimport chalk from \"chalk\";\nimport { glob } from \"glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(chalk.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/build/**\", \"**/.next/**\"],\n absolute: true\n });\n\n console.log(chalk.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(chalk.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.includes(\"main.tsx\") ||\n file.includes(\"index.tsx\")\n )\n return false;\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"],"mappings":"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","names":["import_chalk","fs","path","chalk","chalk","Table","boxen"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzer.ts","../src/cli.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","program","Command","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","error"],"mappings":"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","file":"cli.js","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async () => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const report = await analyzeProject(process.cwd());\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n pc.cyan(\"Package Name\"),\n pc.cyan(\"Usage Count\"),\n pc.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(pc.bold(\"📦 Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n pc.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n const unusedTable = new Table({\n head: [pc.yellow(\"File Path\")],\n colWidths: [80]\n });\n\n console.log(\n boxen(\n pc.bold(\n `⚠️ Potential Unused Files (${report.unusedFiles.length})`\n ),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n report.unusedFiles.forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n } else {\n console.log(\n boxen(pc.bold(\"✅ No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n } catch (error) {\n console.error(pc.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,226 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import chalk2 from "chalk";
|
|
6
|
-
|
|
7
|
-
// src/analyzer.ts
|
|
8
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
9
|
-
import chalk from "chalk";
|
|
10
|
-
import { glob } from "glob";
|
|
11
|
-
import path from "path";
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
function getFolderSize(dirPath) {
|
|
14
|
-
let size = 0;
|
|
15
|
-
try {
|
|
16
|
-
const files = fs.readdirSync(dirPath);
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
const filePath = path.join(dirPath, file);
|
|
19
|
-
const stats = fs.statSync(filePath);
|
|
20
|
-
if (stats.isDirectory()) {
|
|
21
|
-
size += getFolderSize(filePath);
|
|
22
|
-
} else {
|
|
23
|
-
size += stats.size;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
} catch (e) {
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
return size;
|
|
30
|
-
}
|
|
31
|
-
function formatBytes(bytes, decimals = 2) {
|
|
32
|
-
if (bytes === 0) return "0 Bytes";
|
|
33
|
-
const k = 1024;
|
|
34
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
35
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
36
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
37
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
38
|
-
}
|
|
39
|
-
function getPackageSize(rootPath, packageName) {
|
|
40
|
-
const pkgPath = path.join(rootPath, "node_modules", packageName);
|
|
41
|
-
if (fs.existsSync(pkgPath)) {
|
|
42
|
-
const size = getFolderSize(pkgPath);
|
|
43
|
-
return formatBytes(size);
|
|
44
|
-
}
|
|
45
|
-
return "N/A";
|
|
46
|
-
}
|
|
47
|
-
async function analyzeProject(rootPath) {
|
|
48
|
-
console.log(chalk.green(`Analyzing project at ${rootPath}`));
|
|
49
|
-
const files = await glob("**/*.{js,jsx,ts,tsx}", {
|
|
50
|
-
cwd: rootPath,
|
|
51
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"],
|
|
52
|
-
absolute: true
|
|
53
|
-
});
|
|
54
|
-
console.log(chalk.blue(`Found ${files.length} files to analyze.`));
|
|
55
|
-
const project = new Project({
|
|
56
|
-
skipAddingFilesFromTsConfig: true
|
|
57
|
-
});
|
|
58
|
-
files.forEach((file) => {
|
|
59
|
-
try {
|
|
60
|
-
project.addSourceFileAtPath(file);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.warn(chalk.yellow(`Skipping file ${file} due to load error:`), e);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
const packageUsage = {};
|
|
66
|
-
const localUsage = {};
|
|
67
|
-
files.forEach((f) => {
|
|
68
|
-
const relative = path.relative(rootPath, f);
|
|
69
|
-
localUsage[relative] = 0;
|
|
70
|
-
});
|
|
71
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
72
|
-
const imports = sourceFile.getImportDeclarations();
|
|
73
|
-
for (const imp of imports) {
|
|
74
|
-
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
75
|
-
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
76
|
-
try {
|
|
77
|
-
const sourceFilePath = sourceFile.getFilePath();
|
|
78
|
-
const sourceDir = path.dirname(sourceFilePath);
|
|
79
|
-
const resolvedPath = path.resolve(sourceDir, moduleSpecifier);
|
|
80
|
-
const extensions = [
|
|
81
|
-
"",
|
|
82
|
-
".ts",
|
|
83
|
-
".tsx",
|
|
84
|
-
".js",
|
|
85
|
-
".jsx",
|
|
86
|
-
"/index.ts",
|
|
87
|
-
"/index.tsx",
|
|
88
|
-
"/index.js",
|
|
89
|
-
"/index.jsx"
|
|
90
|
-
];
|
|
91
|
-
for (const ext of extensions) {
|
|
92
|
-
const tryPath = resolvedPath + ext;
|
|
93
|
-
const relativeTry = path.relative(rootPath, tryPath);
|
|
94
|
-
if (localUsage.hasOwnProperty(relativeTry)) {
|
|
95
|
-
localUsage[relativeTry]++;
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
} catch (e) {
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
let packageName = moduleSpecifier;
|
|
103
|
-
if (moduleSpecifier.startsWith("@")) {
|
|
104
|
-
const parts = moduleSpecifier.split("/");
|
|
105
|
-
if (parts.length >= 2) {
|
|
106
|
-
packageName = `${parts[0]}/${parts[1]}`;
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
const parts = moduleSpecifier.split("/");
|
|
110
|
-
if (parts.length >= 1) {
|
|
111
|
-
packageName = parts[0];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
118
|
-
SyntaxKind.CallExpression
|
|
119
|
-
);
|
|
120
|
-
for (const call of callExpressions) {
|
|
121
|
-
const expression = call.getExpression();
|
|
122
|
-
if (expression.getText() === "require") {
|
|
123
|
-
const args = call.getArguments();
|
|
124
|
-
if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {
|
|
125
|
-
const rawArg = args[0].getText().replace(/['"`]/g, "");
|
|
126
|
-
if (!rawArg.startsWith(".") && !rawArg.startsWith("/")) {
|
|
127
|
-
let pkg = rawArg.startsWith("@") ? rawArg.split("/").slice(0, 2).join("/") : rawArg.split("/")[0];
|
|
128
|
-
packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const reportPackages = {};
|
|
135
|
-
for (const [pkg, count] of Object.entries(packageUsage)) {
|
|
136
|
-
const size = getPackageSize(rootPath, pkg);
|
|
137
|
-
reportPackages[pkg] = { count, size };
|
|
138
|
-
}
|
|
139
|
-
const unused = Object.entries(localUsage).filter(([file, count]) => {
|
|
140
|
-
if (file.includes("pages/") || file.includes("app/") || file.includes("main.tsx") || file.includes("index.tsx"))
|
|
141
|
-
return false;
|
|
142
|
-
return count === 0;
|
|
143
|
-
}).map(([file]) => file);
|
|
144
|
-
return {
|
|
145
|
-
packages: reportPackages,
|
|
146
|
-
components: localUsage,
|
|
147
|
-
unusedFiles: unused
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// src/cli.ts
|
|
152
|
-
import Table from "cli-table3";
|
|
153
|
-
import boxen from "boxen";
|
|
154
|
-
var program = new Command();
|
|
155
|
-
program.name("react-prune").description(
|
|
156
|
-
"Monitor usage of packages and component imports across your React/Next.js/React Native app"
|
|
157
|
-
).version("1.0.0");
|
|
158
|
-
program.command("analyze").description("Analyze the current project for package and component usage").action(async () => {
|
|
159
|
-
console.log(chalk2.blue("Starting analysis..."));
|
|
160
|
-
try {
|
|
161
|
-
const report = await analyzeProject(process.cwd());
|
|
162
|
-
const packageTable = new Table({
|
|
163
|
-
head: [
|
|
164
|
-
chalk2.cyan("Package Name"),
|
|
165
|
-
chalk2.cyan("Usage Count"),
|
|
166
|
-
chalk2.cyan("Est. Size")
|
|
167
|
-
],
|
|
168
|
-
colWidths: [40, 15, 15]
|
|
169
|
-
});
|
|
170
|
-
const sortedPackages = Object.entries(report.packages).sort(
|
|
171
|
-
(a, b) => b[1].count - a[1].count
|
|
172
|
-
);
|
|
173
|
-
sortedPackages.slice(0, 50).forEach(([pkg, data]) => {
|
|
174
|
-
packageTable.push([pkg, data.count, data.size]);
|
|
175
|
-
});
|
|
176
|
-
console.log(
|
|
177
|
-
boxen(chalk2.bold("\u{1F4E6} Package Usage Report"), {
|
|
178
|
-
padding: 1,
|
|
179
|
-
margin: 1,
|
|
180
|
-
borderStyle: "round",
|
|
181
|
-
borderColor: "green"
|
|
182
|
-
})
|
|
183
|
-
);
|
|
184
|
-
console.log(packageTable.toString());
|
|
185
|
-
if (sortedPackages.length > 50) {
|
|
186
|
-
console.log(
|
|
187
|
-
chalk2.gray(`...and ${sortedPackages.length - 50} more packages.`)
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
if (report.unusedFiles.length > 0) {
|
|
191
|
-
console.log(
|
|
192
|
-
boxen(chalk2.bold("\u26A0\uFE0F Potential Unused Files"), {
|
|
193
|
-
padding: 1,
|
|
194
|
-
margin: 1,
|
|
195
|
-
borderStyle: "round",
|
|
196
|
-
borderColor: "yellow"
|
|
197
|
-
})
|
|
198
|
-
);
|
|
199
|
-
const unusedTable = new Table({
|
|
200
|
-
head: [chalk2.yellow("File Path")],
|
|
201
|
-
colWidths: [80]
|
|
202
|
-
});
|
|
203
|
-
report.unusedFiles.slice(0, 50).forEach((file) => unusedTable.push([file]));
|
|
204
|
-
console.log(unusedTable.toString());
|
|
205
|
-
if (report.unusedFiles.length > 50) {
|
|
206
|
-
console.log(
|
|
207
|
-
chalk2.gray(`...and ${report.unusedFiles.length - 50} more files.`)
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
console.log(
|
|
212
|
-
boxen(chalk2.bold("\u2705 No unused files detected!"), {
|
|
213
|
-
padding: 1,
|
|
214
|
-
margin: 1,
|
|
215
|
-
borderStyle: "round",
|
|
216
|
-
borderColor: "green"
|
|
217
|
-
})
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
} catch (error) {
|
|
221
|
-
console.error(chalk2.red("Analysis failed:"), error);
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
program.parse(process.argv);
|
|
2
|
+
import {Command}from'commander';import a from'picocolors';import {Project,SyntaxKind}from'ts-morph';import R from'fast-glob';import p from'path';import y from'fs';import z from'cli-table3';import x from'boxen';function S(e){let n=0;try{let r=y.readdirSync(e);for(let s of r){let o=p.join(e,s),g=y.statSync(o);g.isDirectory()?n+=S(o):n+=g.size;}}catch{return 0}return n}function E(e,n=2){if(e===0)return "0 Bytes";let r=1024,s=n<0?0:n,o=["Bytes","KB","MB","GB","TB"],g=Math.floor(Math.log(e)/Math.log(r));return parseFloat((e/Math.pow(r,g)).toFixed(s))+" "+o[g]}function T(e,n){let r=p.join(e,"node_modules",n);if(y.existsSync(r)){let s=S(r);return E(s)}return "N/A"}async function F(e){console.log(a.green(`Analyzing project at ${e}`));let n=await R("**/*.{js,jsx,ts,tsx}",{cwd:e,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(a.blue(`Found ${n.length} files to analyze.`));let r=new Project({skipAddingFilesFromTsConfig:true});n.forEach(t=>{try{r.addSourceFileAtPath(t);}catch(l){console.warn(a.yellow(`Skipping file ${t} due to load error:`),l);}});let s={},o={};n.forEach(t=>{let l=p.relative(e,t);o[l]=0;});for(let t of r.getSourceFiles()){let l=t.getImportDeclarations();for(let f of l){let d=f.getModuleSpecifierValue();if(d.startsWith(".")||d.startsWith("/"))try{let c=t.getFilePath(),i=p.dirname(c),m=p.resolve(i,d),v=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let P of v){let W=m+P,j=p.relative(e,W);if(o.hasOwnProperty(j)){o[j]++;break}}}catch{}else {let c=d;if(d.startsWith("@")){let i=d.split("/");i.length>=2&&(c=`${i[0]}/${i[1]}`);}else {let i=d.split("/");i.length>=1&&(c=i[0]);}s[c]=(s[c]||0)+1;}}let u=t.getDescendantsOfKind(SyntaxKind.CallExpression);for(let f of u)if(f.getExpression().getText()==="require"){let c=f.getArguments();if(c.length>0&&c[0].getKind()===SyntaxKind.StringLiteral){let i=c[0].getText().replace(/['"`]/g,"");if(!i.startsWith(".")&&!i.startsWith("/")){let m=i.startsWith("@")?i.split("/").slice(0,2).join("/"):i.split("/")[0];s[m]=(s[m]||0)+1;}}}}let g={};for(let[t,l]of Object.entries(s)){let u=T(e,t);g[t]={count:l,size:u};}let w=Object.entries(o).filter(([t,l])=>t.includes("pages/")||t.includes("app/")||t.endsWith("main.tsx")||t.endsWith("index.tsx")||t.endsWith("index.js")||t.endsWith("App.tsx")||t.endsWith("App.js")||!p.relative(e,t).includes(p.sep)?false:l===0).map(([t])=>t);return {packages:g,components:o,unusedFiles:w}}var b=new Command;b.name("react-prune").description("Monitor usage of packages and component imports across your React/Next.js/React Native app").version("1.0.0");b.command("analyze").description("Analyze the current project for package and component usage").action(async()=>{console.log(a.blue("Starting analysis..."));try{let e=await F(process.cwd()),n=new z({head:[a.cyan("Package Name"),a.cyan("Usage Count"),a.cyan("Est. Size")],colWidths:[40,15,15]}),r=Object.entries(e.packages).sort((s,o)=>o[1].count-s[1].count);if(r.slice(0,50).forEach(([s,o])=>{n.push([s,o.count,o.size]);}),console.log(x(a.bold("\u{1F4E6} Package Usage Report"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"})),console.log(n.toString()),r.length>50&&console.log(a.gray(`...and ${r.length-50} more packages.`)),e.unusedFiles.length>0){let s=new z({head:[a.yellow("File Path")],colWidths:[80]});console.log(x(a.bold(`\u26A0\uFE0F Potential Unused Files (${e.unusedFiles.length})`),{padding:1,margin:1,borderStyle:"round",borderColor:"yellow"})),e.unusedFiles.forEach(o=>s.push([o])),console.log(s.toString());}else console.log(x(a.bold("\u2705 No unused files detected!"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"}));}catch(e){console.error(a.red("Analysis failed:"),e),process.exit(1);}});b.parse(process.argv);//# sourceMappingURL=cli.mjs.map
|
|
226
3
|
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/analyzer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async () => {\n console.log(chalk.blue(\"Starting analysis...\"));\n try {\n const report = await analyzeProject(process.cwd());\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n chalk.cyan(\"Package Name\"),\n chalk.cyan(\"Usage Count\"),\n chalk.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(chalk.bold(\"📦 Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n chalk.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n console.log(\n boxen(chalk.bold(\"⚠️ Potential Unused Files\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n })\n );\n const unusedTable = new Table({\n head: [chalk.yellow(\"File Path\")],\n colWidths: [80]\n });\n report.unusedFiles\n .slice(0, 50)\n .forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n if (report.unusedFiles.length > 50) {\n console.log(\n chalk.gray(`...and ${report.unusedFiles.length - 50} more files.`)\n );\n }\n } else {\n console.log(\n boxen(chalk.bold(\"✅ No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n } catch (error) {\n console.error(chalk.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n","import { Project, SyntaxKind } from \"ts-morph\";\nimport chalk from \"chalk\";\nimport { glob } from \"glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(chalk.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/build/**\", \"**/.next/**\"],\n absolute: true\n });\n\n console.log(chalk.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(chalk.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.includes(\"main.tsx\") ||\n file.includes(\"index.tsx\")\n )\n return false;\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"],"mappings":"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","names":["chalk","chalk"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzer.ts","../src/cli.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","program","Command","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","error"],"mappings":"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","file":"cli.mjs","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async () => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const report = await analyzeProject(process.cwd());\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n pc.cyan(\"Package Name\"),\n pc.cyan(\"Usage Count\"),\n pc.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(pc.bold(\"📦 Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n pc.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n const unusedTable = new Table({\n head: [pc.yellow(\"File Path\")],\n colWidths: [80]\n });\n\n console.log(\n boxen(\n pc.bold(\n `⚠️ Potential Unused Files (${report.unusedFiles.length})`\n ),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n report.unusedFiles.forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n } else {\n console.log(\n boxen(pc.bold(\"✅ No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n } catch (error) {\n console.error(pc.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,184 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
analyzeProject: () => analyzeProject
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(index_exports);
|
|
36
|
-
|
|
37
|
-
// src/analyzer.ts
|
|
38
|
-
var import_ts_morph = require("ts-morph");
|
|
39
|
-
var import_chalk = __toESM(require("chalk"));
|
|
40
|
-
var import_glob = require("glob");
|
|
41
|
-
var import_path = __toESM(require("path"));
|
|
42
|
-
var import_fs = __toESM(require("fs"));
|
|
43
|
-
function getFolderSize(dirPath) {
|
|
44
|
-
let size = 0;
|
|
45
|
-
try {
|
|
46
|
-
const files = import_fs.default.readdirSync(dirPath);
|
|
47
|
-
for (const file of files) {
|
|
48
|
-
const filePath = import_path.default.join(dirPath, file);
|
|
49
|
-
const stats = import_fs.default.statSync(filePath);
|
|
50
|
-
if (stats.isDirectory()) {
|
|
51
|
-
size += getFolderSize(filePath);
|
|
52
|
-
} else {
|
|
53
|
-
size += stats.size;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
} catch (e) {
|
|
57
|
-
return 0;
|
|
58
|
-
}
|
|
59
|
-
return size;
|
|
60
|
-
}
|
|
61
|
-
function formatBytes(bytes, decimals = 2) {
|
|
62
|
-
if (bytes === 0) return "0 Bytes";
|
|
63
|
-
const k = 1024;
|
|
64
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
65
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
66
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
67
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
68
|
-
}
|
|
69
|
-
function getPackageSize(rootPath, packageName) {
|
|
70
|
-
const pkgPath = import_path.default.join(rootPath, "node_modules", packageName);
|
|
71
|
-
if (import_fs.default.existsSync(pkgPath)) {
|
|
72
|
-
const size = getFolderSize(pkgPath);
|
|
73
|
-
return formatBytes(size);
|
|
74
|
-
}
|
|
75
|
-
return "N/A";
|
|
76
|
-
}
|
|
77
|
-
async function analyzeProject(rootPath) {
|
|
78
|
-
console.log(import_chalk.default.green(`Analyzing project at ${rootPath}`));
|
|
79
|
-
const files = await (0, import_glob.glob)("**/*.{js,jsx,ts,tsx}", {
|
|
80
|
-
cwd: rootPath,
|
|
81
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"],
|
|
82
|
-
absolute: true
|
|
83
|
-
});
|
|
84
|
-
console.log(import_chalk.default.blue(`Found ${files.length} files to analyze.`));
|
|
85
|
-
const project = new import_ts_morph.Project({
|
|
86
|
-
skipAddingFilesFromTsConfig: true
|
|
87
|
-
});
|
|
88
|
-
files.forEach((file) => {
|
|
89
|
-
try {
|
|
90
|
-
project.addSourceFileAtPath(file);
|
|
91
|
-
} catch (e) {
|
|
92
|
-
console.warn(import_chalk.default.yellow(`Skipping file ${file} due to load error:`), e);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
const packageUsage = {};
|
|
96
|
-
const localUsage = {};
|
|
97
|
-
files.forEach((f) => {
|
|
98
|
-
const relative = import_path.default.relative(rootPath, f);
|
|
99
|
-
localUsage[relative] = 0;
|
|
100
|
-
});
|
|
101
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
102
|
-
const imports = sourceFile.getImportDeclarations();
|
|
103
|
-
for (const imp of imports) {
|
|
104
|
-
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
105
|
-
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
106
|
-
try {
|
|
107
|
-
const sourceFilePath = sourceFile.getFilePath();
|
|
108
|
-
const sourceDir = import_path.default.dirname(sourceFilePath);
|
|
109
|
-
const resolvedPath = import_path.default.resolve(sourceDir, moduleSpecifier);
|
|
110
|
-
const extensions = [
|
|
111
|
-
"",
|
|
112
|
-
".ts",
|
|
113
|
-
".tsx",
|
|
114
|
-
".js",
|
|
115
|
-
".jsx",
|
|
116
|
-
"/index.ts",
|
|
117
|
-
"/index.tsx",
|
|
118
|
-
"/index.js",
|
|
119
|
-
"/index.jsx"
|
|
120
|
-
];
|
|
121
|
-
for (const ext of extensions) {
|
|
122
|
-
const tryPath = resolvedPath + ext;
|
|
123
|
-
const relativeTry = import_path.default.relative(rootPath, tryPath);
|
|
124
|
-
if (localUsage.hasOwnProperty(relativeTry)) {
|
|
125
|
-
localUsage[relativeTry]++;
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
let packageName = moduleSpecifier;
|
|
133
|
-
if (moduleSpecifier.startsWith("@")) {
|
|
134
|
-
const parts = moduleSpecifier.split("/");
|
|
135
|
-
if (parts.length >= 2) {
|
|
136
|
-
packageName = `${parts[0]}/${parts[1]}`;
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
const parts = moduleSpecifier.split("/");
|
|
140
|
-
if (parts.length >= 1) {
|
|
141
|
-
packageName = parts[0];
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
148
|
-
import_ts_morph.SyntaxKind.CallExpression
|
|
149
|
-
);
|
|
150
|
-
for (const call of callExpressions) {
|
|
151
|
-
const expression = call.getExpression();
|
|
152
|
-
if (expression.getText() === "require") {
|
|
153
|
-
const args = call.getArguments();
|
|
154
|
-
if (args.length > 0 && args[0].getKind() === import_ts_morph.SyntaxKind.StringLiteral) {
|
|
155
|
-
const rawArg = args[0].getText().replace(/['"`]/g, "");
|
|
156
|
-
if (!rawArg.startsWith(".") && !rawArg.startsWith("/")) {
|
|
157
|
-
let pkg = rawArg.startsWith("@") ? rawArg.split("/").slice(0, 2).join("/") : rawArg.split("/")[0];
|
|
158
|
-
packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const reportPackages = {};
|
|
165
|
-
for (const [pkg, count] of Object.entries(packageUsage)) {
|
|
166
|
-
const size = getPackageSize(rootPath, pkg);
|
|
167
|
-
reportPackages[pkg] = { count, size };
|
|
168
|
-
}
|
|
169
|
-
const unused = Object.entries(localUsage).filter(([file, count]) => {
|
|
170
|
-
if (file.includes("pages/") || file.includes("app/") || file.includes("main.tsx") || file.includes("index.tsx"))
|
|
171
|
-
return false;
|
|
172
|
-
return count === 0;
|
|
173
|
-
}).map(([file]) => file);
|
|
174
|
-
return {
|
|
175
|
-
packages: reportPackages,
|
|
176
|
-
components: localUsage,
|
|
177
|
-
unusedFiles: unused
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
181
|
-
0 && (module.exports = {
|
|
182
|
-
analyzeProject
|
|
183
|
-
});
|
|
1
|
+
'use strict';var tsMorph=require('ts-morph'),m=require('picocolors'),W=require('fast-glob'),p=require('path'),x=require('fs');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var m__default=/*#__PURE__*/_interopDefault(m);var W__default=/*#__PURE__*/_interopDefault(W);var p__default=/*#__PURE__*/_interopDefault(p);var x__default=/*#__PURE__*/_interopDefault(x);function y(t){let n=0;try{let r=x__default.default.readdirSync(t);for(let o of r){let c=p__default.default.join(t,o),l=x__default.default.statSync(c);l.isDirectory()?n+=y(c):n+=l.size;}}catch{return 0}return n}function v(t,n=2){if(t===0)return "0 Bytes";let r=1024,o=n<0?0:n,c=["Bytes","KB","MB","GB","TB"],l=Math.floor(Math.log(t)/Math.log(r));return parseFloat((t/Math.pow(r,l)).toFixed(o))+" "+c[l]}function w(t,n){let r=p__default.default.join(t,"node_modules",n);if(x__default.default.existsSync(r)){let o=y(r);return v(o)}return "N/A"}async function E(t){console.log(m__default.default.green(`Analyzing project at ${t}`));let n=await W__default.default("**/*.{js,jsx,ts,tsx}",{cwd:t,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(m__default.default.blue(`Found ${n.length} files to analyze.`));let r=new tsMorph.Project({skipAddingFilesFromTsConfig:true});n.forEach(e=>{try{r.addSourceFileAtPath(e);}catch(a){console.warn(m__default.default.yellow(`Skipping file ${e} due to load error:`),a);}});let o={},c={};n.forEach(e=>{let a=p__default.default.relative(t,e);c[a]=0;});for(let e of r.getSourceFiles()){let a=e.getImportDeclarations();for(let d of a){let g=d.getModuleSpecifierValue();if(g.startsWith(".")||g.startsWith("/"))try{let i=e.getFilePath(),s=p__default.default.dirname(i),f=p__default.default.resolve(s,g),F=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let b of F){let z=f+b,h=p__default.default.relative(t,z);if(c.hasOwnProperty(h)){c[h]++;break}}}catch{}else {let i=g;if(g.startsWith("@")){let s=g.split("/");s.length>=2&&(i=`${s[0]}/${s[1]}`);}else {let s=g.split("/");s.length>=1&&(i=s[0]);}o[i]=(o[i]||0)+1;}}let u=e.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression);for(let d of u)if(d.getExpression().getText()==="require"){let i=d.getArguments();if(i.length>0&&i[0].getKind()===tsMorph.SyntaxKind.StringLiteral){let s=i[0].getText().replace(/['"`]/g,"");if(!s.startsWith(".")&&!s.startsWith("/")){let f=s.startsWith("@")?s.split("/").slice(0,2).join("/"):s.split("/")[0];o[f]=(o[f]||0)+1;}}}}let l={};for(let[e,a]of Object.entries(o)){let u=w(t,e);l[e]={count:a,size:u};}let k=Object.entries(c).filter(([e,a])=>e.includes("pages/")||e.includes("app/")||e.endsWith("main.tsx")||e.endsWith("index.tsx")||e.endsWith("index.js")||e.endsWith("App.tsx")||e.endsWith("App.js")||!p__default.default.relative(t,e).includes(p__default.default.sep)?false:a===0).map(([e])=>e);return {packages:l,components:c,unusedFiles:k}}exports.analyzeProject=E;//# sourceMappingURL=index.js.map
|
|
184
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/analyzer.ts"],"sourcesContent":["export * from \"./analyzer\";\n","import { Project, SyntaxKind } from \"ts-morph\";\nimport chalk from \"chalk\";\nimport { glob } from \"glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(chalk.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/build/**\", \"**/.next/**\"],\n absolute: true\n });\n\n console.log(chalk.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(chalk.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.includes(\"main.tsx\") ||\n file.includes(\"index.tsx\")\n )\n return false;\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"],"mappings":"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","names":["fs","path","chalk"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzer.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused"],"mappings":"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","file":"index.js","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,147 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { glob } from "glob";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
function getFolderSize(dirPath) {
|
|
8
|
-
let size = 0;
|
|
9
|
-
try {
|
|
10
|
-
const files = fs.readdirSync(dirPath);
|
|
11
|
-
for (const file of files) {
|
|
12
|
-
const filePath = path.join(dirPath, file);
|
|
13
|
-
const stats = fs.statSync(filePath);
|
|
14
|
-
if (stats.isDirectory()) {
|
|
15
|
-
size += getFolderSize(filePath);
|
|
16
|
-
} else {
|
|
17
|
-
size += stats.size;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
} catch (e) {
|
|
21
|
-
return 0;
|
|
22
|
-
}
|
|
23
|
-
return size;
|
|
24
|
-
}
|
|
25
|
-
function formatBytes(bytes, decimals = 2) {
|
|
26
|
-
if (bytes === 0) return "0 Bytes";
|
|
27
|
-
const k = 1024;
|
|
28
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
29
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
30
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
31
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
32
|
-
}
|
|
33
|
-
function getPackageSize(rootPath, packageName) {
|
|
34
|
-
const pkgPath = path.join(rootPath, "node_modules", packageName);
|
|
35
|
-
if (fs.existsSync(pkgPath)) {
|
|
36
|
-
const size = getFolderSize(pkgPath);
|
|
37
|
-
return formatBytes(size);
|
|
38
|
-
}
|
|
39
|
-
return "N/A";
|
|
40
|
-
}
|
|
41
|
-
async function analyzeProject(rootPath) {
|
|
42
|
-
console.log(chalk.green(`Analyzing project at ${rootPath}`));
|
|
43
|
-
const files = await glob("**/*.{js,jsx,ts,tsx}", {
|
|
44
|
-
cwd: rootPath,
|
|
45
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"],
|
|
46
|
-
absolute: true
|
|
47
|
-
});
|
|
48
|
-
console.log(chalk.blue(`Found ${files.length} files to analyze.`));
|
|
49
|
-
const project = new Project({
|
|
50
|
-
skipAddingFilesFromTsConfig: true
|
|
51
|
-
});
|
|
52
|
-
files.forEach((file) => {
|
|
53
|
-
try {
|
|
54
|
-
project.addSourceFileAtPath(file);
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.warn(chalk.yellow(`Skipping file ${file} due to load error:`), e);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
const packageUsage = {};
|
|
60
|
-
const localUsage = {};
|
|
61
|
-
files.forEach((f) => {
|
|
62
|
-
const relative = path.relative(rootPath, f);
|
|
63
|
-
localUsage[relative] = 0;
|
|
64
|
-
});
|
|
65
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
66
|
-
const imports = sourceFile.getImportDeclarations();
|
|
67
|
-
for (const imp of imports) {
|
|
68
|
-
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
69
|
-
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
70
|
-
try {
|
|
71
|
-
const sourceFilePath = sourceFile.getFilePath();
|
|
72
|
-
const sourceDir = path.dirname(sourceFilePath);
|
|
73
|
-
const resolvedPath = path.resolve(sourceDir, moduleSpecifier);
|
|
74
|
-
const extensions = [
|
|
75
|
-
"",
|
|
76
|
-
".ts",
|
|
77
|
-
".tsx",
|
|
78
|
-
".js",
|
|
79
|
-
".jsx",
|
|
80
|
-
"/index.ts",
|
|
81
|
-
"/index.tsx",
|
|
82
|
-
"/index.js",
|
|
83
|
-
"/index.jsx"
|
|
84
|
-
];
|
|
85
|
-
for (const ext of extensions) {
|
|
86
|
-
const tryPath = resolvedPath + ext;
|
|
87
|
-
const relativeTry = path.relative(rootPath, tryPath);
|
|
88
|
-
if (localUsage.hasOwnProperty(relativeTry)) {
|
|
89
|
-
localUsage[relativeTry]++;
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch (e) {
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
let packageName = moduleSpecifier;
|
|
97
|
-
if (moduleSpecifier.startsWith("@")) {
|
|
98
|
-
const parts = moduleSpecifier.split("/");
|
|
99
|
-
if (parts.length >= 2) {
|
|
100
|
-
packageName = `${parts[0]}/${parts[1]}`;
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
const parts = moduleSpecifier.split("/");
|
|
104
|
-
if (parts.length >= 1) {
|
|
105
|
-
packageName = parts[0];
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
112
|
-
SyntaxKind.CallExpression
|
|
113
|
-
);
|
|
114
|
-
for (const call of callExpressions) {
|
|
115
|
-
const expression = call.getExpression();
|
|
116
|
-
if (expression.getText() === "require") {
|
|
117
|
-
const args = call.getArguments();
|
|
118
|
-
if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {
|
|
119
|
-
const rawArg = args[0].getText().replace(/['"`]/g, "");
|
|
120
|
-
if (!rawArg.startsWith(".") && !rawArg.startsWith("/")) {
|
|
121
|
-
let pkg = rawArg.startsWith("@") ? rawArg.split("/").slice(0, 2).join("/") : rawArg.split("/")[0];
|
|
122
|
-
packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const reportPackages = {};
|
|
129
|
-
for (const [pkg, count] of Object.entries(packageUsage)) {
|
|
130
|
-
const size = getPackageSize(rootPath, pkg);
|
|
131
|
-
reportPackages[pkg] = { count, size };
|
|
132
|
-
}
|
|
133
|
-
const unused = Object.entries(localUsage).filter(([file, count]) => {
|
|
134
|
-
if (file.includes("pages/") || file.includes("app/") || file.includes("main.tsx") || file.includes("index.tsx"))
|
|
135
|
-
return false;
|
|
136
|
-
return count === 0;
|
|
137
|
-
}).map(([file]) => file);
|
|
138
|
-
return {
|
|
139
|
-
packages: reportPackages,
|
|
140
|
-
components: localUsage,
|
|
141
|
-
unusedFiles: unused
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
export {
|
|
145
|
-
analyzeProject
|
|
146
|
-
};
|
|
1
|
+
import {Project,SyntaxKind}from'ts-morph';import m from'picocolors';import W from'fast-glob';import p from'path';import x from'fs';function y(t){let n=0;try{let r=x.readdirSync(t);for(let o of r){let c=p.join(t,o),l=x.statSync(c);l.isDirectory()?n+=y(c):n+=l.size;}}catch{return 0}return n}function v(t,n=2){if(t===0)return "0 Bytes";let r=1024,o=n<0?0:n,c=["Bytes","KB","MB","GB","TB"],l=Math.floor(Math.log(t)/Math.log(r));return parseFloat((t/Math.pow(r,l)).toFixed(o))+" "+c[l]}function w(t,n){let r=p.join(t,"node_modules",n);if(x.existsSync(r)){let o=y(r);return v(o)}return "N/A"}async function E(t){console.log(m.green(`Analyzing project at ${t}`));let n=await W("**/*.{js,jsx,ts,tsx}",{cwd:t,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(m.blue(`Found ${n.length} files to analyze.`));let r=new Project({skipAddingFilesFromTsConfig:true});n.forEach(e=>{try{r.addSourceFileAtPath(e);}catch(a){console.warn(m.yellow(`Skipping file ${e} due to load error:`),a);}});let o={},c={};n.forEach(e=>{let a=p.relative(t,e);c[a]=0;});for(let e of r.getSourceFiles()){let a=e.getImportDeclarations();for(let d of a){let g=d.getModuleSpecifierValue();if(g.startsWith(".")||g.startsWith("/"))try{let i=e.getFilePath(),s=p.dirname(i),f=p.resolve(s,g),F=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let b of F){let z=f+b,h=p.relative(t,z);if(c.hasOwnProperty(h)){c[h]++;break}}}catch{}else {let i=g;if(g.startsWith("@")){let s=g.split("/");s.length>=2&&(i=`${s[0]}/${s[1]}`);}else {let s=g.split("/");s.length>=1&&(i=s[0]);}o[i]=(o[i]||0)+1;}}let u=e.getDescendantsOfKind(SyntaxKind.CallExpression);for(let d of u)if(d.getExpression().getText()==="require"){let i=d.getArguments();if(i.length>0&&i[0].getKind()===SyntaxKind.StringLiteral){let s=i[0].getText().replace(/['"`]/g,"");if(!s.startsWith(".")&&!s.startsWith("/")){let f=s.startsWith("@")?s.split("/").slice(0,2).join("/"):s.split("/")[0];o[f]=(o[f]||0)+1;}}}}let l={};for(let[e,a]of Object.entries(o)){let u=w(t,e);l[e]={count:a,size:u};}let k=Object.entries(c).filter(([e,a])=>e.includes("pages/")||e.includes("app/")||e.endsWith("main.tsx")||e.endsWith("index.tsx")||e.endsWith("index.js")||e.endsWith("App.tsx")||e.endsWith("App.js")||!p.relative(t,e).includes(p.sep)?false:a===0).map(([e])=>e);return {packages:l,components:c,unusedFiles:k}}export{E as analyzeProject};//# sourceMappingURL=index.mjs.map
|
|
147
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyzer.ts"],"sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport chalk from \"chalk\";\nimport { glob } from \"glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(chalk.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/build/**\", \"**/.next/**\"],\n absolute: true\n });\n\n console.log(chalk.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(chalk.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.includes(\"main.tsx\") ||\n file.includes(\"index.tsx\")\n )\n return false;\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzer.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused"],"mappings":"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","file":"index.mjs","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-prune",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-prune": "./dist/cli.js"
|
|
@@ -18,15 +18,17 @@
|
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"react",
|
|
21
|
-
"analysis",
|
|
22
|
-
"nextjs",
|
|
23
21
|
"react-native",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"vite",
|
|
27
|
-
"react-native-web",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"analysis",
|
|
28
24
|
"dead-code",
|
|
29
|
-
"imports"
|
|
25
|
+
"imports",
|
|
26
|
+
"dependency-analysis",
|
|
27
|
+
"bundle-size",
|
|
28
|
+
"prune",
|
|
29
|
+
"typescript",
|
|
30
|
+
"developer-tools",
|
|
31
|
+
"cli"
|
|
30
32
|
],
|
|
31
33
|
"author": "Daniel Arikawe",
|
|
32
34
|
"license": "MIT",
|
|
@@ -37,17 +39,20 @@
|
|
|
37
39
|
"type": "git",
|
|
38
40
|
"url": "git+https://github.com/danieljohnson18/react-prune.git"
|
|
39
41
|
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
40
45
|
"bugs": {
|
|
41
46
|
"url": "https://github.com/danieljohnson18/react-prune/issues"
|
|
42
47
|
},
|
|
43
48
|
"homepage": "https://github.com/danieljohnson18/react-prune#readme",
|
|
44
|
-
"description": "",
|
|
49
|
+
"description": "A powerful CLI tool to monitor package usage, analyze component imports, and detect dead code across React, Next.js, and React Native applications.",
|
|
45
50
|
"dependencies": {
|
|
46
51
|
"boxen": "^8.0.1",
|
|
47
|
-
"chalk": "^5.6.2",
|
|
48
52
|
"cli-table3": "^0.6.5",
|
|
49
53
|
"commander": "^14.0.2",
|
|
50
|
-
"glob": "^
|
|
54
|
+
"fast-glob": "^3.3.3",
|
|
55
|
+
"picocolors": "^1.1.1",
|
|
51
56
|
"ts-morph": "^27.0.2"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|