DEV Community

Cover image for Simplify Expo releases with Standard Version
Cedric van Putten
Cedric van Putten

Posted on • Updated on

Simplify Expo releases with Standard Version

Releasing your apps can be a repetitive, sometimes even annoying task. You prepared and built your app for the stores, only to find out you forgot to bump a version code. If you don't like to do this, the great people from Conventional Changelog got just the thing for you!

Standard version is a tool to simplify the release process with a single command, yarn standard-version. It's customizable and should have everything you need, like generated changelogs from conventional commits. If you don't know the tool yet, check out the documentation first.

In this guide, I'll show you how to configure Standard version for Expo. We will set up an example project and configure it for automated expo.version, expo.android.versionCode and expo.ios.buildNumber.

🚀 Let's get going

First things first, create an empty Expo project using the blank template.

$ expo init --template blank

Next, we need to define a starting version for our project. Standard version uses the package.json's version property to determine the current version. This value is used to calculate the next release. Let's set this property to 0.0.0.

{
  "version": "0.0.0",
  "main": "node_modules/expo/AppEntry.js",
  ...
}

Now add Standard version, with the Expo extension, to our dev dependencies.

$ yarn add --dev standard-version@next standard-version-expo

Currently, standard-version@7.1.0 is the only version that supports updaters from packages. That's why we need @next here.

You can test the project by running standard-version in dry-run mode. This mode only tells what it will do without doing anything.

$ yarn standard-version --dry-run

If you get the Invalid Version: undefined error, it means the package.json doesn't have a starting version.

⚙️ Configure Standard version

Now that we've set up the basics, we need to configure Standard version for Expo. Create a file named .versionrc.js with:

module.exports = {
  bumpFiles: [
    {
      filename: 'package.json',
    },
    {
      filename: 'app.json',
      updater: require.resolve('standard-version-expo'),
    },
  ],
};

This tells Standard version that we want to update app.json, using the default updater from the Expo extension. The updater bumps expo.version with the new version, so you don't have to! 😬

Note: We also want to update our package.json, with the default updater, to keep a single source of truth for our versions. (See PR #3 for more details)

Test your configuration again with --dry-run, if you see the following output, you did an excellent job. 🦄

$ yarn standard-version --dry-run

✔ bumping version in app.json from 1.0.0 to 0.0.1
✔ created CHANGELOG.md
✔ outputting changes to CHANGELOG.md
...
✔ committing app.json and CHANGELOG.md
✔ tagging release v0.0.1

Now commit these changes and create a new minor version.

$ git add .
$ git commit -m 'feat: add standard version for releases'
$ yarn standard-version --release-as minor

After running the tool, you should have a new CHANGELOG.md and an updated app.json file! You still need to push the local changes to remote, but that also gives you an option to revert changes if something went wrong. 🧑‍🔧

📱 Configure build versions

If you are planning on releasing your app to the stores, we need to configure Standard version a bit more. It also needs to update the Android version codes and iOS build numbers. Let's start by updating our manifest with iOS and Android configuration.

{
  "expo": {
    ...
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.bycedric.awesomeapp",
      "buildNumber": "0.0.0"
    },
    "android": {
      "package": "com.bycedric.awesomeapp",
      "versionCode": 0
    }
  }
}

Great, now we need to tell Standard version about these new properties. The values should change on every release, just like our expo.version. To do this, open up .versionrc.js and add the new version bumpers.

module.exports = {
  bumpFiles: [
    {
      filename: 'package.json',
    },
    {
      filename: 'app.json',
      updater: require.resolve('standard-version-expo'),
    },
    {
      filename: 'app.json',
      updater: require.resolve('standard-version-expo/android'),
    },
    {
      filename: 'app.json',
      updater: require.resolve('standard-version-expo/ios'),
    },
  ],
};

There are multiple types of version updaters, each with their own "tactic". You can find them all here. For simplicity, let's stick to the recommended bumpers for now.

That's it! If you commit these changes and run Standard version, it will update the build version numbers for you too. 🌈

$ git add .
$ git commit -m 'feat: add standard version for releases'
$ yarn standard-version --release-as patch

🤝 Thanks for reading!

I hope this can be useful for anyone. If you use this library, feel free to reach out. I'd love to hear about your experiences with the tool. Also, feel free to reach out if you have great ideas to make it better or if you are stuck. 🙆‍♂️

📚 Extra goodies

Edit: I made a mistake of not adding package.json to our .versionrc.js. Thanks to awinograd for spotting and fixing it!

Cover photo by Robert Metz

Discussion (9)

Collapse
gregfenton profile image
gregfenton

This is an awesome write up, and great tool! Thank you.

I am planning on releasing different instances of my app per customer (each customer is a company with multiple users - each company gets its own backend "stack").

So I have different app.config.js per customer. When I do a build, upload and/or publish, I specify the --config (e.g. expo build:ios --config customer1/app.config.js --release-channel customer1).

If I have dozens of customers & customer-configs, thoughts on how to go about keeping these all balanced? Would I simply add more entries to bumpFiles per customer, thus they all get "bumped" at the same time? (My goal is to keep all customers running essentially the same version - each just get their own splash screen/icon/app-name & API keys to the backend stack).

Collapse
gregfenton profile image
gregfenton

I guess one approach would be to use JS code in .versionrc.js so that it dynamically adds each of the customer configurations to the bumpFiles rather than being required to manually add them to the config. If all my customer configs are in a standard location like ./app-configs/<CUSTOMERNAME>/app.config.js, then I could easily do this dynamically....

Collapse
gregfenton profile image
gregfenton

Hopefully this helps someone besides myself. Any and all feedback welcomed on the gist page:

gist.github.com/gregfenton/b81ec01...

Collapse
darryl profile image
Darryl Young

Just this week I started building my app and pushing it to TestFlight so I'm very happy to have come across this. Thank you, Cedric. It looks to be exactly what I need to make the process a little easier.

Collapse
kaminskypavel profile image
Pavel 'PK' Kaminsky

Excellent tool! exactly what I was looking for 💪 automating CI/CD.
given that the native part of my app will rarely update, does it makes sense updating the package.json only, as a way of keeping the bundle version?
or should I update ios/android versions too and skip uploading the apk/ipa?

Collapse
bycedric profile image
Cedric van Putten Author • Edited

Hi Pavel, thanks! The tool was initially built for managed projects without native code. For the native part, we might need to add a few "version bumpers". I'll look into this asap!

Edit: just created #9, if you have good ideas feel free to share them there! 😁

The package.json is used by standard-version itself as a kind of "single source of truth". So it's always good to update it there. That being said, only updating the version in package.json will still require you to manually update the build numbers, like versionCode in Gradle, when you publish to stores.

Hope it helps!

Collapse
hcatlin profile image
Hampton Catlin

This is excellent!

Collapse
favreleandro profile image
Leandro Favre

This is a great helper! Thank a lot Cedric!

Collapse
bycedric profile image
Cedric van Putten Author

No problem! Thanks for reading it! 😁