How to handle releases and changelogs with Conventional Commits

I’m getting close to the first release for my expo bare project and I’d like to start using standard-version (and, by implication, Conventional Commits) before I do. I’m wondering if anyone knows of a good resource regarding how semver even applies to Expo projects. I have a few initial thoughts, having no experience actually trying this out yet but just from intuition:

  • Breaking changes mean a new app store version and a new Expo release channel.
  • Non-breaking changes are just a new expo publish within the latest Expo release channel.
  • However, this seems to imply that only the major version matters for the app store releases, since minor and patch updates would be done as publishes within that major release’s Expo release channel.
  • I could also see that there could be a bugfix in the shell app that didn’t require a new release channel.

So, I wrote down this initial strategy and started thinking through the implications:

  • Any semver release is a new app store version
  • Only major releases require a new expo release channel
  • Any commit pushed in between releases can be a new bundle release without requiring a new semver (shell app) release
  • Beta releases are simply new versions with a x.y.z-beta.N tag, where N is the build number.

But then this raises the question: if I’m using Conventional Commits to determine the semver release for the shell app, and the JS bundle releases are just versioned by their commit hash, how do I also use the commits to generate the changelog? A couple issues this raises:

  • Changes are delivered in two ways:
    1. New app store releases
    2. OTA updates
  • Each one could have its own changelog
  • How can we separate which changes belong in which changelog?
    • The component part of Conventional Commits would seem like a good fit
    • Perhaps that could be one component for the shell app, and other components for everything else?
      • e.g. fix(shell): update native library to fix bug #123 would go to the shell app’s changelog
      • but feat(one) and fix(two) would go in the OTA updates changelog (assuming one and two are components defined for this app)
    • This could work… but would the shell app release notes also include all the changes that were rolled out as OTA updates? I suppose I’d have the flexibility of filtering which commits appear in which changelog… but I’m not sure if that’s possible/simple to accomplish with existing tooling like standard-version.

Since Expo apps have this inherent divide between one part (the shell app) that’s released more like an npm package and another part (the JS bundle) that’s released more continuously (within a release channel), I’m wondering where standard-version sits in a typical Expo-based release pipeline, with the considerations above in mind. I read this blog on the topic of standard-version-expo, but I didn’t notice anything about release channels.

Interestingly, this all applies just as well to managed-workflow projects; it’s just that the possible variety of breaking changes there is much smaller than in bare-workflow projects. This makes me even more certain that someone must have thought of this already - so I’d love to hear what folks think.



Thinking about this more… how do folks usually identify bundle updates in their app (e.g. an “About” screen)? Ideas that come to mind:

  • Commit short-sha
    • e.g. Version 1.0.3 build ba73ce
    • :white_check_mark: Simple
    • :white_check_mark: Ephemeral; no extra state
    • :white_check_mark: Easy to trace back to a version of the code
    • :no_entry: Not meaningful to the user
    • :no_entry: No simple way to tell which of two builds is more recent
  • Monotonically increasing build number
    • e.g. Version 1.0.3 build 14
    • :white_check_mark: Simple
    • :no_entry: Requires keeping extra state somewhere
    • :no_entry: Hard to trace back to the code version
    • :man_shrugging: Somewhat meaningful to the user
    • :white_check_mark: Easily comparable to other builds of the same version
  • CalVer
    • e.g. Version 1.0.3 build 2020.09.08-003
      • (where the 003 part is monotonically increasing)
    • :white_check_mark: Simple
    • :no_entry: Requires keeping extra state somewhere
    • :no_entry: Hard to trace back to the code version
    • :white_check_mark: Quite meaningful to the user (“this is when it was released”)
    • :white_check_mark: Easily comparable to other builds of the same version

I’ll throw in Updates.updateId from expo-updates as another contender - but it’s really just a variant of using the commit sha that’s a little harder to tie back to a version of the code.


Hmm. So either:

  1. This sort of thing is a trade secret where anyone who’s thought deeply about it and come up with a solution now holds that solution as a competitive advantage; or
  2. I’m overthinking it and should just go with something simple; or
  3. This is a hard problem and no one has a good solution.

Or folks are busy and this got buried. I get it. Still hoping to hear from anyone who’s released an app (and a breaking change in the app store code). In the meantime, here’s my attempt at “something simple”:

  • Any semver release is a new app store version
  • Only major releases require a new expo release channel
    • This means that if I push a change to javascript that depends on a change in the native code - a new native module, for instance - then I must consider that a breaking change and bump the major version - and not release the JS change to previous release channels
    • But, this should be fine so long as the release channel in my CI is generated from the next semver release version.
  • Beta releases are simply new versions with a -beta.N tag, where N is the build number.

We’ll see how it holds up once I start actually releasing. :sweat_smile: