react-prune 1.1.0 โ 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- **๐ฆ Package Analysis**: Scans your codebase to count how many times each npm package is imported.
|
|
14
14
|
- **โ๏ธ Size Estimation**: Estimates the size of your used packages directly from `node_modules` to help you identify heavy dependencies.
|
|
15
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.
|
|
16
17
|
- **โ๏ธ Framework Agnostic**: Works seamlessly with React, Next.js (Pages & App Router), and React Native.
|
|
17
18
|
- **๐ Visual Dashboard**: Provides a beautiful, easy-to-read command-line dashboard using ASCII tables.
|
|
18
19
|
|
|
@@ -88,6 +89,21 @@ The tool will scan your project (ignoring `node_modules`, `dist`, `.next`, etc.)
|
|
|
88
89
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
89
90
|
โ src/utils/deprecated-helper.ts โ
|
|
90
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
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
91
107
|
```
|
|
92
108
|
|
|
93
109
|
## โ๏ธ How it Works
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';var commander=require('commander'),
|
|
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
|
|
3
3
|
//# 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","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","program","Command","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","error"],"mappings":"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","file":"cli.js","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async () => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const report = await analyzeProject(process.cwd());\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n pc.cyan(\"Package Name\"),\n pc.cyan(\"Usage Count\"),\n pc.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(pc.bold(\"๐ฆ Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n pc.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n const unusedTable = new Table({\n head: [pc.yellow(\"File Path\")],\n colWidths: [80]\n });\n\n console.log(\n boxen(\n pc.bold(\n `โ ๏ธ Potential Unused Files (${report.unusedFiles.length})`\n ),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n report.unusedFiles.forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n } else {\n console.log(\n boxen(pc.bold(\"โ
No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n } catch (error) {\n console.error(pc.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzer.ts","../src/cli.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","tsConfigPath","projectConfig","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","resolvedLocalFile","resolvedSourceFile","possibleMatches","match","normalizedMatch","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","unusedExports","relativePath","exportedDeclarations","fileUnusedExports","name","declarations","isUsed","decl","refs","ref","program","Command","directory","targetDir","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","unusedExportsEntries","totalUnusedExports","acc","exports","unusedExportsTable","error"],"mappings":"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","file":"cli.js","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n unusedExports: Record<string, string[]>;\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const tsConfigPath = path.join(rootPath, \"tsconfig.json\");\n const projectConfig: any = {\n skipAddingFilesFromTsConfig: true\n };\n\n if (fs.existsSync(tsConfigPath)) {\n projectConfig.tsConfigFilePath = tsConfigPath;\n }\n\n const project = new Project(projectConfig);\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Check if it's a path alias or non-relative local import\n let resolvedLocalFile: string | undefined;\n\n // Try ts-morph resolution first (if tsconfig loaded)\n try {\n const resolvedSourceFile = imp.getModuleSpecifierSourceFile();\n if (resolvedSourceFile) {\n resolvedLocalFile = resolvedSourceFile.getFilePath();\n }\n } catch (e) {}\n\n // Fallback: Check if the module specifier matches a known local file\n // relative to baseUrl (src) or just fuzzy match\n if (!resolvedLocalFile) {\n const possibleMatches = files.filter((f) =>\n f.includes(moduleSpecifier)\n );\n if (possibleMatches.length > 0) {\n for (const match of possibleMatches) {\n const normalizedMatch = match.replace(/\\\\/g, \"/\");\n if (\n normalizedMatch.endsWith(`${moduleSpecifier}.tsx`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}.ts`) ||\n normalizedMatch.endsWith(`${moduleSpecifier}/index.tsx`)\n ) {\n resolvedLocalFile = match;\n break;\n }\n }\n }\n }\n\n if (resolvedLocalFile) {\n const relativeTry = path.relative(rootPath, resolvedLocalFile);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n continue; // Skip package counting\n }\n }\n\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n const unusedExports: Record<string, string[]> = {};\n\n // 5. Check for unused exports\n for (const sourceFile of project.getSourceFiles()) {\n const filePath = sourceFile.getFilePath();\n const relativePath = path.relative(rootPath, filePath);\n\n // Skip known entry points/framework files from export analysis\n if (\n filePath.includes(\"pages/\") ||\n filePath.includes(\"app/\") ||\n filePath.endsWith(\"main.tsx\") ||\n filePath.endsWith(\"index.tsx\") ||\n filePath.endsWith(\"index.js\") ||\n filePath.endsWith(\"App.tsx\") ||\n filePath.endsWith(\"App.js\") ||\n unused.includes(filePath) // Skip files already marked as unused\n ) {\n continue;\n }\n\n const exportedDeclarations = sourceFile.getExportedDeclarations();\n const fileUnusedExports: string[] = [];\n\n for (const [name, declarations] of exportedDeclarations) {\n let isUsed = false;\n\n // We consider it used if it has references in OTHER files\n // getReferencesAsNodes() is expensive, so we might need a cheaper check?\n // But for accuracy we need references.\n\n for (const decl of declarations) {\n if (\n SyntaxKind.VariableDeclaration === decl.getKind() ||\n SyntaxKind.FunctionDeclaration === decl.getKind() ||\n SyntaxKind.ClassDeclaration === decl.getKind() ||\n SyntaxKind.InterfaceDeclaration === decl.getKind() ||\n SyntaxKind.TypeAliasDeclaration === decl.getKind() ||\n SyntaxKind.EnumDeclaration === decl.getKind()\n ) {\n try {\n // @ts-ignore\n const refs = decl.findReferencesAsNodes();\n for (const ref of refs) {\n const refSourceFile = ref.getSourceFile();\n if (refSourceFile.getFilePath() !== filePath) {\n isUsed = true;\n break;\n }\n }\n } catch (e) {\n // If error, assume used to be safe\n isUsed = true;\n }\n } else {\n // For other kinds (like ExportSpecifier), we might need different handling\n // or assume used.\n // default export is usually a FunctionDeclaration or ClassDeclaration,\n // but could be an expression.\n isUsed = true; // Skip complex cases for now\n }\n if (isUsed) break;\n }\n\n if (!isUsed) {\n fileUnusedExports.push(name);\n }\n }\n\n if (fileUnusedExports.length > 0) {\n unusedExports[relativePath] = fileUnusedExports;\n }\n }\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused,\n unusedExports\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport path from \"path\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze [directory]\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async (directory) => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const targetDir = directory ? path.resolve(directory) : process.cwd();\n const report = await analyzeProject(targetDir);\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n pc.cyan(\"Package Name\"),\n pc.cyan(\"Usage Count\"),\n pc.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(pc.bold(\"๐ฆ Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n pc.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n const unusedTable = new Table({\n head: [pc.yellow(\"File Path\")],\n colWidths: [80]\n });\n\n console.log(\n boxen(\n pc.bold(\n `โ ๏ธ Potential Unused Files (${report.unusedFiles.length})`\n ),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n report.unusedFiles.forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n } else {\n console.log(\n boxen(pc.bold(\"โ
No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n\n // Unused Exports\n const unusedExportsEntries = Object.entries(report.unusedExports);\n if (unusedExportsEntries.length > 0) {\n const totalUnusedExports = unusedExportsEntries.reduce(\n (acc, [, exports]) => acc + exports.length,\n 0\n );\n const unusedExportsTable = new Table({\n head: [pc.yellow(\"File\"), pc.yellow(\"Unused Exports\")],\n colWidths: [40, 40],\n wordWrap: true\n });\n\n console.log(\n boxen(\n pc.bold(`โ ๏ธ Potential Unused Exports (${totalUnusedExports})`),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n unusedExportsEntries.slice(0, 50).forEach(([file, exports]) => {\n unusedExportsTable.push([file, exports.join(\", \")]);\n });\n console.log(unusedExportsTable.toString());\n\n if (unusedExportsEntries.length > 50) {\n console.log(\n pc.gray(\n `...and ${unusedExportsEntries.length - 50} more files with unused exports.`\n )\n );\n }\n }\n } catch (error) {\n console.error(pc.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command}from'commander';import
|
|
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
3
|
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.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","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused","program","Command","report","packageTable","Table","sortedPackages","a","b","data","boxen","unusedTable","error"],"mappings":"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","file":"cli.mjs","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { analyzeProject } from \"./analyzer\";\n// @ts-ignore\nimport Table from \"cli-table3\";\n// @ts-ignore\nimport boxen from \"boxen\";\n\nconst program = new Command();\n\nprogram\n .name(\"react-prune\")\n .description(\n \"Monitor usage of packages and component imports across your React/Next.js/React Native app\"\n )\n .version(\"1.0.0\");\n\nprogram\n .command(\"analyze\")\n .description(\"Analyze the current project for package and component usage\")\n .action(async () => {\n console.log(pc.blue(\"Starting analysis...\"));\n try {\n const report = await analyzeProject(process.cwd());\n\n // Package Usage Table\n const packageTable = new Table({\n head: [\n pc.cyan(\"Package Name\"),\n pc.cyan(\"Usage Count\"),\n pc.cyan(\"Est. Size\")\n ],\n colWidths: [40, 15, 15]\n });\n\n const sortedPackages = Object.entries(report.packages).sort(\n (a, b) => b[1].count - a[1].count\n );\n\n sortedPackages.slice(0, 50).forEach(([pkg, data]) => {\n packageTable.push([pkg, data.count, data.size]);\n });\n\n console.log(\n boxen(pc.bold(\"๐ฆ Package Usage Report\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n console.log(packageTable.toString());\n if (sortedPackages.length > 50) {\n console.log(\n pc.gray(`...and ${sortedPackages.length - 50} more packages.`)\n );\n }\n\n // Unused Files\n if (report.unusedFiles.length > 0) {\n const unusedTable = new Table({\n head: [pc.yellow(\"File Path\")],\n colWidths: [80]\n });\n\n console.log(\n boxen(\n pc.bold(\n `โ ๏ธ Potential Unused Files (${report.unusedFiles.length})`\n ),\n {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\"\n }\n )\n );\n\n report.unusedFiles.forEach((file) => unusedTable.push([file]));\n console.log(unusedTable.toString());\n } else {\n console.log(\n boxen(pc.bold(\"โ
No unused files detected!\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"green\"\n })\n );\n }\n } catch (error) {\n console.error(pc.red(\"Analysis failed:\"), error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
|
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
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var tsMorph=require('ts-morph'),
|
|
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
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyzer.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused"],"mappings":"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","file":"index.js","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"]}
|
|
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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {Project,SyntaxKind}from'ts-morph';import
|
|
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
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyzer.ts"],"names":["getFolderSize","dirPath","size","files","fs","file","filePath","path","stats","formatBytes","bytes","decimals","k","dm","sizes","i","getPackageSize","rootPath","packageName","pkgPath","analyzeProject","pc","glob","project","Project","e","packageUsage","localUsage","f","relative","sourceFile","imports","imp","moduleSpecifier","sourceFilePath","sourceDir","resolvedPath","extensions","ext","tryPath","relativeTry","parts","callExpressions","SyntaxKind","call","args","rawArg","pkg","reportPackages","count","unused"],"mappings":"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","file":"index.mjs","sourcesContent":["import { Project, SyntaxKind } from \"ts-morph\";\nimport pc from \"picocolors\";\nimport glob from \"fast-glob\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nexport interface UsageReport {\n packages: Record<string, { count: number; size: string }>;\n components: Record<string, number>;\n unusedFiles: string[];\n}\n\n// Helper to calculate folder size recursively\nfunction getFolderSize(dirPath: string): number {\n let size = 0;\n try {\n const files = fs.readdirSync(dirPath);\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stats = fs.statSync(filePath);\n if (stats.isDirectory()) {\n size += getFolderSize(filePath);\n } else {\n size += stats.size;\n }\n }\n } catch (e) {\n return 0;\n }\n return size;\n}\n\nfunction formatBytes(bytes: number, decimals = 2) {\n if (bytes === 0) return \"0 Bytes\";\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\nfunction getPackageSize(rootPath: string, packageName: string): string {\n // Try to find module in node_modules\n // Search in local node_modules first, then maybe recursive?\n // For now simple check in root/node_modules\n const pkgPath = path.join(rootPath, \"node_modules\", packageName);\n if (fs.existsSync(pkgPath)) {\n const size = getFolderSize(pkgPath);\n return formatBytes(size);\n }\n return \"N/A\";\n}\n\nexport async function analyzeProject(rootPath: string): Promise<UsageReport> {\n console.log(pc.green(`Analyzing project at ${rootPath}`));\n\n // 1. Find all files\n const files = await glob(\"**/*.{js,jsx,ts,tsx}\", {\n cwd: rootPath,\n ignore: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/coverage/**\",\n \"**/*.config.{js,ts,cjs,mjs}\", // Ignore config files\n \"**/.d.ts\" // Ignore definition files\n ],\n absolute: true\n });\n\n console.log(pc.blue(`Found ${files.length} files to analyze.`));\n\n // 2. Initialize ts-morph project\n const project = new Project({\n skipAddingFilesFromTsConfig: true\n });\n\n // Add files to project\n files.forEach((file) => {\n try {\n project.addSourceFileAtPath(file);\n } catch (e) {\n console.warn(pc.yellow(`Skipping file ${file} due to load error:`), e);\n }\n });\n\n const packageUsage: Record<string, number> = {};\n const localUsage: Record<string, number> = {};\n\n // Initialize local usage with 0 for all files to track unused ones\n files.forEach((f) => {\n // Normalize path to be relative and standard for comparison\n const relative = path.relative(rootPath, f);\n localUsage[relative] = 0;\n });\n\n // 3. Analyze Imports\n for (const sourceFile of project.getSourceFiles()) {\n const imports = sourceFile.getImportDeclarations();\n\n for (const imp of imports) {\n const moduleSpecifier = imp.getModuleSpecifierValue();\n\n if (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n // Local Import\n try {\n // Resolve the import to a file on disk\n const sourceFilePath = sourceFile.getFilePath();\n const sourceDir = path.dirname(sourceFilePath);\n\n const resolvedPath = path.resolve(sourceDir, moduleSpecifier);\n // We need to try extensions\n const extensions = [\n \"\",\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \"/index.ts\",\n \"/index.tsx\",\n \"/index.js\",\n \"/index.jsx\"\n ];\n\n for (const ext of extensions) {\n const tryPath = resolvedPath + ext;\n const relativeTry = path.relative(rootPath, tryPath);\n if (localUsage.hasOwnProperty(relativeTry)) {\n localUsage[relativeTry]++;\n break;\n }\n }\n } catch (e) {\n // ignore resolution errors\n }\n } else {\n // Package Import\n let packageName = moduleSpecifier;\n if (moduleSpecifier.startsWith(\"@\")) {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 2) {\n packageName = `${parts[0]}/${parts[1]}`;\n }\n } else {\n const parts = moduleSpecifier.split(\"/\");\n if (parts.length >= 1) {\n packageName = parts[0];\n }\n }\n\n packageUsage[packageName] = (packageUsage[packageName] || 0) + 1;\n }\n }\n\n // Check for require() calls (CommonJS)\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n for (const call of callExpressions) {\n const expression = call.getExpression();\n if (expression.getText() === \"require\") {\n const args = call.getArguments();\n if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {\n const rawArg = args[0].getText().replace(/['\"`]/g, \"\");\n // Simple duplicate logic for MVP (should refactor)\n if (!rawArg.startsWith(\".\") && !rawArg.startsWith(\"/\")) {\n let pkg = rawArg.startsWith(\"@\")\n ? rawArg.split(\"/\").slice(0, 2).join(\"/\")\n : rawArg.split(\"/\")[0];\n packageUsage[pkg] = (packageUsage[pkg] || 0) + 1;\n }\n }\n }\n }\n }\n\n // 4. Construct Report Data\n const reportPackages: Record<string, { count: number; size: string }> = {};\n\n for (const [pkg, count] of Object.entries(packageUsage)) {\n const size = getPackageSize(rootPath, pkg);\n reportPackages[pkg] = { count, size };\n }\n\n const unused = Object.entries(localUsage)\n .filter(([file, count]) => {\n // Known Entry Points & Framework specifics\n if (\n file.includes(\"pages/\") ||\n file.includes(\"app/\") ||\n file.endsWith(\"main.tsx\") ||\n file.endsWith(\"index.tsx\") ||\n file.endsWith(\"index.js\") ||\n file.endsWith(\"App.tsx\") ||\n file.endsWith(\"App.js\")\n )\n return false;\n\n // Ignore files in the project root (usually configs, scripts, etc.)\n // We check if the relative path contains a separator. If not, it's in the root.\n const relative = path.relative(rootPath, file);\n if (!relative.includes(path.sep)) {\n return false;\n }\n return count === 0;\n })\n .map(([file]) => file);\n\n return {\n packages: reportPackages,\n components: localUsage,\n unusedFiles: unused\n };\n}\n"]}
|
|
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"]}
|