mirror of
				https://github.com/warkanum/monorepo-dep-checker.git
				synced 2025-10-30 23:33:53 +00:00 
			
		
		
		
	Initial Version
This commit is contained in:
		
							parent
							
								
									8438e4c902
								
							
						
					
					
						commit
						07597a3764
					
				
							
								
								
									
										64
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| # os | ||||
| .DS_Store | ||||
| 
 | ||||
| ## yarn | ||||
| .yarn-integrity | ||||
| .yarn/cache | ||||
| .yarn/unplugged | ||||
| .yarn/build-state.yml | ||||
| .yarn/install-state.gz | ||||
| .pnp.* | ||||
| pnpm-lock.yaml | ||||
| 
 | ||||
| 
 | ||||
| # editor | ||||
| .idea/ | ||||
| .vscode/ | ||||
| .nova/ | ||||
| .gitpod.yml | ||||
| 
 | ||||
| # node | ||||
| node_modules/ | ||||
| 
 | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| lerna-debug.log* | ||||
| 
 | ||||
| # env | ||||
| .env.test | ||||
| .env*.local | ||||
| apps/**/.env | ||||
| 
 | ||||
| # build | ||||
| lib/ | ||||
| cjs/ | ||||
| esm/ | ||||
| 
 | ||||
| # typescript | ||||
| *.tsbuildinfo | ||||
| 
 | ||||
| # project | ||||
| next-env.d.ts | ||||
| .eslintcache | ||||
| storybook-static | ||||
| .next | ||||
| packages/*/*/styles.css | ||||
| packages/*/*/styles.layer.css | ||||
| packages/@mantine/core/styles | ||||
| .stylelintcache | ||||
| ____test.* | ||||
| 
 | ||||
| .turbo | ||||
| *.log | ||||
| .next | ||||
| dist | ||||
| dist-ssr | ||||
| .cache | ||||
| *storybook.log | ||||
| *vite.config.ts.timestamp* | ||||
| 
 | ||||
| pnpm-lock.yaml | ||||
							
								
								
									
										1
									
								
								bin/dep-check.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bin/dep-check.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| import('../dist/index.js').then(module => module.default()).catch(console.error); | ||||
							
								
								
									
										57
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| { | ||||
|     "name": "@warkypublic/monorepo-dep-checker", | ||||
|     "version": "1.0.0", | ||||
|     "description": "A CLI tool to check and manage dependencies across packages in a monorepo", | ||||
|     "type": "module", | ||||
|     "main": "./dist/index.js", | ||||
|     "bin": { | ||||
|         "dep-check": "./bin/dep-check.js" | ||||
|     }, | ||||
|     "files": [ | ||||
|         "dist", | ||||
|         "bin", | ||||
|         "README.md" | ||||
|     ], | ||||
|     "scripts": { | ||||
|         "build": "vite build", | ||||
|         "prepublishOnly": "npm run build", | ||||
|         "test": "vitest run", | ||||
|         "lint": "eslint src" | ||||
|     }, | ||||
|     "keywords": [ | ||||
|         "monorepo", | ||||
|         "dependencies", | ||||
|         "workspace", | ||||
|         "package", | ||||
|         "dependency-checker", | ||||
|         "cli", | ||||
|         "tools", | ||||
|         "dependency-management", | ||||
|         "npm", | ||||
|         "yarn", | ||||
|         "pnpm" | ||||
|     ], | ||||
|     "author": "Hein (Warkanum) Puth", | ||||
|     "license": "MIT", | ||||
|     "dependencies": { | ||||
|         "chalk": "^5.3.0", | ||||
|         "semver": "^7.5.4", | ||||
|         "yargs": "^17.7.2" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "eslint": "^8.55.0", | ||||
|         "vite": "^5.0.0", | ||||
|         "vitest": "^1.0.0" | ||||
|     }, | ||||
|     "engines": { | ||||
|         "node": ">=14.16" | ||||
|     }, | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|         "url": "git+https://github.com/warkanum/monorepo-dep-checker" | ||||
|     }, | ||||
|     "bugs": { | ||||
|         "url": "https://github.com/warkanum/monorepo-dep-checker/issues" | ||||
|     }, | ||||
|     "homepage": "https://github.com/warkanum/monorepo-dep-checker#readme" | ||||
| } | ||||
							
								
								
									
										654
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										654
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,654 @@ | ||||
| import fs from 'fs'; | ||||
| import path from 'path'; | ||||
| import { fileURLToPath } from 'url'; | ||||
| import chalk from 'chalk'; | ||||
| import semver from 'semver'; | ||||
| import yargs from 'yargs'; | ||||
| import { hideBin } from 'yargs/helpers'; | ||||
| 
 | ||||
