Skip to content

Instantly share code, notes, and snippets.

@cobaltt7
Last active May 3, 2025 03:56
Show Gist options
  • Save cobaltt7/cb7ed4b65503fefaa7f80f1a07fa5b2d to your computer and use it in GitHub Desktop.
Save cobaltt7/cb7ed4b65503fefaa7f80f1a07fa5b2d to your computer and use it in GitHub Desktop.
Code that can be used in a bookmarklet on PRs to better compare changes in NPM lockfiles.
async function main(token) {
const headers = token ? { Authorization: `token ${token}` } : {};
const prMatch = location.pathname.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
if (!prMatch) return alert("Not a PR page.");
const [, owner, repo, prNumber] = prMatch;
const prRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`, {
headers,
});
const pr = await prRes.json();
const filesRes = await fetch(
`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files`,
{ headers },
);
const files = await filesRes.json();
const lockFiles = files.filter(({ filename }) =>
/(?:package-lock\.json|npm-shrinkwrap\.json)$/.test(filename),
);
if (lockFiles.length === 0) return alert("No lockfiles changed.");
const popup = window.open();
if (!popup) return alert("Please allow popups.");
for (const file of lockFiles) {
const baseUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${pr.base.ref}/${file.filename}`;
const headUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${pr.head.ref}/${file.filename}`;
const [base, head] = await Promise.all([
fetch(baseUrl, { headers }).then((response) => response.json()),
fetch(headUrl, { headers }).then((response) => response.json()),
]);
popup.document.write(
`<details>${
Object.assign(document.createElement("summary"), { innerText: file.filename })
.outerHTML
}<br>${compare(base.packages, head.packages)}</details>`,
);
}
popup.close();
}
function compare(oldPackages, newPackages) {
const changes = new Map();
for (const packageName in newPackages) {
const parsedName =
packageName.split("node_modules/").at(-1) || newPackages[packageName].name;
if (!oldPackages[packageName]) {
const { version } = newPackages[packageName];
changes.set(
Object.assign(document.createElement("a"), {
innerText: `Installed ${parsedName}@${version}`,
href: `https://npmjs.com/package/${parsedName}/v/${version}`,
}).outerHTML,
parsedName,
);
continue;
}
const oldVersion = oldPackages[packageName].version;
const newVersion = newPackages[packageName].version;
if (oldVersion === newVersion) continue;
changes.set(
Object.assign(document.createElement("a"), {
innerText: `Bumped ${parsedName}@${oldVersion} to ${newVersion}`,
href: `https://app.renovatebot.com/package-diff?${new URLSearchParams({
name: parsedName,
from: oldVersion,
to: newVersion,
})}`,
}).outerHTML,
parsedName,
);
}
for (const packageName in oldPackages) {
if (newPackages[packageName]) continue;
const parsedName =
packageName.split("node_modules/").at(-1) || oldPackages[packageName].name;
const { version } = oldPackages[packageName];
changes.set(
Object.assign(document.createElement("a"), {
innerText: `Removed ${parsedName}@${version}`,
href: `https://npmjs.com/package/${parsedName}/v/${version}`,
}).outerHTML,
parsedName,
);
}
return (
[...changes]
.sort((one, two) => one[1] - two[1] || one[0] - two[0])
.map((change) => change[0])
.join("<br>") || "<p>No dependencies bumped.</p>"
);
}
window.lockfileDiff = main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment