Following best practices on new projects is often straightforward, just plug in the recommended tools and configurations. That assumption breaks down quickly in legacy systems or non-standard workflows.
During my internship at Devfolio, I ran into exactly this problem while trying to automate our release process.
Coming from mostly greenfield projects, I had the habit of just letting CI/CD tools handle releases, linting, and testing. But at Devfolio, I saw that we had a manual release process. Which is fine, but my god, did I mess it up so many times during my initial contributions. I kept forgetting steps, misnaming releases, and generally making a mess of things. I assumed I was just bad at releases, until I saw everyone else struggling too.
Then when new colleagues joined, I realized that the manual process was a pain point for everyone. So I took it upon myself to automate as much of the release process as possible, while still accommodating the unique aspects of our workflow.
We don't follow semantic versioning to the letter, and our release branches have specific naming conventions that don't align with standard tools. So I had to get creative with scripting and tooling. Our commit messages also didn't follow conventional formats, which made it tricky to automate changelog generation or version bumps.
Eventually, I gave up on automating version bumps and changelogs. Instead work with the constraints that ARE guaranteed to be there:
- Version bumps are mandatory, even if manual
- On merge, create a new latest release with the tag and version of the latest
package.json - For pre-releases, create a new pre-release with target branch (the feature branch) and version from
package.json
This sounds straightforward, but because we didn't strictly follow semver or conventional commits, I had to write a custom version-check.ts script that fetches all the latest tags and compares them to the package.json version. This workflow will run on all PRs, and block merging if the version in package.json isn't higher than the latest tag.
With this setup, I was able to significantly reduce the friction around releases. While it's not a perfect system, it works well within our constraints. This solved the
Oops, forgot to bump version. Creating a new PR just for that...
Oops, named the release wrong. Now I have to delete and recreate it...
problems, that really messed up my releases. Hopefully this helps the rest of the team as well!
Now for the automatic release creation, I set up a GitHub Action that triggers on merges to the main branch. It runs the version-check.ts script first, and if the version is valid, it creates a new release using a custom .release-it.ts script. For pre-releases, you can run a simple npm run release:staging command that uses the same script but targets the feature branch and marks the release as a pre-release, while also enforcing a -feature-branch-x.y.z versioning scheme.
This taught me that automation is great, but sometimes you have to work within the constraints of your existing systems. By focusing on what can be automated reliably, even in non-standard scenarios, you can still achieve significant improvements in efficiency and developer experience.