| class DependencyChecker { | ||||
|   constructor(appPackageJsonPath, packagesDir) { | ||||
|     this.appPackageJsonPath = appPackageJsonPath; | ||||
|     this.packagesDir = packagesDir; | ||||
|     this.packageJsonFiles = []; | ||||
|     this.dependencyMap = new Map(); | ||||
|     this.workspacePackages = new Set(); | ||||
|   } | ||||
| 
 | ||||
|   findWorkspacePackages() { | ||||
|     const entries = fs.readdirSync(this.packagesDir, { withFileTypes: true }); | ||||
| 
 | ||||
|     entries.forEach((entry) => { | ||||
|       if (entry.isDirectory()) { | ||||
|         const packageJsonPath = path.join(this.packagesDir, entry.name, 'package.json'); | ||||
|         if (fs.existsSync(packageJsonPath)) { | ||||
|           const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); | ||||
|           this.workspacePackages.add(packageJson.name); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   isWorkspaceDep(version) { | ||||
|     return version?.startsWith('workspace:') || version === '*'; | ||||
|   } | ||||
| 
 | ||||
|   isWorkspacePackage(packageName) { | ||||
|     return this.workspacePackages.has(packageName); | ||||
|   } | ||||
| 
 | ||||
|   shouldIncludeDependency(depName, version, packageJson) { | ||||
|     // Skip if it's a workspace dependency
 | ||||
|     if (this.isWorkspaceDep(version)) return false; | ||||
| 
 | ||||
|     // Skip if the dependency is a workspace package
 | ||||
|     if (this.isWorkspacePackage(depName)) return false; | ||||
| 
 | ||||
|     // Skip if the package itself is referenced as a workspace dependency
 | ||||
|     const allWorkspaceDeps = { | ||||
|       ...packageJson.dependencies, | ||||
|       ...packageJson.devDependencies, | ||||
|       ...packageJson.peerDependencies, | ||||
|     }; | ||||
| 
 | ||||
|     return !Object.entries(allWorkspaceDeps).some( | ||||
|       ([name, ver]) => name === depName && this.isWorkspaceDep(ver) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getSemverVersion(version) { | ||||
|     // Handle workspace protocol
 | ||||
|     if (version.startsWith('workspace:')) { | ||||
|       // Extract actual version from the workspace package
 | ||||
|       const workspaceVersion = version.replace('workspace:', ''); | ||||
|       if (workspaceVersion === '*') return null; | ||||
|       // If it's a specific version after workspace:, use that
 | ||||
|       if (semver.valid(workspaceVersion)) return workspaceVersion; | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     // Handle other special cases
 | ||||
|     if (version === '*' || version === 'latest') return null; | ||||
| 
 | ||||
|     // Remove any leading special characters (^, ~, etc)
 | ||||
|     const cleanVersion = version.replace(/^[~^]/, ''); | ||||
| 
 | ||||
|     // Try to parse as semver
 | ||||
|     try { | ||||
|       if (semver.valid(cleanVersion)) return cleanVersion; | ||||
|       return null; | ||||
|     } catch { | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   checkMissingDependencies() { | ||||
|     console.log(chalk.bold('\nChecking for missing dependencies...\n')); | ||||
| 
 | ||||
|     this.findWorkspacePackages(); | ||||
| 
 | ||||
|     const appPackageJson = JSON.parse(fs.readFileSync(this.appPackageJsonPath, 'utf8')); | ||||
|     const appDeps = { | ||||
|       ...appPackageJson.dependencies, | ||||
|       ...appPackageJson.peerDependencies, | ||||
|     }; | ||||
| 
 | ||||
|     const missingDeps = new Map(); | ||||
|     const uniqueMissingDeps = new Set(); | ||||
|     const uniqueExtraDeps = new Set(); | ||||
| 
 | ||||
|     this.packageJsonFiles | ||||
|       .filter((file) => file !== this.appPackageJsonPath) | ||||
|       .forEach((filePath) => { | ||||
|         const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8')); | ||||
|         const packageName = packageJson.name; | ||||
|         const packageDeps = { | ||||
|           ...packageJson.dependencies, | ||||
|           ...packageJson.peerDependencies, | ||||
|         }; | ||||
| 
 | ||||
|         const missing = []; | ||||
|         const extraDeps = []; | ||||
| 
 | ||||
|         Object.entries(packageDeps).forEach(([dep, version]) => { | ||||
|           if (!appDeps[dep] && this.shouldIncludeDependency(dep, version, packageJson)) { | ||||
|             missing.push({ | ||||
|               name: dep, | ||||
|               version, | ||||
|             }); | ||||
|             uniqueMissingDeps.add(dep); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         Object.entries(appDeps).forEach(([dep, version]) => { | ||||
|           if (!packageDeps[dep] && this.shouldIncludeDependency(dep, version, appPackageJson)) { | ||||
|             extraDeps.push({ | ||||
|               name: dep, | ||||
|               version, | ||||
|             }); | ||||
|             uniqueExtraDeps.add(dep); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         if (missing.length > 0 || extraDeps.length > 0) { | ||||
|           missingDeps.set(packageName, { | ||||
|             path: path.relative(process.cwd(), filePath), | ||||
|             missing, | ||||
|             extraDeps, | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|     if (missingDeps.size === 0) { | ||||
|       console.log(chalk.green('✓ All dependencies are properly synchronized\n')); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     missingDeps.forEach(({ path: pkgPath, missing, extraDeps }, packageName) => { | ||||
|       if (missing.length > 0 || extraDeps.length > 0) { | ||||
|         console.log(chalk.yellow(`\n${packageName} (${chalk.gray(pkgPath)}):`)); | ||||
| 
 | ||||
|         if (missing.length > 0) { | ||||
|           console.log(chalk.red('  Missing from main app:')); | ||||
|           missing.forEach(({ name, version }) => { | ||||
|             console.log(`    - ${name}@${version}`); | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         if (extraDeps.length > 0) { | ||||
|           console.log(chalk.blue('  Not used by package but in main app:')); | ||||
|           extraDeps.forEach(({ name, version }) => { | ||||
|             console.log(`    - ${name}@${version}`); | ||||
|           }); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     if (uniqueMissingDeps.size > 0 || uniqueExtraDeps.size > 0) { | ||||
|       console.log(chalk.bold('\nSummary:')); | ||||
|       console.log(`Packages with dependency mismatches: ${missingDeps.size}`); | ||||
|       if (uniqueMissingDeps.size > 0) { | ||||
|         console.log( | ||||
|           chalk.red(`Unique dependencies missing from main app: ${uniqueMissingDeps.size}`) | ||||
|         ); | ||||
|         console.log(chalk.gray('  ' + Array.from(uniqueMissingDeps).join(', '))); | ||||
|       } | ||||
|       if (uniqueExtraDeps.size > 0) { | ||||
|         console.log( | ||||
|           chalk.blue(`Unique unused dependencies from main app: ${uniqueExtraDeps.size}`) | ||||
|         ); | ||||
|         console.log(chalk.gray('  ' + Array.from(uniqueExtraDeps).join(', '))); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   compareDependencyVersions(version1, version2) { | ||||
|     const v1 = this.getSemverVersion(version1); | ||||
|     const v2 = this.getSemverVersion(version2); | ||||
| 
 | ||||
|     // If either version can't be parsed, return 'unknown'
 | ||||
|     if (!v1 || !v2) return 'unknown'; | ||||
| 
 | ||||
|     try { | ||||
|       return semver.diff(v1, v2); | ||||
|     } catch { | ||||
|       return 'unknown'; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getVersionLabel(version) { | ||||
|     if (version.startsWith('workspace:')) { | ||||
|       return `${chalk.cyan('workspace:')}${version.slice(10)}`; | ||||
|     } | ||||
|     return version; | ||||
|   } | ||||
| 
 | ||||
|   findPackageJsonFiles() { | ||||
|     this.packageJsonFiles.push(this.appPackageJsonPath); | ||||
| 
 | ||||
|     const entries = fs.readdirSync(this.packagesDir, { withFileTypes: true }); | ||||
| 
 | ||||
|     entries.forEach((entry) => { | ||||
|       if (entry.isDirectory()) { | ||||
|         const packageJsonPath = path.join(this.packagesDir, entry.name, 'package.json'); | ||||
|         if (fs.existsSync(packageJsonPath)) { | ||||
|           this.packageJsonFiles.push(packageJsonPath); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   analyzeDependencies() { | ||||
|     this.packageJsonFiles.forEach((filePath) => { | ||||
|       const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8')); | ||||
|       const normalDeps = packageJson.dependencies || {}; | ||||
|       const peerDeps = packageJson.peerDependencies || {}; | ||||
|       const packageName = packageJson.name; | ||||
|       const isMainApp = filePath === this.appPackageJsonPath; | ||||
| 
 | ||||
|       [ | ||||
|         { deps: normalDeps, type: 'normal' }, | ||||
|         { deps: peerDeps, type: 'peer' }, | ||||
|       ].forEach(({ deps, type }) => { | ||||
|         Object.entries(deps).forEach(([dep, version]) => { | ||||
|           if (!this.dependencyMap.has(dep)) { | ||||
|             this.dependencyMap.set(dep, { | ||||
|               versions: new Map(), | ||||
|               usedAsNormal: false, | ||||
|               usedAsPeer: false, | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|           const depInfo = this.dependencyMap.get(dep); | ||||
|           if (!depInfo.versions.has(version)) { | ||||
|             depInfo.versions.set(version, { | ||||
|               packages: new Set(), | ||||
|               usages: new Set(), | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|           const versionInfo = depInfo.versions.get(version); | ||||
|           versionInfo.packages.add(packageName); | ||||
|           versionInfo.usages.add(`${type}${isMainApp ? ' (main app)' : ''}`); | ||||
| 
 | ||||
|           if (type === 'normal') depInfo.usedAsNormal = true; | ||||
|           if (type === 'peer') depInfo.usedAsPeer = true; | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   displayVersionDifferences() { | ||||
|     console.log(chalk.bold('\nVersion Differences Summary:\n')); | ||||
| 
 | ||||
|     let hasDifferences = false; | ||||
|     const conflictingDeps = []; | ||||
| 
 | ||||
|     this.dependencyMap.forEach((depInfo, dep) => { | ||||
|       if (depInfo.versions.size > 1) { | ||||
|         hasDifferences = true; | ||||
|         const versions = Array.from(depInfo.versions.entries()).map(([version, info]) => ({ | ||||
|           version, | ||||
|           packages: Array.from(info.packages), | ||||
|           paths: Array.from(info.packages).map((pkg) => { | ||||
|             const filePath = this.packageJsonFiles.find((file) => { | ||||
|               const json = JSON.parse(fs.readFileSync(file, 'utf8')); | ||||
|               return json.name === pkg; | ||||
|             }); | ||||
|             return { | ||||
|               package: pkg, | ||||
|               path: path.relative(process.cwd(), filePath), | ||||
|             }; | ||||
|           }), | ||||
|           usages: Array.from(info.usages), | ||||
|         })); | ||||
| 
 | ||||
|         // Create comparison matrix
 | ||||
|         const comparisons = []; | ||||
|         for (let i = 0; i < versions.length; i++) { | ||||
|           for (let j = i + 1; j < versions.length; j++) { | ||||
|             const v1 = versions[i]; | ||||
|             const v2 = versions[j]; | ||||
|             const comparison = { | ||||
|               version1: v1.version, | ||||
|               version2: v2.version, | ||||
|               packages1: v1.paths, | ||||
|               packages2: v2.paths, | ||||
|               semverDiff: this.compareDependencyVersions(v1.version, v2.version), | ||||
|             }; | ||||
|             comparisons.push(comparison); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         conflictingDeps.push({ | ||||
|           name: dep, | ||||
|           versions, | ||||
|           comparisons, | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     if (!hasDifferences) { | ||||
|       console.log(chalk.green('✓ No version differences found across packages\n')); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     conflictingDeps.forEach(({ name, comparisons }) => { | ||||
|       console.log(chalk.yellow(`\n${name}:`)); | ||||
| 
 | ||||
|       comparisons.forEach(({ version1, version2, packages1, packages2, semverDiff }) => { | ||||
|         console.log(chalk.cyan(`\n  Difference (${semverDiff || 'unknown'}):`)); | ||||
|         console.log(`    ${this.getVersionLabel(version1)} vs ${this.getVersionLabel(version2)}`); | ||||
| 
 | ||||
|         console.log('\n    Packages using', chalk.green(this.getVersionLabel(version1)), ':'); | ||||
|         packages1.forEach(({ package: pkg, path: filePath }) => { | ||||
|           console.log(`      - ${chalk.blue(pkg)} (${chalk.gray(filePath)})`); | ||||
|         }); | ||||
| 
 | ||||
|         console.log('\n    Packages using', chalk.green(this.getVersionLabel(version2)), ':'); | ||||
|         packages2.forEach(({ package: pkg, path: filePath }) => { | ||||
|           console.log(`      - ${chalk.blue(pkg)} (${chalk.gray(filePath)})`); | ||||
|         }); | ||||
| 
 | ||||
|         // Suggest recommended action based on version types
 | ||||
|         console.log('\n    Recommended action:'); | ||||
|         if (version1.startsWith('workspace:') || version2.startsWith('workspace:')) { | ||||
|           console.log(chalk.blue('    ℹ️  Workspace dependency - No action needed')); | ||||
|         } else { | ||||
|           switch (semverDiff) { | ||||
|             case 'major': | ||||
|               console.log( | ||||
|                 chalk.red('    ⚠️  Major version difference - Manual review recommended') | ||||
|               ); | ||||
|               break; | ||||
|             case 'minor': | ||||
|               console.log( | ||||
|                 chalk.yellow('    ℹ️  Minor version difference - Consider updating to latest') | ||||
|               ); | ||||
|               break; | ||||
|             case 'patch': | ||||
|               console.log(chalk.green('    ✓ Patch version difference - Safe to update to latest')); | ||||
|               break; | ||||
|             default: | ||||
|               console.log(chalk.gray('    ℹ️  Version difference analysis not available')); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     // Print total counts
 | ||||
|     console.log(chalk.bold('\nSummary Statistics:')); | ||||
|     const totalComparisons = conflictingDeps.reduce((acc, dep) => acc + dep.comparisons.length, 0); | ||||
|     console.log(`Dependencies with conflicts: ${conflictingDeps.length}`); | ||||
|     console.log(`Total version comparisons: ${totalComparisons}`); | ||||
| 
 | ||||
|     // Show severity breakdown
 | ||||
|     const severityCount = conflictingDeps.reduce((acc, dep) => { | ||||
|       dep.comparisons.forEach(({ version1, version2, semverDiff }) => { | ||||
|         if (version1.startsWith('workspace:') || version2.startsWith('workspace:')) { | ||||
|           acc.workspace = (acc.workspace || 0) + 1; | ||||
|         } else { | ||||
|           acc[semverDiff || 'unknown'] = (acc[semverDiff || 'unknown'] || 0) + 1; | ||||
|         } | ||||
|       }); | ||||
|       return acc; | ||||
|     }, {}); | ||||
| 
 | ||||
|     console.log('\nSeverity breakdown:'); | ||||
|     if (severityCount.workspace) | ||||
|       console.log(chalk.blue(`  Workspace dependencies: ${severityCount.workspace}`)); | ||||
|     if (severityCount.major) console.log(chalk.red(`  Major differences: ${severityCount.major}`)); | ||||
|     if (severityCount.minor) | ||||
|       console.log(chalk.yellow(`  Minor differences: ${severityCount.minor}`)); | ||||
|     if (severityCount.patch) | ||||
|       console.log(chalk.green(`  Patch differences: ${severityCount.patch}`)); | ||||
|     if (severityCount.unknown) | ||||
|       console.log(chalk.gray(`  Unknown differences: ${severityCount.unknown}`)); | ||||
|   } | ||||
| 
 | ||||
|   displayResults(format = 'text') { | ||||
|     // First show the version differences summary
 | ||||
|     if (format === 'text') { | ||||
|       this.displayVersionDifferences(); | ||||
|     } | ||||
| 
 | ||||
|     // Then show the full analysis
 | ||||
|     if (format === 'json') { | ||||
|       const output = { | ||||
|         summary: { | ||||
|           conflicts: Array.from(this.dependencyMap.entries()) | ||||
|             .filter(([, depInfo]) => depInfo.versions.size > 1) | ||||
|             .map(([dep, depInfo]) => ({ | ||||
|               name: dep, | ||||
|               versions: Array.from(depInfo.versions.entries()).map(([version, info]) => ({ | ||||
|                 version, | ||||
|                 packages: Array.from(info.packages), | ||||
|                 paths: Array.from(info.packages).map((pkg) => { | ||||
|                   const filePath = this.packageJsonFiles.find((file) => { | ||||
|                     const json = JSON.parse(fs.readFileSync(file, 'utf8')); | ||||
|                     return json.name === pkg; | ||||
|                   }); | ||||
|                   return { | ||||
|                     package: pkg, | ||||
|                     path: path.relative(process.cwd(), filePath), | ||||
|                   }; | ||||
|                 }), | ||||
|                 usages: Array.from(info.usages), | ||||
|               })), | ||||
|             })), | ||||
|         }, | ||||
|         fullAnalysis: {}, | ||||
|       }; | ||||
| 
 | ||||
|       this.dependencyMap.forEach((depInfo, dep) => { | ||||
|         output.fullAnalysis[dep] = { | ||||
|           versions: Object.fromEntries( | ||||
|             Array.from(depInfo.versions.entries()).map(([version, info]) => [ | ||||
|               version, | ||||
|               { | ||||
|                 packages: Array.from(info.packages), | ||||
|                 usages: Array.from(info.usages), | ||||
|               }, | ||||
|             ]) | ||||
|           ), | ||||
|           usedAsNormal: depInfo.usedAsNormal, | ||||
|           usedAsPeer: depInfo.usedAsPeer, | ||||
|         }; | ||||
|       }); | ||||
| 
 | ||||
|       console.log(JSON.stringify(output, null, 2)); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Original detailed display
 | ||||
|     console.log(chalk.bold('\nDetailed Dependencies Analysis:\n')); | ||||
| 
 | ||||
|     this.dependencyMap.forEach((depInfo, dep) => { | ||||
|       console.log(chalk.blue(`\n${dep}:`)); | ||||
| 
 | ||||
|       if (depInfo.versions.size > 1) { | ||||
|         console.log(chalk.yellow('⚠️  Multiple versions detected:')); | ||||
|       } | ||||
| 
 | ||||
|       const usageTypes = []; | ||||
|       if (depInfo.usedAsNormal) usageTypes.push('normal dependency'); | ||||
|       if (depInfo.usedAsPeer) usageTypes.push('peer dependency'); | ||||
|       console.log(chalk.cyan(`Used as: ${usageTypes.join(' and ')}`)); | ||||
| 
 | ||||
|       depInfo.versions.forEach((versionInfo, version) => { | ||||
|         console.log(`  ${chalk.green(version)}`); | ||||
|         console.log(`    Usage types:`); | ||||
|         versionInfo.usages.forEach((usage) => { | ||||
|           console.log(`      - ${usage}`); | ||||
|         }); | ||||
|         console.log(`    Packages:`); | ||||
|         versionInfo.packages.forEach((pkg) => { | ||||
|           console.log(`      - ${pkg}`); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   async updateDependencies(dryRun = false) { | ||||
|     console.log(chalk.bold('\nUpdating dependencies...\n')); | ||||
| 
 | ||||
|     const updates = []; | ||||
|     this.dependencyMap.forEach((depInfo, dep) => { | ||||
|       const allVersions = Array.from(depInfo.versions.keys()).filter( | ||||
|         (v) => !v.startsWith('workspace:') | ||||
|       ); // Skip workspace dependencies
 | ||||
| 
 | ||||
|       if (allVersions.length === 0) return; // Skip if only workspace versions exist
 | ||||
| 
 | ||||
|       const cleanVersions = allVersions.map((v) => this.getSemverVersion(v)).filter(Boolean); | ||||
| 
 | ||||
|       if (cleanVersions.length === 0) return; // Skip if no valid versions
 | ||||
| 
 | ||||
|       const highestVersion = semver.maxSatisfying(cleanVersions, '*'); | ||||
| 
 | ||||
|       if (highestVersion) { | ||||
|         this.packageJsonFiles.forEach((filePath) => { | ||||
|           const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8')); | ||||
|           let updated = false; | ||||
| 
 | ||||
|           // Don't update workspace dependencies
 | ||||
|           if ( | ||||
|             packageJson.dependencies?.[dep] && | ||||
|             !packageJson.dependencies[dep].startsWith('workspace:') | ||||
|           ) { | ||||
|             if (!dryRun) { | ||||
|               packageJson.dependencies[dep] = `^${highestVersion}`; | ||||
|             } | ||||
|             updated = true; | ||||
|           } | ||||
| 
 | ||||
|           if ( | ||||
|             packageJson.peerDependencies?.[dep] && | ||||
|             !packageJson.peerDependencies[dep].startsWith('workspace:') | ||||
|           ) { | ||||
|             if (!dryRun) { | ||||
|               packageJson.peerDependencies[dep] = `^${highestVersion}`; | ||||
|             } | ||||
|             updated = true; | ||||
|           } | ||||
| 
 | ||||
|           if (updated) { | ||||
|             if (!dryRun) { | ||||
|               fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2)); | ||||
|             } | ||||
|             updates.push({ | ||||
|               package: packageJson.name, | ||||
|               dependency: dep, | ||||
|               version: highestVersion, | ||||
|             }); | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     updates.forEach(({ package: pkgName, dependency, version }) => { | ||||
|       console.log( | ||||
|         chalk.green( | ||||
|           `${dryRun ? '[DRY RUN] Would update' : 'Updated'} ${dependency} to ^${version} in ${pkgName}` | ||||
|         ) | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   async run({ | ||||
|     update = false, | ||||
|     dryRun = false, | ||||
|     format = 'text', | ||||
|     checkVersions = false, | ||||
|     checkMissing = false, | ||||
|   } = {}) { | ||||
|     if (checkMissing) { | ||||
|       this.findPackageJsonFiles(); | ||||
|       this.checkMissingDependencies(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (checkVersions) { | ||||
|       this.findPackageJsonFiles(); | ||||
|       this.analyzeDependencies(); | ||||
|       this.displayVersionDifferences(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (update) { | ||||
|       this.findPackageJsonFiles(); | ||||
|       this.analyzeDependencies(); | ||||
|       await this.updateDependencies(dryRun); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Default behavior - show full analysis
 | ||||
|     this.findPackageJsonFiles(); | ||||
|     this.analyzeDependencies(); | ||||
|     this.displayResults(format); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // CLI implementation
 | ||||
| const run = async () => { | ||||
|   const argv = yargs(hideBin(process.argv)) | ||||
|     .usage('Usage: $0 [options]') | ||||
|     .option('app', { | ||||
|       alias: 'a', | ||||
|       describe: 'Path to main app package.json', | ||||
|       type: 'string', | ||||
|       default: './package.json', | ||||
|     }) | ||||
|     .option('packages', { | ||||
|       alias: 'p', | ||||
|       describe: 'Path to packages directory', | ||||
|       type: 'string', | ||||
|       default: './packages', | ||||
|     }) | ||||
|     .option('update', { | ||||
|       alias: 'u', | ||||
|       describe: 'Update dependencies to highest compatible version', | ||||
|       type: 'boolean', | ||||
|       default: false, | ||||
|     }) | ||||
|     .option('dry-run', { | ||||
|       alias: 'd', | ||||
|       describe: 'Show what would be updated without making changes', | ||||
|       type: 'boolean', | ||||
|       default: false, | ||||
|     }) | ||||
|     .option('check-versions', { | ||||
|       alias: 'v', | ||||
|       describe: 'Check for version differences between packages', | ||||
|       type: 'boolean', | ||||
|       default: false, | ||||
|     }) | ||||
|     .option('check-missing', { | ||||
|       alias: 'm', | ||||
|       describe: 'Check for dependencies missing between app and packages', | ||||
|       type: 'boolean', | ||||
|       default: false, | ||||
|     }) | ||||
|     .option('format', { | ||||
|       alias: 'f', | ||||
|       describe: 'Output format (text or json)', | ||||
|       choices: ['text', 'json'], | ||||
|       default: 'text', | ||||
|     }) | ||||
|     .help() | ||||
|     .alias('help', 'h') | ||||
|     .example('$0 --check-versions', 'Show only version differences') | ||||
|     .example('$0 --check-missing', 'Show missing dependencies') | ||||
|     .example('$0 --update --dry-run', 'Show what would be updated').argv; | ||||
| 
 | ||||
|   const appPackageJson = path.resolve(process.cwd(), argv.app); | ||||
|   const packagesDir = path.resolve(process.cwd(), argv.packages); | ||||
| 
 | ||||
|   // Validate paths
 | ||||
|   if (!fs.existsSync(appPackageJson)) { | ||||
|     console.error(chalk.red(`Error: Main package.json not found at ${appPackageJson}`)); | ||||
|     process.exit(1); | ||||
|   } | ||||
| 
 | ||||
|   if (!fs.existsSync(packagesDir)) { | ||||
|     console.error(chalk.red(`Error: Packages directory not found at ${packagesDir}`)); | ||||
|     process.exit(1); | ||||
|   } | ||||
| 
 | ||||
|   const checker = new DependencyChecker(appPackageJson, packagesDir); | ||||
|   await checker.run({ | ||||
|     update: argv.update, | ||||
|     dryRun: argv.dryRun, | ||||
|     format: argv.format, | ||||
|     checkVersions: argv.checkVersions, | ||||
|     checkMissing: argv.checkMissing, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // Export both the class and run function
 | ||||
| export { DependencyChecker, run as default }; | ||||
| 
 | ||||
| if (process.argv[1] === import.meta.url.slice(7)) { | ||||
|   console.log('Running'); | ||||
|   run(); | ||||
| } | ||||
							
								
								
									
										27
									
								
								vite.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vite.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| import { defineConfig } from 'vite'; | ||||
| import { resolve } from 'path'; | ||||
| 
 | ||||
| export default defineConfig({ | ||||
|   build: { | ||||
|     lib: { | ||||
|       entry: resolve(__dirname, 'src/index.js'), | ||||
|       formats: ['es'], | ||||
|       fileName: 'index' | ||||
|     }, | ||||
|     rollupOptions: { | ||||
|       external: [ | ||||
|         'fs', | ||||
|         'path', | ||||
|         'url', | ||||
|         'chalk', | ||||
|         'semver', | ||||
|         'yargs', | ||||
|         'yargs/helpers' | ||||
|       ] | ||||
|     }, | ||||
|     target: 'node14', | ||||
|     outDir: 'dist', | ||||
|     emptyOutDir: true, | ||||
|     sourcemap: true | ||||
|   } | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user