react-prune 1.3.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -12
- package/bin/react-prune +59 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
- package/scripts/core/check_deps.sh +15 -0
- package/scripts/core/check_package_sizes.sh +31 -0
- package/scripts/core/check_unused_exports.sh +45 -0
- package/scripts/core/check_unused_files.sh +64 -0
- package/scripts/core/find_files.sh +22 -0
- package/scripts/core/find_usage.sh +65 -0
- package/scripts/prune-check.sh +58 -0
package/README.md
CHANGED
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
## 🚀 Features
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
|
|
15
|
+
- **🔎 Component Usage Detection**
|
|
16
|
+
Find where any component or function is used, including line numbers and file sizes.
|
|
17
|
+
|
|
18
|
+
- **📦 Package Usage & Size Analysis**
|
|
19
|
+
Counts usage of external packages and checks **disk size** of dependencies in `node_modules` to spot bloat.
|
|
17
20
|
|
|
18
21
|
- **🚫 Unused Dependency Detection**
|
|
19
22
|
Leverages `depcheck` to identify dependencies in `package.json` that are completely unused.
|
|
@@ -86,15 +89,15 @@ react-prune analyze
|
|
|
86
89
|
|
|
87
90
|
---
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
## 🔎 Finding Usage
|
|
93
|
+
|
|
94
|
+
You can check where a specific component or function is used across your codebase.
|
|
90
95
|
|
|
91
96
|
```bash
|
|
92
|
-
react-prune find <
|
|
97
|
+
react-prune find <Name>
|
|
93
98
|
```
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
#### Example
|
|
100
|
+
Example:
|
|
98
101
|
|
|
99
102
|
```bash
|
|
100
103
|
react-prune find Button
|
|
@@ -103,11 +106,13 @@ react-prune find Button
|
|
|
103
106
|
Output:
|
|
104
107
|
|
|
105
108
|
```
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
🔎 Searching for usage of 'Button'...
|
|
110
|
+
✅ Found 5 occurrences:
|
|
111
|
+
|
|
112
|
+
src/app/page.tsx: 10: <Button>Click me</Button> [5 KB]
|
|
113
|
+
src/components/ui/button.tsx: 12: export { Button } [2 KB]
|
|
114
|
+
|
|
115
|
+
Total: 5 times
|
|
111
116
|
```
|
|
112
117
|
|
|
113
118
|
---
|
package/bin/react-prune
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# bin/react-prune
|
|
4
|
+
|
|
5
|
+
# Get the directory where the script is located
|
|
6
|
+
# Get the directory where the script is located, resolving symlinks
|
|
7
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
8
|
+
while [ -h "$SOURCE" ]; do
|
|
9
|
+
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
10
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
11
|
+
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
12
|
+
done
|
|
13
|
+
SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
14
|
+
PROJECT_ROOT="$(pwd)"
|
|
15
|
+
|
|
16
|
+
CORE_DIR="$SCRIPT_DIR/../scripts/core"
|
|
17
|
+
|
|
18
|
+
function show_help {
|
|
19
|
+
echo "react-prune - Bash Edition"
|
|
20
|
+
echo "Usage:"
|
|
21
|
+
echo " react-prune analyze Run full analysis (deps, files, exports)"
|
|
22
|
+
echo " react-prune help Show this help"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if [ "$1" == "analyze" ]; then
|
|
26
|
+
echo "🚀 Starting React Prune Analysis..."
|
|
27
|
+
|
|
28
|
+
# 1. Check Deps
|
|
29
|
+
"$CORE_DIR/check_deps.sh"
|
|
30
|
+
echo ""
|
|
31
|
+
|
|
32
|
+
# 1b. Check Package Sizes
|
|
33
|
+
"$CORE_DIR/check_package_sizes.sh" "$PROJECT_ROOT"
|
|
34
|
+
echo ""
|
|
35
|
+
|
|
36
|
+
# 2. Check Unused Files
|
|
37
|
+
"$CORE_DIR/check_unused_files.sh" "$PROJECT_ROOT"
|
|
38
|
+
echo ""
|
|
39
|
+
|
|
40
|
+
# 3. Check Unused Exports
|
|
41
|
+
"$CORE_DIR/check_unused_exports.sh" "$PROJECT_ROOT"
|
|
42
|
+
|
|
43
|
+
elif [ "$1" == "find" ]; then
|
|
44
|
+
shift
|
|
45
|
+
"$CORE_DIR/find_usage.sh" "$1" "$PROJECT_ROOT"
|
|
46
|
+
|
|
47
|
+
elif [ "$1" == "--version" ] || [ "$1" == "-v" ]; then
|
|
48
|
+
# Read version from package.json using grep/sed to avoid jq dependency
|
|
49
|
+
VERSION=$(grep '"version":' "$CORE_DIR/../../package.json" | head -1 | sed -E 's/.*"version": "([^"]+)".*/\1/')
|
|
50
|
+
echo "react-prune version $VERSION"
|
|
51
|
+
exit 0
|
|
52
|
+
|
|
53
|
+
elif [ "$1" == "help" ] || [ -z "$1" ]; then
|
|
54
|
+
show_help
|
|
55
|
+
else
|
|
56
|
+
echo "Unknown command: $1"
|
|
57
|
+
show_help
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';var
|
|
2
|
+
'use strict';var x=require('path'),commander=require('commander'),f=require('picocolors'),F=require('cli-table3'),E=require('boxen'),tsMorph=require('ts-morph'),V=require('fast-glob'),k=require('fs');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var x__default=/*#__PURE__*/_interopDefault(x);var f__default=/*#__PURE__*/_interopDefault(f);var F__default=/*#__PURE__*/_interopDefault(F);var E__default=/*#__PURE__*/_interopDefault(E);var V__default=/*#__PURE__*/_interopDefault(V);var k__default=/*#__PURE__*/_interopDefault(k);var T=Object.defineProperty;var P=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,s)=>(typeof require<"u"?require:t)[s]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var L=(e,t)=>()=>(e&&(t=e(e=0)),t);var _=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),q=(e,t)=>{for(var s in t)T(e,s,{get:t[s],enumerable:true});};var I=_((fe,X)=>{X.exports={name:"react-prune",version:"2.0.2",main:"dist/index.js",bin:{"react-prune":"./bin/react-prune"},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:["bin","scripts","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",depcheck:"^1.4.7","fast-glob":"^3.3.3",picocolors:"^1.1.1","ts-morph":"^27.0.2"},devDependencies:{"@changesets/cli":"^2.29.8","@types/depcheck":"^0.9.0","@types/glob":"^8.1.0","@types/node":"^25.0.10",eslint:"^9.39.2",prettier:"^3.8.1",tsup:"^8.5.1",typescript:"^5.9.3",vitest:"^4.0.18"}};});var N={};q(N,{getPackageSize:()=>Z});function Z(e,t){try{let s=P(x__default.default.join(e,"node_modules",t,"package.json"));if(typeof s.size=="number")return `${(s.size/1024).toFixed(2)} KB`}catch{}}var B=L(()=>{});function C(e){let t=0;try{let s=k__default.default.readdirSync(e);for(let i of s){let l=x__default.default.join(e,i),o=k__default.default.statSync(l);o.isDirectory()?t+=C(l):t+=o.size;}}catch{}return t}function G(e,t=2){if(e===0)return "0 Bytes";let s=1024,i=t<0?0:t,l=["Bytes","KB","MB","GB","TB"],o=Math.floor(Math.log(e)/Math.log(s));return parseFloat((e/Math.pow(s,o)).toFixed(i))+" "+l[o]}function H(e,t){let s=x__default.default.join(e,"node_modules",t);return k__default.default.existsSync(s)?G(C(s)):"N/A"}async function v(e){let{rootPath:t,includeSizes:s=true,analyzeExports:i=true,silent:l=false}=e;l||console.log(f__default.default.green(`Analyzing project at ${t}`));let o=await V__default.default("**/*.{js,jsx,ts,tsx}",{cwd:t,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/.next/**","**/coverage/**","**/*.config.{js,ts,cjs,mjs}","**/*.d.ts"],absolute:true});l||console.log(f__default.default.blue(`Found ${o.length} files to analyze.`));let r=new tsMorph.Project({skipAddingFilesFromTsConfig:true}),m=x__default.default.join(t,"tsconfig.json");k__default.default.existsSync(m)&&r.addSourceFilesFromTsConfig(m),o.forEach(n=>{try{r.addSourceFileAtPath(n);}catch{console.warn(f__default.default.yellow(`Skipping ${n}`));}});let u={},h={},d={},D={};for(let n of r.getSourceFiles()){let a=x__default.default.relative(t,n.getFilePath()).replace(/\\/g,"/");D[a]=n;let y=n.getExportedDeclarations(),g=new Set;y.forEach((S,c)=>{c!=="default"&&g.add(c);});let b=y.has("default");h[a]={named:g,default:b},d[a]=new Set,n.getImportDeclarations().forEach(S=>{let c=S.getModuleSpecifierValue();if(!c.startsWith(".")&&!c.startsWith("/")){let p=c.startsWith("@")?c.split("/").slice(0,2).join("/"):c.split("/")[0];u[p]=(u[p]||0)+1;}}),n.getDescendantsOfKind(tsMorph.SyntaxKind.CallExpression).forEach(S=>{if(S.getExpression().getText()==="require"){let c=S.getArguments();if(c.length&&c[0].getKind()===tsMorph.SyntaxKind.StringLiteral){let p=c[0].getText().replace(/['"`]/g,"");if(!p.startsWith(".")&&!p.startsWith("/")){let z=p.startsWith("@")?p.split("/").slice(0,2).join("/"):p.split("/")[0];u[z]=(u[z]||0)+1;}}}});}if(i)for(let n of r.getSourceFiles()){let a=n.getImportDeclarations();x__default.default.relative(t,n.getFilePath()).replace(/\\/g,"/");a.forEach(g=>{let b=g.getModuleSpecifierValue();if(b.startsWith(".")||b.startsWith("/")){let A=x__default.default.dirname(n.getFilePath()),W=x__default.default.resolve(A,b),S=["",".ts",".tsx",".js",".jsx","/index.ts","/index.tsx","/index.js","/index.jsx"],c;for(let p of S){let z=x__default.default.relative(t,W+p).replace(/\\/g,"/");if(h[z]){c=z;break}}c&&(g.getNamedImports().forEach(p=>d[c].add(p.getName())),g.getDefaultImport()&&d[c].add("default"));}});}let K=Object.entries(h).filter(([n,a])=>{if(n.includes("pages/")||n.includes("app/")||n.endsWith("index.tsx")||n.endsWith("App.tsx"))return false;let y=d[n];return a.named.size===0&&!a.default?false:y.size===0}).map(([n])=>n),R={};i&&Object.entries(h).forEach(([n,a])=>{let y=d[n],g=[];a.default&&!y.has("default")&&g.push("default"),a.named.forEach(b=>{y.has(b)||g.push(b);}),g.length&&(R[n]=g);});let $={};Object.entries(u).forEach(([n,a])=>{$[n]={count:a,size:s?H(t,n):"\u2014"};});let O=[];try{O=(await P("depcheck")(t,{ignoreMatches:["react-prune",...e.ignoreMatches||[]],skipMissing:!0})).dependencies;}catch{l||console.warn(f__default.default.yellow("Failed to run depcheck."));}return {packages:$,unusedFiles:K,unusedExports:R,usedExports:d,unusedDependencies:O,sourceFiles:D}}function M(e){try{let s=k__default.default.statSync(e).size;return s<1024?`${s} B`:`${(s/1024).toFixed(2)} KB`}catch{return "N/A"}}var j=new commander.Command;j.name("react-prune").description("Analyze React/Next/Vite/React Native projects").version(I().version).option("--no-size","Skip package size calculation").option("--no-exports","Skip export usage analysis").option("--limit <n>","Limit output rows per table","50");j.command("analyze").description("Run full project analysis").option("--no-size","Skip package size calculation").option("--no-exports","Skip export usage analysis").option("--limit <n>","Limit output rows per table","50").option("--json","Output results as JSON").action(async e=>{let t=process.cwd(),s=Number(e.limit),i=await v({rootPath:t,includeSizes:e.size,analyzeExports:e.exports,silent:e.json});if(e.json){let{sourceFiles:o,usedExports:r,...m}=i,u={};if(r)for(let[h,d]of Object.entries(r))u[h]=Array.from(d);console.log(JSON.stringify({...m,usedExports:u},null,2));return}let l=new F__default.default({head:[f__default.default.cyan("Package"),"Count","Size"],colWidths:[40,10,15]});if(Object.entries(i.packages).sort((o,r)=>r[1].count-o[1].count).slice(0,s).forEach(([o,r])=>l.push([o,r.count,r.size])),console.log(E__default.default(f__default.default.bold("\u{1F4E6} Package Usage"),{padding:1,borderColor:"green",borderStyle:"round"})),console.log(l.toString()),i.unusedFiles.length){let o=new F__default.default({head:[f__default.default.yellow("Unused Files")],colWidths:[80]});i.unusedFiles.slice(0,s).forEach(r=>o.push([r])),console.log(E__default.default(f__default.default.bold(`\u26A0\uFE0F Unused Files (${i.unusedFiles.length})`),{padding:1,borderColor:"yellow",borderStyle:"round"})),console.log(o.toString());}if(e.exports){let o=Object.entries(i.unusedExports);if(o.length){let r=new F__default.default({head:["File","Unused Exports"],colWidths:[50,40],wordWrap:true});o.slice(0,s).forEach(([m,u])=>r.push([m,u.join(", ")])),console.log(E__default.default(f__default.default.bold("\u26A0\uFE0F Unused Exports"),{padding:1,borderColor:"yellow",borderStyle:"round"})),console.log(r.toString());}}if(i.unusedDependencies&&i.unusedDependencies.length){let o=new F__default.default({head:[f__default.default.yellow("Unused Dependencies")],colWidths:[80]});i.unusedDependencies.slice(0,s).forEach(r=>o.push([r])),console.log(E__default.default(f__default.default.bold(`\u26A0\uFE0F Unused Dependencies (${i.unusedDependencies.length})`),{padding:1,borderColor:"yellow",borderStyle:"round"})),console.log(o.toString());}});j.command("size <packageName>").description("Check the size of a specific npm package in node_modules").action(async e=>{let t=process.cwd(),{getPackageSize:s}=await Promise.resolve().then(()=>(B(),N)),i=s(t,e);console.log(i==="N/A"?f__default.default.yellow(`Package '${e}' not found in node_modules.`):f__default.default.green(`\u{1F4E6} ${e} size: ${i}`));});j.command("find <exportName>").description("Find usage count and references (with line numbers) for a component/function/export").action(async e=>{let t=process.cwd(),s=await v({rootPath:t,analyzeExports:true,includeSizes:false}),i=[],l=s.usedExports||{};for(let[o,r]of Object.entries(l)){let m=s.sourceFiles?.[o];if(!m||!r.has(e))continue;m.getDescendantsOfKind(tsMorph.SyntaxKind.Identifier).forEach(h=>{if(h.getText()===e){let d=h.getParentOrThrow().getKindName();(d.includes("Import")||d.includes("PropertyAccess")||d.includes("Identifier"))&&i.push({file:o,line:h.getStartLineNumber()});}});}i.length?(console.log(f__default.default.green(`'${e}' is used ${i.length} time(s):`)),i.forEach(o=>{let r=M(x__default.default.join(t,o.file));console.log(` - ${o.file}:${o.line} (${r})`);})):console.log(f__default.default.yellow(`'${e}' is not used anywhere.`));});j.parse(process.argv);
|
|
3
3
|
//# sourceMappingURL=cli.js.map
|
|
4
4
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../package.json","../src/analyzer/package-size.ts","../src/analyzer/index.ts","../src/analyzer/file-size.ts","../src/cli.ts"],"names":["require_package","__commonJSMin","exports","module","package_size_exports","__export","getPackageSize","rootPath","pkg","pkgJson","__require","path","init_package_size","__esmMin","getFolderSize","dirPath","size","files","fs","file","filePath","stats","formatBytes","bytes","decimals","k","dm","sizes","i","packageName","pkgPath","analyzeProject","options","includeSizes","analyzeExports","silent","pc","glob","project","Project","tsConfigPath","f","packageUsage","fileExports","usedExports","sourceFiles","sourceFile","relativePath","exportsMap","namedExports","decls","name","hasDefault","imp","mod","SyntaxKind","call","args","imports","sourceDir","resolvedPath","extensions","foundFile","ext","tryPath","ni","unusedFiles","used","unusedExports","unused","packages","count","unusedDependencies","getFileSize","program","Command","opts","limit","report","rest","sanitizedUsedExports","key","value","packageTable","Table","a","b","data","boxen","table","entries","d","exportName","usageDetails","usedSet","id","parentKind"],"mappings":"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","file":"cli.js","sourcesContent":["{\n \"name\": \"react-prune\",\n \"version\": \"1.3.0\",\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 \"depcheck\": \"^1.4.7\",\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/depcheck\": \"^0.9.0\",\n \"@types/glob\": \"^8.1.0\",\n \"@types/node\": \"^25.0.10\",\n \"eslint\": \"^9.39.2\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\"\n }\n}\n","import path from \"path\";\n\nexport function getPackageSize(\n rootPath: string,\n pkg: string\n): string | undefined {\n try {\n const pkgJson = require(\n path.join(rootPath, \"node_modules\", pkg, \"package.json\")\n );\n if (typeof pkgJson.size === \"number\") {\n return `${(pkgJson.size / 1024).toFixed(2)} KB`;\n }\n } catch {}\n return undefined;\n}\n","import { Project, SyntaxKind, SourceFile } from \"ts-morph\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport pc from \"picocolors\";\nimport { AnalyzerOptions, UsageReport } from \"./types\";\n\nexport * from \"./types\";\n\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) size += getFolderSize(filePath);\n else size += stats.size;\n }\n } catch {}\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) return formatBytes(getFolderSize(pkgPath));\n return \"N/A\";\n}\n\nexport async function analyzeProject(\n options: AnalyzerOptions\n): Promise<UsageReport> {\n const {\n rootPath,\n includeSizes = true,\n analyzeExports = true,\n silent = false\n } = options;\n\n if (!silent) {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n }\n\n // 1️⃣ Collect all JS/TS files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\",\n \"**/*.d.ts\"\n ],\n absolute: true\n });\n\n if (!silent) {\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n }\n\n const project = new Project({ skipAddingFilesFromTsConfig: true });\n const tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n if (fs.existsSync(tsConfigPath)) {\n project.addSourceFilesFromTsConfig(tsConfigPath);\n }\n files.forEach((f) => {\n try {\n project.addSourceFileAtPath(f);\n } catch {\n console.warn(pc.yellow(`Skipping ${f}`));\n }\n });\n\n // 2️⃣ Track package usage and file exports\n const packageUsage: Record<string, number> = {};\n const fileExports: Record<string, { named: Set<string>; default: boolean }> =\n {};\n const usedExports: Record<string, Set<string>> = {};\n const sourceFiles: Record<string, SourceFile> = {};\n\n for (const sourceFile of project.getSourceFiles()) {\n const relativePath = path\n .relative(rootPath, sourceFile.getFilePath())\n .replace(/\\\\/g, \"/\");\n sourceFiles[relativePath] = sourceFile;\n\n const exportsMap = sourceFile.getExportedDeclarations();\n const namedExports = new Set<string>();\n exportsMap.forEach((decls, name) => {\n if (name !== \"default\") namedExports.add(name);\n });\n const hasDefault = exportsMap.has(\"default\");\n\n fileExports[relativePath] = { named: namedExports, default: hasDefault };\n usedExports[relativePath] = new Set();\n\n // Track package imports\n const imports = sourceFile.getImportDeclarations();\n imports.forEach((imp) => {\n const mod = imp.getModuleSpecifierValue();\n if (!mod.startsWith(\".\") && !mod.startsWith(\"/\")) {\n const pkg = mod.startsWith(\"@\")\n ? mod.split(\"/\").slice(0, 2).join(\"/\")\n : mod.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n });\n\n // Track require()\n const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);\n calls.forEach((call) => {\n if (call.getExpression().getText() === \"require\") {\n const args = call.getArguments();\n if (args.length && args[0].getKind() === SyntaxKind.StringLiteral) {\n const mod = args[0].getText().replace(/['\"`]/g, \"\");\n if (!mod.startsWith(\".\") && !mod.startsWith(\"/\")) {\n const pkg = mod.startsWith(\"@\")\n ? mod.split(\"/\").slice(0, 2).join(\"/\")\n : mod.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n });\n }\n\n // 3️⃣ Track used exports across files\n if (analyzeExports) {\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n const relativePath = path\n .relative(rootPath, sourceFile.getFilePath())\n .replace(/\\\\/g, \"/\");\n\n imports.forEach((imp) => {\n const mod = imp.getModuleSpecifierValue();\n if (mod.startsWith(\".\") || mod.startsWith(\"/\")) {\n const sourceDir = path.dirname(sourceFile.getFilePath());\n let resolvedPath = path.resolve(sourceDir, mod);\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n let foundFile: string | undefined;\n\n for (const ext of extensions) {\n const tryPath = path\n .relative(rootPath, resolvedPath + ext)\n .replace(/\\\\/g, \"/\");\n if (fileExports[tryPath]) {\n foundFile = tryPath;\n break;\n }\n }\n\n if (foundFile) {\n imp\n .getNamedImports()\n .forEach((ni) => usedExports[foundFile].add(ni.getName()));\n if (imp.getDefaultImport()) usedExports[foundFile].add(\"default\");\n }\n }\n });\n }\n }\n\n // 4️⃣ Determine unused files and exports\n const unusedFiles = Object.entries(fileExports)\n .filter(([file, exports]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"App.tsx\")\n )\n return false;\n const used = usedExports[file];\n if (exports.named.size === 0 && !exports.default) return false;\n return used.size === 0;\n })\n .map(([file]) => file);\n\n const unusedExports: Record<string, string[]> = {};\n if (analyzeExports) {\n Object.entries(fileExports).forEach(([file, exports]) => {\n const used = usedExports[file];\n const unused: string[] = [];\n if (exports.default && !used.has(\"default\")) unused.push(\"default\");\n exports.named.forEach((name) => {\n if (!used.has(name)) unused.push(name);\n });\n if (unused.length) unusedExports[file] = unused;\n });\n }\n\n // 5️⃣ Build package report\n const packages: Record<string, { count: number; size: string }> = {};\n Object.entries(packageUsage).forEach(([pkg, count]) => {\n packages[pkg] = {\n count,\n size: includeSizes ? getPackageSize(rootPath, pkg) : \"—\"\n };\n });\n\n // 6️⃣ Run depcheck\n let unusedDependencies: string[] = [];\n try {\n const depcheck = require(\"depcheck\");\n // We can't easily wait for this if not async, but analyzeProject is async.\n const depcheckResult = await depcheck(rootPath, {\n ignoreMatches: [\"react-prune\", ...((options as any).ignoreMatches || [])],\n skipMissing: true\n });\n unusedDependencies = depcheckResult.dependencies;\n } catch (e) {\n if (!silent) console.warn(pc.yellow(\"Failed to run depcheck.\"));\n }\n\n return {\n packages,\n unusedFiles,\n unusedExports,\n usedExports,\n unusedDependencies,\n sourceFiles\n };\n}\n\nexport { getPackageSize };\n","import fs from \"fs\";\n\nexport function getFileSize(filePath: string): string {\n try {\n const stats = fs.statSync(filePath);\n const bytes = stats.size;\n if (bytes < 1024) return `${bytes} B`;\n return `${(bytes / 1024).toFixed(2)} KB`;\n } catch {\n return \"N/A\";\n }\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport Table from \"cli-table3\";\nimport boxen from \"boxen\";\nimport path from \"path\";\nimport { analyzeProject, UsageReport } from \"./analyzer\";\nimport { getFileSize } from \"./analyzer/file-size\";\nimport { SyntaxKind, Identifier } from \"ts-morph\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\"Analyze React/Next/Vite/React Native projects\")\n .version(require(\"../package.json\").version)\n .option(\"--no-size\", \"Skip package size calculation\")\n .option(\"--no-exports\", \"Skip export usage analysis\")\n .option(\"--limit <n>\", \"Limit output rows per table\", \"50\");\n\nprogram\n .command(\"analyze\")\n .description(\"Run full project analysis\")\n .option(\"--no-size\", \"Skip package size calculation\")\n .option(\"--no-exports\", \"Skip export usage analysis\")\n .option(\"--limit <n>\", \"Limit output rows per table\", \"50\")\n .option(\"--json\", \"Output results as JSON\")\n .action(async (opts) => {\n const rootPath = process.cwd();\n const limit = Number(opts.limit);\n\n const report: UsageReport = await analyzeProject({\n rootPath,\n includeSizes: opts.size,\n analyzeExports: opts.exports,\n silent: opts.json\n });\n\n if (opts.json) {\n // 1. Remove circular references (sourceFiles)\n // 2. Convert Sets to Arrays for valid JSON\n const { sourceFiles, usedExports, ...rest } = report;\n\n const sanitizedUsedExports: Record<string, string[]> = {};\n if (usedExports) {\n for (const [key, value] of Object.entries(usedExports)) {\n sanitizedUsedExports[key] = Array.from(value);\n }\n }\n\n console.log(\n JSON.stringify(\n {\n ...rest,\n usedExports: sanitizedUsedExports\n },\n null,\n 2\n )\n );\n return;\n }\n\n // Packages\n const packageTable = new Table({\n head: [pc.cyan(\"Package\"), \"Count\", \"Size\"],\n colWidths: [40, 10, 15]\n });\n Object.entries(report.packages)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, limit)\n .forEach(([pkg, data]) =>\n packageTable.push([pkg, data.count, data.size])\n );\n console.log(\n boxen(pc.bold(\"📦 Package Usage\"), {\n padding: 1,\n borderColor: \"green\",\n borderStyle: \"round\"\n })\n );\n console.log(packageTable.toString());\n\n // Unused Files\n if (report.unusedFiles.length) {\n const table = new Table({\n head: [pc.yellow(\"Unused Files\")],\n colWidths: [80]\n });\n report.unusedFiles.slice(0, limit).forEach((f) => table.push([f]));\n console.log(\n boxen(pc.bold(`⚠️ Unused Files (${report.unusedFiles.length})`), {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n })\n );\n console.log(table.toString());\n }\n\n // Unused Exports\n if (opts.exports) {\n const entries = Object.entries(report.unusedExports);\n if (entries.length) {\n const table = new Table({\n head: [\"File\", \"Unused Exports\"],\n colWidths: [50, 40],\n wordWrap: true\n });\n entries\n .slice(0, limit)\n .forEach(([file, exports]) => table.push([file, exports.join(\", \")]));\n console.log(\n boxen(pc.bold(`⚠️ Unused Exports`), {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n })\n );\n console.log(table.toString());\n }\n }\n\n // Unused Dependencies\n if (report.unusedDependencies && report.unusedDependencies.length) {\n const table = new Table({\n head: [pc.yellow(\"Unused Dependencies\")],\n colWidths: [80]\n });\n report.unusedDependencies.slice(0, limit).forEach((d) => table.push([d]));\n console.log(\n boxen(\n pc.bold(\n `⚠️ Unused Dependencies (${report.unusedDependencies.length})`\n ),\n {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n }\n )\n );\n console.log(table.toString());\n }\n });\n\nprogram\n .command(\"size <packageName>\")\n .description(\"Check the size of a specific npm package in node_modules\")\n .action(async (packageName) => {\n const rootPath = process.cwd();\n\n // Reuse helper from analyzer\n const { getPackageSize } = await import(\"./analyzer/package-size\");\n\n const size = getPackageSize(rootPath, packageName);\n\n if (size === \"N/A\") {\n console.log(\n pc.yellow(`Package '${packageName}' not found in node_modules.`)\n );\n } else {\n console.log(pc.green(`📦 ${packageName} size: ${size}`));\n }\n });\n// --- New find command with line numbers\nprogram\n .command(\"find <exportName>\")\n .description(\n \"Find usage count and references (with line numbers) for a component/function/export\"\n )\n .action(async (exportName) => {\n const rootPath = process.cwd();\n const report: UsageReport = await analyzeProject({\n rootPath,\n analyzeExports: true,\n includeSizes: false\n });\n\n const usageDetails: { file: string; line: number }[] = [];\n\n const usedExports = report.usedExports || {};\n\n for (const [file, usedSet] of Object.entries(usedExports)) {\n const sourceFile = report.sourceFiles?.[file];\n if (!sourceFile) continue;\n\n // Only process if this file actually uses the export\n if (!usedSet.has(exportName)) continue;\n\n // Traverse identifiers in the file\n const identifiers = sourceFile.getDescendantsOfKind(\n SyntaxKind.Identifier\n );\n\n identifiers.forEach((id: Identifier) => {\n // Check if identifier matches the export name\n if (id.getText() === exportName) {\n // Make sure this usage is actually an import/reference, not a declaration\n const parentKind = id.getParentOrThrow().getKindName();\n\n if (\n parentKind.includes(\"Import\") || // ImportSpecifier, ImportClause, etc\n parentKind.includes(\"PropertyAccess\") || // obj.exportName\n parentKind.includes(\"Identifier\") // usage in code\n ) {\n usageDetails.push({ file, line: id.getStartLineNumber() });\n }\n }\n });\n }\n\n if (usageDetails.length) {\n console.log(\n pc.green(`'${exportName}' is used ${usageDetails.length} time(s):`)\n );\n usageDetails.forEach((d) => {\n const size = getFileSize(path.join(rootPath, d.file));\n console.log(` - ${d.file}:${d.line} (${size})`);\n });\n } else {\n console.log(pc.yellow(`'${exportName}' is not used anywhere.`));\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
|
1
|
+
{"version":3,"sources":["../package.json","../src/analyzer/package-size.ts","../src/analyzer/index.ts","../src/analyzer/file-size.ts","../src/cli.ts"],"names":["require_package","__commonJSMin","exports","module","package_size_exports","__export","getPackageSize","rootPath","pkg","pkgJson","__require","path","init_package_size","__esmMin","getFolderSize","dirPath","size","files","fs","file","filePath","stats","formatBytes","bytes","decimals","k","dm","sizes","i","packageName","pkgPath","analyzeProject","options","includeSizes","analyzeExports","silent","pc","glob","project","Project","tsConfigPath","f","packageUsage","fileExports","usedExports","sourceFiles","sourceFile","relativePath","exportsMap","namedExports","decls","name","hasDefault","imp","mod","SyntaxKind","call","args","imports","sourceDir","resolvedPath","extensions","foundFile","ext","tryPath","ni","unusedFiles","used","unusedExports","unused","packages","count","unusedDependencies","getFileSize","program","Command","opts","limit","report","rest","sanitizedUsedExports","key","value","packageTable","Table","a","b","data","boxen","table","entries","d","exportName","usageDetails","usedSet","id","parentKind"],"mappings":";s9BAAA,IAAAA,CAAAA,CAAAC,EAAA,CAAAC,EAAAA,CAAAC,IAAA,CAAAA,CAAAA,CAAA,OAAA,CAAA,CACE,IAAA,CAAQ,aAAA,CACR,OAAA,CAAW,QACX,IAAA,CAAQ,eAAA,CACR,IAAO,CACL,aAAA,CAAe,mBACjB,CAAA,CACA,OAAA,CAAW,CACT,KAAA,CAAS,oCAAA,CACT,GAAA,CAAO,0BACP,IAAA,CAAQ,eAAA,CACR,OAAU,oBAAA,CACV,cAAA,CAAkB,gBAClB,OAAA,CAAW,mBAAA,CACX,IAAA,CAAQ,QACV,CAAA,CACA,aAAA,CAAiB,CACf,MAAA,CAAU,QACZ,EACA,QAAA,CAAY,CACV,QACA,cAAA,CACA,QAAA,CACA,UAAA,CACA,WAAA,CACA,SAAA,CACA,qBAAA,CACA,cACA,OAAA,CACA,YAAA,CACA,kBACA,KACF,CAAA,CACA,OAAU,gBAAA,CACV,OAAA,CAAW,KAAA,CACX,KAAA,CAAS,CACP,KAAA,CACA,UACA,MACF,CAAA,CACA,WAAc,CACZ,IAAA,CAAQ,MACR,GAAA,CAAO,wDACT,CAAA,CACA,OAAA,CAAW,CACT,IAAA,CAAQ,MACV,CAAA,CACA,IAAA,CAAQ,CACN,GAAA,CAAO,uDACT,EACA,QAAA,CAAY,uDAAA,CACZ,WAAA,CAAe,qJAAA,CACf,YAAA,CAAgB,CACd,MAAS,QAAA,CACT,YAAA,CAAc,QAAA,CACd,SAAA,CAAa,SAAA,CACb,QAAA,CAAY,SACZ,WAAA,CAAa,QAAA,CACb,UAAA,CAAc,QAAA,CACd,UAAA,CAAY,SACd,EACA,eAAA,CAAmB,CACjB,kBAAmB,SAAA,CACnB,iBAAA,CAAmB,SACnB,aAAA,CAAe,QAAA,CACf,aAAA,CAAe,UAAA,CACf,MAAA,CAAU,SAAA,CACV,SAAY,QAAA,CACZ,IAAA,CAAQ,SACR,UAAA,CAAc,QAAA,CACd,OAAU,SACZ,CACF,EAAA,CAAA,CAAA,CCxEA,IAAAC,CAAAA,CAAA,EAAA,CAAAC,EAAAD,CAAAA,CAAA,CAAA,cAAA,CAAA,IAAAE,IAEO,SAASA,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACoB,CACpB,GAAI,CACF,IAAMC,CAAAA,CAAUC,CAAAA,CACdC,kBAAAA,CAAK,IAAA,CAAKJ,CAAAA,CAAU,eAAgBC,CAAAA,CAAK,cAAc,CACzD,CAAA,CACA,GAAI,OAAOC,EAAQ,IAAA,EAAS,QAAA,CAC1B,OAAO,CAAA,EAAA,CAAIA,CAAAA,CAAQ,KAAO,IAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,GAAA,CAE9C,CAAA,KAAQ,CAAC,CAEX,CAfA,IAAAG,CAAAA,CAAAC,CAAAA,CAAA,QCSA,SAASC,CAAAA,CAAcC,EAAyB,CAC9C,IAAIC,EAAO,CAAA,CACX,GAAI,CACF,IAAMC,CAAAA,CAAQC,kBAAAA,CAAG,YAAYH,CAAO,CAAA,CACpC,QAAWI,CAAAA,IAAQF,CAAAA,CAAO,CACxB,IAAMG,CAAAA,CAAWT,kBAAAA,CAAK,IAAA,CAAKI,CAAAA,CAASI,CAAI,EAClCE,CAAAA,CAAQH,kBAAAA,CAAG,SAASE,CAAQ,CAAA,CAC9BC,EAAM,WAAA,EAAY,CAAGL,CAAAA,EAAQF,CAAAA,CAAcM,CAAQ,CAAA,CAClDJ,GAAQK,CAAAA,CAAM,KACrB,CACF,CAAA,KAAQ,CAAC,CACT,OAAOL,CACT,CAEA,SAASM,CAAAA,CAAYC,CAAAA,CAAeC,EAAW,CAAA,CAAG,CAChD,GAAID,CAAAA,GAAU,CAAA,CAAG,OAAO,SAAA,CACxB,IAAME,CAAAA,CAAI,IAAA,CACJC,CAAAA,CAAKF,CAAAA,CAAW,EAAI,CAAA,CAAIA,CAAAA,CACxBG,EAAQ,CAAC,OAAA,CAAS,KAAM,IAAA,CAAM,IAAA,CAAM,IAAI,CAAA,CACxCC,CAAAA,CAAI,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,CAAIL,CAAK,CAAA,CAAI,IAAA,CAAK,IAAIE,CAAC,CAAC,CAAA,CAClD,OAAO,UAAA,CAAA,CAAYF,CAAAA,CAAQ,KAAK,GAAA,CAAIE,CAAAA,CAAGG,CAAC,CAAA,EAAG,OAAA,CAAQF,CAAE,CAAC,CAAA,CAAI,GAAA,CAAMC,CAAAA,CAAMC,CAAC,CACzE,CAEA,SAAStB,CAAAA,CAAeC,CAAAA,CAAkBsB,EAA6B,CACrE,IAAMC,EAAUnB,kBAAAA,CAAK,IAAA,CAAKJ,CAAAA,CAAU,cAAA,CAAgBsB,CAAW,CAAA,CAC/D,OAAIX,kBAAAA,CAAG,UAAA,CAAWY,CAAO,CAAA,CAAUR,CAAAA,CAAYR,EAAcgB,CAAO,CAAC,CAAA,CAC9D,KACT,CAEA,eAAsBC,EACpBC,CAAAA,CACsB,CACtB,GAAM,CACJ,QAAA,CAAAzB,EACA,YAAA,CAAA0B,CAAAA,CAAe,IAAA,CACf,cAAA,CAAAC,CAAAA,CAAiB,IAAA,CACjB,OAAAC,CAAAA,CAAS,KACX,CAAA,CAAIH,CAAAA,CAECG,CAAAA,EACH,OAAA,CAAQ,IAAIC,kBAAAA,CAAG,KAAA,CAAM,CAAA,qBAAA,EAAwB7B,CAAQ,CAAA,CAAE,CAAC,EAI1D,IAAMU,CAAAA,CAAQ,MAAMoB,kBAAAA,CAAK,sBAAA,CAAwB,CAC/C,GAAA,CAAK9B,CAAAA,CACL,MAAA,CAAQ,CACN,oBAAA,CACA,YAAA,CACA,cACA,aAAA,CACA,gBAAA,CACA,8BACA,WACF,CAAA,CACA,SAAU,IACZ,CAAC,CAAA,CAEI4B,CAAAA,EACH,OAAA,CAAQ,GAAA,CAAIC,mBAAG,IAAA,CAAK,CAAA,MAAA,EAASnB,EAAM,MAAM,CAAA,kBAAA,CAAoB,CAAC,CAAA,CAGhE,IAAMqB,CAAAA,CAAU,IAAIC,eAAAA,CAAQ,CAAE,4BAA6B,IAAK,CAAC,CAAA,CAC3DC,CAAAA,CAAe7B,kBAAAA,CAAK,IAAA,CAAKJ,EAAU,eAAe,CAAA,CACpDW,kBAAAA,CAAG,UAAA,CAAWsB,CAAY,CAAA,EAC5BF,EAAQ,0BAAA,CAA2BE,CAAY,EAEjDvB,CAAAA,CAAM,OAAA,CAASwB,GAAM,CACnB,GAAI,CACFH,CAAAA,CAAQ,mBAAA,CAAoBG,CAAC,EAC/B,CAAA,KAAQ,CACN,QAAQ,IAAA,CAAKL,kBAAAA,CAAG,OAAO,CAAA,SAAA,EAAYK,CAAC,CAAA,CAAE,CAAC,EACzC,CACF,CAAC,CAAA,CAGD,IAAMC,EAAuC,EAAC,CACxCC,EACJ,EAAC,CACGC,CAAAA,CAA2C,EAAC,CAC5CC,CAAAA,CAA0C,EAAC,CAEjD,IAAA,IAAWC,KAAcR,CAAAA,CAAQ,cAAA,GAAkB,CACjD,IAAMS,CAAAA,CAAepC,kBAAAA,CAClB,QAAA,CAASJ,CAAAA,CAAUuC,EAAW,WAAA,EAAa,EAC3C,OAAA,CAAQ,KAAA,CAAO,GAAG,CAAA,CACrBD,CAAAA,CAAYE,CAAY,CAAA,CAAID,CAAAA,CAE5B,IAAME,EAAaF,CAAAA,CAAW,uBAAA,GACxBG,CAAAA,CAAe,IAAI,IACzBD,CAAAA,CAAW,OAAA,CAAQ,CAACE,CAAAA,CAAOC,CAAAA,GAAS,CAC9BA,IAAS,SAAA,EAAWF,CAAAA,CAAa,IAAIE,CAAI,EAC/C,CAAC,CAAA,CACD,IAAMC,CAAAA,CAAaJ,CAAAA,CAAW,GAAA,CAAI,SAAS,EAE3CL,CAAAA,CAAYI,CAAY,CAAA,CAAI,CAAE,KAAA,CAAOE,CAAAA,CAAc,QAASG,CAAW,CAAA,CACvER,CAAAA,CAAYG,CAAY,CAAA,CAAI,IAAI,IAGhBD,CAAAA,CAAW,qBAAA,GACnB,OAAA,CAASO,CAAAA,EAAQ,CACvB,IAAMC,CAAAA,CAAMD,CAAAA,CAAI,uBAAA,EAAwB,CACxC,GAAI,CAACC,CAAAA,CAAI,UAAA,CAAW,GAAG,CAAA,EAAK,CAACA,EAAI,UAAA,CAAW,GAAG,CAAA,CAAG,CAChD,IAAM9C,CAAAA,CAAM8C,EAAI,UAAA,CAAW,GAAG,EAC1BA,CAAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,KAAA,CAAM,CAAA,CAAG,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,CACnCA,CAAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,EACpBZ,CAAAA,CAAalC,CAAG,CAAA,CAAA,CAAKkC,CAAAA,CAAalC,CAAG,CAAA,EAAK,GAAK,EACjD,CACF,CAAC,CAAA,CAGasC,CAAAA,CAAW,qBAAqBS,kBAAAA,CAAW,cAAc,CAAA,CACjE,OAAA,CAASC,CAAAA,EAAS,CACtB,GAAIA,CAAAA,CAAK,aAAA,GAAgB,OAAA,EAAQ,GAAM,UAAW,CAChD,IAAMC,CAAAA,CAAOD,CAAAA,CAAK,YAAA,EAAa,CAC/B,GAAIC,CAAAA,CAAK,MAAA,EAAUA,EAAK,CAAC,CAAA,CAAE,SAAQ,GAAMF,kBAAAA,CAAW,aAAA,CAAe,CACjE,IAAMD,CAAAA,CAAMG,EAAK,CAAC,CAAA,CAAE,OAAA,EAAQ,CAAE,OAAA,CAAQ,QAAA,CAAU,EAAE,CAAA,CAClD,GAAI,CAACH,CAAAA,CAAI,UAAA,CAAW,GAAG,GAAK,CAACA,CAAAA,CAAI,WAAW,GAAG,CAAA,CAAG,CAChD,IAAM9C,CAAAA,CAAM8C,CAAAA,CAAI,UAAA,CAAW,GAAG,CAAA,CAC1BA,EAAI,KAAA,CAAM,GAAG,EAAE,KAAA,CAAM,CAAA,CAAG,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,CACnCA,CAAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,EACpBZ,CAAAA,CAAalC,CAAG,GAAKkC,CAAAA,CAAalC,CAAG,CAAA,EAAK,CAAA,EAAK,EACjD,CACF,CACF,CACF,CAAC,EACH,CAGA,GAAI0B,CAAAA,CACF,QAAWY,CAAAA,IAAcR,CAAAA,CAAQ,cAAA,EAAe,CAAG,CACjD,IAAMoB,EAAUZ,CAAAA,CAAW,qBAAA,GACNnC,kBAAAA,CAClB,SAASJ,CAAAA,CAAUuC,CAAAA,CAAW,WAAA,EAAa,CAAA,CAC3C,OAAA,CAAQ,MAAO,GAAG,EAErBY,EAAQ,OAAA,CAASL,CAAAA,EAAQ,CACvB,IAAMC,CAAAA,CAAMD,CAAAA,CAAI,uBAAA,EAAwB,CACxC,GAAIC,EAAI,UAAA,CAAW,GAAG,GAAKA,CAAAA,CAAI,UAAA,CAAW,GAAG,CAAA,CAAG,CAC9C,IAAMK,CAAAA,CAAYhD,kBAAAA,CAAK,OAAA,CAAQmC,EAAW,WAAA,EAAa,CAAA,CACnDc,CAAAA,CAAejD,kBAAAA,CAAK,OAAA,CAAQgD,EAAWL,CAAG,CAAA,CACxCO,CAAAA,CAAa,CACjB,EAAA,CACA,KAAA,CACA,OACA,KAAA,CACA,MAAA,CACA,YACA,YAAA,CACA,WAAA,CACA,YACF,CAAA,CACIC,CAAAA,CAEJ,IAAA,IAAWC,CAAAA,IAAOF,CAAAA,CAAY,CAC5B,IAAMG,CAAAA,CAAUrD,kBAAAA,CACb,SAASJ,CAAAA,CAAUqD,CAAAA,CAAeG,CAAG,CAAA,CACrC,OAAA,CAAQ,KAAA,CAAO,GAAG,CAAA,CACrB,GAAIpB,EAAYqB,CAAO,CAAA,CAAG,CACxBF,CAAAA,CAAYE,CAAAA,CACZ,KACF,CACF,CAEIF,CAAAA,GACFT,CAAAA,CACG,eAAA,EAAgB,CAChB,QAASY,CAAAA,EAAOrB,CAAAA,CAAYkB,CAAS,CAAA,CAAE,GAAA,CAAIG,CAAAA,CAAG,SAAS,CAAC,CAAA,CACvDZ,CAAAA,CAAI,gBAAA,EAAiB,EAAGT,EAAYkB,CAAS,CAAA,CAAE,IAAI,SAAS,CAAA,EAEpE,CACF,CAAC,EACH,CAIF,IAAMI,CAAAA,CAAc,MAAA,CAAO,QAAQvB,CAAW,CAAA,CAC3C,OAAO,CAAC,CAACxB,EAAMjB,CAAO,CAAA,GAAM,CAC3B,GACEiB,CAAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,EACtBA,CAAAA,CAAK,SAAS,MAAM,CAAA,EACpBA,EAAK,QAAA,CAAS,WAAW,CAAA,EACzBA,CAAAA,CAAK,QAAA,CAAS,SAAS,EAEvB,OAAO,MAAA,CACT,IAAMgD,CAAAA,CAAOvB,CAAAA,CAAYzB,CAAI,EAC7B,OAAIjB,CAAAA,CAAQ,KAAA,CAAM,IAAA,GAAS,CAAA,EAAK,CAACA,EAAQ,OAAA,CAAgB,KAAA,CAClDiE,EAAK,IAAA,GAAS,CACvB,CAAC,CAAA,CACA,GAAA,CAAI,CAAC,CAAChD,CAAI,CAAA,GAAMA,CAAI,CAAA,CAEjBiD,CAAAA,CAA0C,EAAC,CAC7ClC,CAAAA,EACF,OAAO,OAAA,CAAQS,CAAW,CAAA,CAAE,OAAA,CAAQ,CAAC,CAACxB,EAAMjB,CAAO,CAAA,GAAM,CACvD,IAAMiE,CAAAA,CAAOvB,EAAYzB,CAAI,CAAA,CACvBkD,CAAAA,CAAmB,EAAC,CACtBnE,CAAAA,CAAQ,SAAW,CAACiE,CAAAA,CAAK,IAAI,SAAS,CAAA,EAAGE,EAAO,IAAA,CAAK,SAAS,CAAA,CAClEnE,CAAAA,CAAQ,KAAA,CAAM,OAAA,CAASiD,GAAS,CACzBgB,CAAAA,CAAK,IAAIhB,CAAI,CAAA,EAAGkB,EAAO,IAAA,CAAKlB,CAAI,EACvC,CAAC,CAAA,CACGkB,CAAAA,CAAO,SAAQD,CAAAA,CAAcjD,CAAI,EAAIkD,CAAAA,EAC3C,CAAC,EAIH,IAAMC,CAAAA,CAA4D,EAAC,CACnE,MAAA,CAAO,OAAA,CAAQ5B,CAAY,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAClC,CAAAA,CAAK+D,CAAK,CAAA,GAAM,CACrDD,CAAAA,CAAS9D,CAAG,CAAA,CAAI,CACd,MAAA+D,CAAAA,CACA,IAAA,CAAMtC,CAAAA,CAAe3B,CAAAA,CAAeC,CAAAA,CAAUC,CAAG,EAAI,QACvD,EACF,CAAC,CAAA,CAGD,IAAIgE,CAAAA,CAA+B,EAAC,CACpC,GAAI,CAOFA,CAAAA,CAAAA,CAJuB,QAFE,UAAU,CAAA,CAEGjE,CAAAA,CAAU,CAC9C,aAAA,CAAe,CAAC,cAAe,GAAKyB,CAAAA,CAAgB,eAAiB,EAAG,EACxE,WAAA,CAAa,CAAA,CACf,CAAC,CAAA,EACmC,aACtC,CAAA,KAAY,CACLG,CAAAA,EAAQ,OAAA,CAAQ,KAAKC,kBAAAA,CAAG,MAAA,CAAO,yBAAyB,CAAC,EAChE,CAEA,OAAO,CACL,QAAA,CAAAkC,EACA,WAAA,CAAAJ,CAAAA,CACA,aAAA,CAAAE,CAAAA,CACA,WAAA,CAAAxB,CAAAA,CACA,mBAAA4B,CAAAA,CACA,WAAA,CAAA3B,CACF,CACF,CClPO,SAAS4B,EAAYrD,CAAAA,CAA0B,CACpD,GAAI,CAEF,IAAMG,CAAAA,CADQL,kBAAAA,CAAG,QAAA,CAASE,CAAQ,EACd,IAAA,CACpB,OAAIG,EAAQ,IAAA,CAAa,CAAA,EAAGA,CAAK,CAAA,EAAA,CAAA,CAC1B,CAAA,EAAA,CAAIA,CAAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,GAAA,CACrC,CAAA,KAAQ,CACN,OAAO,KACT,CACF,CCDA,IAAMmD,EAAU,IAAIC,iBAAAA,CAEpBD,CAAAA,CACG,IAAA,CAAK,aAAa,CAAA,CAClB,YAAY,+CAA+C,CAAA,CAC3D,OAAA,CAAQ,CAAA,EAAA,CAA2B,OAAO,CAAA,CAC1C,OAAO,WAAA,CAAa,+BAA+B,EACnD,MAAA,CAAO,cAAA,CAAgB,4BAA4B,CAAA,CACnD,MAAA,CAAO,aAAA,CAAe,6BAAA,CAA+B,IAAI,CAAA,CAE5DA,EACG,OAAA,CAAQ,SAAS,EACjB,WAAA,CAAY,2BAA2B,EACvC,MAAA,CAAO,WAAA,CAAa,+BAA+B,CAAA,CACnD,MAAA,CAAO,cAAA,CAAgB,4BAA4B,CAAA,CACnD,MAAA,CAAO,cAAe,6BAAA,CAA+B,IAAI,EACzD,MAAA,CAAO,QAAA,CAAU,wBAAwB,CAAA,CACzC,MAAA,CAAO,MAAOE,GAAS,CACtB,IAAMrE,CAAAA,CAAW,OAAA,CAAQ,GAAA,EAAI,CACvBsE,EAAQ,MAAA,CAAOD,CAAAA,CAAK,KAAK,CAAA,CAEzBE,CAAAA,CAAsB,MAAM/C,EAAe,CAC/C,QAAA,CAAAxB,EACA,YAAA,CAAcqE,CAAAA,CAAK,KACnB,cAAA,CAAgBA,CAAAA,CAAK,OAAA,CACrB,MAAA,CAAQA,CAAAA,CAAK,IACf,CAAC,CAAA,CAED,GAAIA,EAAK,IAAA,CAAM,CAGb,GAAM,CAAE,WAAA,CAAA/B,CAAAA,CAAa,WAAA,CAAAD,CAAAA,CAAa,GAAGmC,CAAK,CAAA,CAAID,CAAAA,CAExCE,EAAiD,EAAC,CACxD,GAAIpC,CAAAA,CACF,IAAA,GAAW,CAACqC,CAAAA,CAAKC,CAAK,CAAA,GAAK,OAAO,OAAA,CAAQtC,CAAW,CAAA,CACnDoC,CAAAA,CAAqBC,CAAG,CAAA,CAAI,MAAM,IAAA,CAAKC,CAAK,CAAA,CAIhD,OAAA,CAAQ,GAAA,CACN,IAAA,CAAK,UACH,CACE,GAAGH,EACH,WAAA,CAAaC,CACf,EACA,IAAA,CACA,CACF,CACF,CAAA,CACA,MACF,CAGA,IAAMG,CAAAA,CAAe,IAAIC,mBAAM,CAC7B,IAAA,CAAM,CAAChD,kBAAAA,CAAG,IAAA,CAAK,SAAS,CAAA,CAAG,OAAA,CAAS,MAAM,EAC1C,SAAA,CAAW,CAAC,GAAI,EAAA,CAAI,EAAE,CACxB,CAAC,CAAA,CAiBD,GAhBA,MAAA,CAAO,OAAA,CAAQ0C,CAAAA,CAAO,QAAQ,CAAA,CAC3B,IAAA,CAAK,CAACO,CAAAA,CAAGC,CAAAA,GAAMA,CAAAA,CAAE,CAAC,CAAA,CAAE,KAAA,CAAQD,CAAAA,CAAE,CAAC,CAAA,CAAE,KAAK,EACtC,KAAA,CAAM,CAAA,CAAGR,CAAK,CAAA,CACd,OAAA,CAAQ,CAAC,CAACrE,CAAAA,CAAK+E,CAAI,CAAA,GAClBJ,CAAAA,CAAa,IAAA,CAAK,CAAC3E,CAAAA,CAAK+E,CAAAA,CAAK,MAAOA,CAAAA,CAAK,IAAI,CAAC,CAChD,CAAA,CACF,OAAA,CAAQ,GAAA,CACNC,kBAAAA,CAAMpD,kBAAAA,CAAG,KAAK,yBAAkB,CAAA,CAAG,CACjC,OAAA,CAAS,CAAA,CACT,YAAa,OAAA,CACb,WAAA,CAAa,OACf,CAAC,CACH,CAAA,CACA,QAAQ,GAAA,CAAI+C,CAAAA,CAAa,QAAA,EAAU,CAAA,CAG/BL,CAAAA,CAAO,YAAY,MAAA,CAAQ,CAC7B,IAAMW,CAAAA,CAAQ,IAAIL,kBAAAA,CAAM,CACtB,IAAA,CAAM,CAAChD,mBAAG,MAAA,CAAO,cAAc,CAAC,CAAA,CAChC,SAAA,CAAW,CAAC,EAAE,CAChB,CAAC,EACD0C,CAAAA,CAAO,WAAA,CAAY,MAAM,CAAA,CAAGD,CAAK,EAAE,OAAA,CAASpC,CAAAA,EAAMgD,CAAAA,CAAM,IAAA,CAAK,CAAChD,CAAC,CAAC,CAAC,CAAA,CACjE,QAAQ,GAAA,CACN+C,kBAAAA,CAAMpD,mBAAG,IAAA,CAAK,CAAA,2BAAA,EAAoB0C,CAAAA,CAAO,WAAA,CAAY,MAAM,CAAA,CAAA,CAAG,EAAG,CAC/D,OAAA,CAAS,EACT,WAAA,CAAa,QAAA,CACb,YAAa,OACf,CAAC,CACH,CAAA,CACA,OAAA,CAAQ,GAAA,CAAIW,EAAM,QAAA,EAAU,EAC9B,CAGA,GAAIb,EAAK,OAAA,CAAS,CAChB,IAAMc,CAAAA,CAAU,MAAA,CAAO,OAAA,CAAQZ,EAAO,aAAa,CAAA,CACnD,GAAIY,CAAAA,CAAQ,MAAA,CAAQ,CAClB,IAAMD,CAAAA,CAAQ,IAAIL,kBAAAA,CAAM,CACtB,IAAA,CAAM,CAAC,MAAA,CAAQ,gBAAgB,EAC/B,SAAA,CAAW,CAAC,GAAI,EAAE,CAAA,CAClB,QAAA,CAAU,IACZ,CAAC,CAAA,CACDM,EACG,KAAA,CAAM,CAAA,CAAGb,CAAK,CAAA,CACd,OAAA,CAAQ,CAAC,CAAC1D,CAAAA,CAAMjB,CAAO,CAAA,GAAMuF,CAAAA,CAAM,IAAA,CAAK,CAACtE,EAAMjB,CAAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAC,CAAC,CAAA,CACtE,OAAA,CAAQ,GAAA,CACNsF,kBAAAA,CAAMpD,kBAAAA,CAAG,IAAA,CAAK,6BAAmB,CAAA,CAAG,CAClC,QAAS,CAAA,CACT,WAAA,CAAa,SACb,WAAA,CAAa,OACf,CAAC,CACH,CAAA,CACA,OAAA,CAAQ,IAAIqD,CAAAA,CAAM,QAAA,EAAU,EAC9B,CACF,CAGA,GAAIX,CAAAA,CAAO,kBAAA,EAAsBA,CAAAA,CAAO,kBAAA,CAAmB,MAAA,CAAQ,CACjE,IAAMW,CAAAA,CAAQ,IAAIL,kBAAAA,CAAM,CACtB,IAAA,CAAM,CAAChD,kBAAAA,CAAG,MAAA,CAAO,qBAAqB,CAAC,CAAA,CACvC,SAAA,CAAW,CAAC,EAAE,CAChB,CAAC,CAAA,CACD0C,CAAAA,CAAO,mBAAmB,KAAA,CAAM,CAAA,CAAGD,CAAK,CAAA,CAAE,OAAA,CAASc,CAAAA,EAAMF,EAAM,IAAA,CAAK,CAACE,CAAC,CAAC,CAAC,EACxE,OAAA,CAAQ,GAAA,CACNH,kBAAAA,CACEpD,kBAAAA,CAAG,IAAA,CACD,CAAA,kCAAA,EAA2B0C,EAAO,kBAAA,CAAmB,MAAM,GAC7D,CAAA,CACA,CACE,QAAS,CAAA,CACT,WAAA,CAAa,QAAA,CACb,WAAA,CAAa,OACf,CACF,CACF,CAAA,CACA,OAAA,CAAQ,GAAA,CAAIW,CAAAA,CAAM,QAAA,EAAU,EAC9B,CACF,CAAC,CAAA,CAEHf,CAAAA,CACG,OAAA,CAAQ,oBAAoB,EAC5B,WAAA,CAAY,0DAA0D,EACtE,MAAA,CAAO,MAAO7C,GAAgB,CAC7B,IAAMtB,CAAAA,CAAW,OAAA,CAAQ,GAAA,EAAI,CAGvB,CAAE,cAAA,CAAAD,CAAe,EAAI,MAAM,OAAA,CAAA,OAAA,EAAA,CAAA,IAAA,CAAA,KAAA,CAAA,EAAA,CAAA,CAAA,CAAA,CAAA,CAE3BU,EAAOV,CAAAA,CAAeC,CAAAA,CAAUsB,CAAW,CAAA,CAG/C,OAAA,CAAQ,GAAA,CADNb,IAAS,KAAA,CAEToB,kBAAAA,CAAG,OAAO,CAAA,SAAA,EAAYP,CAAW,8BAA8B,CAAA,CAGrDO,kBAAAA,CAAG,KAAA,CAAM,CAAA,UAAA,EAAMP,CAAW,CAAA,OAAA,EAAUb,CAAI,CAAA,CAAE,CAFtD,EAIJ,CAAC,CAAA,CAEH0D,CAAAA,CACG,QAAQ,mBAAmB,CAAA,CAC3B,WAAA,CACC,qFACF,CAAA,CACC,MAAA,CAAO,MAAOkB,CAAAA,EAAe,CAC5B,IAAMrF,CAAAA,CAAW,OAAA,CAAQ,KAAI,CACvBuE,CAAAA,CAAsB,MAAM/C,CAAAA,CAAe,CAC/C,QAAA,CAAAxB,EACA,cAAA,CAAgB,IAAA,CAChB,aAAc,KAChB,CAAC,EAEKsF,CAAAA,CAAiD,EAAC,CAElDjD,CAAAA,CAAckC,CAAAA,CAAO,WAAA,EAAe,EAAC,CAE3C,IAAA,GAAW,CAAC3D,CAAAA,CAAM2E,CAAO,IAAK,MAAA,CAAO,OAAA,CAAQlD,CAAW,CAAA,CAAG,CACzD,IAAME,EAAagC,CAAAA,CAAO,WAAA,GAAc3D,CAAI,CAAA,CAI5C,GAHI,CAAC2B,GAGD,CAACgD,CAAAA,CAAQ,GAAA,CAAIF,CAAU,CAAA,CAAG,SAGV9C,EAAW,oBAAA,CAC7BS,kBAAAA,CAAW,UACb,CAAA,CAEY,OAAA,CAASwC,GAAmB,CAEtC,GAAIA,CAAAA,CAAG,OAAA,EAAQ,GAAMH,CAAAA,CAAY,CAE/B,IAAMI,CAAAA,CAAaD,EAAG,gBAAA,EAAiB,CAAE,aAAY,CAAA,CAGnDC,CAAAA,CAAW,QAAA,CAAS,QAAQ,CAAA,EAC5BA,CAAAA,CAAW,SAAS,gBAAgB,CAAA,EACpCA,EAAW,QAAA,CAAS,YAAY,IAEhCH,CAAAA,CAAa,IAAA,CAAK,CAAE,IAAA,CAAA1E,CAAAA,CAAM,IAAA,CAAM4E,EAAG,kBAAA,EAAqB,CAAC,EAE7D,CACF,CAAC,EACH,CAEIF,CAAAA,CAAa,MAAA,EACf,OAAA,CAAQ,GAAA,CACNzD,kBAAAA,CAAG,MAAM,CAAA,CAAA,EAAIwD,CAAU,aAAaC,CAAAA,CAAa,MAAM,WAAW,CACpE,CAAA,CACAA,CAAAA,CAAa,OAAA,CAASF,CAAAA,EAAM,CAC1B,IAAM3E,CAAAA,CAAOyD,CAAAA,CAAY9D,mBAAK,IAAA,CAAKJ,CAAAA,CAAUoF,EAAE,IAAI,CAAC,CAAA,CACpD,OAAA,CAAQ,GAAA,CAAI,CAAA,GAAA,EAAMA,EAAE,IAAI,CAAA,CAAA,EAAIA,EAAE,IAAI,CAAA,EAAA,EAAK3E,CAAI,CAAA,CAAA,CAAG,EAChD,CAAC,CAAA,EAED,OAAA,CAAQ,GAAA,CAAIoB,mBAAG,MAAA,CAAO,CAAA,CAAA,EAAIwD,CAAU,CAAA,uBAAA,CAAyB,CAAC,EAElE,CAAC,CAAA,CAEHlB,CAAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA","file":"cli.js","sourcesContent":["{\n \"name\": \"react-prune\",\n \"version\": \"2.0.2\",\n \"main\": \"dist/index.js\",\n \"bin\": {\n \"react-prune\": \"./bin/react-prune\"\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 \"bin\",\n \"scripts\",\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 \"depcheck\": \"^1.4.7\",\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/depcheck\": \"^0.9.0\",\n \"@types/glob\": \"^8.1.0\",\n \"@types/node\": \"^25.0.10\",\n \"eslint\": \"^9.39.2\",\n \"prettier\": \"^3.8.1\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\"\n }\n}\n","import path from \"path\";\n\nexport function getPackageSize(\n rootPath: string,\n pkg: string\n): string | undefined {\n try {\n const pkgJson = require(\n path.join(rootPath, \"node_modules\", pkg, \"package.json\")\n );\n if (typeof pkgJson.size === \"number\") {\n return `${(pkgJson.size / 1024).toFixed(2)} KB`;\n }\n } catch {}\n return undefined;\n}\n","import { Project, SyntaxKind, SourceFile } from \"ts-morph\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport pc from \"picocolors\";\nimport { AnalyzerOptions, UsageReport } from \"./types\";\n\nexport * from \"./types\";\n\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) size += getFolderSize(filePath);\n else size += stats.size;\n }\n } catch {}\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) return formatBytes(getFolderSize(pkgPath));\n return \"N/A\";\n}\n\nexport async function analyzeProject(\n options: AnalyzerOptions\n): Promise<UsageReport> {\n const {\n rootPath,\n includeSizes = true,\n analyzeExports = true,\n silent = false\n } = options;\n\n if (!silent) {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n }\n\n // 1️⃣ Collect all JS/TS files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\",\n \"**/*.d.ts\"\n ],\n absolute: true\n });\n\n if (!silent) {\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n }\n\n const project = new Project({ skipAddingFilesFromTsConfig: true });\n const tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n if (fs.existsSync(tsConfigPath)) {\n project.addSourceFilesFromTsConfig(tsConfigPath);\n }\n files.forEach((f) => {\n try {\n project.addSourceFileAtPath(f);\n } catch {\n console.warn(pc.yellow(`Skipping ${f}`));\n }\n });\n\n // 2️⃣ Track package usage and file exports\n const packageUsage: Record<string, number> = {};\n const fileExports: Record<string, { named: Set<string>; default: boolean }> =\n {};\n const usedExports: Record<string, Set<string>> = {};\n const sourceFiles: Record<string, SourceFile> = {};\n\n for (const sourceFile of project.getSourceFiles()) {\n const relativePath = path\n .relative(rootPath, sourceFile.getFilePath())\n .replace(/\\\\/g, \"/\");\n sourceFiles[relativePath] = sourceFile;\n\n const exportsMap = sourceFile.getExportedDeclarations();\n const namedExports = new Set<string>();\n exportsMap.forEach((decls, name) => {\n if (name !== \"default\") namedExports.add(name);\n });\n const hasDefault = exportsMap.has(\"default\");\n\n fileExports[relativePath] = { named: namedExports, default: hasDefault };\n usedExports[relativePath] = new Set();\n\n // Track package imports\n const imports = sourceFile.getImportDeclarations();\n imports.forEach((imp) => {\n const mod = imp.getModuleSpecifierValue();\n if (!mod.startsWith(\".\") && !mod.startsWith(\"/\")) {\n const pkg = mod.startsWith(\"@\")\n ? mod.split(\"/\").slice(0, 2).join(\"/\")\n : mod.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n });\n\n // Track require()\n const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);\n calls.forEach((call) => {\n if (call.getExpression().getText() === \"require\") {\n const args = call.getArguments();\n if (args.length && args[0].getKind() === SyntaxKind.StringLiteral) {\n const mod = args[0].getText().replace(/['\"`]/g, \"\");\n if (!mod.startsWith(\".\") && !mod.startsWith(\"/\")) {\n const pkg = mod.startsWith(\"@\")\n ? mod.split(\"/\").slice(0, 2).join(\"/\")\n : mod.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n });\n }\n\n // 3️⃣ Track used exports across files\n if (analyzeExports) {\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n const relativePath = path\n .relative(rootPath, sourceFile.getFilePath())\n .replace(/\\\\/g, \"/\");\n\n imports.forEach((imp) => {\n const mod = imp.getModuleSpecifierValue();\n if (mod.startsWith(\".\") || mod.startsWith(\"/\")) {\n const sourceDir = path.dirname(sourceFile.getFilePath());\n let resolvedPath = path.resolve(sourceDir, mod);\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n let foundFile: string | undefined;\n\n for (const ext of extensions) {\n const tryPath = path\n .relative(rootPath, resolvedPath + ext)\n .replace(/\\\\/g, \"/\");\n if (fileExports[tryPath]) {\n foundFile = tryPath;\n break;\n }\n }\n\n if (foundFile) {\n imp\n .getNamedImports()\n .forEach((ni) => usedExports[foundFile].add(ni.getName()));\n if (imp.getDefaultImport()) usedExports[foundFile].add(\"default\");\n }\n }\n });\n }\n }\n\n // 4️⃣ Determine unused files and exports\n const unusedFiles = Object.entries(fileExports)\n .filter(([file, exports]) => {\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"App.tsx\")\n )\n return false;\n const used = usedExports[file];\n if (exports.named.size === 0 && !exports.default) return false;\n return used.size === 0;\n })\n .map(([file]) => file);\n\n const unusedExports: Record<string, string[]> = {};\n if (analyzeExports) {\n Object.entries(fileExports).forEach(([file, exports]) => {\n const used = usedExports[file];\n const unused: string[] = [];\n if (exports.default && !used.has(\"default\")) unused.push(\"default\");\n exports.named.forEach((name) => {\n if (!used.has(name)) unused.push(name);\n });\n if (unused.length) unusedExports[file] = unused;\n });\n }\n\n // 5️⃣ Build package report\n const packages: Record<string, { count: number; size: string }> = {};\n Object.entries(packageUsage).forEach(([pkg, count]) => {\n packages[pkg] = {\n count,\n size: includeSizes ? getPackageSize(rootPath, pkg) : \"—\"\n };\n });\n\n // 6️⃣ Run depcheck\n let unusedDependencies: string[] = [];\n try {\n const depcheck = require(\"depcheck\");\n // We can't easily wait for this if not async, but analyzeProject is async.\n const depcheckResult = await depcheck(rootPath, {\n ignoreMatches: [\"react-prune\", ...((options as any).ignoreMatches || [])],\n skipMissing: true\n });\n unusedDependencies = depcheckResult.dependencies;\n } catch (e) {\n if (!silent) console.warn(pc.yellow(\"Failed to run depcheck.\"));\n }\n\n return {\n packages,\n unusedFiles,\n unusedExports,\n usedExports,\n unusedDependencies,\n sourceFiles\n };\n}\n\nexport { getPackageSize };\n","import fs from \"fs\";\n\nexport function getFileSize(filePath: string): string {\n try {\n const stats = fs.statSync(filePath);\n const bytes = stats.size;\n if (bytes < 1024) return `${bytes} B`;\n return `${(bytes / 1024).toFixed(2)} KB`;\n } catch {\n return \"N/A\";\n }\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport Table from \"cli-table3\";\nimport boxen from \"boxen\";\nimport path from \"path\";\nimport { analyzeProject, UsageReport } from \"./analyzer\";\nimport { getFileSize } from \"./analyzer/file-size\";\nimport { SyntaxKind, Identifier } from \"ts-morph\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\"Analyze React/Next/Vite/React Native projects\")\n .version(require(\"../package.json\").version)\n .option(\"--no-size\", \"Skip package size calculation\")\n .option(\"--no-exports\", \"Skip export usage analysis\")\n .option(\"--limit <n>\", \"Limit output rows per table\", \"50\");\n\nprogram\n .command(\"analyze\")\n .description(\"Run full project analysis\")\n .option(\"--no-size\", \"Skip package size calculation\")\n .option(\"--no-exports\", \"Skip export usage analysis\")\n .option(\"--limit <n>\", \"Limit output rows per table\", \"50\")\n .option(\"--json\", \"Output results as JSON\")\n .action(async (opts) => {\n const rootPath = process.cwd();\n const limit = Number(opts.limit);\n\n const report: UsageReport = await analyzeProject({\n rootPath,\n includeSizes: opts.size,\n analyzeExports: opts.exports,\n silent: opts.json\n });\n\n if (opts.json) {\n // 1. Remove circular references (sourceFiles)\n // 2. Convert Sets to Arrays for valid JSON\n const { sourceFiles, usedExports, ...rest } = report;\n\n const sanitizedUsedExports: Record<string, string[]> = {};\n if (usedExports) {\n for (const [key, value] of Object.entries(usedExports)) {\n sanitizedUsedExports[key] = Array.from(value);\n }\n }\n\n console.log(\n JSON.stringify(\n {\n ...rest,\n usedExports: sanitizedUsedExports\n },\n null,\n 2\n )\n );\n return;\n }\n\n // Packages\n const packageTable = new Table({\n head: [pc.cyan(\"Package\"), \"Count\", \"Size\"],\n colWidths: [40, 10, 15]\n });\n Object.entries(report.packages)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, limit)\n .forEach(([pkg, data]) =>\n packageTable.push([pkg, data.count, data.size])\n );\n console.log(\n boxen(pc.bold(\"📦 Package Usage\"), {\n padding: 1,\n borderColor: \"green\",\n borderStyle: \"round\"\n })\n );\n console.log(packageTable.toString());\n\n // Unused Files\n if (report.unusedFiles.length) {\n const table = new Table({\n head: [pc.yellow(\"Unused Files\")],\n colWidths: [80]\n });\n report.unusedFiles.slice(0, limit).forEach((f) => table.push([f]));\n console.log(\n boxen(pc.bold(`⚠️ Unused Files (${report.unusedFiles.length})`), {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n })\n );\n console.log(table.toString());\n }\n\n // Unused Exports\n if (opts.exports) {\n const entries = Object.entries(report.unusedExports);\n if (entries.length) {\n const table = new Table({\n head: [\"File\", \"Unused Exports\"],\n colWidths: [50, 40],\n wordWrap: true\n });\n entries\n .slice(0, limit)\n .forEach(([file, exports]) => table.push([file, exports.join(\", \")]));\n console.log(\n boxen(pc.bold(`⚠️ Unused Exports`), {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n })\n );\n console.log(table.toString());\n }\n }\n\n // Unused Dependencies\n if (report.unusedDependencies && report.unusedDependencies.length) {\n const table = new Table({\n head: [pc.yellow(\"Unused Dependencies\")],\n colWidths: [80]\n });\n report.unusedDependencies.slice(0, limit).forEach((d) => table.push([d]));\n console.log(\n boxen(\n pc.bold(\n `⚠️ Unused Dependencies (${report.unusedDependencies.length})`\n ),\n {\n padding: 1,\n borderColor: \"yellow\",\n borderStyle: \"round\"\n }\n )\n );\n console.log(table.toString());\n }\n });\n\nprogram\n .command(\"size <packageName>\")\n .description(\"Check the size of a specific npm package in node_modules\")\n .action(async (packageName) => {\n const rootPath = process.cwd();\n\n // Reuse helper from analyzer\n const { getPackageSize } = await import(\"./analyzer/package-size\");\n\n const size = getPackageSize(rootPath, packageName);\n\n if (size === \"N/A\") {\n console.log(\n pc.yellow(`Package '${packageName}' not found in node_modules.`)\n );\n } else {\n console.log(pc.green(`📦 ${packageName} size: ${size}`));\n }\n });\n// --- New find command with line numbers\nprogram\n .command(\"find <exportName>\")\n .description(\n \"Find usage count and references (with line numbers) for a component/function/export\"\n )\n .action(async (exportName) => {\n const rootPath = process.cwd();\n const report: UsageReport = await analyzeProject({\n rootPath,\n analyzeExports: true,\n includeSizes: false\n });\n\n const usageDetails: { file: string; line: number }[] = [];\n\n const usedExports = report.usedExports || {};\n\n for (const [file, usedSet] of Object.entries(usedExports)) {\n const sourceFile = report.sourceFiles?.[file];\n if (!sourceFile) continue;\n\n // Only process if this file actually uses the export\n if (!usedSet.has(exportName)) continue;\n\n // Traverse identifiers in the file\n const identifiers = sourceFile.getDescendantsOfKind(\n SyntaxKind.Identifier\n );\n\n identifiers.forEach((id: Identifier) => {\n // Check if identifier matches the export name\n if (id.getText() === exportName) {\n // Make sure this usage is actually an import/reference, not a declaration\n const parentKind = id.getParentOrThrow().getKindName();\n\n if (\n parentKind.includes(\"Import\") || // ImportSpecifier, ImportClause, etc\n parentKind.includes(\"PropertyAccess\") || // obj.exportName\n parentKind.includes(\"Identifier\") // usage in code\n ) {\n usageDetails.push({ file, line: id.getStartLineNumber() });\n }\n }\n });\n }\n\n if (usageDetails.length) {\n console.log(\n pc.green(`'${exportName}' is used ${usageDetails.length} time(s):`)\n );\n usageDetails.forEach((d) => {\n const size = getFileSize(path.join(rootPath, d.file));\n console.log(` - ${d.file}:${d.line} (${size})`);\n });\n } else {\n console.log(pc.yellow(`'${exportName}' is not used anywhere.`));\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-prune",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
|
-
"react-prune": "./
|
|
6
|
+
"react-prune": "./bin/react-prune"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsup src/cli.ts --format cjs --dts",
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
"author": "Daniel Arikawe",
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"files": [
|
|
37
|
+
"bin",
|
|
38
|
+
"scripts",
|
|
37
39
|
"dist"
|
|
38
40
|
],
|
|
39
41
|
"repository": {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/core/check_deps.sh
|
|
4
|
+
|
|
5
|
+
echo "📦 Checking for unused dependencies..."
|
|
6
|
+
|
|
7
|
+
if ! command -v depcheck &> /dev/null; then
|
|
8
|
+
DEPCHECK_CMD="npx depcheck"
|
|
9
|
+
else
|
|
10
|
+
DEPCHECK_CMD="depcheck"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Run depcheck outputting JSON for parsing if needed, but for now human readable is fine for CLI
|
|
14
|
+
# Using --skip-missing to avoid erroring on missing peers etc.
|
|
15
|
+
$DEPCHECK_CMD --skip-missing=true --ignores="react-prune" .
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/core/check_package_sizes.sh
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="${1:-.}"
|
|
6
|
+
|
|
7
|
+
echo "⚖️ Checking package sizes (top 15 heaviest)..."
|
|
8
|
+
|
|
9
|
+
if [ ! -d "$ROOT_DIR/node_modules" ]; then
|
|
10
|
+
echo "⚠️ node_modules not found. Cannot calculate sizes. Please install dependencies."
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# We use du (disk usage) to get sizes of folders in node_modules.
|
|
15
|
+
# We explicitly check folders that match dependencies in package.json to be cleaner,
|
|
16
|
+
# OR we just check immediate children of node_modules to find the biggest ones (simplest and most useful for bloat).
|
|
17
|
+
|
|
18
|
+
# Approach: List usage of all folders in node_modules, sort by size, take top 15.
|
|
19
|
+
# du -h -d 1 node_modules | sort -h -r | head -n 15
|
|
20
|
+
|
|
21
|
+
# Note: -d 1 is for depth 1. -h is human readable. sort -h sorts human readable (e.g. 1G > 500M).
|
|
22
|
+
# Mac stats might differ slightly but modern du/sort often support -h.
|
|
23
|
+
# If separate sort is needed we can try standard bytes.
|
|
24
|
+
|
|
25
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
26
|
+
# MacOS 'du' uses -d for depth. 'sort' supports -h.
|
|
27
|
+
du -h -d 1 "$ROOT_DIR/node_modules" 2>/dev/null | sort -h -r | head -n 15 | awk '{print $1, $2}' | sed "s|$ROOT_DIR/node_modules/||"
|
|
28
|
+
else
|
|
29
|
+
# Linux (GNU)
|
|
30
|
+
du -h --max-depth=1 "$ROOT_DIR/node_modules" 2>/dev/null | sort -h -r | head -n 15 | awk '{print $1, $2}' | sed "s|$ROOT_DIR/node_modules/||"
|
|
31
|
+
fi
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/core/check_unused_exports.sh
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="${1:-.}"
|
|
6
|
+
FIND_SCRIPT="${0%/*}/find_files.sh"
|
|
7
|
+
|
|
8
|
+
echo "🔎 Checking for unused exports (heuristic)..."
|
|
9
|
+
|
|
10
|
+
FILES=$($FIND_SCRIPT "$ROOT_DIR")
|
|
11
|
+
UNUSED_COUNT=0
|
|
12
|
+
|
|
13
|
+
for file in $FILES; do
|
|
14
|
+
# 1. Extract exports
|
|
15
|
+
# Matches: export const|function|class|let|var|type|interface Name ...
|
|
16
|
+
# Exclude 'default' from regex capture immediately
|
|
17
|
+
EXPORTS=$(grep -E "^export (const|function|class|let|var|type|interface) " "$file" | sed -E 's/^export (const|function|class|let|var|type|interface) ([a-zA-Z0-9_]+).*/\2/')
|
|
18
|
+
|
|
19
|
+
for exp in $EXPORTS; do
|
|
20
|
+
# Ignore common false positives
|
|
21
|
+
if [[ "$exp" == "default" ]] || [[ "$exp" == "metadata" ]] || [[ "$exp" == "generateMetadata" ]] || [[ "$exp" == "viewport" ]]; then
|
|
22
|
+
continue
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Ignore Next.js API route handlers
|
|
26
|
+
if [[ "$exp" == "GET" ]] || [[ "$exp" == "POST" ]] || [[ "$exp" == "PUT" ]] || [[ "$exp" == "DELETE" ]] || [[ "$exp" == "PATCH" ]] || [[ "$exp" == "HEAD" ]] || [[ "$exp" == "OPTIONS" ]]; then
|
|
27
|
+
continue
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
usage=$(grep -r --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
|
|
31
|
+
--exclude-dir="node_modules" --exclude-dir=".git" --exclude-dir="dist" --exclude-dir="build" \
|
|
32
|
+
"\b$exp\b" "$ROOT_DIR" | grep -v "$file" | wc -l)
|
|
33
|
+
|
|
34
|
+
if [ "$usage" -eq 0 ]; then
|
|
35
|
+
echo "⚠️ Unused export: $exp in $file"
|
|
36
|
+
UNUSED_COUNT=$((UNUSED_COUNT + 1))
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
if [ "$UNUSED_COUNT" -eq 0 ]; then
|
|
42
|
+
echo "✅ No unused exports found!"
|
|
43
|
+
else
|
|
44
|
+
echo "found $UNUSED_COUNT unused exports."
|
|
45
|
+
fi
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/core/check_unused_files.sh
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="${1:-.}"
|
|
6
|
+
FIND_SCRIPT="${0%/*}/find_files.sh"
|
|
7
|
+
|
|
8
|
+
echo "🔍 Checking for unused files..."
|
|
9
|
+
|
|
10
|
+
FILES=$($FIND_SCRIPT "$ROOT_DIR")
|
|
11
|
+
UNUSED_COUNT=0
|
|
12
|
+
|
|
13
|
+
for file in $FILES; do
|
|
14
|
+
BASENAME=$(basename "$file")
|
|
15
|
+
|
|
16
|
+
# Skip common config files
|
|
17
|
+
if [[ "$BASENAME" =~ \.config\.(js|ts|mjs|cjs)$ ]]; then
|
|
18
|
+
continue
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Skip Next.js App Router conventions
|
|
22
|
+
if [[ "$file" == *"app/"* ]] || [[ "$file" == *"pages/"* ]]; then
|
|
23
|
+
if [[ "$BASENAME" =~ ^(page|layout|loading|error|not-found|template|route|middleware|default|global-error)\.(tsx|ts|js|jsx)$ ]]; then
|
|
24
|
+
continue
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Skip middleware at root
|
|
29
|
+
if [[ "$BASENAME" == "middleware.ts" ]] || [[ "$BASENAME" == "middleware.js" ]]; then
|
|
30
|
+
continue
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
NAME_WITHOUT_EXT="${BASENAME%.*}"
|
|
34
|
+
|
|
35
|
+
# Grep for the filename (without extension) in all files
|
|
36
|
+
# We exclude the file itself from the search
|
|
37
|
+
USAGE_COUNT=$(grep -r --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
|
|
38
|
+
--exclude-dir="node_modules" --exclude-dir=".git" --exclude-dir="dist" --exclude-dir="build" \
|
|
39
|
+
"$NAME_WITHOUT_EXT" "$ROOT_DIR" | grep -v "$file" | wc -l)
|
|
40
|
+
|
|
41
|
+
if [ "$USAGE_COUNT" -eq 0 ]; then
|
|
42
|
+
# Heuristic: if file is index.ts, check if parent folder is imported
|
|
43
|
+
if [[ "$BASENAME" == "index.ts" ]] || [[ "$BASENAME" == "index.tsx" ]] || [[ "$BASENAME" == "index.js" ]]; then
|
|
44
|
+
PARENT_DIR=$(basename "$(dirname "$file")")
|
|
45
|
+
USAGE_COUNT=$(grep -r --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
|
|
46
|
+
--exclude-dir="node_modules" --exclude-dir=".git" --exclude-dir="dist" --exclude-dir="build" \
|
|
47
|
+
"$PARENT_DIR" "$ROOT_DIR" | grep -v "$file" | wc -l)
|
|
48
|
+
|
|
49
|
+
if [ "$USAGE_COUNT" -eq 0 ]; then
|
|
50
|
+
echo "⚠️ Unused file: $file"
|
|
51
|
+
UNUSED_COUNT=$((UNUSED_COUNT + 1))
|
|
52
|
+
fi
|
|
53
|
+
else
|
|
54
|
+
echo "⚠️ Unused file: $file"
|
|
55
|
+
UNUSED_COUNT=$((UNUSED_COUNT + 1))
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
if [ "$UNUSED_COUNT" -eq 0 ]; then
|
|
61
|
+
echo "✅ No unused files found!"
|
|
62
|
+
else
|
|
63
|
+
echo "found $UNUSED_COUNT unused files."
|
|
64
|
+
fi
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/core/find_files.sh
|
|
4
|
+
|
|
5
|
+
# Finds all relevant source files in the project, excluding ignored directories.
|
|
6
|
+
# Arguments:
|
|
7
|
+
# $1: Root directory (optional, defaults to current dir)
|
|
8
|
+
|
|
9
|
+
ROOT_DIR="${1:-.}"
|
|
10
|
+
|
|
11
|
+
find "$ROOT_DIR" \
|
|
12
|
+
-type f \
|
|
13
|
+
\( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
|
|
14
|
+
-not -path "*/node_modules/*" \
|
|
15
|
+
-not -path "*/dist/*" \
|
|
16
|
+
-not -path "*/build/*" \
|
|
17
|
+
-not -path "*/.next/*" \
|
|
18
|
+
-not -path "*/coverage/*" \
|
|
19
|
+
-not -path "*/.git/*" \
|
|
20
|
+
-not -name "*.d.ts" \
|
|
21
|
+
-not -name "*.config.js" \
|
|
22
|
+
-not -name "*.config.ts"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/core/find_usage.sh
|
|
4
|
+
|
|
5
|
+
TERM="$1"
|
|
6
|
+
ROOT_DIR="${2:-.}"
|
|
7
|
+
FIND_SCRIPT="${0%/*}/find_files.sh"
|
|
8
|
+
|
|
9
|
+
if [ -z "$TERM" ]; then
|
|
10
|
+
echo "Usage: react-prune find <ComponentOrFunction> [RootDir]"
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
echo "🔎 Searching for usage of '${TERM}'..."
|
|
15
|
+
|
|
16
|
+
FILES=$($FIND_SCRIPT "$ROOT_DIR")
|
|
17
|
+
TOTAL_COUNT=0
|
|
18
|
+
|
|
19
|
+
# Temporary file to store results
|
|
20
|
+
RESULTS_FILE=$(mktemp)
|
|
21
|
+
|
|
22
|
+
for file in $FILES; do
|
|
23
|
+
# Grep for the term, print line number (-n).
|
|
24
|
+
# Exclude the definition if possible? Hard in bash without better context,
|
|
25
|
+
# but we can try to exclude "export const searchTerm =" etc if we wanted,
|
|
26
|
+
# but simplest is just showing all occurrences and letting user judge.
|
|
27
|
+
|
|
28
|
+
# We use grep with line numbers.
|
|
29
|
+
MATCHES=$(grep -n "\b${TERM}\b" "$file")
|
|
30
|
+
|
|
31
|
+
if [ ! -z "$MATCHES" ]; then
|
|
32
|
+
# Check file size
|
|
33
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
34
|
+
FILE_SIZE=$(stat -f%z "$file")
|
|
35
|
+
else
|
|
36
|
+
FILE_SIZE=$(stat -c%s "$file")
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Format size human readable (basic logic)
|
|
40
|
+
if [ "$FILE_SIZE" -lt 1024 ]; then
|
|
41
|
+
SIZE_STR="${FILE_SIZE} B"
|
|
42
|
+
else
|
|
43
|
+
SIZE_STR="$((FILE_SIZE / 1024)) KB"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Process each match
|
|
47
|
+
while IFS= read -r line; do
|
|
48
|
+
# Use | as delimiter to avoid conflict with / in paths
|
|
49
|
+
echo "$line" | sed "s|^|${file}: |" | sed "s|$| [${SIZE_STR}]|" >> "$RESULTS_FILE"
|
|
50
|
+
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
51
|
+
done <<< "$MATCHES"
|
|
52
|
+
fi
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
if [ "$TOTAL_COUNT" -eq 0 ]; then
|
|
56
|
+
echo "❌ No usage found for '${TERM}'."
|
|
57
|
+
else
|
|
58
|
+
echo "✅ Found ${TOTAL_COUNT} occurrences:"
|
|
59
|
+
echo ""
|
|
60
|
+
cat "$RESULTS_FILE"
|
|
61
|
+
echo ""
|
|
62
|
+
echo "Total: ${TOTAL_COUNT} times"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
rm "$RESULTS_FILE"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# scripts/prune-check.sh
|
|
4
|
+
|
|
5
|
+
# Colors
|
|
6
|
+
GREEN='\033[0;32m'
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
NC='\033[0m' # No Color
|
|
10
|
+
|
|
11
|
+
echo -e "${YELLOW}🔍 Running Project Health Check...${NC}\n"
|
|
12
|
+
|
|
13
|
+
# 1. Run Depcheck
|
|
14
|
+
echo -e "${YELLOW}📦 Checking for unused dependencies (depcheck)...${NC}"
|
|
15
|
+
if ! command -v depcheck &> /dev/null; then
|
|
16
|
+
echo "depcheck could not be found, running via npx..."
|
|
17
|
+
DEPCHECK_CMD="npx depcheck"
|
|
18
|
+
else
|
|
19
|
+
DEPCHECK_CMD="depcheck"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Run depcheck and capture output
|
|
23
|
+
# We use --json to parse, or just run it plainly for the user to see
|
|
24
|
+
$DEPCHECK_CMD --skip-missing=true --ignores="react-prune" .
|
|
25
|
+
|
|
26
|
+
DEPCHECK_EXIT_CODE=$?
|
|
27
|
+
|
|
28
|
+
if [ $DEPCHECK_EXIT_CODE -eq 0 ]; then
|
|
29
|
+
echo -e "${GREEN}✅ Dependencies look clean!${NC}\n"
|
|
30
|
+
else
|
|
31
|
+
echo -e "${RED}❌ Unused dependencies found.${NC}\n"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# 2. Run React Prune
|
|
35
|
+
echo -e "${YELLOW}✂️ Running React Prune Analysis...${NC}"
|
|
36
|
+
|
|
37
|
+
# Assuming we are running this from the root of the repo where react-prune is being developed
|
|
38
|
+
# We can use the local built CLI or npx react-prune if installed
|
|
39
|
+
# For this dev context, we'll try to use the local build
|
|
40
|
+
if [ -f "./dist/cli.js" ]; then
|
|
41
|
+
echo "Using local build..."
|
|
42
|
+
node ./dist/cli.js analyze
|
|
43
|
+
else
|
|
44
|
+
echo "Local build not found, running build..."
|
|
45
|
+
npm run build --silent
|
|
46
|
+
node ./dist/cli.js analyze
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
PRUNE_EXIT_CODE=$?
|
|
50
|
+
|
|
51
|
+
echo -e "\n${YELLOW}📊 Summary:${NC}"
|
|
52
|
+
if [ $DEPCHECK_EXIT_CODE -eq 0 ] && [ $PRUNE_EXIT_CODE -eq 0 ]; then
|
|
53
|
+
echo -e "${GREEN}🎉 Project is clean!${NC}"
|
|
54
|
+
exit 0
|
|
55
|
+
else
|
|
56
|
+
echo -e "${RED}⚠️ Issues detected. Please review above output.${NC}"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|