react-prune 1.2.0 โ†’ 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,136 +4,155 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![npm downloads](https://img.shields.io/npm/dm/react-prune)](https://www.npmjs.com/package/react-prune)
6
6
 
7
- > **Monitor usage of packages and component imports across your React, Next.js, and React Native apps.**
7
+ > **Static analysis for identifying unused files and package usage in React-based codebases.**
8
8
 
9
- `react-prune` is a powerful CLI tool designed to help you maintain a healthy codebase by identifying unused files, analyzing package usage, and estimating dependency sizes.
9
+ `react-prune` is a lightweight CLI tool that analyzes your React, Next.js, and React Native projects to surface **unused local files** and **package import usage**, helping you reduce dead code and dependency bloat.
10
+
11
+ ---
10
12
 
11
13
  ## ๐Ÿš€ Features
12
14
 
13
- - **๐Ÿ“ฆ Package Analysis**: Scans your codebase to count how many times each npm package is imported.
14
- - **โš–๏ธ Size Estimation**: Estimates the size of your used packages directly from `node_modules` to help you identify heavy dependencies.
15
- - **๐Ÿงน Dead Code Detection**: Identifies local component files that are _never_ imported, helping you prune dead code.
16
- - **๐Ÿ” Unused Exports**: Detects named exports that are defined but never used in other files.
17
- - **โš›๏ธ Framework Agnostic**: Works seamlessly with React, Next.js (Pages & App Router), and React Native.
18
- - **๐Ÿ“Š Visual Dashboard**: Provides a beautiful, easy-to-read command-line dashboard using ASCII tables.
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
+ - **โš›๏ธ React Ecosystem Support**
25
+ Works with React, Next.js (Pages & App Router), and React Native projects.
26
+
27
+ - **๐Ÿ“Š CLI-Friendly Output**
28
+ Displays results in readable tables or as structured JSON for automation.
29
+
30
+ ---
19
31
 
20
32
  ## ๐Ÿ“ฆ Installation
21
33
 
22
- To save `react-prune` to your `package.json` (recommended as a Dev Dependency):
34
+ Install as a dev dependency (recommended):
23
35
 
24
- ### Using npm
36
+ ### npm
25
37
 
26
38
  ```bash
27
39
  npm install -D react-prune
28
40
  ```
29
41
 
30
- ### Using yarn
42
+ ### yarn
31
43
 
32
44
  ```bash
33
45
  yarn add -D react-prune
34
46
  ```
35
47
 
36
- ### Using pnpm
48
+ ### pnpm
37
49
 
38
50
  ```bash
39
51
  pnpm add -D react-prune
40
52
  ```
41
53
 
42
- You can also run it one-off using `npx`:
54
+ Or run once via `npx`:
43
55
 
44
56
  ```bash
45
- npx react-prune analyze
57
+ npx react-prune
46
58
  ```
47
59
 
60
+ ---
61
+
48
62
  ## ๐Ÿ›  Usage
49
63
 
50
- Navigate to the root of your project and run:
64
+ Run from the **root of your project**:
51
65
 
52
66
  ```bash
53
- react-prune analyze
67
+ react-prune
54
68
  ```
55
69
 
56
- The tool will scan your project (ignoring `node_modules`, `dist`, `.next`, etc.) and output a report.
70
+ ### Options
71
+
72
+ | Option | Description |
73
+ | ------------- | ------------------------------------------ |
74
+ | `--json` | Output the report as JSON |
75
+ | `--no-size` | Skip package size calculation |
76
+ | `--limit <n>` | Limit displayed package rows (default: 50) |
57
77
 
58
- ### Example Output
78
+ ### Example
79
+
80
+ ```bash
81
+ react-prune --limit 20
82
+ ```
83
+
84
+ ---
85
+
86
+ ## ๐Ÿ“Š Example Output
59
87
 
60
88
  ```text
61
89
  โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
62
- โ”‚ โ”‚
63
90
  โ”‚ ๐Ÿ“ฆ Package Usage โ”‚
64
- โ”‚ Report โ”‚
65
- โ”‚ โ”‚
66
91
  โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
67
92
 
68
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
69
- โ”‚ Package Name โ”‚ Usage Count โ”‚ Est. Size โ”‚
70
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
71
- โ”‚ react โ”‚ 142 โ”‚ 312 KB โ”‚
72
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
73
- โ”‚ lodash โ”‚ 5 โ”‚ 4.2 MB โ”‚
74
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
75
- โ”‚ framer-motion โ”‚ 23 โ”‚ 1.1 MB โ”‚
76
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
93
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
94
+ โ”‚ Package โ”‚ Count โ”‚ Size โ”‚
95
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
96
+ โ”‚ react โ”‚ 142 โ”‚ 312 KB โ”‚
97
+ โ”‚ lodash โ”‚ 5 โ”‚ 4.2 MB โ”‚
98
+ โ”‚ framer-motion โ”‚ 23 โ”‚ 1.1 MB โ”‚
99
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
77
100
 
78
101
  โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
79
- โ”‚ โ”‚
80
- โ”‚ โš ๏ธ Potential โ”‚
81
- โ”‚ Unused Files โ”‚
82
- โ”‚ โ”‚
102
+ โ”‚ โš ๏ธ Unused Files (2) โ”‚
83
103
  โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
84
104
 
85
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
86
- โ”‚ File Path โ”‚
87
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
88
- โ”‚ src/components/OldButton.tsx โ”‚
89
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
90
- โ”‚ src/utils/deprecated-helper.ts โ”‚
91
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
92
-
93
- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
94
- โ”‚ โ”‚
95
- โ”‚ โš ๏ธ Potential โ”‚
96
- โ”‚ Unused Exports โ”‚
97
- โ”‚ โ”‚
98
- โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
99
-
100
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
101
- โ”‚ File โ”‚ Unused Exports โ”‚
102
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
103
- โ”‚ src/hooks/useMetrics.ts โ”‚ useOldMetric โ”‚
104
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
105
- โ”‚ src/utils/formatters.ts โ”‚ formatCurrency โ”‚
106
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
105
+ src/components/OldButton.tsx
106
+ src/utils/deprecated-helper.ts
107
107
  ```
108
108
 
109
- ## โš™๏ธ How it Works
109
+ ---
110
+
111
+ ## โš™๏ธ How It Works
112
+
113
+ 1. **File Discovery**
114
+ Recursively scans `.js`, `.jsx`, `.ts`, and `.tsx` files (excluding `node_modules`, `.next`, `dist`, etc.).
110
115
 
111
- 1. **File Scanning**: It uses `glob` to recursively find all `.js`, `.jsx`, `.ts`, and `.tsx` files in your project.
112
- 2. **AST Analysis**: It uses `ts-morph` to parse the Abstract Syntax Tree (AST) of each file. This is far more accurate than Regex as it understands the code structure.
113
- 3. **Import Resolution**: It resolves import paths to physical files on disk to track internal usage.
114
- 4. **Size Calculation**: It looks up the package in your local `node_modules` folder and calculates the total size of the directory to give you an estimate of the impact.
116
+ 2. **AST Parsing**
117
+ Uses `ts-morph` to parse TypeScript/JavaScript ASTs for accurate import analysis.
118
+
119
+ 3. **Dependency Resolution**
120
+ Differentiates between local file imports and external package imports.
121
+
122
+ 4. **Static Usage Mapping**
123
+ Tracks which files and packages are actually referenced in the project.
124
+
125
+ ---
126
+
127
+ ## โš ๏ธ Limitations (Important)
128
+
129
+ - This is **static analysis** โ€” dynamic imports and runtime usage may not be detected.
130
+ - Files referenced only via tooling configuration (e.g. Storybook, tests, build scripts) may appear unused.
131
+ - Package size estimates are approximate and based on disk size, not bundle size.
132
+
133
+ ---
115
134
 
116
135
  ## ๐Ÿค Contributing
117
136
 
118
- Contributions are welcome!
119
-
120
- 1. Clone the repository:
121
- ```bash
122
- git clone https://github.com/danieljohnson18/react-prune.git
123
- ```
124
- 2. Install dependencies:
125
- ```bash
126
- npm install
127
- ```
128
- 3. Run the build in watch mode:
129
- ```bash
130
- npm run dev
131
- ```
132
- 4. Test the analyzer on the project itself:
133
- ```bash
134
- node dist/cli.js analyze
135
- ```
137
+ Contributions are welcome.
138
+
139
+ ```bash
140
+ git clone https://github.com/danieljohnson18/react-prune.git
141
+ cd react-prune
142
+ npm install
143
+ npm run dev
144
+ ```
145
+
146
+ Test locally:
147
+
148
+ ```bash
149
+ node dist/cli.js
150
+ ```
151
+
152
+ ---
136
153
 
137
154
  ## ๐Ÿ“„ License
138
155
 
139
156
  MIT ยฉ [Daniel Arikawe](https://github.com/danieljohnson18)
157
+
158
+ ---
package/dist/cli.js CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';var commander=require('commander'),u=require('picocolors'),m=require('path'),tsMorph=require('ts-morph'),C=require('fast-glob'),k=require('fs'),P=require('cli-table3'),W=require('boxen');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var u__default=/*#__PURE__*/_interopDefault(u);var m__default=/*#__PURE__*/_interopDefault(m);var C__default=/*#__PURE__*/_interopDefault(C);var k__default=/*#__PURE__*/_interopDefault(k);var P__default=/*#__PURE__*/_interopDefault(P);var W__default=/*#__PURE__*/_interopDefault(W);function A(s){let n=0;try{let r=k__default.default.readdirSync(s);for(let h of r){let p=m__default.default.join(s,h),i=k__default.default.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__default.default.join(s,"node_modules",n);if(k__default.default.existsSync(r)){let h=A(r);return U(h)}return "N/A"}async function D(s){console.log(u__default.default.green(`Analyzing project at ${s}`));let n=await C__default.default("**/*.{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__default.default.blue(`Found ${n.length} files to analyze.`));let r=m__default.default.join(s,"tsconfig.json"),h={skipAddingFilesFromTsConfig:true};k__default.default.existsSync(r)&&(h.tsConfigFilePath=r);let p=new tsMorph.Project(h);n.forEach(t=>{try{p.addSourceFileAtPath(t);}catch(o){console.warn(u__default.default.yellow(`Skipping file ${t} due to load error:`),o);}});let i={},c={};n.forEach(t=>{let o=m__default.default.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__default.default.dirname(l),e=m__default.default.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__default.default.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__default.default.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(tsMorph.SyntaxKind.CallExpression);for(let b of S)if(b.getExpression().getText()==="require"){let l=b.getArguments();if(l.length>0&&l[0].getKind()===tsMorph.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__default.default.relative(s,t).includes(m__default.default.sep)?false:o===0).map(([t])=>t),j={};for(let t of p.getSourceFiles()){let o=t.getFilePath(),S=m__default.default.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(tsMorph.SyntaxKind.VariableDeclaration===g.getKind()||tsMorph.SyntaxKind.FunctionDeclaration===g.getKind()||tsMorph.SyntaxKind.ClassDeclaration===g.getKind()||tsMorph.SyntaxKind.InterfaceDeclaration===g.getKind()||tsMorph.SyntaxKind.TypeAliasDeclaration===g.getKind()||tsMorph.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 commander.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__default.default.blue("Starting analysis..."));try{let n=s?m__default.default.resolve(s):process.cwd(),r=await D(n),h=new P__default.default({head:[u__default.default.cyan("Package Name"),u__default.default.cyan("Usage Count"),u__default.default.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__default.default(u__default.default.bold("\u{1F4E6} Package Usage Report"),{padding:1,margin:1,borderStyle:"round",borderColor:"green"})),console.log(h.toString()),p.length>50&&console.log(u__default.default.gray(`...and ${p.length-50} more packages.`)),r.unusedFiles.length>0){let c=new P__default.default({head:[u__default.default.yellow("File Path")],colWidths:[80]});console.log(W__default.default(u__default.default.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__default.default(u__default.default.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__default.default({head:[u__default.default.yellow("File"),u__default.default.yellow("Unused Exports")],colWidths:[40,40],wordWrap:!0});console.log(W__default.default(u__default.default.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__default.default.gray(`...and ${i.length-50} more files with unused exports.`));}}catch(n){console.error(u__default.default.red("Analysis failed:"),n),process.exit(1);}});z.parse(process.argv);//# sourceMappingURL=cli.js.map
2
+ 'use strict';var commander=require('commander'),j=require('picocolors'),U=require('cli-table3'),F=require('boxen'),tsMorph=require('ts-morph'),M=require('fast-glob'),h=require('path'),k=require('fs');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var j__default=/*#__PURE__*/_interopDefault(j);var U__default=/*#__PURE__*/_interopDefault(U);var F__default=/*#__PURE__*/_interopDefault(F);var M__default=/*#__PURE__*/_interopDefault(M);var h__default=/*#__PURE__*/_interopDefault(h);var k__default=/*#__PURE__*/_interopDefault(k);var O=(o,t)=>()=>(t||o((t={exports:{}}).exports,t),t.exports);var T=O((X,K)=>{K.exports={name:"react-prune",version:"1.2.1",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"}};});function C(o){let t=0;try{let i=k__default.default.readdirSync(o);for(let n of i){let a=h__default.default.join(o,n),r=k__default.default.statSync(a);r.isDirectory()?t+=C(a):t+=r.size;}}catch{return 0}return t}function $(o,t=2){if(o===0)return "0 Bytes";let i=1024,n=t<0?0:t,a=["Bytes","KB","MB","GB","TB"],r=Math.floor(Math.log(o)/Math.log(i));return parseFloat((o/Math.pow(i,r)).toFixed(n))+" "+a[r]}function I(o,t){let i=h__default.default.join(o,"node_modules",t);if(k__default.default.existsSync(i)){let n=C(i);return $(n)}return "N/A"}async function D(o){let{rootPath:t,includeSizes:i=true,analyzeExports:n=true}=o;console.log(j__default.default.green(`Analyzing project at ${t}`));let a=await M__default.default("**/*.{js,jsx,ts,tsx,ios.tsx,android.tsx,native.tsx}",{cwd:t,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/*.d.ts"],absolute:true});console.log(j__default.default.blue(`Found ${a.length} files to analyze.`));let r=h__default.default.join(t,"tsconfig.json"),c={skipAddingFilesFromTsConfig:true};k__default.default.existsSync(r)&&(c.tsConfigFilePath=r);let d=new tsMorph.Project(c);a.forEach(e=>{try{d.addSourceFileAtPath(e);}catch(s){console.warn(j__default.default.yellow(`Skipping file ${e}:`),s);}});let m={},y={};a.forEach(e=>{let s=h__default.default.relative(t,e).replace(/\\/g,"/");y[s]=0;});for(let e of d.getSourceFiles()){let s=e.getImportDeclarations();for(let x of s){let l=x.getModuleSpecifierValue();if(l.startsWith(".")||l.startsWith("/")){let u=h__default.default.dirname(e.getFilePath()),g=h__default.default.resolve(u,l),p=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"];for(let f of p){let S=g+f,z=h__default.default.relative(t,S).replace(/\\/g,"/");if(y.hasOwnProperty(z)){y[z]++;break}}}else {let u=l.startsWith("@")?l.split("/").slice(0,2).join("/"):l.split("/")[0];m[u]=(m[u]||0)+1;}}let P=e.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression);for(let x of P)if(x.getExpression().getText()==="require"){let u=x.getArguments();if(u.length&&u[0].getKind()===tsMorph.SyntaxKind.StringLiteral){let g=u[0].getText().replace(/['"`]/g,"");if(!g.startsWith(".")&&!g.startsWith("/")){let p=g.startsWith("@")?g.split("/").slice(0,2).join("/"):g.split("/")[0];m[p]=(m[p]||0)+1;}}}}let E={};for(let[e,s]of Object.entries(m))E[e]={count:s,size:i?I(t,e):"\u2014"};let W=Object.entries(y).filter(([e,s])=>e.includes("pages/")||e.includes("app/")||e.endsWith("main.tsx")||e.endsWith("index.tsx")||e.endsWith("index.js")||e.endsWith("App.tsx")||e.endsWith("App.js")||!e.includes(h__default.default.sep)&&!e.endsWith(".ts")&&!e.endsWith(".tsx")&&!e.endsWith(".js")&&!e.endsWith(".jsx")?false:s===0).map(([e])=>e),A={};if(n)for(let e of d.getSourceFiles()){let s=e.getFilePath(),P=h__default.default.relative(t,s).replace(/\\/g,"/");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")||W.includes(s))continue;let x=e.getExportedDeclarations(),l=[];for(let[u,g]of x){let p=false;for(let f of g){if(tsMorph.Node.isFunctionDeclaration(f)||tsMorph.Node.isClassDeclaration(f)||tsMorph.Node.isVariableDeclaration(f)||tsMorph.Node.isEnumDeclaration(f)||tsMorph.Node.isInterfaceDeclaration(f)||tsMorph.Node.isTypeAliasDeclaration(f))try{let S=f.findReferences();for(let z of S){for(let N of z.getReferences())if(N.getSourceFile().getFilePath()!==s){p=!0;break}if(p)break}}catch{p=true;}else p=true;if(p)break}p||l.push(u);}l.length&&(A[P]=l);}return {packages:E,components:y,unusedFiles:W,unusedExports:A}}var v=new commander.Command;v.name("react-prune").description("Analyze React/Next/Vite/React Native codebases for unused files, exports, and packages").version(T().version).option("--no-size","Skip calculating package sizes").option("--no-exports","Skip analyzing unused exports").option("--limit <n>","Limit output rows","50");v.action(async o=>{let t=process.cwd(),i=Number(o.limit),n=await D({rootPath:t,includeSizes:o.size,analyzeExports:o.exports});console.log(F__default.default(j__default.default.bold("\u{1F4E6} Package Usage"),{padding:1,borderColor:"green",borderStyle:"round"}));let a=new U__default.default({head:["Package","Count","Size"]});Object.entries(n.packages).sort((c,d)=>d[1].count-c[1].count).slice(0,i).forEach(([c,d])=>a.push([c,d.count,d.size])),console.log(a.toString()),n.unusedFiles.length?(console.log(F__default.default(j__default.default.bold(`\u26A0\uFE0F Unused Files (${n.unusedFiles.length})`),{padding:1,borderColor:"yellow"})),n.unusedFiles.forEach(c=>console.log(j__default.default.yellow(c)))):console.log(F__default.default(j__default.default.green("\u2705 No unused files detected!"),{padding:1,borderColor:"green"}));let r=Object.entries(n.unusedExports);if(r.length){console.log(F__default.default(j__default.default.bold(`\u26A0\uFE0F Potential Unused Exports (${r.length})`),{padding:1,borderColor:"yellow"}));let c=new U__default.default({head:["File","Unused Exports"],wordWrap:true});r.slice(0,i).forEach(([d,m])=>{c.push([d,m.join(", ")]);}),console.log(c.toString());}});v.parse();
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":";miBAcA,SAASA,CAAAA,CAAcC,EAAyB,CAC9C,IAAIC,CAAAA,CAAO,CAAA,CACX,GAAI,CACF,IAAMC,CAAAA,CAAQC,kBAAAA,CAAG,WAAA,CAAYH,CAAO,CAAA,CACpC,IAAA,IAAWI,KAAQF,CAAAA,CAAO,CACxB,IAAMG,CAAAA,CAAWC,mBAAK,IAAA,CAAKN,CAAAA,CAASI,CAAI,CAAA,CAClCG,EAAQJ,kBAAAA,CAAG,QAAA,CAASE,CAAQ,CAAA,CAC9BE,EAAM,WAAA,EAAY,CACpBN,CAAAA,EAAQF,CAAAA,CAAcM,CAAQ,CAAA,CAE9BJ,CAAAA,EAAQM,CAAAA,CAAM,KAElB,CACF,CAAA,KAAY,CACV,OAAO,CACT,CACA,OAAON,CACT,CAEA,SAASO,CAAAA,CAAYC,CAAAA,CAAeC,CAAAA,CAAW,CAAA,CAAG,CAChD,GAAID,CAAAA,GAAU,CAAA,CAAG,OAAO,UACxB,IAAME,CAAAA,CAAI,IAAA,CACJC,CAAAA,CAAKF,EAAW,CAAA,CAAI,CAAA,CAAIA,CAAAA,CACxBG,CAAAA,CAAQ,CAAC,OAAA,CAAS,IAAA,CAAM,IAAA,CAAM,IAAA,CAAM,IAAI,CAAA,CACxC,CAAA,CAAI,IAAA,CAAK,KAAA,CAAM,KAAK,GAAA,CAAIJ,CAAK,CAAA,CAAI,IAAA,CAAK,IAAIE,CAAC,CAAC,CAAA,CAClD,OAAO,YAAYF,CAAAA,CAAQ,IAAA,CAAK,GAAA,CAAIE,CAAAA,CAAG,CAAC,CAAA,EAAG,OAAA,CAAQC,CAAE,CAAC,EAAI,GAAA,CAAMC,CAAAA,CAAM,CAAC,CACzE,CAEA,SAASC,CAAAA,CAAeC,CAAAA,CAAkBC,CAAAA,CAA6B,CAIrE,IAAMC,CAAAA,CAAUX,kBAAAA,CAAK,IAAA,CAAKS,EAAU,cAAA,CAAgBC,CAAW,CAAA,CAC/D,GAAIb,mBAAG,UAAA,CAAWc,CAAO,CAAA,CAAG,CAC1B,IAAMhB,CAAAA,CAAOF,CAAAA,CAAckB,CAAO,CAAA,CAClC,OAAOT,CAAAA,CAAYP,CAAI,CACzB,CACA,OAAO,KACT,CAEA,eAAsBiB,EAAeH,CAAAA,CAAwC,CAC3E,OAAA,CAAQ,GAAA,CAAII,mBAAG,KAAA,CAAM,CAAA,qBAAA,EAAwBJ,CAAQ,CAAA,CAAE,CAAC,CAAA,CAGxD,IAAMb,CAAAA,CAAQ,MAAMkB,mBAAK,sBAAA,CAAwB,CAC/C,GAAA,CAAKL,CAAAA,CACL,OAAQ,CACN,oBAAA,CACA,YAAA,CACA,aAAA,CACA,cACA,gBAAA,CACA,6BAAA,CACA,UACF,CAAA,CACA,SAAU,IACZ,CAAC,CAAA,CAED,OAAA,CAAQ,IAAII,kBAAAA,CAAG,IAAA,CAAK,CAAA,MAAA,EAASjB,CAAAA,CAAM,MAAM,CAAA,kBAAA,CAAoB,CAAC,CAAA,CAG9D,IAAMmB,EAAef,kBAAAA,CAAK,IAAA,CAAKS,CAAAA,CAAU,eAAe,EAClDO,CAAAA,CAAqB,CACzB,2BAAA,CAA6B,IAC/B,EAEInB,kBAAAA,CAAG,UAAA,CAAWkB,CAAY,CAAA,GAC5BC,EAAc,gBAAA,CAAmBD,CAAAA,CAAAA,CAGnC,IAAME,CAAAA,CAAU,IAAIC,eAAAA,CAAQF,CAAa,CAAA,CAGzCpB,CAAAA,CAAM,OAAA,CAASE,CAAAA,EAAS,CACtB,GAAI,CACFmB,CAAAA,CAAQ,mBAAA,CAAoBnB,CAAI,EAClC,OAASqB,CAAAA,CAAG,CACV,OAAA,CAAQ,IAAA,CAAKN,mBAAG,MAAA,CAAO,CAAA,cAAA,EAAiBf,CAAI,CAAA,mBAAA,CAAqB,EAAGqB,CAAC,EACvE,CACF,CAAC,EAED,IAAMC,CAAAA,CAAuC,EAAC,CACxCC,EAAqC,EAAC,CAG5CzB,CAAAA,CAAM,OAAA,CAAS0B,GAAM,CAEnB,IAAMC,CAAAA,CAAWvB,kBAAAA,CAAK,SAASS,CAAAA,CAAUa,CAAC,CAAA,CAC1CD,CAAAA,CAAWE,CAAQ,CAAA,CAAI,EACzB,CAAC,CAAA,CAGD,QAAWC,CAAAA,IAAcP,CAAAA,CAAQ,cAAA,EAAe,CAAG,CACjD,IAAMQ,CAAAA,CAAUD,CAAAA,CAAW,qBAAA,GAE3B,IAAA,IAAWE,CAAAA,IAAOD,CAAAA,CAAS,CACzB,IAAME,CAAAA,CAAkBD,CAAAA,CAAI,uBAAA,EAAwB,CAEpD,GAAIC,CAAAA,CAAgB,UAAA,CAAW,GAAG,CAAA,EAAKA,EAAgB,UAAA,CAAW,GAAG,CAAA,CAEnE,GAAI,CAEF,IAAMC,CAAAA,CAAiBJ,CAAAA,CAAW,aAAY,CACxCK,CAAAA,CAAY7B,kBAAAA,CAAK,OAAA,CAAQ4B,CAAc,CAAA,CAEvCE,CAAAA,CAAe9B,kBAAAA,CAAK,OAAA,CAAQ6B,EAAWF,CAAe,CAAA,CAEtDI,CAAAA,CAAa,CACjB,GACA,KAAA,CACA,MAAA,CACA,KAAA,CACA,MAAA,CACA,YACA,YAAA,CACA,WAAA,CACA,YACF,CAAA,CAEA,QAAWC,CAAAA,IAAOD,CAAAA,CAAY,CAC5B,IAAME,EAAUH,CAAAA,CAAeE,CAAAA,CACzBE,CAAAA,CAAclC,kBAAAA,CAAK,SAASS,CAAAA,CAAUwB,CAAO,CAAA,CACnD,GAAIZ,EAAW,cAAA,CAAea,CAAW,CAAA,CAAG,CAC1Cb,EAAWa,CAAW,CAAA,EAAA,CACtB,KACF,CACF,CACF,CAAA,KAAY,CAEZ,CAAA,KACK,CAEL,IAAIC,CAAAA,CAGJ,GAAI,CACF,IAAMC,EAAqBV,CAAAA,CAAI,4BAAA,EAA6B,CACxDU,CAAAA,GACFD,EAAoBC,CAAAA,CAAmB,WAAA,EAAY,EAEvD,CAAA,KAAY,CAAC,CAIb,GAAI,CAACD,CAAAA,CAAmB,CACtB,IAAME,CAAAA,CAAkBzC,CAAAA,CAAM,OAAQ0B,CAAAA,EACpCA,CAAAA,CAAE,QAAA,CAASK,CAAe,CAC5B,CAAA,CACA,GAAIU,CAAAA,CAAgB,MAAA,CAAS,EAC3B,IAAA,IAAWC,CAAAA,IAASD,CAAAA,CAAiB,CACnC,IAAME,CAAAA,CAAkBD,CAAAA,CAAM,OAAA,CAAQ,KAAA,CAAO,GAAG,CAAA,CAChD,GACEC,CAAAA,CAAgB,QAAA,CAAS,GAAGZ,CAAe,CAAA,IAAA,CAAM,CAAA,EACjDY,CAAAA,CAAgB,SAAS,CAAA,EAAGZ,CAAe,CAAA,GAAA,CAAK,CAAA,EAChDY,EAAgB,QAAA,CAAS,CAAA,EAAGZ,CAAe,CAAA,UAAA,CAAY,EACvD,CACAQ,CAAAA,CAAoBG,CAAAA,CACpB,KACF,CACF,CAEJ,CAEA,GAAIH,CAAAA,CAAmB,CACrB,IAAMD,CAAAA,CAAclC,kBAAAA,CAAK,QAAA,CAASS,EAAU0B,CAAiB,CAAA,CAC7D,GAAId,CAAAA,CAAW,eAAea,CAAW,CAAA,CAAG,CAC1Cb,CAAAA,CAAWa,CAAW,CAAA,EAAA,CACtB,QACF,CACF,CAGA,IAAIxB,CAAAA,CAAciB,CAAAA,CAClB,GAAIA,CAAAA,CAAgB,UAAA,CAAW,GAAG,CAAA,CAAG,CACnC,IAAMa,CAAAA,CAAQb,CAAAA,CAAgB,KAAA,CAAM,GAAG,EACnCa,CAAAA,CAAM,MAAA,EAAU,CAAA,GAClB9B,CAAAA,CAAc,GAAG8B,CAAAA,CAAM,CAAC,CAAC,CAAA,CAAA,EAAIA,EAAM,CAAC,CAAC,CAAA,CAAA,EAEzC,CAAA,KAAO,CACL,IAAMA,CAAAA,CAAQb,CAAAA,CAAgB,KAAA,CAAM,GAAG,CAAA,CACnCa,CAAAA,CAAM,MAAA,EAAU,CAAA,GAClB9B,EAAc8B,CAAAA,CAAM,CAAC,CAAA,EAEzB,CAEApB,EAAaV,CAAW,CAAA,CAAA,CAAKU,CAAAA,CAAaV,CAAW,GAAK,CAAA,EAAK,EACjE,CACF,CAGA,IAAM+B,CAAAA,CAAkBjB,CAAAA,CAAW,oBAAA,CACjCkB,kBAAAA,CAAW,cACb,CAAA,CACA,IAAA,IAAWC,CAAAA,IAAQF,CAAAA,CAEjB,GADmBE,CAAAA,CAAK,aAAA,EAAc,CACvB,OAAA,KAAc,SAAA,CAAW,CACtC,IAAMC,CAAAA,CAAOD,EAAK,YAAA,EAAa,CAC/B,GAAIC,CAAAA,CAAK,OAAS,CAAA,EAAKA,CAAAA,CAAK,CAAC,CAAA,CAAE,OAAA,EAAQ,GAAMF,kBAAAA,CAAW,aAAA,CAAe,CACrE,IAAMG,CAAAA,CAASD,CAAAA,CAAK,CAAC,EAAE,OAAA,EAAQ,CAAE,OAAA,CAAQ,QAAA,CAAU,EAAE,CAAA,CAErD,GAAI,CAACC,CAAAA,CAAO,WAAW,GAAG,CAAA,EAAK,CAACA,CAAAA,CAAO,WAAW,GAAG,CAAA,CAAG,CACtD,IAAIC,EAAMD,CAAAA,CAAO,UAAA,CAAW,GAAG,CAAA,CAC3BA,EAAO,KAAA,CAAM,GAAG,CAAA,CAAE,KAAA,CAAM,EAAG,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,EACtCA,CAAAA,CAAO,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,CACvBzB,CAAAA,CAAa0B,CAAG,CAAA,CAAA,CAAK1B,EAAa0B,CAAG,CAAA,EAAK,CAAA,EAAK,EACjD,CACF,CACF,CAEJ,CAGA,IAAMC,EAAkE,EAAC,CAEzE,IAAA,GAAW,CAACD,EAAKE,CAAK,CAAA,GAAK,MAAA,CAAO,OAAA,CAAQ5B,CAAY,CAAA,CAAG,CACvD,IAAMzB,CAAAA,CAAOa,CAAAA,CAAeC,CAAAA,CAAUqC,CAAG,CAAA,CACzCC,EAAeD,CAAG,CAAA,CAAI,CAAE,KAAA,CAAAE,EAAO,IAAA,CAAArD,CAAK,EACtC,CAEA,IAAMsD,CAAAA,CAAS,MAAA,CAAO,OAAA,CAAQ5B,CAAU,EACrC,MAAA,CAAO,CAAC,CAACvB,CAAAA,CAAMkD,CAAK,CAAA,GAGjBlD,CAAAA,CAAK,QAAA,CAAS,QAAQ,GACtBA,CAAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EACpBA,EAAK,QAAA,CAAS,UAAU,CAAA,EACxBA,CAAAA,CAAK,QAAA,CAAS,WAAW,CAAA,EACzBA,CAAAA,CAAK,SAAS,UAAU,CAAA,EACxBA,CAAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EACvBA,CAAAA,CAAK,QAAA,CAAS,QAAQ,GAOpB,CADaE,kBAAAA,CAAK,QAAA,CAASS,CAAAA,CAAUX,CAAI,CAAA,CAC/B,QAAA,CAASE,kBAAAA,CAAK,GAAG,EACtB,KAAA,CAEFgD,CAAAA,GAAU,CAClB,CAAA,CACA,IAAI,CAAC,CAAClD,CAAI,CAAA,GAAMA,CAAI,CAAA,CAEjBoD,CAAAA,CAA0C,EAAC,CAGjD,IAAA,IAAW1B,CAAAA,IAAcP,CAAAA,CAAQ,cAAA,GAAkB,CACjD,IAAMlB,CAAAA,CAAWyB,CAAAA,CAAW,aAAY,CAClC2B,CAAAA,CAAenD,kBAAAA,CAAK,QAAA,CAASS,EAAUV,CAAQ,CAAA,CAGrD,GACEA,CAAAA,CAAS,SAAS,QAAQ,CAAA,EAC1BA,CAAAA,CAAS,QAAA,CAAS,MAAM,CAAA,EACxBA,CAAAA,CAAS,QAAA,CAAS,UAAU,GAC5BA,CAAAA,CAAS,QAAA,CAAS,WAAW,CAAA,EAC7BA,EAAS,QAAA,CAAS,UAAU,CAAA,EAC5BA,CAAAA,CAAS,SAAS,SAAS,CAAA,EAC3BA,CAAAA,CAAS,QAAA,CAAS,QAAQ,CAAA,EAC1BkD,CAAAA,CAAO,QAAA,CAASlD,CAAQ,EAExB,SAGF,IAAMqD,CAAAA,CAAuB5B,CAAAA,CAAW,yBAAwB,CAC1D6B,CAAAA,CAA8B,EAAC,CAErC,OAAW,CAACC,CAAAA,CAAMC,CAAY,CAAA,GAAKH,EAAsB,CACvD,IAAII,CAAAA,CAAS,KAAA,CAMb,QAAWC,CAAAA,IAAQF,CAAAA,CAAc,CAC/B,GACEb,mBAAW,mBAAA,GAAwBe,CAAAA,CAAK,OAAA,EAAQ,EAChDf,kBAAAA,CAAW,mBAAA,GAAwBe,CAAAA,CAAK,OAAA,IACxCf,kBAAAA,CAAW,gBAAA,GAAqBe,CAAAA,CAAK,OAAA,IACrCf,kBAAAA,CAAW,oBAAA,GAAyBe,CAAAA,CAAK,OAAA,IACzCf,kBAAAA,CAAW,oBAAA,GAAyBe,CAAAA,CAAK,OAAA,IACzCf,kBAAAA,CAAW,eAAA,GAAoBe,CAAAA,CAAK,OAAA,GAEpC,GAAI,CAEF,IAAMC,CAAAA,CAAOD,EAAK,qBAAA,EAAsB,CACxC,IAAA,IAAWE,CAAAA,IAAOD,EAEhB,GADsBC,CAAAA,CAAI,aAAA,EAAc,CACtB,aAAY,GAAM5D,CAAAA,CAAU,CAC5CyD,CAAAA,CAAS,GACT,KACF,CAEJ,CAAA,KAAY,CAEVA,EAAS,KACX,CAAA,KAMAA,CAAAA,CAAS,IAAA,CAEX,GAAIA,CAAAA,CAAQ,KACd,CAEKA,CAAAA,EACHH,EAAkB,IAAA,CAAKC,CAAI,EAE/B,CAEID,EAAkB,MAAA,CAAS,CAAA,GAC7BH,CAAAA,CAAcC,CAAY,EAAIE,CAAAA,EAElC,CAEA,OAAO,CACL,SAAUN,CAAAA,CACV,UAAA,CAAY1B,CAAAA,CACZ,WAAA,CAAa4B,CAAAA,CACb,aAAA,CAAAC,CACF,CACF,CCvUA,IAAMU,CAAAA,CAAU,IAAIC,kBAEpBD,CAAAA,CACG,IAAA,CAAK,aAAa,CAAA,CAClB,YACC,4FACF,CAAA,CACC,OAAA,CAAQ,OAAO,EAElBA,CAAAA,CACG,OAAA,CAAQ,qBAAqB,CAAA,CAC7B,YAAY,6DAA6D,CAAA,CACzE,MAAA,CAAO,MAAOE,GAAc,CAC3B,OAAA,CAAQ,GAAA,CAAIjD,kBAAAA,CAAG,KAAK,sBAAsB,CAAC,CAAA,CAC3C,GAAI,CACF,IAAMkD,CAAAA,CAAYD,CAAAA,CAAY9D,kBAAAA,CAAK,QAAQ8D,CAAS,CAAA,CAAI,OAAA,CAAQ,GAAA,GAC1DE,CAAAA,CAAS,MAAMpD,CAAAA,CAAemD,CAAS,EAGvCE,CAAAA,CAAe,IAAIC,kBAAAA,CAAM,CAC7B,KAAM,CACJrD,kBAAAA,CAAG,IAAA,CAAK,cAAc,EACtBA,kBAAAA,CAAG,IAAA,CAAK,aAAa,CAAA,CACrBA,mBAAG,IAAA,CAAK,WAAW,CACrB,CAAA,CACA,SAAA,CAAW,CAAC,EAAA,CAAI,EAAA,CAAI,EAAE,CACxB,CAAC,CAAA,CAEKsD,CAAAA,CAAiB,OAAO,OAAA,CAAQH,CAAAA,CAAO,QAAQ,CAAA,CAAE,KACrD,CAACI,CAAAA,CAAGC,CAAAA,GAAMA,CAAAA,CAAE,CAAC,CAAA,CAAE,KAAA,CAAQD,CAAAA,CAAE,CAAC,EAAE,KAC9B,CAAA,CAsBA,GApBAD,CAAAA,CAAe,MAAM,CAAA,CAAG,EAAE,CAAA,CAAE,OAAA,CAAQ,CAAC,CAACrB,CAAAA,CAAKwB,CAAI,CAAA,GAAM,CACnDL,CAAAA,CAAa,IAAA,CAAK,CAACnB,CAAAA,CAAKwB,EAAK,KAAA,CAAOA,CAAAA,CAAK,IAAI,CAAC,EAChD,CAAC,CAAA,CAED,OAAA,CAAQ,GAAA,CACNC,mBAAM1D,kBAAAA,CAAG,IAAA,CAAK,gCAAyB,CAAA,CAAG,CACxC,OAAA,CAAS,CAAA,CACT,MAAA,CAAQ,CAAA,CACR,YAAa,OAAA,CACb,WAAA,CAAa,OACf,CAAC,CACH,CAAA,CACA,OAAA,CAAQ,GAAA,CAAIoD,CAAAA,CAAa,UAAU,CAAA,CAC/BE,CAAAA,CAAe,MAAA,CAAS,EAAA,EAC1B,OAAA,CAAQ,GAAA,CACNtD,kBAAAA,CAAG,KAAK,CAAA,OAAA,EAAUsD,CAAAA,CAAe,MAAA,CAAS,EAAE,iBAAiB,CAC/D,CAAA,CAIEH,CAAAA,CAAO,WAAA,CAAY,OAAS,CAAA,CAAG,CACjC,IAAMQ,CAAAA,CAAc,IAAIN,kBAAAA,CAAM,CAC5B,IAAA,CAAM,CAACrD,mBAAG,MAAA,CAAO,WAAW,CAAC,CAAA,CAC7B,UAAW,CAAC,EAAE,CAChB,CAAC,EAED,OAAA,CAAQ,GAAA,CACN0D,kBAAAA,CACE1D,kBAAAA,CAAG,KACD,CAAA,sCAAA,EAA+BmD,CAAAA,CAAO,WAAA,CAAY,MAAM,GAC1D,CAAA,CACA,CACE,OAAA,CAAS,CAAA,CACT,OAAQ,CAAA,CACR,WAAA,CAAa,OAAA,CACb,WAAA,CAAa,QACf,CACF,CACF,CAAA,CAEAA,CAAAA,CAAO,YAAY,OAAA,CAASlE,CAAAA,EAAS0E,CAAAA,CAAY,IAAA,CAAK,CAAC1E,CAAI,CAAC,CAAC,CAAA,CAC7D,QAAQ,GAAA,CAAI0E,CAAAA,CAAY,QAAA,EAAU,EACpC,CAAA,KACE,OAAA,CAAQ,GAAA,CACND,kBAAAA,CAAM1D,kBAAAA,CAAG,IAAA,CAAK,kCAA6B,CAAA,CAAG,CAC5C,OAAA,CAAS,CAAA,CACT,MAAA,CAAQ,CAAA,CACR,YAAa,OAAA,CACb,WAAA,CAAa,OACf,CAAC,CACH,CAAA,CAIF,IAAM4D,CAAAA,CAAuB,MAAA,CAAO,QAAQT,CAAAA,CAAO,aAAa,CAAA,CAChE,GAAIS,EAAqB,MAAA,CAAS,CAAA,CAAG,CACnC,IAAMC,EAAqBD,CAAAA,CAAqB,MAAA,CAC9C,CAACE,CAAAA,CAAK,EAAGC,CAAO,CAAA,GAAMD,CAAAA,CAAMC,EAAQ,MAAA,CACpC,CACF,CAAA,CACMC,CAAAA,CAAqB,IAAIX,kBAAAA,CAAM,CACnC,IAAA,CAAM,CAACrD,mBAAG,MAAA,CAAO,MAAM,CAAA,CAAGA,kBAAAA,CAAG,OAAO,gBAAgB,CAAC,CAAA,CACrD,SAAA,CAAW,CAAC,EAAA,CAAI,EAAE,CAAA,CAClB,QAAA,CAAU,EACZ,CAAC,CAAA,CAED,OAAA,CAAQ,GAAA,CACN0D,mBACE1D,kBAAAA,CAAG,IAAA,CAAK,CAAA,wCAAA,EAAiC6D,CAAkB,GAAG,CAAA,CAC9D,CACE,OAAA,CAAS,CAAA,CACT,MAAA,CAAQ,CAAA,CACR,WAAA,CAAa,OAAA,CACb,YAAa,QACf,CACF,CACF,CAAA,CAEAD,EAAqB,KAAA,CAAM,CAAA,CAAG,EAAE,CAAA,CAAE,QAAQ,CAAC,CAAC3E,CAAAA,CAAM8E,CAAO,IAAM,CAC7DC,CAAAA,CAAmB,IAAA,CAAK,CAAC/E,EAAM8E,CAAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAC,EACpD,CAAC,CAAA,CACD,OAAA,CAAQ,IAAIC,CAAAA,CAAmB,QAAA,EAAU,CAAA,CAErCJ,EAAqB,MAAA,CAAS,EAAA,EAChC,OAAA,CAAQ,GAAA,CACN5D,mBAAG,IAAA,CACD,CAAA,OAAA,EAAU4D,CAAAA,CAAqB,MAAA,CAAS,EAAE,CAAA,gCAAA,CAC5C,CACF,EAEJ,CACF,OAASK,CAAAA,CAAO,CACd,OAAA,CAAQ,KAAA,CAAMjE,mBAAG,GAAA,CAAI,kBAAkB,CAAA,CAAGiE,CAAK,EAC/C,OAAA,CAAQ,IAAA,CAAK,CAAC,EAChB,CACF,CAAC,CAAA,CAEHlB,CAAAA,CAAQ,KAAA,CAAM,QAAQ,IAAI,CAAA","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/index.ts","../src/cli.ts"],"names":["require_package","__commonJSMin","exports","module","getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","options","includeSizes","analyzeExports","pc","glob","tsConfigPath","projectConfig","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","unusedExports","relativePath","exportedDeclarations","fileUnusedExports","name","declarations","isUsed","decl","Node","refs","ref","entry","program","Command","opts","limit","report","boxen","packageTable","Table","a","b","data","unusedExportsEntries","exportsTable"],"mappings":"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","file":"cli.js","sourcesContent":["{\n \"name\": \"react-prune\",\n \"version\": \"1.2.1\",\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 { Project, SyntaxKind, Node } from \"ts-morph\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport pc from \"picocolors\";\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\nexport interface AnalyzerOptions {\n rootPath: string;\n includeSizes?: boolean;\n analyzeExports?: boolean;\n}\n\n// --- Helper: 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()) size += getFolderSize(filePath);\n else size += stats.size;\n }\n } catch {\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 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\n// --- Analyzer function\nexport async function analyzeProject(\n options: AnalyzerOptions\n): Promise<UsageReport> {\n const { rootPath, includeSizes = true, analyzeExports = true } = options;\n\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // --- 1. Find all JS/TS files\n const files = await glob(\n \"**/*.{js,jsx,ts,tsx,ios.tsx,android.tsx,native.tsx}\",\n {\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\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 = { skipAddingFilesFromTsConfig: true };\n if (fs.existsSync(tsConfigPath))\n projectConfig.tsConfigFilePath = tsConfigPath;\n\n const project = new Project(projectConfig);\n\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file}:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n files.forEach((f) => {\n const relative = path.relative(rootPath, f).replace(/\\\\/g, \"/\");\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 const sourceDir = path.dirname(sourceFile.getFilePath());\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\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\n .relative(rootPath, tryPath)\n .replace(/\\\\/g, \"/\");\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } else {\n // Package import\n const packageName = moduleSpecifier.startsWith(\"@\")\n ? moduleSpecifier.split(\"/\").slice(0, 2).join(\"/\")\n : moduleSpecifier.split(\"/\")[0];\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // --- Require() 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 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n const 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. Packages\n const reportPackages: Record<string, { count: number; size: string }> = {};\n for (const [pkg, count] of Object.entries(packageUsage)) {\n reportPackages[pkg] = {\n count,\n size: includeSizes ? getPackageSize(rootPath, pkg) : \"โ€”\"\n };\n }\n\n // --- 5. Unused files\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\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 if (\n !file.includes(path.sep) &&\n !file.endsWith(\".ts\") &&\n !file.endsWith(\".tsx\") &&\n !file.endsWith(\".js\") &&\n !file.endsWith(\".jsx\")\n )\n return false;\n return count === 0;\n })\n .map(([file]) => file);\n\n // --- 6. Unused exports\n const unusedExports: Record<string, string[]> = {};\n if (analyzeExports) {\n for (const sourceFile of project.getSourceFiles()) {\n const filePath = sourceFile.getFilePath();\n const relativePath = path\n .relative(rootPath, filePath)\n .replace(/\\\\/g, \"/\");\n\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)\n )\n continue;\n\n const exportedDeclarations = sourceFile.getExportedDeclarations();\n const fileUnusedExports: string[] = [];\n\n for (const [name, declarations] of exportedDeclarations) {\n let isUsed = false;\n\n for (const decl of declarations) {\n // --- Type guard: only check certain declaration types\n if (\n Node.isFunctionDeclaration(decl) ||\n Node.isClassDeclaration(decl) ||\n Node.isVariableDeclaration(decl) ||\n Node.isEnumDeclaration(decl) ||\n Node.isInterfaceDeclaration(decl) ||\n Node.isTypeAliasDeclaration(decl)\n ) {\n try {\n const refs = decl.findReferences();\n for (const ref of refs) {\n for (const entry of ref.getReferences()) {\n if (entry.getSourceFile().getFilePath() !== filePath) {\n isUsed = true;\n break;\n }\n }\n if (isUsed) break;\n }\n } catch {\n isUsed = true;\n }\n } else {\n // Other export types: assume used\n isUsed = true;\n }\n\n if (isUsed) break;\n }\n\n if (!isUsed) fileUnusedExports.push(name);\n }\n\n if (fileUnusedExports.length)\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 Table from \"cli-table3\";\nimport boxen from \"boxen\";\nimport { analyzeProject } from \"./analyzer\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Analyze React/Next/Vite/React Native codebases for unused files, exports, and packages\"\n )\n .version(require(\"../package.json\").version)\n .option(\"--no-size\", \"Skip calculating package sizes\")\n .option(\"--no-exports\", \"Skip analyzing unused exports\")\n .option(\"--limit <n>\", \"Limit output rows\", \"50\");\n\nprogram.action(async (opts) => {\n const rootPath = process.cwd();\n const limit = Number(opts.limit);\n\n const report = await analyzeProject({\n rootPath,\n includeSizes: opts.size,\n analyzeExports: opts.exports\n });\n\n // ---------- Packages ----------\n console.log(\n boxen(pc.bold(\"๐Ÿ“ฆ Package Usage\"), {\n padding: 1,\n borderColor: \"green\",\n borderStyle: \"round\"\n })\n );\n\n const packageTable = new Table({ head: [\"Package\", \"Count\", \"Size\"] });\n Object.entries(report.packages)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, limit)\n .forEach(([pkg, data]) => packageTable.push([pkg, data.count, data.size]));\n\n console.log(packageTable.toString());\n\n // ---------- Unused Files ----------\n if (report.unusedFiles.length) {\n console.log(\n boxen(pc.bold(`โš ๏ธ Unused Files (${report.unusedFiles.length})`), {\n padding: 1,\n borderColor: \"yellow\"\n })\n );\n report.unusedFiles.forEach((f) => console.log(pc.yellow(f)));\n } else {\n console.log(\n boxen(pc.green(\"โœ… No unused files detected!\"), {\n padding: 1,\n borderColor: \"green\"\n })\n );\n }\n\n // ---------- Unused Exports ----------\n const unusedExportsEntries = Object.entries(report.unusedExports);\n if (unusedExportsEntries.length) {\n console.log(\n boxen(\n pc.bold(`โš ๏ธ Potential Unused Exports (${unusedExportsEntries.length})`),\n {\n padding: 1,\n borderColor: \"yellow\"\n }\n )\n );\n const exportsTable = new Table({\n head: [\"File\", \"Unused Exports\"],\n wordWrap: true\n });\n unusedExportsEntries.slice(0, limit).forEach(([file, exports]) => {\n exportsTable.push([file, exports.join(\", \")]);\n });\n console.log(exportsTable.toString());\n }\n});\n\nprogram.parse();\n"]}
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "react-prune",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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
@@ -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"]}