Created
January 19, 2026 10:18
-
-
Save bearzk/976b6bdab9ffc74842b64ac387c88a90 to your computer and use it in GitHub Desktop.
compare 2 npm lock files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env tsx | |
| /** | |
| * Compare two package-lock.json files and show differences | |
| * | |
| * Usage: | |
| * npm run compare-lockfiles <file1> <file2> [--filter=pattern] | |
| * | |
| * Examples: | |
| * npm run compare-lockfiles package-lock.json main-package-lock.json | |
| * npm run compare-lockfiles package-lock.json main-package-lock.json --filter=sharp | |
| * npm run compare-lockfiles package-lock.json main-package-lock.json --filter=@img | |
| */ | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| interface PackageLockPackage { | |
| version?: string; | |
| resolved?: string; | |
| integrity?: string; | |
| dev?: boolean; | |
| optional?: boolean; | |
| cpu?: string[]; | |
| os?: string[]; | |
| } | |
| interface PackageLock { | |
| packages: Record<string, PackageLockPackage>; | |
| } | |
| function loadLockFile(filePath: string): PackageLock { | |
| const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); | |
| if (!fs.existsSync(absolutePath)) { | |
| console.error(`β File not found: ${absolutePath}`); | |
| process.exit(1); | |
| } | |
| try { | |
| const content = fs.readFileSync(absolutePath, 'utf-8'); | |
| return JSON.parse(content); | |
| } catch (error) { | |
| console.error(`β Error parsing ${filePath}:`, error); | |
| process.exit(1); | |
| } | |
| } | |
| function comparePackages( | |
| pkg1: Record<string, PackageLockPackage>, | |
| pkg2: Record<string, PackageLockPackage>, | |
| filter?: string | |
| ): void { | |
| const allKeys = new Set([...Object.keys(pkg1), ...Object.keys(pkg2)]); | |
| const added: string[] = []; | |
| const removed: string[] = []; | |
| const changed: Array<{ name: string; from: string; to: string }> = []; | |
| const unchanged = new Set<string>(); | |
| for (const key of allKeys) { | |
| // Apply filter if specified | |
| if (filter && !key.includes(filter)) { | |
| continue; | |
| } | |
| const p1 = pkg1[key]; | |
| const p2 = pkg2[key]; | |
| if (!p1 && p2) { | |
| added.push(key); | |
| } else if (p1 && !p2) { | |
| removed.push(key); | |
| } else if (p1 && p2) { | |
| if (p1.version !== p2.version) { | |
| changed.push({ | |
| name: key, | |
| from: p1.version || 'unknown', | |
| to: p2.version || 'unknown', | |
| }); | |
| } else { | |
| unchanged.add(key); | |
| } | |
| } | |
| } | |
| // Print results | |
| console.log('\nπ Package Lock Comparison Results\n'); | |
| console.log('β'.repeat(80)); | |
| if (added.length > 0) { | |
| console.log('\n⨠Added Packages:', added.length); | |
| console.log('β'.repeat(80)); | |
| added.sort().forEach(name => { | |
| const pkg = pkg2[name]; | |
| const version = pkg?.version || 'unknown'; | |
| const platform = getPlatformInfo(pkg); | |
| console.log(` + ${name}@${version}${platform}`); | |
| }); | |
| } | |
| if (removed.length > 0) { | |
| console.log('\nποΈ Removed Packages:', removed.length); | |
| console.log('β'.repeat(80)); | |
| removed.sort().forEach(name => { | |
| const pkg = pkg1[name]; | |
| const version = pkg?.version || 'unknown'; | |
| const platform = getPlatformInfo(pkg); | |
| console.log(` - ${name}@${version}${platform}`); | |
| }); | |
| } | |
| if (changed.length > 0) { | |
| console.log('\nπ Changed Versions:', changed.length); | |
| console.log('β'.repeat(80)); | |
| changed.sort((a, b) => a.name.localeCompare(b.name)).forEach(({ name, from, to }) => { | |
| const pkg = pkg2[name]; | |
| const platform = getPlatformInfo(pkg); | |
| console.log(` ~ ${name}: ${from} β ${to}${platform}`); | |
| }); | |
| } | |
| if (!filter && unchanged.size > 0) { | |
| console.log(`\nβ Unchanged: ${unchanged.size} packages`); | |
| } | |
| console.log('\n' + 'β'.repeat(80)); | |
| console.log(`\nTotal packages compared: ${allKeys.size}`); | |
| console.log(` Added: ${added.length} | Removed: ${removed.length} | Changed: ${changed.length} | Unchanged: ${unchanged.size}\n`); | |
| } | |
| function getPlatformInfo(pkg?: PackageLockPackage): string { | |
| if (!pkg) return ''; | |
| const parts: string[] = []; | |
| if (pkg.cpu && pkg.cpu.length > 0) { | |
| parts.push(`cpu=${pkg.cpu.join(',')}`); | |
| } | |
| if (pkg.os && pkg.os.length > 0) { | |
| parts.push(`os=${pkg.os.join(',')}`); | |
| } | |
| if (pkg.optional) { | |
| parts.push('optional'); | |
| } | |
| return parts.length > 0 ? ` [${parts.join(', ')}]` : ''; | |
| } | |
| function main() { | |
| const args = process.argv.slice(2); | |
| if (args.length < 2) { | |
| console.log('Usage: npm run compare-lockfiles <file1> <file2> [--filter=pattern]'); | |
| console.log(''); | |
| console.log('Examples:'); | |
| console.log(' npm run compare-lockfiles package-lock.json main-package-lock.json'); | |
| console.log(' npm run compare-lockfiles package-lock.json main-package-lock.json --filter=sharp'); | |
| console.log(' npm run compare-lockfiles package-lock.json main-package-lock.json --filter=@img'); | |
| process.exit(1); | |
| } | |
| const file1 = args[0]; | |
| const file2 = args[1]; | |
| const filterArg = args.find(arg => arg.startsWith('--filter=')); | |
| const filter = filterArg ? filterArg.split('=')[1] : undefined; | |
| console.log(`\nπ Comparing package lock files:`); | |
| console.log(` File 1: ${file1}`); | |
| console.log(` File 2: ${file2}`); | |
| if (filter) { | |
| console.log(` Filter: "${filter}"`); | |
| } | |
| const lock1 = loadLockFile(file1); | |
| const lock2 = loadLockFile(file2); | |
| comparePackages(lock1.packages, lock2.packages, filter); | |
| } | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment