2021-09-12
|~3 min read
|529 words
I spent a few hours this weekend writing a Github action to accomplish a different way of managing the versions of my library.
I think the final answer is actually quite readable, which makes me quite happy!
First, the Github workflow, saved in .github/workflows/bumpVersionOnPushToMain.yml
:
name: "Update the package version on pushes to main"
on:
push:
branches:
- main
jobs:
scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: chmod +x ./scripts/incrementMinor.js
- run: ./scripts/incrementMinor.js
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "Automated version bump on merge to main"
One of the things I stumbled over as this was one of the first Github Action I’d written was how I could access the actual code. It turns out that’s exactly what actions/checkout@v2 is for - and by default it checks out the current repository.
Before I was using that action, the following lines which assume the presence of files failed.
Now, let’s step through the actual pieces.
The script of incrementMinor
is just a few lines of code. In time I’d love to make this “smarter” so that it can receive arguments to dictate which version to increment.
#!/usr/bin/env node
const { incrementVersion } = require("./incrementVersion")
function incMinorVersion(version) {
const [major, minor, patch] = version.split(".")
if (isNaN(Number(minor)))
throw Error(`Version is not made up of numbers! "${version}"`)
const newMinor = String(Number(minor) + 1)
return [major, newMinor, patch].join(".")
}
incrementVersion(incMinorVersion)
The way this works is that this function is actually passed into a more generic incrementVersion
function which receives an incrementer
:
const fs = require("fs")
const { getLatestVersion } = require("./getVersion")
const { saveVersion } = require("./saveVersion")
function incrementVersion(incrementer) {
// read and parse package.json
const rawPackage = fs.readFileSync("./package.json")
const parsedPackage = JSON.parse(rawPackage)
// get latest published version
const latestVersion = getLatestVersion(parsedPackage)
// update the version
const nextVersion = incrementer(latestVersion)
parsedPackage.version = nextVersion
// save and commit the package.json
saveVersion(parsedPackage)
}
module.exports = { incrementVersion }
This has two helper functions of its own:
getLatestVersion
andsaveVersion
First the getLatestVersion
:
const { spawnSync } = require("child_process")
function getLatestVersion(packageJson) {
let latestVersion
const latestPublishedVersion = spawnSync("npm", [
"show",
packageJson.name,
"version",
])
if (latestPublishedVersion.stderr.toString()) {
// The package has never been published
latestVersion = packageJson.version
} else {
latestVersion = latestPublishedVersion.stdout.toString().trim()
}
return latestVersion
}
module.exports = { getLatestVersion }
const fs = require("fs")
const { gitAdd, gitCommit } = require("./simpleGit")
function saveVersion(packageJson) {
const { version } = packageJson
fs.writeFile(
"./package.json",
Buffer.from(JSON.stringify(packageJson, null, 2)),
{ encoding: "utf8" },
(err) => {
if (err) {
return console.log("Error!", err)
}
gitAdd()
gitCommit(`Published version: ${version}`)
return console.log(
`Successfully updated and committed package.json to version ${version}`,
)
},
)
}
module.exports = { saveVersion }
This one uses a few “simple” git commands:
const { spawnSync } = require("child_process")
const gitAdd = () => spawnSync("git", ["add", "package.json"])
const gitCommit = (message) => spawnSync("git", ["commit", `-m '${message}'`])
module.exports = { gitAdd, gitCommit }
All of these helper scripts rely on the spawnSync
method on the child_process
module.
That’s the whole thing. With this workflow in place, whenever code is merged to main
, I run a workflow that will automatically bump the minor version of the project.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!