Skip to content

Instantly share code, notes, and snippets.

@mmazzarolo
Last active June 26, 2021 14:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmazzarolo/64c7b0c48085f3ce05ebb2fc3d6c62f7 to your computer and use it in GitHub Desktop.
Save mmazzarolo/64c7b0c48085f3ce05ebb2fc3d6c62f7 to your computer and use it in GitHub Desktop.
Bump the version number of an NPM dependency and commits the changes in a new branch
// Just a Deno script that bumps an NPM dependency of the project
// in the current directory.
// Might be helpful if you need to automate the bumping of a
// dependency across multiple repos.
// It:
// - Makes sure you start from a clean state
// - Auto-detects if using yarn or npm
// - Creates, commits, and pushes the changes in a separate branch
import * as Colors from "https://deno.land/std/fmt/colors.ts";
import yargs from "https://deno.land/x/yargs/deno.ts";
import { existsSync } from "https://deno.land/std/fs/mod.ts";
import { iter } from "https://deno.land/std/io/util.ts";
function print(str: string) {
console.log(Colors.blue(str));
}
interface Arguments {
depName: string;
depTargetVersion: string;
packageManager: string;
defaultBranch: string;
push: boolean;
verbose: boolean;
}
const argv: Arguments = yargs(Deno.args)
.usage(
"Bumps the version number of an NPM dependency and commits the changes in a new branch.",
)
.option("dep-name", {
type: "string",
description: "Dependency name",
demandOption: true,
})
.option("dep-target-version", {
type: "string",
description: "Desired dependency version (defaults to latest)",
})
.option("package-manager", {
type: "string",
description: "'yarn' or 'npm' (defaults to auto-detect)",
})
.option("default-branch", {
type: "string",
description: "Default branch name (defaults to auto-detect)",
})
.option("push", {
type: "boolean",
description: "Push the generated branch?",
default: false,
})
.option("verbose", {
type: "boolean",
description: "Show CLI output",
default: true,
})
.parseSync();
async function run(params: Arguments) {
let {
depName,
depTargetVersion,
packageManager,
defaultBranch,
push,
verbose,
} = params;
const decoder = new TextDecoder();
async function exec(cmd: string) {
const p = Deno.run({
cmd: ["/bin/sh", "-c", cmd],
stdout: "piped",
stderr: "piped",
});
let stdout = "";
let stderr = "";
const stdoutPromise = (async function () {
for await (const chunk of iter(p.stdout)) {
const decoded = decoder.decode(chunk);
if (verbose) await Deno.stdout.write(chunk);
stdout += decoded;
}
p.stdout.close();
})();
const stderrPromise = (async function () {
for await (const chunk of iter(p.stderr)) {
const decoded = decoder.decode(chunk);
if (verbose) await Deno.stderr.write(chunk);
stderr += decoded;
}
p.stderr.close();
})();
const { success } = await p.status();
await Promise.all([stdoutPromise, stderrPromise]);
p.close();
if (!success) {
throw new Error(stderr);
}
return stdout.trim();
}
print(
`Bumping "${depName}"${
depTargetVersion ? ` to v${depTargetVersion}` : ""
}.`,
);
if (!packageManager) {
packageManager = existsSync("./yarn.lock") ? "yarn" : "npm";
}
print(`The project is using ${packageManager}.`);
if (!depTargetVersion) {
print(`Checking the latest "${depName}" version...`);
}
depTargetVersion = await exec(`npm show ${depName} version`);
print(`Latest version is v${depTargetVersion}`);
if (!existsSync("./package.json")) {
throw new Error(`./package.json does not exist`);
}
const packageJson = JSON.parse(await Deno.readTextFile("./package.json"));
let dependencyType;
let depCurrentVersion;
["dependencies", "devDependencies", "peerDependencies"].forEach(
(packageDependencyType) => {
if (packageJson?.[packageDependencyType]?.[depName]) {
dependencyType = packageDependencyType;
depCurrentVersion = packageJson?.[packageDependencyType]?.[depName]
?.trim();
}
},
);
if (!dependencyType) {
throw new Error(
`Couldn't find "${depName}" in package.json`,
);
}
if (depCurrentVersion === depTargetVersion) {
print(`"${depName}" in package.json is already at ${depTargetVersion} 👍`);
return;
}
print(
`"${depName}" is currenty installed as a ${dependencyType} at version ${depCurrentVersion}`,
);
if (!defaultBranch) {
defaultBranch = await exec(
`git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'`,
);
}
print(`Running checkout of the "${defaultBranch}" branch...`);
await exec(`git checkout '${defaultBranch}' || exit 1`);
await exec(`git pull origin '${defaultBranch}' || exit 1`);
const branchName = `bump-${depName}-${depTargetVersion}`;
print(`Creating a new branch "${branchName}" branch...`);
await exec(`git checkout -b '${branchName}' || exit 1;`);
print(`Bumping ${depName}...`);
const lib = `${depName}@${depTargetVersion}`;
if (packageManager === "yarn") {
if (dependencyType === "devDependency") {
await exec(`yarn -D add ${lib}`);
} else {
await exec(`yarn add ${lib}`);
}
} else {
if (dependencyType === "devDependency") {
await exec(`npm i -D ${lib}`);
} else {
await exec(`npm i ${lib}`);
}
}
print("Committing the changes...");
if (packageManager === "yarn") {
await exec(`git add package.json yarn.lock`);
} else {
await exec(`git add package.json package-lock.json`);
}
const commitTitle = `Bump \`${depName}\` to \`${depTargetVersion}\``;
const commitDescription = `Bump \`${depName}\` to \`${depTargetVersion}\``;
await exec(`git commit -m '${commitTitle}' -m '${commitDescription}' -n`);
if (push) {
print("Pushing the changes...");
await exec(`git push origin '${branchName}' --no-verify`);
print(
`All good 👍, your bump has been pushed to the "${branchName}" branch`,
);
} else {
print(`All good 👍, run "git push origin ${branchName}" when you're ready`);
}
}
try {
await run(argv);
} catch (err) {
console.error(Colors.bold(Colors.red("Error:")), err?.message);
}
@mmazzarolo
Copy link
Author

Usage:

deno run --unstable --allow-run --allow-read https://gist.githubusercontent.com/mmazzarolo/64c7b0c48085f3ce05ebb2fc3d6c62f7/raw/bump-npm-dep.ts --dep-name react

Must be run with --unstable because its required to run existsSync (yes, even if it comes from stdlib)

@mmazzarolo
Copy link
Author

You can then pipe | gh pr create to easily create the Github PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment