Skip to content

Instantly share code, notes, and snippets.

@bearzk
Created January 19, 2026 10:18
Show Gist options
  • Select an option

  • Save bearzk/976b6bdab9ffc74842b64ac387c88a90 to your computer and use it in GitHub Desktop.

Select an option

Save bearzk/976b6bdab9ffc74842b64ac387c88a90 to your computer and use it in GitHub Desktop.
compare 2 npm lock files
#!/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