mirror of
				https://github.com/warkanum/monorepo-dep-checker.git
				synced 2025-10-31 07:43:53 +00:00 
			
		
		
		
	Added typescript, test and feature for comma seperated packages
This commit is contained in:
		
							parent
							
								
									9851a91bb3
								
							
						
					
					
						commit
						e522e73b7a
					
				| @ -3,10 +3,13 @@ | |||||||
| import { createRequire } from 'module'; | import { createRequire } from 'module'; | ||||||
| import { fileURLToPath } from 'url'; | import { fileURLToPath } from 'url'; | ||||||
| import { dirname, join } from 'path'; | import { dirname, join } from 'path'; | ||||||
|  | import { pathToFileURL } from 'url'; | ||||||
| 
 | 
 | ||||||
| const __filename = fileURLToPath(import.meta.url); | const __filename = fileURLToPath(import.meta.url); | ||||||
| const __dirname = dirname(__filename); | const __dirname = dirname(__filename); | ||||||
| const require = createRequire(import.meta.url); | const require = createRequire(import.meta.url); | ||||||
| 
 | 
 | ||||||
| const { default: run } = await import(join(__dirname, '../dist/index.js')); | // Convert the file path to a proper file:// URL
 | ||||||
|  | const modulePath = pathToFileURL(join(__dirname, '../dist/index.js')).href; | ||||||
|  | const { default: run } = await import(modulePath); | ||||||
| run().catch(console.error); | run().catch(console.error); | ||||||
| @ -1,9 +1,96 @@ | |||||||
| import globals from "globals"; | import eslintJs from '@eslint/js'; | ||||||
| import pluginJs from "@eslint/js"; | import tsParser from '@typescript-eslint/parser'; | ||||||
| 
 | 
 | ||||||
|  | import globals from 'globals'; | ||||||
|  | import eslintTs from 'typescript-eslint'; | ||||||
| 
 | 
 | ||||||
| /** @type {import('eslint').Linter.Config[]} */ | const tsFiles = ['{src,lib}/**/*.{ts,tsx}']; | ||||||
| export default [ | 
 | ||||||
|   {languageOptions: { globals: globals.browser }}, | const languageOptions = { | ||||||
|   pluginJs.configs.recommended, |   globals: { | ||||||
|  |     ...globals.node, | ||||||
|  |     ...globals.jest, | ||||||
|  |   }, | ||||||
|  |   ecmaVersion: 2023, | ||||||
|  |   sourceType: 'module', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const rules = { | ||||||
|  |   '@typescript-eslint/no-use-before-define': 'off', | ||||||
|  |   'require-await': 'off', | ||||||
|  |   'no-duplicate-imports': 'error', | ||||||
|  |   'no-unneeded-ternary': 'error', | ||||||
|  |   'prefer-object-spread': 'error', | ||||||
|  |   '@typescript-eslint/array-type': 'off', | ||||||
|  |   '@typescript-eslint/ban-ts-comment': 'off', | ||||||
|  |   '@typescript-eslint/no-explicit-any': 'off', | ||||||
|  |   '@typescript-eslint/no-unused-vars': [ | ||||||
|  |     'error', | ||||||
|  |     { | ||||||
|  |       ignoreRestSiblings: true, | ||||||
|  |       args: 'none', | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const commonIgnores = [ | ||||||
|  |   'node_modules', | ||||||
|  |   '**/node_modules', | ||||||
|  |   '**/dist/*', | ||||||
|  |   'docs/*', | ||||||
|  |   'build/*', | ||||||
|  |   'dist/*', | ||||||
|  |   'next.config.mjs', | ||||||
| ]; | ]; | ||||||
|  | 
 | ||||||
|  | const customTypescriptConfig = { | ||||||
|  |   files: tsFiles, | ||||||
|  |   plugins: { | ||||||
|  |     'import/parsers': tsParser, | ||||||
|  |   }, | ||||||
|  |   languageOptions: { | ||||||
|  |     ...languageOptions, | ||||||
|  |     parser: tsParser, | ||||||
|  |     parserOptions: { | ||||||
|  |       project: './tsconfig.json', | ||||||
|  |       sourceType: 'module', | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   settings: { | ||||||
|  |     'import/parsers': { | ||||||
|  |       '@typescript-eslint/parser': ['.ts'], | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   rules: { | ||||||
|  | 
 | ||||||
|  |     ...rules, | ||||||
|  |   }, | ||||||
|  |   ignores: commonIgnores, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Add the files for applying the recommended TypeScript configs
 | ||||||
|  | // only for the Typescript files.
 | ||||||
|  | // This is necessary when we have the multiple extensions files
 | ||||||
|  | // (e.g. .ts, .tsx, .js, .cjs, .mjs, etc.).
 | ||||||
|  | const recommendedTypeScriptConfigs = [ | ||||||
|  |   ...eslintTs.configs.recommended.map((config) => ({ | ||||||
|  |     ...config, | ||||||
|  |     files: tsFiles, | ||||||
|  |     rules: { | ||||||
|  |       ...config.rules, | ||||||
|  |       ...rules, | ||||||
|  |     }, | ||||||
|  |     ignores: commonIgnores, | ||||||
|  |   })), | ||||||
|  |   ...eslintTs.configs.stylistic.map((config) => ({ | ||||||
|  |     ...config, | ||||||
|  |     files: tsFiles, | ||||||
|  |     rules: { | ||||||
|  |       ...config.rules, | ||||||
|  |       ...rules, | ||||||
|  |     }, | ||||||
|  |     ignores: commonIgnores, | ||||||
|  |   })), | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | export default [...recommendedTypeScriptConfigs, customTypescriptConfig]; | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								package.json
									
									
									
									
									
								
							| @ -18,8 +18,8 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "build": "vite build", |         "build": "vite build", | ||||||
|         "prepublishOnly": "npm run build", |         "prepublishOnly": "npm run build", | ||||||
|         "test": "vitest run", |         "test": "vitest run --silent=false", | ||||||
|         "lint": "eslint src" |         "lint": "eslint ./src" | ||||||
|     }, |     }, | ||||||
|     "keywords": [ |     "keywords": [ | ||||||
|         "monorepo", |         "monorepo", | ||||||
| @ -42,11 +42,17 @@ | |||||||
|         "yargs": "^17.7.2" |         "yargs": "^17.7.2" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@eslint/js": "^9.15.0", |         "@eslint/js": "^9.16.0", | ||||||
|         "eslint": "^8.57.1", |         "@types/node": "^22.10.1", | ||||||
|         "globals": "^15.12.0", |         "@types/semver": "~7.5.8", | ||||||
|         "vite": "^5.0.0", |         "@types/yargs": "~17.0.33", | ||||||
|         "vitest": "^1.0.0" |         "eslint": "^9.16.0", | ||||||
|  |         "globals": "^15.13.0", | ||||||
|  |         "prettier-eslint": "^16.3.0", | ||||||
|  |         "typescript-eslint": "^8.18.0", | ||||||
|  |         "typesync": "^0.14.0", | ||||||
|  |         "vite": "^5.4.11", | ||||||
|  |         "vitest": "^1.6.0" | ||||||
|     }, |     }, | ||||||
|     "engines": { |     "engines": { | ||||||
|         "node": ">=14.16" |         "node": ">=14.16" | ||||||
| @ -58,5 +64,6 @@ | |||||||
|     "bugs": { |     "bugs": { | ||||||
|         "url": "https://github.com/warkanum/monorepo-dep-checker/issues" |         "url": "https://github.com/warkanum/monorepo-dep-checker/issues" | ||||||
|     }, |     }, | ||||||
|     "homepage": "https://github.com/warkanum/monorepo-dep-checker#readme" |     "homepage": "https://github.com/warkanum/monorepo-dep-checker#readme", | ||||||
|  |     "packageManager": "pnpm@9.6.0+sha256.dae0f7e822c56b20979bb5965e3b73b8bdabb6b8b8ef121da6d857508599ca35" | ||||||
| } | } | ||||||
							
								
								
									
										671
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										671
									
								
								src/index.js
									
									
									
									
									
								
							| @ -1,671 +0,0 @@ | |||||||
| import fs from 'fs'; |  | ||||||
| import path from 'path'; |  | ||||||
| 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, { |  | ||||||
|             // eslint-disable-next-line no-undef
 |  | ||||||
|             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, |  | ||||||
|               // eslint-disable-next-line no-undef
 |  | ||||||
|               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, |  | ||||||
|                     // eslint-disable-next-line no-undef
 |  | ||||||
|                     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')); |  | ||||||
| 
 |  | ||||||
|     // Read the main app's package.json
 |  | ||||||
|     const appPackageJson = JSON.parse(fs.readFileSync(this.appPackageJsonPath, 'utf8')); |  | ||||||
|     const appDependencies = { |  | ||||||
|       ...appPackageJson.dependencies, |  | ||||||
|       ...appPackageJson.devDependencies, |  | ||||||
|       ...appPackageJson.peerDependencies |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const updates = []; |  | ||||||
| 
 |  | ||||||
|     // Process each package.json except the main app
 |  | ||||||
|     this.packageJsonFiles |  | ||||||
|       .filter(filePath => filePath !== this.appPackageJsonPath) |  | ||||||
|       .forEach(filePath => { |  | ||||||
|         const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8')); |  | ||||||
|         let hasUpdates = false; |  | ||||||
| 
 |  | ||||||
|         // Helper function to update dependencies of a specific type
 |  | ||||||
|         const updateDependencySection = (section) => { |  | ||||||
|           if (!packageJson[section]) return; |  | ||||||
| 
 |  | ||||||
|           Object.entries(packageJson[section]).forEach(([dep, version]) => { |  | ||||||
|             // Skip workspace dependencies
 |  | ||||||
|             if (version.startsWith('workspace:')) return; |  | ||||||
|              |  | ||||||
|             // If the dependency exists in the main app, sync the version
 |  | ||||||
|             if (appDependencies[dep]) { |  | ||||||
|               const appVersion = appDependencies[dep]; |  | ||||||
|               if (version !== appVersion) { |  | ||||||
|                 if (!dryRun) { |  | ||||||
|                   packageJson[section][dep] = appVersion; |  | ||||||
|                 } |  | ||||||
|                 updates.push({ |  | ||||||
|                   package: packageJson.name, |  | ||||||
|                   dependency: dep, |  | ||||||
|                   from: version, |  | ||||||
|                   to: appVersion, |  | ||||||
|                   type: section |  | ||||||
|                 }); |  | ||||||
|                 hasUpdates = true; |  | ||||||
|               } |  | ||||||
|             } |  | ||||||
|           }); |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         // Update all dependency sections
 |  | ||||||
|         updateDependencySection('dependencies'); |  | ||||||
|         updateDependencySection('devDependencies'); |  | ||||||
|         updateDependencySection('peerDependencies'); |  | ||||||
| 
 |  | ||||||
|         // Write updated package.json if there were changes
 |  | ||||||
|         if (hasUpdates && !dryRun) { |  | ||||||
|           fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n'); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|     // Display updates
 |  | ||||||
|     if (updates.length === 0) { |  | ||||||
|       console.log(chalk.green('No updates needed - all versions match the main app')); |  | ||||||
|     } else { |  | ||||||
|       updates.forEach(({ package: pkgName, dependency, from, to, type }) => { |  | ||||||
|         console.log( |  | ||||||
|           chalk.green( |  | ||||||
|             `${dryRun ? '[DRY RUN] Would update' : 'Updated'} ${dependency} in ${pkgName} (${type})\n` + |  | ||||||
|             `  ${chalk.red(from)} → ${chalk.green(to)}` |  | ||||||
|           ) |  | ||||||
|         ); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return updates; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   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 () => { |  | ||||||
|   // eslint-disable-next-line no-undef
 |  | ||||||
|   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; |  | ||||||
| 
 |  | ||||||
|   // eslint-disable-next-line no-undef
 |  | ||||||
|   const appPackageJson = path.resolve(process.cwd(), argv.app); |  | ||||||
|   // eslint-disable-next-line no-undef
 |  | ||||||
|   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}`)); |  | ||||||
|     // eslint-disable-next-line no-undef
 |  | ||||||
|     process.exit(1); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (!fs.existsSync(packagesDir)) { |  | ||||||
|     console.error(chalk.red(`Error: Packages directory not found at ${packagesDir}`)); |  | ||||||
|     // eslint-disable-next-line no-undef
 |  | ||||||
|     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 }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line no-undef
 |  | ||||||
| if (process.argv[1] === import.meta.url.slice(7)) { |  | ||||||
|   run(); |  | ||||||
| } |  | ||||||
							
								
								
									
										159
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | |||||||
|  | import fs from 'fs'; | ||||||
|  | import path from 'path'; | ||||||
|  | import chalk from 'chalk'; | ||||||
|  | import yargs from 'yargs'; | ||||||
|  | import { hideBin } from 'yargs/helpers'; | ||||||
|  | import { DependencyChecker } from './lib'; | ||||||
|  | export {DependencyChecker} | ||||||
|  | 
 | ||||||
|  | const cli = async () => { | ||||||
|  |   const yargsInstance = 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: 'Comma-separated list of paths to package.json files or directories containing packages', | ||||||
|  |       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', | ||||||
|  |       type:'string' | ||||||
|  |     }) | ||||||
|  |     .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') | ||||||
|  |     .example('$0 --packages ./packages,./other-packages', 'Check multiple package directories') | ||||||
|  |     .example('$0 --packages ./pkg1/package.json,./pkg2/package.json', 'Check specific package.json files') | ||||||
|  |     .example('$0 --packages ./packages,./other/package.json', 'Mix of directories and files') | ||||||
|  |     .epilogue('For more information, visit: https://github.com/warkanum/monorepo-dep-checker') | ||||||
|  |     .wrap(Math.min(120, process.stdout.columns)) | ||||||
|  |     .version() | ||||||
|  |     .help() | ||||||
|  |     .alias('help', 'h') | ||||||
|  |     .alias('version', 'V'); | ||||||
|  | 
 | ||||||
|  |   // Parse arguments
 | ||||||
|  |   const argv = await yargsInstance.parse(); | ||||||
|  | 
 | ||||||
|  |   // If help or version was requested, exit early
 | ||||||
|  |   // yargs.argv internally tracks if help or version was requested
 | ||||||
|  |   if (argv.help || argv.version) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Resolve paths
 | ||||||
|  |   const appPackageJson = path.resolve(process.cwd(), argv.app); | ||||||
|  |   const packagesInput = argv.packages; | ||||||
|  | 
 | ||||||
|  |   // Validate main app package.json
 | ||||||
|  |   if (!fs.existsSync(appPackageJson)) { | ||||||
|  |     console.error(chalk.red(`Error: Main package.json not found at ${appPackageJson}`)); | ||||||
|  |     process.exit(1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Validate all package paths
 | ||||||
|  |   const paths = packagesInput.split(',').map(p => path.resolve(process.cwd(), p.trim())); | ||||||
|  |   let hasErrors = false; | ||||||
|  | 
 | ||||||
|  |   // biome-ignore lint/complexity/noForEach: Ease of reading and preference
 | ||||||
|  |   paths.forEach(inputPath => { | ||||||
|  |     try { | ||||||
|  |       if (!fs.existsSync(inputPath)) { | ||||||
|  |         console.error(chalk.red(`Error: Path not found: ${inputPath}`)); | ||||||
|  |         hasErrors = true; | ||||||
|  |       } else { | ||||||
|  |         const stats = fs.statSync(inputPath); | ||||||
|  |         if (!stats.isDirectory() && !(stats.isFile() && inputPath.endsWith('package.json'))) { | ||||||
|  |           console.error(chalk.red(`Error: Path must be a directory or package.json file: ${inputPath}`)); | ||||||
|  |           hasErrors = true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } catch (error:any) { | ||||||
|  |       console.error(chalk.red(`Error accessing path ${inputPath}: ${error?.message}`)); | ||||||
|  |       hasErrors = true; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (hasErrors) { | ||||||
|  |     process.exit(1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Validate mutually exclusive options
 | ||||||
|  |   const exclusiveOptions = ['update', 'check-versions', 'check-missing'] | ||||||
|  |     .filter(opt => argv[opt]) | ||||||
|  |     .length; | ||||||
|  | 
 | ||||||
|  |   if (exclusiveOptions > 1) { | ||||||
|  |     console.error(chalk.red('Error: --update, --check-versions, and --check-missing are mutually exclusive')); | ||||||
|  |     process.exit(1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Validate dry-run is only used with update
 | ||||||
|  |   if (argv.dryRun && !argv.update) { | ||||||
|  |     console.error(chalk.yellow('Warning: --dry-run has no effect without --update')); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const checker = new DependencyChecker(appPackageJson, packagesInput); | ||||||
|  |    | ||||||
|  |   try { | ||||||
|  |     await checker.run({ | ||||||
|  |       update: argv.update, | ||||||
|  |       dryRun: argv.dryRun, | ||||||
|  |       format: argv.format as any, | ||||||
|  |       checkVersions: argv.checkVersions, | ||||||
|  |       checkMissing: argv.checkMissing, | ||||||
|  |     }); | ||||||
|  |   } catch (error:any) { | ||||||
|  |     console.error(chalk.red('Error during execution:')); | ||||||
|  |     console.error(error?.message); | ||||||
|  |     if (argv.format === 'json') { | ||||||
|  |       console.log(JSON.stringify({ error: error?.message })); | ||||||
|  |     } | ||||||
|  |     process.exit(1); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Export both the CLI function and run it if this is the main module
 | ||||||
|  | export { cli as default }; | ||||||
|  | 
 | ||||||
|  | if (process.argv[1] === import.meta.url.slice(7)) { | ||||||
|  |   cli().catch(error => { | ||||||
|  |     console.error(chalk.red('Unexpected error:')); | ||||||
|  |     console.error(error); | ||||||
|  |     process.exit(1); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										786
									
								
								src/lib.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										786
									
								
								src/lib.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,786 @@ | |||||||
|  | import fs, { type Dirent } from "fs"; | ||||||
|  | import path from "path"; | ||||||
|  | import chalk from "chalk"; | ||||||
|  | import semver from "semver"; | ||||||
|  | 
 | ||||||
|  | interface PackageJson { | ||||||
|  |   name: string; | ||||||
|  |   dependencies?: Record<string, string>; | ||||||
|  |   devDependencies?: Record<string, string>; | ||||||
|  |   peerDependencies?: Record<string, string>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface RunOptions { | ||||||
|  |   update?: boolean; | ||||||
|  |   dryRun?: boolean; | ||||||
|  |   format?: "text" | "json"; | ||||||
|  |   checkVersions?: boolean; | ||||||
|  |   checkMissing?: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface VersionInfo { | ||||||
|  |   packages: Set<string>; | ||||||
|  |   usages: Set<string>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface DependencyInfo { | ||||||
|  |   versions: Map<string, VersionInfo>; | ||||||
|  |   usedAsNormal: boolean; | ||||||
|  |   usedAsPeer: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface PackagePathInfo { | ||||||
|  |   package: string; | ||||||
|  |   path: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface DependencyComparison { | ||||||
|  |   version1: string; | ||||||
|  |   version2: string; | ||||||
|  |   packages1: PackagePathInfo[]; | ||||||
|  |   packages2: PackagePathInfo[]; | ||||||
|  |   semverDiff: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ConflictingDep { | ||||||
|  |   name: string; | ||||||
|  |   versions: { | ||||||
|  |     version: string; | ||||||
|  |     packages: string[]; | ||||||
|  |     paths: PackagePathInfo[]; | ||||||
|  |     usages: string[]; | ||||||
|  |   }[]; | ||||||
|  |   comparisons: DependencyComparison[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface DependencyUpdate { | ||||||
|  |   package: string; | ||||||
|  |   dependency: string; | ||||||
|  |   from: string; | ||||||
|  |   to: string; | ||||||
|  |   type: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MissingDepsInfo { | ||||||
|  |   path: string; | ||||||
|  |   missing: Array<{ name: string; version: string }>; | ||||||
|  |   extraDeps: Array<{ name: string; version: string }>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DependencyTypes = "dependencies" | "devDependencies" | "peerDependencies"; | ||||||
|  | 
 | ||||||
|  | class DependencyChecker { | ||||||
|  |   private readonly appPackageJsonPath: string; | ||||||
|  |   private readonly packagesInput: string; | ||||||
|  |   private packageJsonFiles: string[]; | ||||||
|  |   private dependencyMap: Map<string, DependencyInfo>; | ||||||
|  |   private workspacePackages: Set<string>; | ||||||
|  | 
 | ||||||
|  |   constructor(appPackageJsonPath: string, packagesInput: string) { | ||||||
|  |     this.appPackageJsonPath = appPackageJsonPath; | ||||||
|  |     this.packagesInput = packagesInput; | ||||||
|  |     this.packageJsonFiles = []; | ||||||
|  |     this.dependencyMap = new Map(); | ||||||
|  |     this.workspacePackages = new Set(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private findWorkspacePackages(): void { | ||||||
|  |     const paths = this.packagesInput.split(",").map((p) => p.trim()); | ||||||
|  | 
 | ||||||
|  |     paths.forEach((inputPath) => { | ||||||
|  |       if (fs.statSync(inputPath).isDirectory()) { | ||||||
|  |         const entries = fs.readdirSync(inputPath, { withFileTypes: true }); | ||||||
|  |         entries.forEach((entry: Dirent) => { | ||||||
|  |           if (entry.isDirectory()) { | ||||||
|  |             const packageJsonPath = path.join( | ||||||
|  |               inputPath, | ||||||
|  |               entry.name, | ||||||
|  |               "package.json" | ||||||
|  |             ); | ||||||
|  |             if (fs.existsSync(packageJsonPath)) { | ||||||
|  |               const packageJson = JSON.parse( | ||||||
|  |                 fs.readFileSync(packageJsonPath, "utf8") | ||||||
|  |               ) as PackageJson; | ||||||
|  |               this.workspacePackages.add(packageJson.name); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } else if (inputPath.endsWith("package.json")) { | ||||||
|  |         const packageJson = JSON.parse( | ||||||
|  |           fs.readFileSync(inputPath, "utf8") | ||||||
|  |         ) as PackageJson; | ||||||
|  |         this.workspacePackages.add(packageJson.name); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private findPackageJsonFiles(): void { | ||||||
|  |     this.packageJsonFiles = [this.appPackageJsonPath]; | ||||||
|  |     const paths = this.packagesInput.split(",").map((p) => p.trim()); | ||||||
|  | 
 | ||||||
|  |     paths.forEach((inputPath) => { | ||||||
|  |       if (fs.statSync(inputPath).isDirectory()) { | ||||||
|  |         const entries = fs.readdirSync(inputPath, { withFileTypes: true }); | ||||||
|  |         entries.forEach((entry: Dirent) => { | ||||||
|  |           if (entry.isDirectory()) { | ||||||
|  |             const packageJsonPath = path.join( | ||||||
|  |               inputPath, | ||||||
|  |               entry.name, | ||||||
|  |               "package.json" | ||||||
|  |             ); | ||||||
|  |             if (fs.existsSync(packageJsonPath)) { | ||||||
|  |               this.packageJsonFiles.push(packageJsonPath); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } else if (inputPath.endsWith("package.json")) { | ||||||
|  |         this.packageJsonFiles.push(inputPath); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private isWorkspaceDep(version?: string): boolean { | ||||||
|  |     return !!version && (version.startsWith("workspace:") || version === "*"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private isWorkspacePackage(packageName: string): boolean { | ||||||
|  |     return this.workspacePackages.has(packageName); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private shouldIncludeDependency( | ||||||
|  |     depName: string, | ||||||
|  |     version: string, | ||||||
|  |     packageJson: PackageJson | ||||||
|  |   ): boolean { | ||||||
|  |     if (this.isWorkspaceDep(version)) return false; | ||||||
|  |     if (this.isWorkspacePackage(depName)) return false; | ||||||
|  | 
 | ||||||
|  |     const allWorkspaceDeps = { | ||||||
|  |       ...packageJson.dependencies, | ||||||
|  |       ...packageJson.devDependencies, | ||||||
|  |       ...packageJson.peerDependencies, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return !Object.entries(allWorkspaceDeps).some( | ||||||
|  |       ([name, ver]) => name === depName && this.isWorkspaceDep(ver) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getSemverVersion(version: string): string | null { | ||||||
|  |     if (version.startsWith("workspace:")) { | ||||||
|  |       const workspaceVersion = version.replace("workspace:", ""); | ||||||
|  |       if (workspaceVersion === "*") return null; | ||||||
|  |       if (semver.valid(workspaceVersion)) return workspaceVersion; | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (version === "*" || version === "latest") return null; | ||||||
|  | 
 | ||||||
|  |     const cleanVersion = version.replace(/^[~^]/, ""); | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       if (semver.valid(cleanVersion)) return cleanVersion; | ||||||
|  |       return null; | ||||||
|  |     } catch { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private checkMissingDependencies(): void { | ||||||
|  |     console.log(chalk.bold("\nChecking for missing dependencies...\n")); | ||||||
|  | 
 | ||||||
|  |     this.findWorkspacePackages(); | ||||||
|  | 
 | ||||||
|  |     const appPackageJson = JSON.parse( | ||||||
|  |       fs.readFileSync(this.appPackageJsonPath, "utf8") | ||||||
|  |     ) as PackageJson; | ||||||
|  |     const appDeps = { | ||||||
|  |       ...appPackageJson.dependencies, | ||||||
|  |       ...appPackageJson.devDependencies, | ||||||
|  |       ...appPackageJson.peerDependencies, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     const missingDeps = new Map<string, MissingDepsInfo>(); | ||||||
|  |     const uniqueMissingDeps = new Set<string>(); | ||||||
|  |     const uniqueExtraDeps = new Set<string>(); | ||||||
|  | 
 | ||||||
|  |     this.packageJsonFiles | ||||||
|  |       .filter((file) => file !== this.appPackageJsonPath) | ||||||
|  |       .forEach((filePath) => { | ||||||
|  |         const packageJson = JSON.parse( | ||||||
|  |           fs.readFileSync(filePath, "utf8") | ||||||
|  |         ) as PackageJson; | ||||||
|  |         const packageName = packageJson.name; | ||||||
|  | 
 | ||||||
|  |         const packageDeps = { | ||||||
|  |           ...packageJson.dependencies, | ||||||
|  |           ...packageJson.peerDependencies, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         const missing: Array<{ name: string; version: string }> = []; | ||||||
|  |         const extraDeps: Array<{ name: string; version: string }> = []; | ||||||
|  | 
 | ||||||
|  |         Object.entries(packageDeps).forEach(([dep, version]) => { | ||||||
|  |           if ( | ||||||
|  |             (!appDeps[dep] && | ||||||
|  |               this.shouldIncludeDependency(dep, version, packageJson)) || | ||||||
|  |             (packageJson.peerDependencies?.[dep] && !appDeps[dep]) | ||||||
|  |           ) { | ||||||
|  |             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(", "))); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private compareDependencyVersions( | ||||||
|  |     version1: string, | ||||||
|  |     version2: string | ||||||
|  |   ): string { | ||||||
|  |     const v1 = this.getSemverVersion(version1); | ||||||
|  |     const v2 = this.getSemverVersion(version2); | ||||||
|  | 
 | ||||||
|  |     if (!v1 || !v2) return "unknown"; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       const diff = semver.diff(v1, v2); | ||||||
|  |       return diff || "unknown"; | ||||||
|  |     } catch { | ||||||
|  |       return "unknown"; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getVersionLabel(version: string): string { | ||||||
|  |     if (version.startsWith("workspace:")) { | ||||||
|  |       return `${chalk.cyan("workspace:")}${version.slice(10)}`; | ||||||
|  |     } | ||||||
|  |     return version; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private analyzeDependencies(): void { | ||||||
|  |     this.findWorkspacePackages(); | ||||||
|  | 
 | ||||||
|  |     this.packageJsonFiles.forEach((filePath) => { | ||||||
|  |       const packageJson = JSON.parse( | ||||||
|  |         fs.readFileSync(filePath, "utf8") | ||||||
|  |       ) as PackageJson; | ||||||
|  |       const normalDeps = packageJson.dependencies || {}; | ||||||
|  |       const peerDeps = packageJson.peerDependencies || {}; | ||||||
|  |       const devDeps = packageJson.devDependencies || {}; | ||||||
|  |       const packageName = packageJson.name; | ||||||
|  |       const isMainApp = filePath === this.appPackageJsonPath; | ||||||
|  | 
 | ||||||
|  |       // Process all dependency types
 | ||||||
|  |       [ | ||||||
|  |         { deps: normalDeps, type: "normal" }, | ||||||
|  |         { deps: peerDeps, type: "peer" }, | ||||||
|  |         { deps: devDeps, type: "dev" }, | ||||||
|  |       ].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; | ||||||
|  | 
 | ||||||
|  |           // Track workspace dependencies explicitly
 | ||||||
|  |           if (this.isWorkspaceDep(version)) { | ||||||
|  |             versionInfo.usages.add("workspace"); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private displayVersionDifferences(): void { | ||||||
|  |     console.log(chalk.bold("\nVersion Differences Summary:\n")); | ||||||
|  | 
 | ||||||
|  |     let hasDifferences = false; | ||||||
|  |     const conflictingDeps: ConflictingDep[] = []; | ||||||
|  | 
 | ||||||
|  |     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") | ||||||
|  |                 ) as PackageJson; | ||||||
|  |                 return json.name === pkg; | ||||||
|  |               }); | ||||||
|  |               return { | ||||||
|  |                 package: pkg, | ||||||
|  |                 path: path.relative(process.cwd(), filePath || ""), | ||||||
|  |               }; | ||||||
|  |             }), | ||||||
|  |             usages: Array.from(info.usages), | ||||||
|  |           }) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const comparisons: DependencyComparison[] = []; | ||||||
|  |         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]; | ||||||
|  |             comparisons.push({ | ||||||
|  |               version1: v1.version, | ||||||
|  |               version2: v2.version, | ||||||
|  |               packages1: v1.paths, | ||||||
|  |               packages2: v2.paths, | ||||||
|  |               semverDiff: this.compareDependencyVersions( | ||||||
|  |                 v1.version, | ||||||
|  |                 v2.version | ||||||
|  |               ), | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         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)})`); | ||||||
|  |           }); | ||||||
|  | 
 | ||||||
|  |           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" | ||||||
|  |                   ) | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     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}`); | ||||||
|  | 
 | ||||||
|  |     const severityCount: Record<string, number> = {}; | ||||||
|  |     // biome-ignore lint/complexity/noForEach: <explanation>
 | ||||||
|  |     conflictingDeps.forEach((dep) => { | ||||||
|  |       dep.comparisons.forEach(({ version1, version2, semverDiff }) => { | ||||||
|  |         if ( | ||||||
|  |           version1.startsWith("workspace:") || | ||||||
|  |           version2.startsWith("workspace:") | ||||||
|  |         ) { | ||||||
|  |           severityCount.workspace = (severityCount.workspace || 0) + 1; | ||||||
|  |         } else { | ||||||
|  |           severityCount[semverDiff || "unknown"] = | ||||||
|  |             (severityCount[semverDiff || "unknown"] || 0) + 1; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     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}`) | ||||||
|  |       ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async updateDependencies( | ||||||
|  |     dryRun = false | ||||||
|  |   ): Promise<DependencyUpdate[]> { | ||||||
|  |     console.log(chalk.bold("\nUpdating dependencies...\n")); | ||||||
|  | 
 | ||||||
|  |     const appPackageJson = JSON.parse( | ||||||
|  |       fs.readFileSync(this.appPackageJsonPath, "utf8") | ||||||
|  |     ) as PackageJson; | ||||||
|  |     const appDependencies = { | ||||||
|  |       ...appPackageJson.dependencies, | ||||||
|  |       ...appPackageJson.devDependencies, | ||||||
|  |       ...appPackageJson.peerDependencies, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     const updates: DependencyUpdate[] = []; | ||||||
|  | 
 | ||||||
|  |     this.packageJsonFiles | ||||||
|  |       .filter((filePath) => filePath !== this.appPackageJsonPath) | ||||||
|  |       .forEach((filePath) => { | ||||||
|  |         const packageJson = JSON.parse( | ||||||
|  |           fs.readFileSync(filePath, "utf8") | ||||||
|  |         ) as PackageJson; | ||||||
|  |         let hasUpdates = false; | ||||||
|  | 
 | ||||||
|  |         const updateDependencySection = (section: DependencyTypes): void => { | ||||||
|  |           if (!packageJson[section]) return; | ||||||
|  | 
 | ||||||
|  |           Object.entries(packageJson[section]!).forEach(([dep, version]) => { | ||||||
|  |             if (version.startsWith("workspace:")) return; | ||||||
|  | 
 | ||||||
|  |             if (appDependencies[dep]) { | ||||||
|  |               const appVersion = appDependencies[dep]; | ||||||
|  |               if (version !== appVersion) { | ||||||
|  |                 if (!dryRun) { | ||||||
|  |                   packageJson[section]![dep] = appVersion; | ||||||
|  |                 } | ||||||
|  |                 updates.push({ | ||||||
|  |                   package: packageJson.name, | ||||||
|  |                   dependency: dep, | ||||||
|  |                   from: version, | ||||||
|  |                   to: appVersion, | ||||||
|  |                   type: section, | ||||||
|  |                 }); | ||||||
|  |                 hasUpdates = true; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         updateDependencySection("dependencies"); | ||||||
|  |         updateDependencySection("devDependencies"); | ||||||
|  |         updateDependencySection("peerDependencies"); | ||||||
|  | 
 | ||||||
|  |         if (hasUpdates && !dryRun) { | ||||||
|  |           fs.writeFileSync( | ||||||
|  |             filePath, | ||||||
|  |             JSON.stringify(packageJson, null, 2) + "\n" | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |     if (updates.length === 0) { | ||||||
|  |       console.log( | ||||||
|  |         chalk.green("No updates needed - all versions match the main app") | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       updates.forEach(({ package: pkgName, dependency, from, to, type }) => { | ||||||
|  |         console.log( | ||||||
|  |           chalk.green( | ||||||
|  |             `${dryRun ? "[DRY RUN] Would update" : "Updated"} ${dependency} in ${pkgName} (${type})\n` + | ||||||
|  |               `  ${chalk.red(from)} → ${chalk.green(to)}` | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return updates; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private displayResults(format: "text" | "json" = "text"): void { | ||||||
|  |     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") | ||||||
|  |                       ) as PackageJson; | ||||||
|  |                       return json.name === pkg; | ||||||
|  |                     }); | ||||||
|  |                     return { | ||||||
|  |                       package: pkg, | ||||||
|  |                       path: path.relative(process.cwd(), filePath || ""), | ||||||
|  |                     }; | ||||||
|  |                   }), | ||||||
|  |                   usages: Array.from(info.usages), | ||||||
|  |                   isWorkspace: this.isWorkspaceDep(version), | ||||||
|  |                 }) | ||||||
|  |               ), | ||||||
|  |             })), | ||||||
|  |         }, | ||||||
|  |         fullAnalysis: Object.fromEntries( | ||||||
|  |           Array.from(this.dependencyMap.entries()).map(([dep, depInfo]) => [ | ||||||
|  |             dep, | ||||||
|  |             { | ||||||
|  |               versions: Object.fromEntries( | ||||||
|  |                 Array.from(depInfo.versions.entries()).map( | ||||||
|  |                   ([version, info]) => [ | ||||||
|  |                     version, | ||||||
|  |                     { | ||||||
|  |                       packages: Array.from(info.packages), | ||||||
|  |                       usages: Array.from(info.usages), | ||||||
|  |                       isWorkspace: this.isWorkspaceDep(version), | ||||||
|  |                     }, | ||||||
|  |                   ] | ||||||
|  |                 ) | ||||||
|  |               ), | ||||||
|  |               usedAsNormal: depInfo.usedAsNormal, | ||||||
|  |               usedAsPeer: depInfo.usedAsPeer, | ||||||
|  |             }, | ||||||
|  |           ]) | ||||||
|  |         ), | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       console.log(JSON.stringify(output, null, 2)); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (format === "text") { | ||||||
|  |       this.displayVersionDifferences(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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") | ||||||
|  |                     ) as PackageJson; | ||||||
|  |                     return json.name === pkg; | ||||||
|  |                   }); | ||||||
|  |                   return { | ||||||
|  |                     package: pkg, | ||||||
|  |                     path: path.relative(process.cwd(), filePath || ""), | ||||||
|  |                   }; | ||||||
|  |                 }), | ||||||
|  |                 usages: Array.from(info.usages), | ||||||
|  |               }) | ||||||
|  |             ), | ||||||
|  |           })), | ||||||
|  |       }, | ||||||
|  |       fullAnalysis: Object.fromEntries( | ||||||
|  |         Array.from(this.dependencyMap.entries()).map(([dep, depInfo]) => [ | ||||||
|  |           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)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public async run(options: RunOptions = {}): Promise<void> { | ||||||
|  |     const { | ||||||
|  |       update = false, | ||||||
|  |       dryRun = false, | ||||||
|  |       format = "text", | ||||||
|  |       checkVersions = false, | ||||||
|  |       checkMissing = false, | ||||||
|  |     } = options; | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.findPackageJsonFiles(); | ||||||
|  |     this.analyzeDependencies(); | ||||||
|  |     this.displayResults(format); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { DependencyChecker }; | ||||||
							
								
								
									
										13
									
								
								tests/app/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/app/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "name": "test-app", | ||||||
|  |     "version": "1.0.0", | ||||||
|  |     "dependencies": { | ||||||
|  |       "react": "^18.2.0", | ||||||
|  |       "lodash": "^4.17.21", | ||||||
|  |       "express": "^4.18.2" | ||||||
|  |     }, | ||||||
|  |     "devDependencies": { | ||||||
|  |       "typescript": "^5.0.0", | ||||||
|  |       "jest": "^29.0.0" | ||||||
|  |     } | ||||||
|  |   } | ||||||
							
								
								
									
										156
									
								
								tests/dependency-checker.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								tests/dependency-checker.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,156 @@ | |||||||
|  | import { describe, test, expect, beforeEach, vi } from 'vitest'; | ||||||
|  | import fs from 'fs'; | ||||||
|  | import path from 'path'; | ||||||
|  | import { DependencyChecker } from '../src/lib'; | ||||||
|  | 
 | ||||||
|  | // Mock console.log to prevent output during tests
 | ||||||
|  | const _log = console.log | ||||||
|  | console.log = vi.fn(); | ||||||
|  | console.error = vi.fn(); | ||||||
|  | 
 | ||||||
|  | describe('DependencyChecker', () => { | ||||||
|  |   const appPackageJsonPath = path.resolve(__dirname, './app/package.json'); | ||||||
|  |   const packagesPath = path.resolve(__dirname, './packages'); | ||||||
|  |   let checker: DependencyChecker; | ||||||
|  | 
 | ||||||
|  |   beforeEach(() => { | ||||||
|  |     checker = new DependencyChecker(appPackageJsonPath, packagesPath); | ||||||
|  |     // Clear console mocks before each test
 | ||||||
|  |     console.log = vi.fn(); | ||||||
|  |     console.error = vi.fn(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should correctly identify version differences', async () => { | ||||||
|  |     await checker.run({ checkVersions: true }); | ||||||
|  |      | ||||||
|  |     expect(console.log).toHaveBeenCalledWith( | ||||||
|  |       expect.stringContaining('Version Differences Summary') | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     // Verify major version difference detection
 | ||||||
|  |     const calls = (console.log as jest.Mock).mock.calls.map(call => call[0]).join('\n'); | ||||||
|  |     expect(calls).toContain('Major version difference'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should detect missing dependencies', async () => { | ||||||
|  |     await checker.run({ checkMissing: true }); | ||||||
|  |      | ||||||
|  |     expect(console.log).toHaveBeenCalledWith( | ||||||
|  |       expect.stringContaining('Checking for missing dependencies') | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should handle update with dry run', async () => { | ||||||
|  |     const originalContent = fs.readFileSync( | ||||||
|  |       path.join(packagesPath, 'pkg1/package.json'), | ||||||
|  |       'utf8' | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |     await checker.run({ update: true, dryRun: true }); | ||||||
|  |      | ||||||
|  |     const newContent = fs.readFileSync( | ||||||
|  |       path.join(packagesPath, 'pkg1/package.json'), | ||||||
|  |       'utf8' | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |     expect(originalContent).toBe(newContent); | ||||||
|  |     expect(console.log).toHaveBeenCalledWith( | ||||||
|  |       expect.stringContaining('[DRY RUN]') | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should output JSON format correctly', async () => { | ||||||
|  |     const consoleSpy = vi.spyOn(console, 'log'); | ||||||
|  |      | ||||||
|  |     await checker.run({ format: 'json' }); | ||||||
|  |      | ||||||
|  |     const lastCall = consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0]; | ||||||
|  |      | ||||||
|  |     expect(() => JSON.parse(lastCall)).not.toThrow(); | ||||||
|  |     const output = JSON.parse(lastCall); | ||||||
|  |     expect(output).toHaveProperty('summary'); | ||||||
|  |     expect(output).toHaveProperty('fullAnalysis'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should handle multiple package paths', async () => { | ||||||
|  |     const multiPathChecker = new DependencyChecker( | ||||||
|  |       appPackageJsonPath, | ||||||
|  |       `${packagesPath},${path.join(packagesPath, 'pkg1/package.json')}` | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |     await multiPathChecker.run({ checkVersions: true }); | ||||||
|  |      | ||||||
|  |     // Verify that dependencies from both paths are processed
 | ||||||
|  |     const calls = (console.log as jest.Mock).mock.calls.map(call => call[0]).join('\n'); | ||||||
|  |     expect(calls).toContain('Version Differences Summary'); | ||||||
|  |     expect(calls).toContain('pkg1'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should identify workspace dependencies', async () => { | ||||||
|  |     // First create test files with workspace deps
 | ||||||
|  |     const pkg3Path = path.join(packagesPath, 'pkg3/package.json'); | ||||||
|  |     const pkg3Content = { | ||||||
|  |       name: "pkg3", | ||||||
|  |       version: "1.0.0", | ||||||
|  |       dependencies: { | ||||||
|  |         "workspace-pkg": "workspace:*", | ||||||
|  |         "react": "^18.2.0" | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |      // Ensure directory exists
 | ||||||
|  |      fs.mkdirSync(path.dirname(pkg3Path), { recursive: true }); | ||||||
|  |      fs.writeFileSync(pkg3Path, JSON.stringify(pkg3Content, null, 2)); | ||||||
|  | 
 | ||||||
|  |     // Run with default options to get full analysis
 | ||||||
|  |     await checker.run({ format: 'json' }); | ||||||
|  |      | ||||||
|  |     // Get the last console.log call
 | ||||||
|  |     const calls = (console.log as jest.Mock).mock.calls; | ||||||
|  |     expect(calls.length).toBeGreaterThan(0); | ||||||
|  |      | ||||||
|  |     const lastCall = calls[calls.length - 1][0]; | ||||||
|  |     expect(() => JSON.parse(lastCall)).not.toThrow(); | ||||||
|  |      | ||||||
|  |     const output = JSON.parse(lastCall); | ||||||
|  |     expect(output.fullAnalysis).toHaveProperty('workspace-pkg'); | ||||||
|  |      | ||||||
|  |     // Clean up
 | ||||||
|  |     fs.unlinkSync(pkg3Path); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   test('should handle errors gracefully', async () => { | ||||||
|  |     const invalidChecker = new DependencyChecker( | ||||||
|  |       'invalid/path/package.json', | ||||||
|  |       packagesPath | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |     await expect(invalidChecker.run()).rejects.toThrow(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should handle empty package directories', async () => { | ||||||
|  |     // Create a temporary empty directory
 | ||||||
|  |     const emptyDir = path.join(__dirname, 'empty'); | ||||||
|  |     fs.mkdirSync(emptyDir, { recursive: true }); | ||||||
|  |      | ||||||
|  |     const emptyChecker = new DependencyChecker(appPackageJsonPath, emptyDir); | ||||||
|  |     await emptyChecker.run({ checkVersions: true }); | ||||||
|  |      | ||||||
|  |     // Clean up
 | ||||||
|  |     fs.rmdirSync(emptyDir); | ||||||
|  |      | ||||||
|  |     // Verify appropriate handling of empty directory
 | ||||||
|  |     const calls = (console.log as jest.Mock).mock.calls.map(call => call[0]).join('\n'); | ||||||
|  |     expect(calls).toContain('No version differences found'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('should detect version conflicts correctly', async () => { | ||||||
|  |     await checker.run({ checkVersions: true }); | ||||||
|  |      | ||||||
|  |     const calls = (console.log as jest.Mock).mock.calls.map(call => call[0]).join('\n'); | ||||||
|  |     expect(calls).toMatch(/Difference \((?:major|minor|patch)\)/); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | }); | ||||||
							
								
								
									
										11
									
								
								tests/packages/pkg1/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/packages/pkg1/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | { | ||||||
|  |   "name": "pkg1", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "dependencies": { | ||||||
|  |     "react": "^17.0.2", | ||||||
|  |     "lodash": "^4.17.21" | ||||||
|  |   }, | ||||||
|  |   "peerDependencies": { | ||||||
|  |     "typescript": "^4.9.0" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								tests/packages/pkg2/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tests/packages/pkg2/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |     "name": "pkg2", | ||||||
|  |     "version": "1.0.0", | ||||||
|  |     "dependencies": { | ||||||
|  |       "express": "^4.17.1",  | ||||||
|  |       "moment": "^2.29.4"    | ||||||
|  |     } | ||||||
|  |   } | ||||||
							
								
								
									
										20
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | { | ||||||
|  |   "include": ["*.d.ts", "./src"], | ||||||
|  |   "exclude": ["public", "dist", "./dist", "node_modules", "vite.config.js"], | ||||||
|  |   "compilerOptions": { | ||||||
|  |     "types": ["node"], | ||||||
|  |     "target": "ES6", | ||||||
|  |     "lib": ["DOM", "ESNext"], | ||||||
|  |     "module": "ESNext", | ||||||
|  |     "moduleResolution": "Node", | ||||||
|  |     "jsx": "react-jsx", | ||||||
|  |     "resolveJsonModule": true, | ||||||
|  |     "esModuleInterop": true, | ||||||
|  |     "skipLibCheck": true, | ||||||
|  |     "noEmit": true, | ||||||
|  |     "strict": true, | ||||||
|  |     "paths": { | ||||||
|  |       "@/*": ["./*"] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -4,10 +4,12 @@ import { resolve } from 'path'; | |||||||
| export default defineConfig({ | export default defineConfig({ | ||||||
|   build: { |   build: { | ||||||
|     lib: { |     lib: { | ||||||
|       entry: resolve(__dirname, 'src/index.js'), |       // eslint-disable-next-line no-undef
 | ||||||
|  |       entry: resolve(__dirname, 'src/index.ts'), | ||||||
|       formats: ['es'], |       formats: ['es'], | ||||||
|       fileName: 'index' |       fileName: 'index' | ||||||
|     }, |     }, | ||||||
|  |     ssr:true, | ||||||
|     rollupOptions: { |     rollupOptions: { | ||||||
|       external: [ |       external: [ | ||||||
|         'fs', |         'fs', | ||||||
| @ -17,10 +19,12 @@ export default defineConfig({ | |||||||
|         'semver', |         'semver', | ||||||
|         'yargs', |         'yargs', | ||||||
|         'yargs/helpers' |         'yargs/helpers' | ||||||
|       ] |       ], | ||||||
|     }, |     }, | ||||||
|     target: 'node14', |     target: 'node14', | ||||||
|  |      | ||||||
|     outDir: 'dist', |     outDir: 'dist', | ||||||
|  |      | ||||||
|     emptyOutDir: true, |     emptyOutDir: true, | ||||||
|     sourcemap: true |     sourcemap: true | ||||||
|   } |   } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user