UNPKG

crapifyme

Version:

Ultra-fast developer productivity CLI tools - remove comments, logs, and more

477 lines 17.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PackageAnalyzer = void 0; const child_process_1 = require("child_process"); const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const util_1 = require("util"); const types_1 = require("./types"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class PackageAnalyzer { constructor(cwd = process.cwd()) { this.packageManager = null; this.cwd = cwd; } async findProjectRoot() { let currentDir = path_1.default.resolve(this.cwd); const root = path_1.default.parse(currentDir).root; const lockFiles = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'pnpm-lock.yml']; while (currentDir !== root) { try { await promises_1.default.access(path_1.default.join(currentDir, 'package.json')); for (const lockFile of lockFiles) { try { await promises_1.default.access(path_1.default.join(currentDir, lockFile)); return currentDir; } catch { continue; } } } catch { } currentDir = path_1.default.dirname(currentDir); } try { await promises_1.default.access(path_1.default.join(root, 'package.json')); for (const lockFile of lockFiles) { try { await promises_1.default.access(path_1.default.join(root, lockFile)); return root; } catch { continue; } } } catch { } return null; } async detectPackageManager() { if (this.packageManager) return this.packageManager; const projectRoot = await this.findProjectRoot(); if (!projectRoot) { throw new Error('No package.json found in current directory or any parent directory'); } this.cwd = projectRoot; const lockFiles = [ { file: 'package-lock.json', type: 'npm', auditCmd: 'npm audit' }, { file: 'yarn.lock', type: 'yarn', auditCmd: 'yarn audit' }, { file: 'pnpm-lock.yaml', type: 'pnpm', auditCmd: 'pnpm audit' }, { file: 'pnpm-lock.yml', type: 'pnpm', auditCmd: 'pnpm audit' } ]; for (const { file, type, auditCmd } of lockFiles) { try { await promises_1.default.access(path_1.default.join(this.cwd, file)); const version = await this.getPackageManagerVersion(type); this.packageManager = { type, version, lockFile: file, auditCommand: auditCmd }; if (type === 'npm' || type === 'yarn') { this.packageManager.workspaces = await this.detectWorkspaces(); } await this.validateProjectContext(); return this.packageManager; } catch { continue; } } throw new Error('No supported package manager detected (npm, yarn, pnpm)'); } async getPackageManagerVersion(pm) { try { const { stdout } = await execAsync(`${pm} --version`, { cwd: this.cwd, maxBuffer: 1024 * 1024 }); return stdout.trim(); } catch { return 'unknown'; } } async detectWorkspaces() { try { const pkgJson = await this.readPackageJson(); if (pkgJson.workspaces) { return Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages || []; } } catch { } return undefined; } async validateProjectContext() { try { const pkgJson = await this.readPackageJson(); const isWorkspaceRoot = !!pkgJson.workspaces && (!pkgJson.dependencies || Object.keys(pkgJson.dependencies).length === 0); if (isWorkspaceRoot) { console.warn('⚠️ Running in workspace root with minimal dependencies.'); console.warn('💡 For better analysis, run from a specific workspace package (e.g., cd packages/website && npx crapifyme deps)'); } } catch { } } async readPackageJson(filePath) { const packagePath = filePath || path_1.default.join(this.cwd, 'package.json'); try { const content = await promises_1.default.readFile(packagePath, 'utf-8'); const packageJson = JSON.parse(content); return { name: packageJson.name || 'unnamed-project', version: packageJson.version || '0.0.0', description: packageJson.description, homepage: packageJson.homepage, repository: packageJson.repository, keywords: packageJson.keywords, license: packageJson.license, ...packageJson }; } catch (error) { throw new Error(`Failed to read package.json: ${error.message}`); } } async getInstalledDependencies() { const pm = await this.detectPackageManager(); const dependencies = new Map(); try { const pkgJson = await this.readPackageJson(); const depTypes = [ { deps: pkgJson.dependencies || {}, type: types_1.DependencyType.PRODUCTION }, { deps: pkgJson.devDependencies || {}, type: types_1.DependencyType.DEVELOPMENT }, { deps: pkgJson.peerDependencies || {}, type: types_1.DependencyType.PEER }, { deps: pkgJson.optionalDependencies || {}, type: types_1.DependencyType.OPTIONAL } ]; for (const { deps, type } of depTypes) { for (const [name, version] of Object.entries(deps)) { const depInfo = { name, currentVersion: version, isOutdated: false, isDev: type === types_1.DependencyType.DEVELOPMENT, isOptional: type === types_1.DependencyType.OPTIONAL, isPeer: type === types_1.DependencyType.PEER }; dependencies.set(name, depInfo); } } const outdatedInfo = await this.checkOutdatedDependencies(); for (const [name, info] of outdatedInfo) { if (dependencies.has(name)) { const dep = dependencies.get(name); dep.latestVersion = info.latestVersion; dep.wantedVersion = info.wantedVersion; dep.isOutdated = info.isOutdated; } } } catch (error) { throw new Error(`Failed to analyze dependencies: ${error.message}`); } return dependencies; } async checkOutdatedDependencies() { const pm = await this.detectPackageManager(); const outdated = new Map(); try { let command; let parser; switch (pm.type) { case 'npm': command = 'npm outdated --json'; parser = this.parseNpmOutdated.bind(this); break; case 'yarn': command = 'yarn outdated --json'; parser = this.parseYarnOutdated.bind(this); break; case 'pnpm': command = 'pnpm outdated --format json'; parser = this.parsePnpmOutdated.bind(this); break; default: throw new Error(`Unsupported package manager: ${pm.type}`); } const { stdout } = await execAsync(command, { cwd: this.cwd, timeout: 30000, maxBuffer: 10 * 1024 * 1024 }); if (stdout.trim()) { return parser(stdout); } } catch (error) { if (error.code !== 1) { console.warn(`Warning: Failed to check outdated dependencies: ${error.message}`); } } return outdated; } parseNpmOutdated(output) { const outdated = new Map(); try { const data = JSON.parse(output); for (const [name, info] of Object.entries(data)) { const pkgInfo = info; outdated.set(name, { name, currentVersion: pkgInfo.current, latestVersion: pkgInfo.latest, wantedVersion: pkgInfo.wanted, isOutdated: true, isDev: false, isOptional: false, isPeer: false }); } } catch (error) { console.warn(`Warning: Failed to parse npm outdated output: ${error.message}`); } return outdated; } parseYarnOutdated(output) { const outdated = new Map(); try { const lines = output.split('\n').filter(line => line.trim()); for (const line of lines) { const data = JSON.parse(line); if (data.type === 'table' && data.data?.body) { for (const row of data.data.body) { const [name, current, wanted, latest] = row; if (name && current !== latest) { outdated.set(name, { name, currentVersion: current, latestVersion: latest, wantedVersion: wanted, isOutdated: true, isDev: false, isOptional: false, isPeer: false }); } } } } } catch (error) { console.warn(`Warning: Failed to parse yarn outdated output: ${error.message}`); } return outdated; } parsePnpmOutdated(output) { const outdated = new Map(); try { const data = JSON.parse(output); if (Array.isArray(data)) { for (const pkg of data) { if (pkg.current !== pkg.latest) { outdated.set(pkg.packageName, { name: pkg.packageName, currentVersion: pkg.current, latestVersion: pkg.latest, wantedVersion: pkg.wanted, isOutdated: true, isDev: pkg.dependencyType === 'devDependencies', isOptional: pkg.dependencyType === 'optionalDependencies', isPeer: pkg.dependencyType === 'peerDependencies' }); } } } } catch (error) { console.warn(`Warning: Failed to parse pnpm outdated output: ${error.message}`); } return outdated; } async getDependencyTree() { const pm = await this.detectPackageManager(); try { let command; let parser; switch (pm.type) { case 'npm': command = 'npm ls --json --depth=0'; parser = this.parseNpmTree.bind(this); break; case 'yarn': command = 'yarn list --json --depth=0'; parser = this.parseYarnTree.bind(this); break; case 'pnpm': command = 'pnpm ls --json --depth=0'; parser = this.parsePnpmTree.bind(this); break; default: throw new Error(`Unsupported package manager: ${pm.type}`); } const { stdout } = await execAsync(command, { cwd: this.cwd, timeout: 30000, maxBuffer: 5 * 1024 * 1024 }); return parser(stdout); } catch (error) { return this.createBasicTreeFromPackageJson(); } } parseNpmTree(output) { const data = JSON.parse(output); return this.convertNpmNodeToTreeNode(data); } convertNpmNodeToTreeNode(node) { const treeNode = { name: node.name || 'root', version: node.version || '0.0.0', path: node.path || this.cwd, dev: node.dev || false, optional: node.optional || false, resolved: node.resolved }; if (node.dependencies) { treeNode.dependencies = new Map(); for (const [name, depNode] of Object.entries(node.dependencies)) { treeNode.dependencies.set(name, this.convertNpmNodeToTreeNode(depNode)); } } return treeNode; } parseYarnTree(output) { const lines = output.split('\n').filter(line => line.trim()); let rootNode = null; for (const line of lines) { try { const data = JSON.parse(line); if (data.type === 'tree' && data.data?.trees?.length > 0) { const tree = data.data.trees[0]; rootNode = { name: tree.name?.split('@')[0] || 'root', version: tree.name?.split('@')[1] || '0.0.0', path: this.cwd, dev: false, optional: false, dependencies: new Map() }; break; } } catch { continue; } } return (rootNode || { name: 'root', version: '0.0.0', path: this.cwd, dev: false, optional: false }); } parsePnpmTree(output) { const data = JSON.parse(output); if (Array.isArray(data) && data.length > 0) { const root = data[0]; return { name: root.name || 'root', version: root.version || '0.0.0', path: root.path || this.cwd, dev: false, optional: false, dependencies: new Map() }; } return { name: 'root', version: '0.0.0', path: this.cwd, dev: false, optional: false }; } async findDuplicateDependencies() { const tree = await this.getDependencyTree(); const duplicates = new Map(); const versionMap = new Map(); if (!tree) return duplicates; this.collectVersions(tree, versionMap); for (const [name, versions] of versionMap) { if (versions.size > 1) { duplicates.set(name, Array.from(versions)); } } return duplicates; } collectVersions(node, versionMap) { if (node.name !== 'root') { if (!versionMap.has(node.name)) { versionMap.set(node.name, new Set()); } versionMap.get(node.name).add(node.version); } if (node.dependencies) { for (const [, childNode] of node.dependencies) { this.collectVersions(childNode, versionMap); } } } async createBasicTreeFromPackageJson() { try { const pkgJson = await this.readPackageJson(); const dependencies = new Map(); const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies, ...pkgJson.peerDependencies, ...pkgJson.optionalDependencies }; for (const [name, version] of Object.entries(allDeps)) { dependencies.set(name, { name, version: version, path: this.cwd, dev: !!pkgJson.devDependencies?.[name], optional: !!pkgJson.optionalDependencies?.[name] }); } return { name: pkgJson.name || 'root', version: pkgJson.version || '0.0.0', path: this.cwd, dev: false, optional: false, dependencies }; } catch { return null; } } formatSize(bytes) { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(unitIndex === 0 ? 0 : 1)}${units[unitIndex]}`; } } exports.PackageAnalyzer = PackageAnalyzer; //# sourceMappingURL=package-analyzer.js.map