UNPKG

@rayburst/sharity

Version:

Analyze shared package usage across monorepos - calculate symbol sharing percentages, track exclusive imports, and identify unused exports

189 lines 8.2 kB
"use strict"; /** * Main analyzer engine */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SharedPackageAnalyzer = void 0; const path = __importStar(require("path")); const symbol_tracker_1 = require("./symbol-tracker"); const consumer_tracker_1 = require("./consumer-tracker"); const line_counter_1 = require("./line-counter"); const workspace_scanner_1 = require("../utils/workspace-scanner"); const line_counter_2 = require("./line-counter"); /** * Main analyzer class */ class SharedPackageAnalyzer { constructor(config) { this.config = config; this.tracker = new symbol_tracker_1.SymbolTracker(); } /** * Run the full analysis */ async analyze() { const { packagePath, consumerGlobs, includeTests = false } = this.config; // Get package name const packageName = (0, workspace_scanner_1.getPackageName)(packagePath); if (this.config.verbose) { console.log(`\nAnalyzing package: ${packageName}`); console.log(`Package path: ${packagePath}\n`); } // Step 1: Scan target package exports if (this.config.verbose) { console.log('Step 1: Scanning package exports...'); } await this.tracker.scanPackageExports(packagePath, packageName, includeTests); const exportedSymbols = this.tracker.getExportedSymbols(); if (this.config.verbose) { console.log(`Found ${exportedSymbols.length} exported symbols\n`); } // Step 2: Find consumer packages if (this.config.verbose) { console.log('Step 2: Finding consumer packages...'); } const workspaceRoot = this.findWorkspaceRoot(packagePath); const consumers = await (0, workspace_scanner_1.findPackages)(consumerGlobs, workspaceRoot); // Filter out the target package itself from consumers const filteredConsumers = consumers.filter((c) => path.resolve(c.path) !== path.resolve(packagePath)); if (this.config.verbose) { console.log(`Found ${filteredConsumers.length} consumer packages:`); filteredConsumers.forEach((c) => console.log(` - ${c.name}`)); console.log(''); } // Step 3: Scan each consumer for imports if (this.config.verbose) { console.log('Step 3: Scanning consumer imports...'); } for (const consumer of filteredConsumers) { if (this.config.verbose) { console.log(` Scanning ${consumer.name}...`); } await this.tracker.scanConsumerImports(consumer.path, consumer.name, packageName, includeTests); } if (this.config.verbose) { console.log('\nStep 4: Analyzing symbol usage...'); } // Step 4: Analyze symbols const symbolAnalyses = this.tracker.analyzeSymbols(); // Step 5: Calculate metrics const totalLines = await this.calculateTotalPackageLines(packagePath); const totalSymbols = exportedSymbols.length; // Symbol counts const sharedSymbols = symbolAnalyses.filter((a) => a.isShared).length; const exclusiveSymbols = symbolAnalyses.filter((a) => a.isExclusive) .length; const unusedSymbols = symbolAnalyses.filter((a) => a.isUnused).length; // Line counts const { sharedLines, exclusiveLines, unusedLines } = (0, line_counter_1.calculateLinesBreakdown)(symbolAnalyses); // Build consumer analyses const consumerAnalyses = (0, consumer_tracker_1.buildConsumerAnalyses)(symbolAnalyses, filteredConsumers); // Calculate averages const avgLinesPerSharedSymbol = sharedSymbols > 0 ? Math.round(sharedLines / sharedSymbols) : 0; const avgLinesPerExclusiveSymbol = exclusiveSymbols > 0 ? Math.round(exclusiveLines / exclusiveSymbols) : 0; const avgLinesPerUnusedSymbol = unusedSymbols > 0 ? Math.round(unusedLines / unusedSymbols) : 0; const result = { packageName, packagePath, totalLines, totalSymbols, sharedSymbols, exclusiveSymbols, unusedSymbols, sharedLines, exclusiveLines, unusedLines, sharedSymbolsPercentage: (0, line_counter_1.calculatePercentage)(sharedSymbols, totalSymbols), exclusiveSymbolsPercentage: (0, line_counter_1.calculatePercentage)(exclusiveSymbols, totalSymbols), unusedSymbolsPercentage: (0, line_counter_1.calculatePercentage)(unusedSymbols, totalSymbols), sharedLinesPercentage: (0, line_counter_1.calculatePercentage)(sharedLines, totalLines), exclusiveLinesPercentage: (0, line_counter_1.calculatePercentage)(exclusiveLines, totalLines), unusedLinesPercentage: (0, line_counter_1.calculatePercentage)(unusedLines, totalLines), avgLinesPerSharedSymbol, avgLinesPerExclusiveSymbol, avgLinesPerUnusedSymbol, symbolAnalyses, consumerAnalyses, }; if (this.config.verbose) { console.log('Analysis complete!\n'); } return result; } /** * Calculate total lines in the package */ async calculateTotalPackageLines(packagePath) { const files = await (0, workspace_scanner_1.findSourceFiles)(packagePath, [], false); let totalLines = 0; for (const file of files) { try { totalLines += (0, line_counter_2.countCodeLines)(file); } catch (error) { console.warn(`Warning: Failed to count lines in ${file}`); } } return totalLines; } /** * Find workspace root (directory containing package.json with workspaces or pnpm-workspace.yaml) */ findWorkspaceRoot(startPath) { let currentPath = startPath; while (currentPath !== path.parse(currentPath).root) { // Check for pnpm workspace if (require('fs').existsSync(path.join(currentPath, 'pnpm-workspace.yaml'))) { return currentPath; } // Check for npm/yarn workspaces const packageJsonPath = path.join(currentPath, 'package.json'); if (require('fs').existsSync(packageJsonPath)) { const packageJson = JSON.parse(require('fs').readFileSync(packageJsonPath, 'utf-8')); if (packageJson.workspaces) { return currentPath; } } currentPath = path.dirname(currentPath); } // Fallback to start path return startPath; } } exports.SharedPackageAnalyzer = SharedPackageAnalyzer; //# sourceMappingURL=analyzer.js.map