react-prune 1.2.0 → 1.2.2
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 +158 -81
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/package.json +7 -5
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -3
- package/dist/cli.mjs.map +0 -1
- package/dist/index.d.mts +0 -12
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -4,136 +4,213 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.npmjs.com/package/react-prune)
|
|
6
6
|
|
|
7
|
-
> **
|
|
7
|
+
> **Static analysis for identifying unused files and package usage in React-based codebases.**
|
|
8
8
|
|
|
9
|
-
`react-prune` is a
|
|
9
|
+
`react-prune` is a lightweight CLI tool that analyzes your React, Next.js, Vite, and React Native projects to surface **unused local files**, **unused exports**, and **package import usage**, helping you reduce dead code and dependency bloat.
|
|
10
|
+
|
|
11
|
+
---
|
|
10
12
|
|
|
11
13
|
## 🚀 Features
|
|
12
14
|
|
|
13
|
-
- **📦 Package Analysis
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
- **📦 Package Usage Analysis**
|
|
16
|
+
Counts how often each external npm package is imported across your codebase.
|
|
17
|
+
|
|
18
|
+
- **⚖️ Optional Package Size Estimation**
|
|
19
|
+
Estimates package sizes from `node_modules` to highlight heavy dependencies.
|
|
20
|
+
|
|
21
|
+
- **🧹 Unused File Detection**
|
|
22
|
+
Identifies local source files that are never imported anywhere in the project.
|
|
23
|
+
|
|
24
|
+
- **📝 Unused Export Detection**
|
|
25
|
+
Finds named exports (functions, components, constants) that are defined but never used.
|
|
26
|
+
|
|
27
|
+
- **🔍 Export/Component Search**
|
|
28
|
+
Search for a component or function to see **how many times it’s used** and **all file references with line numbers**.
|
|
29
|
+
|
|
30
|
+
- **📊 CLI-Friendly Output**
|
|
31
|
+
Displays results in readable tables.
|
|
32
|
+
|
|
33
|
+
---
|
|
19
34
|
|
|
20
35
|
## 📦 Installation
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
Install as a dev dependency (recommended):
|
|
23
38
|
|
|
24
|
-
###
|
|
39
|
+
### npm
|
|
25
40
|
|
|
26
41
|
```bash
|
|
27
42
|
npm install -D react-prune
|
|
28
43
|
```
|
|
29
44
|
|
|
30
|
-
###
|
|
45
|
+
### yarn
|
|
31
46
|
|
|
32
47
|
```bash
|
|
33
48
|
yarn add -D react-prune
|
|
34
49
|
```
|
|
35
50
|
|
|
36
|
-
###
|
|
51
|
+
### pnpm
|
|
37
52
|
|
|
38
53
|
```bash
|
|
39
54
|
pnpm add -D react-prune
|
|
40
55
|
```
|
|
41
56
|
|
|
42
|
-
|
|
57
|
+
Or run once via `npx`:
|
|
43
58
|
|
|
44
59
|
```bash
|
|
45
|
-
npx react-prune
|
|
60
|
+
npx react-prune
|
|
46
61
|
```
|
|
47
62
|
|
|
63
|
+
---
|
|
64
|
+
|
|
48
65
|
## 🛠 Usage
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
Run from the **root of your project**.
|
|
68
|
+
|
|
69
|
+
### Analyze the project
|
|
51
70
|
|
|
52
71
|
```bash
|
|
53
72
|
react-prune analyze
|
|
54
73
|
```
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
#### Options
|
|
76
|
+
|
|
77
|
+
| Option | Description |
|
|
78
|
+
| -------------- | ------------------------------------------ |
|
|
79
|
+
| `--json` | Output the report as JSON |
|
|
80
|
+
| `--no-size` | Skip package size calculation |
|
|
81
|
+
| `--no-exports` | Skip unused export analysis |
|
|
82
|
+
| `--limit <n>` | Limit displayed package rows (default: 50) |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Search for a component or export
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
react-prune find <exportName>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Example
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
react-prune find Button
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Output:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
'Button' is used 4 time(s):
|
|
102
|
+
- src/components/Header.tsx:12
|
|
103
|
+
- src/components/Footer.tsx:8
|
|
104
|
+
- src/pages/index.tsx:22
|
|
105
|
+
- src/pages/about.tsx:15
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Check the size of a specific npm package
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
react-prune size <packageName>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Example
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
react-prune size react
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Output:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
📦 react size: 312 KB
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If the package is not installed:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Package 'some-package' not found in node_modules.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
57
135
|
|
|
58
|
-
|
|
136
|
+
## 📊 Example Output (Analyze)
|
|
59
137
|
|
|
60
138
|
```text
|
|
61
139
|
╭─────────────────────────╮
|
|
62
|
-
│ │
|
|
63
140
|
│ 📦 Package Usage │
|
|
64
|
-
│ Report │
|
|
65
|
-
│ │
|
|
66
141
|
╰─────────────────────────╯
|
|
67
142
|
|
|
68
|
-
|
|
69
|
-
│ Package
|
|
70
|
-
|
|
71
|
-
│ react
|
|
72
|
-
|
|
73
|
-
│
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
143
|
+
┌────────────────────────┬────────┬──────────┐
|
|
144
|
+
│ Package │ Count │ Size │
|
|
145
|
+
├────────────────────────┼────────┼──────────┤
|
|
146
|
+
│ react │ 142 │ 312 KB │
|
|
147
|
+
│ lodash │ 5 │ 4.2 MB │
|
|
148
|
+
│ framer-motion │ 23 │ 1.1 MB │
|
|
149
|
+
└────────────────────────┴────────┴──────────┘
|
|
150
|
+
|
|
151
|
+
╭─────────────────────────╮
|
|
152
|
+
│ ⚠️ Unused Files (2) │
|
|
153
|
+
╰─────────────────────────╯
|
|
154
|
+
|
|
155
|
+
src/components/OldButton.tsx
|
|
156
|
+
src/utils/deprecated-helper.ts
|
|
77
157
|
|
|
78
158
|
╭─────────────────────────╮
|
|
79
|
-
│
|
|
80
|
-
│ ⚠️ Potential │
|
|
81
|
-
│ Unused Files │
|
|
82
|
-
│ │
|
|
159
|
+
│ ⚠️ Unused Exports (3) │
|
|
83
160
|
╰─────────────────────────╯
|
|
84
161
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
162
|
+
src/hooks/useMetrics.ts useOldMetric
|
|
163
|
+
src/utils/formatters.ts formatCurrency
|
|
164
|
+
src/components/OldButton.tsx OldButton
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## ⚙️ How It Works
|
|
170
|
+
|
|
171
|
+
1. **File Discovery**
|
|
172
|
+
Recursively scans `.js`, `.jsx`, `.ts`, and `.tsx` files (excluding `node_modules`, `.next`, `dist`, etc.).
|
|
173
|
+
|
|
174
|
+
2. **AST Parsing**
|
|
175
|
+
Uses `ts-morph` to parse TypeScript/JavaScript ASTs for accurate import/export analysis.
|
|
176
|
+
|
|
177
|
+
3. **Dependency Resolution**
|
|
178
|
+
Differentiates between local file imports and external package imports.
|
|
179
|
+
|
|
180
|
+
4. **Static Usage Mapping**
|
|
181
|
+
Tracks which files, exports, and packages are actually referenced in the project.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## ⚠️ Limitations
|
|
186
|
+
|
|
187
|
+
- This is **static analysis** — dynamic imports and runtime usage may not be detected.
|
|
188
|
+
- Files referenced only via tooling (e.g., Storybook, tests) may appear unused.
|
|
189
|
+
- Package size estimates are **disk-based**, not bundle size.
|
|
190
|
+
|
|
191
|
+
---
|
|
115
192
|
|
|
116
193
|
## 🤝 Contributing
|
|
117
194
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
node dist/cli.js analyze
|
|
135
|
-
```
|
|
195
|
+
```bash
|
|
196
|
+
git clone https://github.com/danieljohnson18/react-prune.git
|
|
197
|
+
cd react-prune
|
|
198
|
+
npm install
|
|
199
|
+
npm run dev
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Test locally:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
node dist/cli.js analyze
|
|
206
|
+
node dist/cli.js find Button
|
|
207
|
+
node dist/cli.js size react
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
136
211
|
|
|
137
212
|
## 📄 License
|
|
138
213
|
|
|
139
214
|
MIT © [Daniel Arikawe](https://github.com/danieljohnson18)
|
|
215
|
+
|
|
216
|
+
---
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';var
|
|
2
|
+
'use strict';var b=require('path'),commander=require('commander'),h=require('picocolors'),k=require('cli-table3'),P=require('boxen'),tsMorph=require('ts-morph'),q=require('fast-glob'),w=require('fs');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var b__default=/*#__PURE__*/_interopDefault(b);var h__default=/*#__PURE__*/_interopDefault(h);var k__default=/*#__PURE__*/_interopDefault(k);var P__default=/*#__PURE__*/_interopDefault(P);var q__default=/*#__PURE__*/_interopDefault(q);var w__default=/*#__PURE__*/_interopDefault(w);var K=Object.defineProperty;var $=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,s)=>(typeof require<"u"?require:t)[s]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var M=(e,t)=>()=>(e&&(t=e(e=0)),t);var B=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),L=(e,t)=>{for(var s in t)K(e,s,{get:t[s],enumerable:true});};var U=B((ae,G)=>{G.exports={name:"react-prune",version:"1.2.2",main:"dist/index.js",bin:{"react-prune":"./dist/cli.js"},scripts:{build:"tsup src/cli.ts --format cjs --dts",dev:"tsup src/cli.ts --watch",lint:"eslint src/**",format:"prettier --write .",prepublishOnly:"npm run build",release:"changeset publish",test:"vitest"},publishConfig:{access:"public"},keywords:["react","react-native","nextjs","analysis","dead-code","imports","dependency-analysis","bundle-size","prune","typescript","developer-tools","cli"],author:"Daniel Arikawe",license:"MIT",files:["dist"],repository:{type:"git",url:"git+https://github.com/danieljohnson18/react-prune.git"},engines:{node:">=18"},bugs:{url:"https://github.com/danieljohnson18/react-prune/issues"},homepage:"https://github.com/danieljohnson18/react-prune#readme",description:"A powerful CLI tool to monitor package usage, analyze component imports, and detect dead code across React, Next.js, and React Native applications.",dependencies:{boxen:"^8.0.1","cli-table3":"^0.6.5",commander:"^14.0.2","fast-glob":"^3.3.3",picocolors:"^1.1.1","ts-morph":"^27.0.2"},devDependencies:{"@changesets/cli":"^2.29.8","@types/glob":"^8.1.0","@types/node":"^25.0.10",eslint:"^9.39.2",prettier:"^3.8.1",tsup:"^8.5.1",typescript:"^5.9.3",vitest:"^4.0.18"}};});var I={};L(I,{getPackageSize:()=>Q});function Q(e,t){try{let s=$(b__default.default.join(e,"node_modules",t,"package.json"));if(typeof s.size=="number")return `${(s.size/1024).toFixed(2)} KB`}catch{}}var N=M(()=>{});function D(e){let t=0;try{let s=w__default.default.readdirSync(e);for(let i of s){let l=b__default.default.join(e,i),n=w__default.default.statSync(l);n.isDirectory()?t+=D(l):t+=n.size;}}catch{}return t}function J(e,t=2){if(e===0)return "0 Bytes";let s=1024,i=t<0?0:t,l=["Bytes","KB","MB","GB","TB"],n=Math.floor(Math.log(e)/Math.log(s));return parseFloat((e/Math.pow(s,n)).toFixed(i))+" "+l[n]}function V(e,t){let s=b__default.default.join(e,"node_modules",t);return w__default.default.existsSync(s)?J(D(s)):"N/A"}async function F(e){let{rootPath:t,includeSizes:s=true,analyzeExports:i=true,silent:l=false}=e;l||console.log(h__default.default.green(`Analyzing project at ${t}`));let n=await q__default.default("**/*.{js,jsx,ts,tsx}",{cwd:t,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/*.d.ts"],absolute:true});l||console.log(h__default.default.blue(`Found ${n.length} files to analyze.`));let r=new tsMorph.Project({skipAddingFilesFromTsConfig:true}),m=b__default.default.join(t,"tsconfig.json");w__default.default.existsSync(m)&&r.addSourceFilesFromTsConfig(m),n.forEach(o=>{try{r.addSourceFileAtPath(o);}catch{console.warn(h__default.default.yellow(`Skipping ${o}`));}});let u={},f={},d={},v={};for(let o of r.getSourceFiles()){let a=b__default.default.relative(t,o.getFilePath()).replace(/\\/g,"/");v[a]=o;let y=o.getExportedDeclarations(),g=new Set;y.forEach((j,c)=>{c!=="default"&&g.add(c);});let x=y.has("default");f[a]={named:g,default:x},d[a]=new Set,o.getImportDeclarations().forEach(j=>{let c=j.getModuleSpecifierValue();if(!c.startsWith(".")&&!c.startsWith("/")){let p=c.startsWith("@")?c.split("/").slice(0,2).join("/"):c.split("/")[0];u[p]=(u[p]||0)+1;}}),o.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression).forEach(j=>{if(j.getExpression().getText()==="require"){let c=j.getArguments();if(c.length&&c[0].getKind()===tsMorph.SyntaxKind.StringLiteral){let p=c[0].getText().replace(/['"`]/g,"");if(!p.startsWith(".")&&!p.startsWith("/")){let S=p.startsWith("@")?p.split("/").slice(0,2).join("/"):p.split("/")[0];u[S]=(u[S]||0)+1;}}}});}if(i)for(let o of r.getSourceFiles()){let a=o.getImportDeclarations();b__default.default.relative(t,o.getFilePath()).replace(/\\/g,"/");a.forEach(g=>{let x=g.getModuleSpecifierValue();if(x.startsWith(".")||x.startsWith("/")){let A=b__default.default.dirname(o.getFilePath()),W=b__default.default.resolve(A,x),j=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"],c;for(let p of j){let S=b__default.default.relative(t,W+p).replace(/\\/g,"/");if(f[S]){c=S;break}}c&&(g.getNamedImports().forEach(p=>d[c].add(p.getName())),g.getDefaultImport()&&d[c].add("default"));}});}let T=Object.entries(f).filter(([o,a])=>{if(o.includes("pages/")||o.includes("app/")||o.endsWith("index.tsx")||o.endsWith("App.tsx"))return false;let y=d[o];return a.named.size===0&&!a.default?false:y.size===0}).map(([o])=>o),R={};i&&Object.entries(f).forEach(([o,a])=>{let y=d[o],g=[];a.default&&!y.has("default")&&g.push("default"),a.named.forEach(x=>{y.has(x)||g.push(x);}),g.length&&(R[o]=g);});let O={};return Object.entries(u).forEach(([o,a])=>{O[o]={count:a,size:s?V(t,o):"\u2014"};}),{packages:O,unusedFiles:T,unusedExports:R,usedExports:d,sourceFiles:v}}var z=new commander.Command;z.name("react-prune").description("Analyze React/Next/Vite/React Native projects").version(U().version).option("--no-size","Skip package size calculation").option("--no-exports","Skip export usage analysis").option("--limit <n>","Limit output rows per table","50");z.command("analyze").description("Run full project analysis").option("--no-size","Skip package size calculation").option("--no-exports","Skip export usage analysis").option("--limit <n>","Limit output rows per table","50").option("--json","Output results as JSON").action(async e=>{let t=process.cwd(),s=Number(e.limit),i=await F({rootPath:t,includeSizes:e.size,analyzeExports:e.exports,silent:e.json});if(e.json){let{sourceFiles:n,usedExports:r,...m}=i,u={};if(r)for(let[f,d]of Object.entries(r))u[f]=Array.from(d);console.log(JSON.stringify({...m,usedExports:u},null,2));return}let l=new k__default.default({head:[h__default.default.cyan("Package"),"Count","Size"],colWidths:[40,10,15]});if(Object.entries(i.packages).sort((n,r)=>r[1].count-n[1].count).slice(0,s).forEach(([n,r])=>l.push([n,r.count,r.size])),console.log(P__default.default(h__default.default.bold("\u{1F4E6} Package Usage"),{padding:1,borderColor:"green",borderStyle:"round"})),console.log(l.toString()),i.unusedFiles.length){let n=new k__default.default({head:[h__default.default.yellow("Unused Files")],colWidths:[80]});i.unusedFiles.slice(0,s).forEach(r=>n.push([r])),console.log(P__default.default(h__default.default.bold(`\u26A0\uFE0F Unused Files (${i.unusedFiles.length})`),{padding:1,borderColor:"yellow",borderStyle:"round"})),console.log(n.toString());}if(e.exports){let n=Object.entries(i.unusedExports);if(n.length){let r=new k__default.default({head:["File","Unused Exports"],colWidths:[50,40],wordWrap:true});n.slice(0,s).forEach(([m,u])=>r.push([m,u.join(", ")])),console.log(P__default.default(h__default.default.bold("\u26A0\uFE0F Unused Exports"),{padding:1,borderColor:"yellow",borderStyle:"round"})),console.log(r.toString());}}});z.command("size <packageName>").description("Check the size of a specific npm package in node_modules").action(async e=>{let t=process.cwd(),{getPackageSize:s}=await Promise.resolve().then(()=>(N(),I)),i=s(t,e);console.log(i==="N/A"?h__default.default.yellow(`Package '${e}' not found in node_modules.`):h__default.default.green(`\u{1F4E6} ${e} size: ${i}`));});z.command("find <exportName>").description("Find usage count and references (with line numbers) for a component/function/export").action(async e=>{let t=process.cwd(),s=await F({rootPath:t,analyzeExports:true,includeSizes:false}),i=[],l=s.usedExports||{};for(let[n,r]of Object.entries(l)){let m=s.sourceFiles?.[n];if(!m||!r.has(e))continue;m.getDescendantsOfKind(tsMorph.SyntaxKind.Identifier).forEach(f=>{if(f.getText()===e){let d=f.getParentOrThrow().getKindName();(d.includes("Import")||d.includes("PropertyAccess")||d.includes("Identifier"))&&i.push({file:n,line:f.getStartLineNumber()});}});}i.length?(console.log(h__default.default.green(`'${e}' is used ${i.length} time(s):`)),i.forEach(n=>console.log(` - ${n.file}:${n.line}`))):console.log(h__default.default.yellow(`'${e}' is not used anywhere.`));});z.parse(process.argv);
|
|
3
|
+
//# sourceMappingURL=cli.js.map
|
|
3
4
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","tsConfigPath","projectConfig","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","resolvedLocalFile","resolvedSourceFile","possibleMatches","match","normalizedMatch","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","unusedExports","relativePath","exportedDeclarations","fileUnusedExports","name","declarations","isUsed","decl","refs","ref","program","Command","directory","targetDir","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","unusedExportsEntries","totalUnusedExports","acc","exports","unusedExportsTable","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 unusedExports: Record<string, 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 tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n const projectConfig: any = {\n skipAddingFilesFromTsConfig: true\n };\n\n if (fs.existsSync(tsConfigPath)) {\n projectConfig.tsConfigFilePath = tsConfigPath;\n }\n\n const project = new Project(projectConfig);\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 // Check if it's a path alias or non-relative local import\n let resolvedLocalFile: string | undefined;\n\n // Try ts-morph resolution first (if tsconfig loaded)\n try {\n const resolvedSourceFile = imp.getModuleSpecifierSourceFile();\n if (resolvedSourceFile) {\n resolvedLocalFile = resolvedSourceFile.getFilePath();\n }\n } catch (e) {}\n\n // Fallback: Check if the module specifier matches a known local file\n // relative to baseUrl (src) or just fuzzy match\n if (!resolvedLocalFile) {\n const possibleMatches = files.filter((f) =>\n f.includes(moduleSpecifier)\n );\n if (possibleMatches.length > 0) {\n for (const match of possibleMatches) {\n const normalizedMatch = match.replace(/\\\\/g, \"/\");\n if (\n normalizedMatch.endsWith(`${moduleSpecifier}.tsx`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}.ts`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}/index.tsx`)\n ) {\n resolvedLocalFile = match;\n break;\n }\n }\n }\n }\n\n if (resolvedLocalFile) {\n const relativeTry = path.relative(rootPath, resolvedLocalFile);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n continue; // Skip package counting\n }\n }\n\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 const unusedExports: Record<string, string[]> = {};\n\n // 5. Check for unused exports\n for (const sourceFile of project.getSourceFiles()) {\n const filePath = sourceFile.getFilePath();\n const relativePath = path.relative(rootPath, filePath);\n\n // Skip known entry points/framework files from export analysis\n if (\n filePath.includes(\"pages/\") ||\n filePath.includes(\"app/\") ||\n filePath.endsWith(\"main.tsx\") ||\n filePath.endsWith(\"index.tsx\") ||\n filePath.endsWith(\"index.js\") ||\n filePath.endsWith(\"App.tsx\") ||\n filePath.endsWith(\"App.js\") ||\n unused.includes(filePath) // Skip files already marked as unused\n ) {\n continue;\n }\n\n const exportedDeclarations = sourceFile.getExportedDeclarations();\n const fileUnusedExports: string[] = [];\n\n for (const [name, declarations] of exportedDeclarations) {\n let isUsed = false;\n\n // We consider it used if it has references in OTHER files\n // getReferencesAsNodes() is expensive, so we might need a cheaper check?\n // But for accuracy we need references.\n\n for (const decl of declarations) {\n if (\n SyntaxKind.VariableDeclaration === decl.getKind() ||\n SyntaxKind.FunctionDeclaration === decl.getKind() ||\n SyntaxKind.ClassDeclaration === decl.getKind() ||\n SyntaxKind.InterfaceDeclaration === decl.getKind() ||\n SyntaxKind.TypeAliasDeclaration === decl.getKind() ||\n SyntaxKind.EnumDeclaration === decl.getKind()\n ) {\n try {\n // @ts-ignore\n const refs = decl.findReferencesAsNodes();\n for (const ref of refs) {\n const refSourceFile = ref.getSourceFile();\n if (refSourceFile.getFilePath() !== filePath) {\n isUsed = true;\n break;\n }\n }\n } catch (e) {\n // If error, assume used to be safe\n isUsed = true;\n }\n } else {\n // For other kinds (like ExportSpecifier), we might need different handling\n // or assume used.\n // default export is usually a FunctionDeclaration or ClassDeclaration,\n // but could be an expression.\n isUsed = true; // Skip complex cases for now\n }\n if (isUsed) break;\n }\n\n if (!isUsed) {\n fileUnusedExports.push(name);\n }\n }\n\n if (fileUnusedExports.length > 0) {\n unusedExports[relativePath] = fileUnusedExports;\n }\n }\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused,\n unusedExports\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport path from \"path\";\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 [directory]\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async (directory) => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const targetDir = directory ? path.resolve(directory) : process.cwd();\n const report = await analyzeProject(targetDir);\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\n // Unused Exports\n const unusedExportsEntries = Object.entries(report.unusedExports);\n if (unusedExportsEntries.length > 0) {\n const totalUnusedExports = unusedExportsEntries.reduce(\n (acc, [, exports]) => acc + exports.length,\n 0\n );\n const unusedExportsTable = new Table({\n head: [pc.yellow(\"File\"), pc.yellow(\"Unused Exports\")],\n colWidths: [40, 40],\n wordWrap: true\n });\n\n console.log(\n boxen(\n pc.bold(`⚠️ Potential Unused Exports (${totalUnusedExports})`),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n unusedExportsEntries.slice(0, 50).forEach(([file, exports]) => {\n unusedExportsTable.push([file, exports.join(\", \")]);\n });\n console.log(unusedExportsTable.toString());\n\n if (unusedExportsEntries.length > 50) {\n console.log(\n pc.gray(\n `...and ${unusedExportsEntries.length - 50} more files with unused exports.`\n )\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"]}
|
|
1
|
+
{"version":3,"sources":["../package.json","../src/analyzer/package-size.ts","../src/analyzer/index.ts","../src/cli.ts"],"names":["require_package","__commonJSMin","exports","module","package_size_exports","__export","getPackageSize","rootPath","pkg","pkgJson","__require","path","init_package_size","__esmMin","getFolderSize","dirPath","size","files","fs","file","filePath","stats","formatBytes","bytes","decimals","k","dm","sizes","i","packageName","pkgPath","analyzeProject","options","includeSizes","analyzeExports","silent","pc","glob","project","Project","tsConfigPath","f","packageUsage","fileExports","usedExports","sourceFiles","sourceFile","relativePath","exportsMap","namedExports","decls","name","hasDefault","imp","mod","SyntaxKind","call","args","imports","sourceDir","resolvedPath","extensions","foundFile","ext","tryPath","ni","unusedFiles","used","unusedExports","unused","packages","count","program","Command","opts","limit","report","rest","sanitizedUsedExports","key","value","packageTable","Table","a","b","data","boxen","table","entries","exportName","usageDetails","usedSet","id","parentKind","d"],"mappings":"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","file":"cli.js","sourcesContent":["{\n \"name\": \"react-prune\",\n \"version\": \"1.2.2\",\n \"main\": \"dist/index.js\",\n \"bin\": {\n \"react-prune\": \"./dist/cli.js\"\n },\n \"scripts\": {\n \"build\": \"tsup src/cli.ts --format cjs --dts\",\n \"dev\": \"tsup src/cli.ts --watch\",\n \"lint\": \"eslint src/**\",\n \"format\": \"prettier --write .\",\n \"prepublishOnly\": \"npm run build\",\n \"release\": \"changeset publish\",\n \"test\": \"vitest\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"keywords\": [\n \"react\",\n \"react-native\",\n \"nextjs\",\n \"analysis\",\n \"dead-code\",\n \"imports\",\n \"dependency-analysis\",\n \"bundle-size\",\n \"prune\",\n \"typescript\",\n \"developer-tools\",\n \"cli\"\n ],\n \"author\": \"Daniel Arikawe\",\n \"license\": \"MIT\",\n \"files\": [\n \"dist\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/danieljohnson18/react-prune.git\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/danieljohnson18/react-prune/issues\"\n },\n \"homepage\": \"https://github.com/danieljohnson18/react-prune#readme\",\n \"description\": \"A powerful CLI tool to monitor package usage, analyze component imports, and detect dead code across React, Next.js, and React Native applications.\",\n \"dependencies\": {\n \"boxen\": \"^8.0.1\",\n \"cli-table3\": \"^0.6.5\",\n \"commander\": \"^14.0.2\",\n \"fast-glob\": \"^3.3.3\",\n \"picocolors\": \"^1.1.1\",\n \"ts-morph\": \"^27.0.2\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.29.8\",\n \"@types/glob\": \"^8.1.0\",\n \"@types/node\": \"^25.0.10\",\n \"eslint\": \"^9.39.2\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\"\n }\n}\n","import path from \"path\";\n\nexport function getPackageSize(\n rootPath: string,\n pkg: string\n): string | undefined {\n try {\n const pkgJson = require(\n path.join(rootPath, \"node_modules\", pkg, \"package.json\")\n );\n if (typeof pkgJson.size === \"number\") {\n return `${(pkgJson.size / 1024).toFixed(2)} KB`;\n }\n } catch {}\n return undefined;\n}\n","import { Project, SyntaxKind, SourceFile } from \"ts-morph\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport pc from \"picocolors\";\nimport { AnalyzerOptions, UsageReport } from \"./types\";\n\nexport * from \"./types\";\n\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()) size += getFolderSize(filePath);\n else size += stats.size;\n }\n } catch {}\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 const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) return formatBytes(getFolderSize(pkgPath));\n return \"N/A\";\n}\n\nexport async function analyzeProject(\n options: AnalyzerOptions\n): Promise<UsageReport> {\n const {\n rootPath,\n includeSizes = true,\n analyzeExports = true,\n silent = false\n } = options;\n\n if (!silent) {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n }\n\n // 1️⃣ Collect all JS/TS 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}\",\n \"**/*.d.ts\"\n ],\n absolute: true\n });\n\n if (!silent) {\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n }\n\n const project = new Project({ skipAddingFilesFromTsConfig: true });\n const tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n if (fs.existsSync(tsConfigPath)) {\n project.addSourceFilesFromTsConfig(tsConfigPath);\n }\n files.forEach((f) => {\n try {\n project.addSourceFileAtPath(f);\n } catch {\n console.warn(pc.yellow(`Skipping ${f}`));\n }\n });\n\n // 2️⃣ Track package usage and file exports\n const packageUsage: Record<string, number> = {};\n const fileExports: Record<string, { named: Set<string>; default: boolean }> =\n {};\n const usedExports: Record<string, Set<string>> = {};\n const sourceFiles: Record<string, SourceFile> = {};\n\n for (const sourceFile of project.getSourceFiles()) {\n const relativePath = path\n .relative(rootPath, sourceFile.getFilePath())\n .replace(/\\\\/g, \"/\");\n sourceFiles[relativePath] = sourceFile;\n\n const exportsMap = sourceFile.getExportedDeclarations();\n const namedExports = new Set<string>();\n exportsMap.forEach((decls, name) => {\n if (name !== \"default\") namedExports.add(name);\n });\n const hasDefault = exportsMap.has(\"default\");\n\n fileExports[relativePath] = { named: namedExports, default: hasDefault };\n usedExports[relativePath] = new Set();\n\n // Track package imports\n const imports = sourceFile.getImportDeclarations();\n imports.forEach((imp) => {\n const mod = imp.getModuleSpecifierValue();\n if (!mod.startsWith(\".\") && !mod.startsWith(\"/\")) {\n const pkg = mod.startsWith(\"@\")\n ? mod.split(\"/\").slice(0, 2).join(\"/\")\n : mod.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n });\n\n // Track require()\n const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);\n calls.forEach((call) => {\n if (call.getExpression().getText() === \"require\") {\n const args = call.getArguments();\n if (args.length && args[0].getKind() === SyntaxKind.StringLiteral) {\n const mod = args[0].getText().replace(/['\"`]/g, \"\");\n if (!mod.startsWith(\".\") && !mod.startsWith(\"/\")) {\n const pkg = mod.startsWith(\"@\")\n ? mod.split(\"/\").slice(0, 2).join(\"/\")\n : mod.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n });\n }\n\n // 3️⃣ Track used exports across files\n if (analyzeExports) {\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n const relativePath = path\n .relative(rootPath, sourceFile.getFilePath())\n .replace(/\\\\/g, \"/\");\n\n imports.forEach((imp) => {\n const mod = imp.getModuleSpecifierValue();\n if (mod.startsWith(\".\") || mod.startsWith(\"/\")) {\n const sourceDir = path.dirname(sourceFile.getFilePath());\n let resolvedPath = path.resolve(sourceDir, mod);\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 let foundFile: string | undefined;\n\n for (const ext of extensions) {\n const tryPath = path\n .relative(rootPath, resolvedPath + ext)\n .replace(/\\\\/g, \"/\");\n if (fileExports[tryPath]) {\n foundFile = tryPath;\n break;\n }\n }\n\n if (foundFile) {\n imp\n .getNamedImports()\n .forEach((ni) => usedExports[foundFile].add(ni.getName()));\n if (imp.getDefaultImport()) usedExports[foundFile].add(\"default\");\n }\n }\n });\n }\n }\n\n // 4️⃣ Determine unused files and exports\n const unusedFiles = Object.entries(fileExports)\n .filter(([file, exports]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"App.tsx\")\n )\n return false;\n const used = usedExports[file];\n if (exports.named.size === 0 && !exports.default) return false;\n return used.size === 0;\n })\n .map(([file]) => file);\n\n const unusedExports: Record<string, string[]> = {};\n if (analyzeExports) {\n Object.entries(fileExports).forEach(([file, exports]) => {\n const used = usedExports[file];\n const unused: string[] = [];\n if (exports.default && !used.has(\"default\")) unused.push(\"default\");\n exports.named.forEach((name) => {\n if (!used.has(name)) unused.push(name);\n });\n if (unused.length) unusedExports[file] = unused;\n });\n }\n\n // 5️⃣ Build package report\n const packages: Record<string, { count: number; size: string }> = {};\n Object.entries(packageUsage).forEach(([pkg, count]) => {\n packages[pkg] = {\n count,\n size: includeSizes ? getPackageSize(rootPath, pkg) : \"—\"\n };\n });\n\n return {\n packages,\n unusedFiles,\n unusedExports,\n usedExports,\n sourceFiles\n };\n}\n\nexport { getPackageSize };\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport Table from \"cli-table3\";\nimport boxen from \"boxen\";\nimport path from \"path\";\nimport { analyzeProject, UsageReport } from \"./analyzer\";\nimport { SyntaxKind, Identifier } from \"ts-morph\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\"Analyze React/Next/Vite/React Native projects\")\n .version(require(\"../package.json\").version)\n .option(\"--no-size\", \"Skip package size calculation\")\n .option(\"--no-exports\", \"Skip export usage analysis\")\n .option(\"--limit <n>\", \"Limit output rows per table\", \"50\");\n\nprogram\n .command(\"analyze\")\n .description(\"Run full project analysis\")\n .option(\"--no-size\", \"Skip package size calculation\")\n .option(\"--no-exports\", \"Skip export usage analysis\")\n .option(\"--limit <n>\", \"Limit output rows per table\", \"50\")\n .option(\"--json\", \"Output results as JSON\")\n .action(async (opts) => {\n const rootPath = process.cwd();\n const limit = Number(opts.limit);\n\n const report: UsageReport = await analyzeProject({\n rootPath,\n includeSizes: opts.size,\n analyzeExports: opts.exports,\n silent: opts.json\n });\n\n if (opts.json) {\n // 1. Remove circular references (sourceFiles)\n // 2. Convert Sets to Arrays for valid JSON\n const { sourceFiles, usedExports, ...rest } = report;\n\n const sanitizedUsedExports: Record<string, string[]> = {};\n if (usedExports) {\n for (const [key, value] of Object.entries(usedExports)) {\n sanitizedUsedExports[key] = Array.from(value);\n }\n }\n\n console.log(\n JSON.stringify(\n {\n ...rest,\n usedExports: sanitizedUsedExports\n },\n null,\n 2\n )\n );\n return;\n }\n\n // Packages\n const packageTable = new Table({\n head: [pc.cyan(\"Package\"), \"Count\", \"Size\"],\n colWidths: [40, 10, 15]\n });\n Object.entries(report.packages)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, limit)\n .forEach(([pkg, data]) =>\n packageTable.push([pkg, data.count, data.size])\n );\n console.log(\n boxen(pc.bold(\"📦 Package Usage\"), {\n padding: 1,\n borderColor: \"green\",\n borderStyle: \"round\"\n })\n );\n console.log(packageTable.toString());\n\n // Unused Files\n if (report.unusedFiles.length) {\n const table = new Table({\n head: [pc.yellow(\"Unused Files\")],\n colWidths: [80]\n });\n report.unusedFiles.slice(0, limit).forEach((f) => table.push([f]));\n console.log(\n boxen(pc.bold(`⚠️ Unused Files (${report.unusedFiles.length})`), {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n })\n );\n console.log(table.toString());\n }\n\n // Unused Exports\n if (opts.exports) {\n const entries = Object.entries(report.unusedExports);\n if (entries.length) {\n const table = new Table({\n head: [\"File\", \"Unused Exports\"],\n colWidths: [50, 40],\n wordWrap: true\n });\n entries\n .slice(0, limit)\n .forEach(([file, exports]) => table.push([file, exports.join(\", \")]));\n console.log(\n boxen(pc.bold(`⚠️ Unused Exports`), {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n })\n );\n console.log(table.toString());\n }\n }\n });\n\nprogram\n .command(\"size <packageName>\")\n .description(\"Check the size of a specific npm package in node_modules\")\n .action(async (packageName) => {\n const rootPath = process.cwd();\n\n // Reuse helper from analyzer\n const { getPackageSize } = await import(\"./analyzer/package-size\");\n\n const size = getPackageSize(rootPath, packageName);\n\n if (size === \"N/A\") {\n console.log(\n pc.yellow(`Package '${packageName}' not found in node_modules.`)\n );\n } else {\n console.log(pc.green(`📦 ${packageName} size: ${size}`));\n }\n });\n// --- New find command with line numbers\nprogram\n .command(\"find <exportName>\")\n .description(\n \"Find usage count and references (with line numbers) for a component/function/export\"\n )\n .action(async (exportName) => {\n const rootPath = process.cwd();\n const report: UsageReport = await analyzeProject({\n rootPath,\n analyzeExports: true,\n includeSizes: false\n });\n\n const usageDetails: { file: string; line: number }[] = [];\n\n const usedExports = report.usedExports || {};\n\n for (const [file, usedSet] of Object.entries(usedExports)) {\n const sourceFile = report.sourceFiles?.[file];\n if (!sourceFile) continue;\n\n // Only process if this file actually uses the export\n if (!usedSet.has(exportName)) continue;\n\n // Traverse identifiers in the file\n const identifiers = sourceFile.getDescendantsOfKind(\n SyntaxKind.Identifier\n );\n\n identifiers.forEach((id: Identifier) => {\n // Check if identifier matches the export name\n if (id.getText() === exportName) {\n // Make sure this usage is actually an import/reference, not a declaration\n const parentKind = id.getParentOrThrow().getKindName();\n\n if (\n parentKind.includes(\"Import\") || // ImportSpecifier, ImportClause, etc\n parentKind.includes(\"PropertyAccess\") || // obj.exportName\n parentKind.includes(\"Identifier\") // usage in code\n ) {\n usageDetails.push({ file, line: id.getStartLineNumber() });\n }\n }\n });\n }\n\n if (usageDetails.length) {\n console.log(\n pc.green(`'${exportName}' is used ${usageDetails.length} time(s):`)\n );\n usageDetails.forEach((d) => console.log(` - ${d.file}:${d.line}`));\n } else {\n console.log(pc.yellow(`'${exportName}' is not used anywhere.`));\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-prune",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-prune": "./dist/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsup",
|
|
10
|
-
"dev": "tsup --watch",
|
|
9
|
+
"build": "tsup src/cli.ts --format cjs --dts",
|
|
10
|
+
"dev": "tsup src/cli.ts --watch",
|
|
11
11
|
"lint": "eslint src/**",
|
|
12
12
|
"format": "prettier --write .",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
|
-
"release": "changeset publish"
|
|
14
|
+
"release": "changeset publish",
|
|
15
|
+
"test": "vitest"
|
|
15
16
|
},
|
|
16
17
|
"publishConfig": {
|
|
17
18
|
"access": "public"
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"eslint": "^9.39.2",
|
|
63
64
|
"prettier": "^3.8.1",
|
|
64
65
|
"tsup": "^8.5.1",
|
|
65
|
-
"typescript": "^5.9.3"
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"vitest": "^4.0.18"
|
|
66
68
|
}
|
|
67
69
|
}
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/dist/cli.mjs
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {Command}from'commander';import u from'picocolors';import m from'path';import {Project,SyntaxKind}from'ts-morph';import C from'fast-glob';import k from'fs';import P from'cli-table3';import W from'boxen';function A(s){let n=0;try{let r=k.readdirSync(s);for(let h of r){let p=m.join(s,h),i=k.statSync(p);i.isDirectory()?n+=A(p):n+=i.size;}}catch{return 0}return n}function U(s,n=2){if(s===0)return "0 Bytes";let r=1024,h=n<0?0:n,p=["Bytes","KB","MB","GB","TB"],i=Math.floor(Math.log(s)/Math.log(r));return parseFloat((s/Math.pow(r,i)).toFixed(h))+" "+p[i]}function $(s,n){let r=m.join(s,"node_modules",n);if(k.existsSync(r)){let h=A(r);return U(h)}return "N/A"}async function D(s){console.log(u.green(`Analyzing project at ${s}`));let n=await C("**/*.{js,jsx,ts,tsx}",{cwd:s,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(u.blue(`Found ${n.length} files to analyze.`));let r=m.join(s,"tsconfig.json"),h={skipAddingFilesFromTsConfig:true};k.existsSync(r)&&(h.tsConfigFilePath=r);let p=new Project(h);n.forEach(t=>{try{p.addSourceFileAtPath(t);}catch(o){console.warn(u.yellow(`Skipping file ${t} due to load error:`),o);}});let i={},c={};n.forEach(t=>{let o=m.relative(s,t);c[o]=0;});for(let t of p.getSourceFiles()){let o=t.getImportDeclarations();for(let b of o){let a=b.getModuleSpecifierValue();if(a.startsWith(".")||a.startsWith("/"))try{let l=t.getFilePath(),d=m.dirname(l),e=m.resolve(d,a),g=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let x of g){let w=e+x,v=m.relative(s,w);if(c.hasOwnProperty(v)){c[v]++;break}}}catch{}else {let l;try{let e=b.getModuleSpecifierSourceFile();e&&(l=e.getFilePath());}catch{}if(!l){let e=n.filter(g=>g.includes(a));if(e.length>0)for(let g of e){let x=g.replace(/\\/g,"/");if(x.endsWith(`${a}.tsx`)||x.endsWith(`${a}.ts`)||x.endsWith(`${a}/index.tsx`)){l=g;break}}}if(l){let e=m.relative(s,l);if(c.hasOwnProperty(e)){c[e]++;continue}}let d=a;if(a.startsWith("@")){let e=a.split("/");e.length>=2&&(d=`${e[0]}/${e[1]}`);}else {let e=a.split("/");e.length>=1&&(d=e[0]);}i[d]=(i[d]||0)+1;}}let S=t.getDescendantsOfKind(SyntaxKind.CallExpression);for(let b of S)if(b.getExpression().getText()==="require"){let l=b.getArguments();if(l.length>0&&l[0].getKind()===SyntaxKind.StringLiteral){let d=l[0].getText().replace(/['"`]/g,"");if(!d.startsWith(".")&&!d.startsWith("/")){let e=d.startsWith("@")?d.split("/").slice(0,2).join("/"):d.split("/")[0];i[e]=(i[e]||0)+1;}}}}let f={};for(let[t,o]of Object.entries(i)){let S=$(s,t);f[t]={count:o,size:S};}let F=Object.entries(c).filter(([t,o])=>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")||!m.relative(s,t).includes(m.sep)?false:o===0).map(([t])=>t),j={};for(let t of p.getSourceFiles()){let o=t.getFilePath(),S=m.relative(s,o);if(o.includes("pages/")||o.includes("app/")||o.endsWith("main.tsx")||o.endsWith("index.tsx")||o.endsWith("index.js")||o.endsWith("App.tsx")||o.endsWith("App.js")||F.includes(o))continue;let b=t.getExportedDeclarations(),a=[];for(let[l,d]of b){let e=false;for(let g of d){if(SyntaxKind.VariableDeclaration===g.getKind()||SyntaxKind.FunctionDeclaration===g.getKind()||SyntaxKind.ClassDeclaration===g.getKind()||SyntaxKind.InterfaceDeclaration===g.getKind()||SyntaxKind.TypeAliasDeclaration===g.getKind()||SyntaxKind.EnumDeclaration===g.getKind())try{let x=g.findReferencesAsNodes();for(let w of x)if(w.getSourceFile().getFilePath()!==o){e=!0;break}}catch{e=true;}else e=true;if(e)break}e||a.push(l);}a.length>0&&(j[S]=a);}return {packages:f,components:c,unusedFiles:F,unusedExports:j}}var z=new Command;z.name("react-prune").description("Monitor usage of packages and component imports across your React/Next.js/React Native app").version("1.0.0");z.command("analyze [directory]").description("Analyze the current project for package and component usage").action(async s=>{console.log(u.blue("Starting analysis..."));try{let n=s?m.resolve(s):process.cwd(),r=await D(n),h=new P({head:[u.cyan("Package Name"),u.cyan("Usage Count"),u.cyan("Est. Size")],colWidths:[40,15,15]}),p=Object.entries(r.packages).sort((c,f)=>f[1].count-c[1].count);if(p.slice(0,50).forEach(([c,f])=>{h.push([c,f.count,f.size]);}),console.log(W(u.bold("\u{1F4E6} Package Usage Report"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"})),console.log(h.toString()),p.length>50&&console.log(u.gray(`...and ${p.length-50} more packages.`)),r.unusedFiles.length>0){let c=new P({head:[u.yellow("File Path")],colWidths:[80]});console.log(W(u.bold(`\u26A0\uFE0F Potential Unused Files (${r.unusedFiles.length})`),{padding:1,margin:1,borderStyle:"round",borderColor:"yellow"})),r.unusedFiles.forEach(f=>c.push([f])),console.log(c.toString());}else console.log(W(u.bold("\u2705 No unused files detected!"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"}));let i=Object.entries(r.unusedExports);if(i.length>0){let c=i.reduce((F,[,j])=>F+j.length,0),f=new P({head:[u.yellow("File"),u.yellow("Unused Exports")],colWidths:[40,40],wordWrap:!0});console.log(W(u.bold(`\u26A0\uFE0F Potential Unused Exports (${c})`),{padding:1,margin:1,borderStyle:"round",borderColor:"yellow"})),i.slice(0,50).forEach(([F,j])=>{f.push([F,j.join(", ")]);}),console.log(f.toString()),i.length>50&&console.log(u.gray(`...and ${i.length-50} more files with unused exports.`));}}catch(n){console.error(u.red("Analysis failed:"),n),process.exit(1);}});z.parse(process.argv);//# sourceMappingURL=cli.mjs.map
|
|
3
|
-
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","tsConfigPath","projectConfig","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","resolvedLocalFile","resolvedSourceFile","possibleMatches","match","normalizedMatch","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","unusedExports","relativePath","exportedDeclarations","fileUnusedExports","name","declarations","isUsed","decl","refs","ref","program","Command","directory","targetDir","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","unusedExportsEntries","totalUnusedExports","acc","exports","unusedExportsTable","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 unusedExports: Record<string, 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 tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n const projectConfig: any = {\n skipAddingFilesFromTsConfig: true\n };\n\n if (fs.existsSync(tsConfigPath)) {\n projectConfig.tsConfigFilePath = tsConfigPath;\n }\n\n const project = new Project(projectConfig);\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 // Check if it's a path alias or non-relative local import\n let resolvedLocalFile: string | undefined;\n\n // Try ts-morph resolution first (if tsconfig loaded)\n try {\n const resolvedSourceFile = imp.getModuleSpecifierSourceFile();\n if (resolvedSourceFile) {\n resolvedLocalFile = resolvedSourceFile.getFilePath();\n }\n } catch (e) {}\n\n // Fallback: Check if the module specifier matches a known local file\n // relative to baseUrl (src) or just fuzzy match\n if (!resolvedLocalFile) {\n const possibleMatches = files.filter((f) =>\n f.includes(moduleSpecifier)\n );\n if (possibleMatches.length > 0) {\n for (const match of possibleMatches) {\n const normalizedMatch = match.replace(/\\\\/g, \"/\");\n if (\n normalizedMatch.endsWith(`${moduleSpecifier}.tsx`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}.ts`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}/index.tsx`)\n ) {\n resolvedLocalFile = match;\n break;\n }\n }\n }\n }\n\n if (resolvedLocalFile) {\n const relativeTry = path.relative(rootPath, resolvedLocalFile);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n continue; // Skip package counting\n }\n }\n\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 const unusedExports: Record<string, string[]> = {};\n\n // 5. Check for unused exports\n for (const sourceFile of project.getSourceFiles()) {\n const filePath = sourceFile.getFilePath();\n const relativePath = path.relative(rootPath, filePath);\n\n // Skip known entry points/framework files from export analysis\n if (\n filePath.includes(\"pages/\") ||\n filePath.includes(\"app/\") ||\n filePath.endsWith(\"main.tsx\") ||\n filePath.endsWith(\"index.tsx\") ||\n filePath.endsWith(\"index.js\") ||\n filePath.endsWith(\"App.tsx\") ||\n filePath.endsWith(\"App.js\") ||\n unused.includes(filePath) // Skip files already marked as unused\n ) {\n continue;\n }\n\n const exportedDeclarations = sourceFile.getExportedDeclarations();\n const fileUnusedExports: string[] = [];\n\n for (const [name, declarations] of exportedDeclarations) {\n let isUsed = false;\n\n // We consider it used if it has references in OTHER files\n // getReferencesAsNodes() is expensive, so we might need a cheaper check?\n // But for accuracy we need references.\n\n for (const decl of declarations) {\n if (\n SyntaxKind.VariableDeclaration === decl.getKind() ||\n SyntaxKind.FunctionDeclaration === decl.getKind() ||\n SyntaxKind.ClassDeclaration === decl.getKind() ||\n SyntaxKind.InterfaceDeclaration === decl.getKind() ||\n SyntaxKind.TypeAliasDeclaration === decl.getKind() ||\n SyntaxKind.EnumDeclaration === decl.getKind()\n ) {\n try {\n // @ts-ignore\n const refs = decl.findReferencesAsNodes();\n for (const ref of refs) {\n const refSourceFile = ref.getSourceFile();\n if (refSourceFile.getFilePath() !== filePath) {\n isUsed = true;\n break;\n }\n }\n } catch (e) {\n // If error, assume used to be safe\n isUsed = true;\n }\n } else {\n // For other kinds (like ExportSpecifier), we might need different handling\n // or assume used.\n // default export is usually a FunctionDeclaration or ClassDeclaration,\n // but could be an expression.\n isUsed = true; // Skip complex cases for now\n }\n if (isUsed) break;\n }\n\n if (!isUsed) {\n fileUnusedExports.push(name);\n }\n }\n\n if (fileUnusedExports.length > 0) {\n unusedExports[relativePath] = fileUnusedExports;\n }\n }\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused,\n unusedExports\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport path from \"path\";\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 [directory]\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async (directory) => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const targetDir = directory ? path.resolve(directory) : process.cwd();\n const report = await analyzeProject(targetDir);\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\n // Unused Exports\n const unusedExportsEntries = Object.entries(report.unusedExports);\n if (unusedExportsEntries.length > 0) {\n const totalUnusedExports = unusedExportsEntries.reduce(\n (acc, [, exports]) => acc + exports.length,\n 0\n );\n const unusedExportsTable = new Table({\n head: [pc.yellow(\"File\"), pc.yellow(\"Unused Exports\")],\n colWidths: [40, 40],\n wordWrap: true\n });\n\n console.log(\n boxen(\n pc.bold(`⚠️ Potential Unused Exports (${totalUnusedExports})`),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n unusedExportsEntries.slice(0, 50).forEach(([file, exports]) => {\n unusedExportsTable.push([file, exports.join(\", \")]);\n });\n console.log(unusedExportsTable.toString());\n\n if (unusedExportsEntries.length > 50) {\n console.log(\n pc.gray(\n `...and ${unusedExportsEntries.length - 50} more files with unused exports.`\n )\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.d.mts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
interface UsageReport {
|
|
2
|
-
packages: Record<string, {
|
|
3
|
-
count: number;
|
|
4
|
-
size: string;
|
|
5
|
-
}>;
|
|
6
|
-
components: Record<string, number>;
|
|
7
|
-
unusedFiles: string[];
|
|
8
|
-
unusedExports: Record<string, string[]>;
|
|
9
|
-
}
|
|
10
|
-
declare function analyzeProject(rootPath: string): Promise<UsageReport>;
|
|
11
|
-
|
|
12
|
-
export { type UsageReport, analyzeProject };
|
package/dist/index.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
interface UsageReport {
|
|
2
|
-
packages: Record<string, {
|
|
3
|
-
count: number;
|
|
4
|
-
size: string;
|
|
5
|
-
}>;
|
|
6
|
-
components: Record<string, number>;
|
|
7
|
-
unusedFiles: string[];
|
|
8
|
-
unusedExports: Record<string, string[]>;
|
|
9
|
-
}
|
|
10
|
-
declare function analyzeProject(rootPath: string): Promise<UsageReport>;
|
|
11
|
-
|
|
12
|
-
export { type UsageReport, analyzeProject };
|
package/dist/index.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
'use strict';var tsMorph=require('ts-morph'),W=require('picocolors'),D=require('fast-glob'),d=require('path'),j=require('fs');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var W__default=/*#__PURE__*/_interopDefault(W);var D__default=/*#__PURE__*/_interopDefault(D);var d__default=/*#__PURE__*/_interopDefault(d);var j__default=/*#__PURE__*/_interopDefault(j);function z(n){let a=0;try{let l=j__default.default.readdirSync(n);for(let f of l){let u=d__default.default.join(n,f),g=j__default.default.statSync(u);g.isDirectory()?a+=z(u):a+=g.size;}}catch{return 0}return a}function P(n,a=2){if(n===0)return "0 Bytes";let l=1024,f=a<0?0:a,u=["Bytes","KB","MB","GB","TB"],g=Math.floor(Math.log(n)/Math.log(l));return parseFloat((n/Math.pow(l,g)).toFixed(f))+" "+u[g]}function E(n,a){let l=d__default.default.join(n,"node_modules",a);if(j__default.default.existsSync(l)){let f=z(l);return P(f)}return "N/A"}async function B(n){console.log(W__default.default.green(`Analyzing project at ${n}`));let a=await D__default.default("**/*.{js,jsx,ts,tsx}",{cwd:n,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(W__default.default.blue(`Found ${a.length} files to analyze.`));let l=d__default.default.join(n,"tsconfig.json"),f={skipAddingFilesFromTsConfig:true};j__default.default.existsSync(l)&&(f.tsConfigFilePath=l);let u=new tsMorph.Project(f);a.forEach(t=>{try{u.addSourceFileAtPath(t);}catch(s){console.warn(W__default.default.yellow(`Skipping file ${t} due to load error:`),s);}});let g={},x={};a.forEach(t=>{let s=d__default.default.relative(n,t);x[s]=0;});for(let t of u.getSourceFiles()){let s=t.getImportDeclarations();for(let m of s){let i=m.getModuleSpecifierValue();if(i.startsWith(".")||i.startsWith("/"))try{let r=t.getFilePath(),o=d__default.default.dirname(r),e=d__default.default.resolve(o,i),c=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let p of c){let y=e+p,S=d__default.default.relative(n,y);if(x.hasOwnProperty(S)){x[S]++;break}}}catch{}else {let r;try{let e=m.getModuleSpecifierSourceFile();e&&(r=e.getFilePath());}catch{}if(!r){let e=a.filter(c=>c.includes(i));if(e.length>0)for(let c of e){let p=c.replace(/\\/g,"/");if(p.endsWith(`${i}.tsx`)||p.endsWith(`${i}.ts`)||p.endsWith(`${i}/index.tsx`)){r=c;break}}}if(r){let e=d__default.default.relative(n,r);if(x.hasOwnProperty(e)){x[e]++;continue}}let o=i;if(i.startsWith("@")){let e=i.split("/");e.length>=2&&(o=`${e[0]}/${e[1]}`);}else {let e=i.split("/");e.length>=1&&(o=e[0]);}g[o]=(g[o]||0)+1;}}let F=t.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression);for(let m of F)if(m.getExpression().getText()==="require"){let r=m.getArguments();if(r.length>0&&r[0].getKind()===tsMorph.SyntaxKind.StringLiteral){let o=r[0].getText().replace(/['"`]/g,"");if(!o.startsWith(".")&&!o.startsWith("/")){let e=o.startsWith("@")?o.split("/").slice(0,2).join("/"):o.split("/")[0];g[e]=(g[e]||0)+1;}}}}let b={};for(let[t,s]of Object.entries(g)){let F=E(n,t);b[t]={count:s,size:F};}let k=Object.entries(x).filter(([t,s])=>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")||!d__default.default.relative(n,t).includes(d__default.default.sep)?false:s===0).map(([t])=>t),v={};for(let t of u.getSourceFiles()){let s=t.getFilePath(),F=d__default.default.relative(n,s);if(s.includes("pages/")||s.includes("app/")||s.endsWith("main.tsx")||s.endsWith("index.tsx")||s.endsWith("index.js")||s.endsWith("App.tsx")||s.endsWith("App.js")||k.includes(s))continue;let m=t.getExportedDeclarations(),i=[];for(let[r,o]of m){let e=false;for(let c of o){if(tsMorph.SyntaxKind.VariableDeclaration===c.getKind()||tsMorph.SyntaxKind.FunctionDeclaration===c.getKind()||tsMorph.SyntaxKind.ClassDeclaration===c.getKind()||tsMorph.SyntaxKind.InterfaceDeclaration===c.getKind()||tsMorph.SyntaxKind.TypeAliasDeclaration===c.getKind()||tsMorph.SyntaxKind.EnumDeclaration===c.getKind())try{let p=c.findReferencesAsNodes();for(let y of p)if(y.getSourceFile().getFilePath()!==s){e=!0;break}}catch{e=true;}else e=true;if(e)break}e||i.push(r);}i.length>0&&(v[F]=i);}return {packages:b,components:x,unusedFiles:k,unusedExports:v}}exports.analyzeProject=B;//# sourceMappingURL=index.js.map
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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","tsConfigPath","projectConfig","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","resolvedLocalFile","resolvedSourceFile","possibleMatches","match","normalizedMatch","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","unusedExports","relativePath","exportedDeclarations","fileUnusedExports","name","declarations","isUsed","decl","refs","ref"],"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 unusedExports: Record<string, 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 tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n const projectConfig: any = {\n skipAddingFilesFromTsConfig: true\n };\n\n if (fs.existsSync(tsConfigPath)) {\n projectConfig.tsConfigFilePath = tsConfigPath;\n }\n\n const project = new Project(projectConfig);\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 // Check if it's a path alias or non-relative local import\n let resolvedLocalFile: string | undefined;\n\n // Try ts-morph resolution first (if tsconfig loaded)\n try {\n const resolvedSourceFile = imp.getModuleSpecifierSourceFile();\n if (resolvedSourceFile) {\n resolvedLocalFile = resolvedSourceFile.getFilePath();\n }\n } catch (e) {}\n\n // Fallback: Check if the module specifier matches a known local file\n // relative to baseUrl (src) or just fuzzy match\n if (!resolvedLocalFile) {\n const possibleMatches = files.filter((f) =>\n f.includes(moduleSpecifier)\n );\n if (possibleMatches.length > 0) {\n for (const match of possibleMatches) {\n const normalizedMatch = match.replace(/\\\\/g, \"/\");\n if (\n normalizedMatch.endsWith(`${moduleSpecifier}.tsx`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}.ts`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}/index.tsx`)\n ) {\n resolvedLocalFile = match;\n break;\n }\n }\n }\n }\n\n if (resolvedLocalFile) {\n const relativeTry = path.relative(rootPath, resolvedLocalFile);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n continue; // Skip package counting\n }\n }\n\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 const unusedExports: Record<string, string[]> = {};\n\n // 5. Check for unused exports\n for (const sourceFile of project.getSourceFiles()) {\n const filePath = sourceFile.getFilePath();\n const relativePath = path.relative(rootPath, filePath);\n\n // Skip known entry points/framework files from export analysis\n if (\n filePath.includes(\"pages/\") ||\n filePath.includes(\"app/\") ||\n filePath.endsWith(\"main.tsx\") ||\n filePath.endsWith(\"index.tsx\") ||\n filePath.endsWith(\"index.js\") ||\n filePath.endsWith(\"App.tsx\") ||\n filePath.endsWith(\"App.js\") ||\n unused.includes(filePath) // Skip files already marked as unused\n ) {\n continue;\n }\n\n const exportedDeclarations = sourceFile.getExportedDeclarations();\n const fileUnusedExports: string[] = [];\n\n for (const [name, declarations] of exportedDeclarations) {\n let isUsed = false;\n\n // We consider it used if it has references in OTHER files\n // getReferencesAsNodes() is expensive, so we might need a cheaper check?\n // But for accuracy we need references.\n\n for (const decl of declarations) {\n if (\n SyntaxKind.VariableDeclaration === decl.getKind() ||\n SyntaxKind.FunctionDeclaration === decl.getKind() ||\n SyntaxKind.ClassDeclaration === decl.getKind() ||\n SyntaxKind.InterfaceDeclaration === decl.getKind() ||\n SyntaxKind.TypeAliasDeclaration === decl.getKind() ||\n SyntaxKind.EnumDeclaration === decl.getKind()\n ) {\n try {\n // @ts-ignore\n const refs = decl.findReferencesAsNodes();\n for (const ref of refs) {\n const refSourceFile = ref.getSourceFile();\n if (refSourceFile.getFilePath() !== filePath) {\n isUsed = true;\n break;\n }\n }\n } catch (e) {\n // If error, assume used to be safe\n isUsed = true;\n }\n } else {\n // For other kinds (like ExportSpecifier), we might need different handling\n // or assume used.\n // default export is usually a FunctionDeclaration or ClassDeclaration,\n // but could be an expression.\n isUsed = true; // Skip complex cases for now\n }\n if (isUsed) break;\n }\n\n if (!isUsed) {\n fileUnusedExports.push(name);\n }\n }\n\n if (fileUnusedExports.length > 0) {\n unusedExports[relativePath] = fileUnusedExports;\n }\n }\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused,\n unusedExports\n };\n}\n"]}
|
package/dist/index.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import {Project,SyntaxKind}from'ts-morph';import W from'picocolors';import D from'fast-glob';import d from'path';import j from'fs';function z(n){let a=0;try{let l=j.readdirSync(n);for(let f of l){let u=d.join(n,f),g=j.statSync(u);g.isDirectory()?a+=z(u):a+=g.size;}}catch{return 0}return a}function P(n,a=2){if(n===0)return "0 Bytes";let l=1024,f=a<0?0:a,u=["Bytes","KB","MB","GB","TB"],g=Math.floor(Math.log(n)/Math.log(l));return parseFloat((n/Math.pow(l,g)).toFixed(f))+" "+u[g]}function E(n,a){let l=d.join(n,"node_modules",a);if(j.existsSync(l)){let f=z(l);return P(f)}return "N/A"}async function B(n){console.log(W.green(`Analyzing project at ${n}`));let a=await D("**/*.{js,jsx,ts,tsx}",{cwd:n,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/.d.ts"],absolute:true});console.log(W.blue(`Found ${a.length} files to analyze.`));let l=d.join(n,"tsconfig.json"),f={skipAddingFilesFromTsConfig:true};j.existsSync(l)&&(f.tsConfigFilePath=l);let u=new Project(f);a.forEach(t=>{try{u.addSourceFileAtPath(t);}catch(s){console.warn(W.yellow(`Skipping file ${t} due to load error:`),s);}});let g={},x={};a.forEach(t=>{let s=d.relative(n,t);x[s]=0;});for(let t of u.getSourceFiles()){let s=t.getImportDeclarations();for(let m of s){let i=m.getModuleSpecifierValue();if(i.startsWith(".")||i.startsWith("/"))try{let r=t.getFilePath(),o=d.dirname(r),e=d.resolve(o,i),c=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let p of c){let y=e+p,S=d.relative(n,y);if(x.hasOwnProperty(S)){x[S]++;break}}}catch{}else {let r;try{let e=m.getModuleSpecifierSourceFile();e&&(r=e.getFilePath());}catch{}if(!r){let e=a.filter(c=>c.includes(i));if(e.length>0)for(let c of e){let p=c.replace(/\\/g,"/");if(p.endsWith(`${i}.tsx`)||p.endsWith(`${i}.ts`)||p.endsWith(`${i}/index.tsx`)){r=c;break}}}if(r){let e=d.relative(n,r);if(x.hasOwnProperty(e)){x[e]++;continue}}let o=i;if(i.startsWith("@")){let e=i.split("/");e.length>=2&&(o=`${e[0]}/${e[1]}`);}else {let e=i.split("/");e.length>=1&&(o=e[0]);}g[o]=(g[o]||0)+1;}}let F=t.getDescendantsOfKind(SyntaxKind.CallExpression);for(let m of F)if(m.getExpression().getText()==="require"){let r=m.getArguments();if(r.length>0&&r[0].getKind()===SyntaxKind.StringLiteral){let o=r[0].getText().replace(/['"`]/g,"");if(!o.startsWith(".")&&!o.startsWith("/")){let e=o.startsWith("@")?o.split("/").slice(0,2).join("/"):o.split("/")[0];g[e]=(g[e]||0)+1;}}}}let b={};for(let[t,s]of Object.entries(g)){let F=E(n,t);b[t]={count:s,size:F};}let k=Object.entries(x).filter(([t,s])=>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")||!d.relative(n,t).includes(d.sep)?false:s===0).map(([t])=>t),v={};for(let t of u.getSourceFiles()){let s=t.getFilePath(),F=d.relative(n,s);if(s.includes("pages/")||s.includes("app/")||s.endsWith("main.tsx")||s.endsWith("index.tsx")||s.endsWith("index.js")||s.endsWith("App.tsx")||s.endsWith("App.js")||k.includes(s))continue;let m=t.getExportedDeclarations(),i=[];for(let[r,o]of m){let e=false;for(let c of o){if(SyntaxKind.VariableDeclaration===c.getKind()||SyntaxKind.FunctionDeclaration===c.getKind()||SyntaxKind.ClassDeclaration===c.getKind()||SyntaxKind.InterfaceDeclaration===c.getKind()||SyntaxKind.TypeAliasDeclaration===c.getKind()||SyntaxKind.EnumDeclaration===c.getKind())try{let p=c.findReferencesAsNodes();for(let y of p)if(y.getSourceFile().getFilePath()!==s){e=!0;break}}catch{e=true;}else e=true;if(e)break}e||i.push(r);}i.length>0&&(v[F]=i);}return {packages:b,components:x,unusedFiles:k,unusedExports:v}}export{B as analyzeProject};//# sourceMappingURL=index.mjs.map
|
|
2
|
-
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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","tsConfigPath","projectConfig","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","resolvedLocalFile","resolvedSourceFile","possibleMatches","match","normalizedMatch","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","unusedExports","relativePath","exportedDeclarations","fileUnusedExports","name","declarations","isUsed","decl","refs","ref"],"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 unusedExports: Record<string, 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 tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n const projectConfig: any = {\n skipAddingFilesFromTsConfig: true\n };\n\n if (fs.existsSync(tsConfigPath)) {\n projectConfig.tsConfigFilePath = tsConfigPath;\n }\n\n const project = new Project(projectConfig);\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 // Check if it's a path alias or non-relative local import\n let resolvedLocalFile: string | undefined;\n\n // Try ts-morph resolution first (if tsconfig loaded)\n try {\n const resolvedSourceFile = imp.getModuleSpecifierSourceFile();\n if (resolvedSourceFile) {\n resolvedLocalFile = resolvedSourceFile.getFilePath();\n }\n } catch (e) {}\n\n // Fallback: Check if the module specifier matches a known local file\n // relative to baseUrl (src) or just fuzzy match\n if (!resolvedLocalFile) {\n const possibleMatches = files.filter((f) =>\n f.includes(moduleSpecifier)\n );\n if (possibleMatches.length > 0) {\n for (const match of possibleMatches) {\n const normalizedMatch = match.replace(/\\\\/g, \"/\");\n if (\n normalizedMatch.endsWith(`${moduleSpecifier}.tsx`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}.ts`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}/index.tsx`)\n ) {\n resolvedLocalFile = match;\n break;\n }\n }\n }\n }\n\n if (resolvedLocalFile) {\n const relativeTry = path.relative(rootPath, resolvedLocalFile);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n continue; // Skip package counting\n }\n }\n\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 const unusedExports: Record<string, string[]> = {};\n\n // 5. Check for unused exports\n for (const sourceFile of project.getSourceFiles()) {\n const filePath = sourceFile.getFilePath();\n const relativePath = path.relative(rootPath, filePath);\n\n // Skip known entry points/framework files from export analysis\n if (\n filePath.includes(\"pages/\") ||\n filePath.includes(\"app/\") ||\n filePath.endsWith(\"main.tsx\") ||\n filePath.endsWith(\"index.tsx\") ||\n filePath.endsWith(\"index.js\") ||\n filePath.endsWith(\"App.tsx\") ||\n filePath.endsWith(\"App.js\") ||\n unused.includes(filePath) // Skip files already marked as unused\n ) {\n continue;\n }\n\n const exportedDeclarations = sourceFile.getExportedDeclarations();\n const fileUnusedExports: string[] = [];\n\n for (const [name, declarations] of exportedDeclarations) {\n let isUsed = false;\n\n // We consider it used if it has references in OTHER files\n // getReferencesAsNodes() is expensive, so we might need a cheaper check?\n // But for accuracy we need references.\n\n for (const decl of declarations) {\n if (\n SyntaxKind.VariableDeclaration === decl.getKind() ||\n SyntaxKind.FunctionDeclaration === decl.getKind() ||\n SyntaxKind.ClassDeclaration === decl.getKind() ||\n SyntaxKind.InterfaceDeclaration === decl.getKind() ||\n SyntaxKind.TypeAliasDeclaration === decl.getKind() ||\n SyntaxKind.EnumDeclaration === decl.getKind()\n ) {\n try {\n // @ts-ignore\n const refs = decl.findReferencesAsNodes();\n for (const ref of refs) {\n const refSourceFile = ref.getSourceFile();\n if (refSourceFile.getFilePath() !== filePath) {\n isUsed = true;\n break;\n }\n }\n } catch (e) {\n // If error, assume used to be safe\n isUsed = true;\n }\n } else {\n // For other kinds (like ExportSpecifier), we might need different handling\n // or assume used.\n // default export is usually a FunctionDeclaration or ClassDeclaration,\n // but could be an expression.\n isUsed = true; // Skip complex cases for now\n }\n if (isUsed) break;\n }\n\n if (!isUsed) {\n fileUnusedExports.push(name);\n }\n }\n\n if (fileUnusedExports.length > 0) {\n unusedExports[relativePath] = fileUnusedExports;\n }\n }\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused,\n unusedExports\n };\n}\n"]}
|