DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. Versioning: The Chicken and Egg

Versioning: The Chicken and Egg

The solution to the chicken and egg problem of releases is to create the release in one step, fully formed.

Remy Sharp user avatar by
Remy Sharp
·
Jul. 04, 16 · Tutorial
Like (2)
Save
Tweet
Share
3.92K Views

Join the DZone community and get the full member experience.

Join For Free

When I originally created this post I was working on a project where I kept running into the constraints of versioning a package that I was pushing to npm. I'll explain the problem, but now I have a near perfect workflow that completely mitigates the entire issue of versioning.

Background

I have a [Node-based] project that I want to publish to npm, and I want to follow semver for versioning.

That's fine, I know when I'm adding features (minor) and fixes (patched) and I rarely, if ever, commit breaking changes (major).

In the last year, I've moved to standardized structure for commit messages (instead of whatever I fancy). I'm using the AngularJS commit style (though I don't write any AngularJS code) which prefixes with fix: or feature: and subjects are limited to 50 characters with more detail in the body.

The commit history now, to me, looks so clear and so much easier to scan through. Then I run npm version patch which creates a new commit and a new tag. Finally, I push changes and tags to GitHub and push to npm.

There are two problems with this process. The first is aesthetic. The history contains the version anomaly that just doesn't quite look right, and when we look at that tag on the project, it only ever contains the single commit that changed the package version:

version in history

The second is much more serious: what if I distribute a compiled (either minified or transpiled) copy of the code and I want the version string to appear in the build?

To reiterate, the workflow is:

  • fix: removed that bug
  • feat: added that awesome thing
  • chore: do some stuff
  • chore: generate build, the version here is 3.35.8
  • 3.36.0

The tagged build of version 3.36.0 now has a version string inside of it that says 3.35.8. This is wrong, and indeed bad.

Chicken and Egg

In the situation I describe above, I need to include the version in the build, otherwise, I can't debug what other people are seeing (and can't narrow down to a specific version of code). One alternative would be to build after the tag, and deploy the package, but now if I look at the Git history, the version tag is the commit before the distribution build.

I did write a postversion script that went through an insane amount of backtracking and Git juggling to revert commits and rewrite them. It doesn't scale well, and frankly, it's brittle as hell. All of these individual steps were triggered on the post version life cycle:

# build the dist output using babel
npm run build &&
# then add it to git
git add dist/* &&
# amend the last commit(!!!) inline and use the version as the commit
git commit --amend -m"$npm_package_version" &&
# delete the old tag(!!!)
git tag -d v$npm_package_version &&
# then re-tag :-\
git tag -a v$npm_package_version -m "chore: release $npm_package_version"

What a total mess. Originally, this post was going to stop here. What on earth do you do? How do other developers get around this issue, if at all?

But then, thanks to Stephan Bönnemann (the author), Semantic Release found its way into my development life

Versioning Solution: Semantic Release

Semantic release is a tool that will read your commits and do the following automatically for you if there's a fix or feature:

  1. Work out the next semver version, and push that to npm
  2. Add a new release to GitHub and include full release notes (pointing to commits and related issues from the commits)

This is huge because I don't need to worry about versioning myself (which if I'm honest, could be occasionally missed), it builds that ever-illusive changelog and there are no nasty v1.0.4 commits in my history.

One huge benefit to me is that semantic release will generate and publish the release notes automatically for me in GitHub.

The process has evolved over time, so if you've heard of it and had a bad experience, bear with me.

You want to use the CLI tool when setting up your projects each time. You can't just copy and paste the changes to each project because the CLI tool is also configuring Travis with specific environment values that allow Semantic Release to read your git commit history and also ensure that the final publish step to npm is under your account.

To get started install the CLI and run it in your project directory and answer the questions:

npm install -g semantic-release-cli

Gotcha: I'm sure this will be fixed eventually, but at the moment semantic release will overwrite your .travis.yml file, so I always make sure to manually edit and resolve the changes by hand.

Fixing the Distribution Problem

The crux of how semantic release works is that there's a life cycle that runs the preconditions, then executes the user code (unusually just npm publish), then runs the post-release process (adding changelog and tags to GitHub).

Semantic release does have a plugin architecture so you can add your own preconditions, but what interests me is the middle step, the "let's do this" part.

Here is where I can run the build process and generate the dist directory. Since the npm publish step takes everything in the directory, the distribution files will be included in the npm release—and in fact, don't even have to live in GitHub (which IMHO is ideal).

Gotcha: I got caught out by this recently: I ignored the dist directory in my .gitignore file so it stays out of git. That's good. Except that npm publish also uses the git ignore file if there's no .npmignore file present. So, I need to either make an empty .npmignore file (which is a workaround) or put !dist explicitly in the .npmignore. This ensures the distribution directory is included in the npm release.

Will This Create a Release for Every Change I Commit?

Good question (if I do say so myself). It depends entirely on your release workflow.

If you're committing directly into master and pushing every individual commit to GitHub, then yes, you'll get a release for every fix and feature.

However, as soon as you stop pushing every commit, semantic release will bundle together all your commits since the last release, and calculate the correct version change and generate change log (which is a PITA to manage yourself). You can see this in some of my inliner releases:

Example change log for a release

This is then amplified when you're working off a develop (for instance) branch and merging a group of changes at once (the type of GitFlow workflow).

It's also worth remembering that by default semantic release is only running on master, and comparing to your last release in npm.

But, What if I Want to Show the Version?

One question I got stuck on early on was being able to echo out the version number (since I write a lot of CLI tools). I would rely on the package.json to hold the version, and with a semantic release set up, you don't have a version property in the package.

So, as it turns out, this isn't an issue at all. It only affects me during development. Once the users of my CLI tool have installed, they have the copy that does have the version property (as semantic release adds it right before the npm publish step).

I've worked around this problem (that only affects the development copy) using my own version promise code which I use in a number of CLI tools now.

A Brief Walk Through

I've added semantic release to both new projects and existing projects, and I find the process fairly painless. First though, you want the setup tool (that I mentioned earlier):

npm install -g semantic-release-cli

Then from your project directory (assuming it has a package.json), run semantic-release-cli setup and answer the questions:

Example output from semantic release setup tool

In this particular case, I had a private project run on Travis Pro, but for many of my projects, I'll be using public repos and regular Travis. This process will update package.json and .travis.yml. Semantic release also sets two private environment values in Travis: your npm token and a newly created personal GitHub token. This is all it needs to manage the process of setting new versions and pushing to npm.

Now, I'll commit the changes semantic release made and push to GitHub. Travis will automatically receive the project (set up by semantic release) and if the tests pass, semantic release will work out the new version number, add it to package.jsoninside Travis and push all the local code as a new npm release.

Pretty neat.

Other Concerns

There are a couple of extra notes I wanted to share: first off, if you're maintaining "maintenance releases" (ie. previous major versions), you can do this, but you need to follow specific directions.

Then also you can use semantic release to do some funkier stuff. For instance, I use it to deploy this static blog! But, how I do that is for another post, another day!

Release (agency) Commit (data management) Semantics (computer science) Npm (software) Git EGG (file format) Build (game engine)

Published at DZone with permission of Remy Sharp, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Spring Boot, Quarkus, or Micronaut?
  • Full Lifecycle API Management Is Dead
  • mTLS Everywere
  • NoSQL vs SQL: What, Where, and How

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: