Versioning React Native apps

React Native brings together JavaScript, Android and iOS. With that comes three different build tools, npm, Xcode, and Gradle. What happens when we want to release and increment the version? We have to increment the package.json, build.gradle, and info.plist!

Single command versioning?

Wouldn’t it be great if we could just run

npm version [major|minor|patch]

Lets try it

Being an Android developer I know that Gradle can do pretty much anything, so I started there.

In my build.gradle file I need to read the package.json file, which turns out is pretty easy in Gradle.

import groovy.json.JsonSlurper
def getNpmVersion() {
def inputFile = new File("../package.json")
def packageJson = new JsonSlurper().parseText(inputFile.text)
return packageJson["version"]
}

I can then split it into individual strings, so I can use it in my build scripts.

def (major, minor, patch) = getNpmVersion().tokenize('.')

How you use the package.json version to calculate the versionCode and versionName is up to you. Below is one example, you can see a fully working example on GitHub.

android/build.gradle

def getNpmVersionArray() { // major [0], minor [1], patch [2]
def (major, minor, patch) = getNpmVersion().tokenize('.')
return [Integer.parseInt(major), Integer.parseInt(minor), Integer.parseInt(patch)] as int[]
}
subprojects {
ext {
def npmVersion = getNpmVersionArray()
versionMajor = npmVersion[0]
versionMinor = npmVersion[1]
versionPatch = npmVersion[2]
}
}

android/app/build.gradle

android {
...

defaultConfig {
...
versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
}
}

iOS

Xcode doesn’t have anything as good as Gradle, so I’ve used bash for this.

#!/usr/bin/env bash -e

PROJECT_DIR="ios/ReactNativeApp"
INFOPLIST_FILE="Info.plist"
INFOPLIST_DIR="${PROJECT_DIR}/${INFOPLIST_FILE}"

PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]')

BUILD_NUMBER=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_DIR}")
BUILD_NUMBER=$(($BUILD_NUMBER + 1))


# Update plist with new values
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${PACKAGE_VERSION#*v}" "${INFOPLIST_DIR}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "${INFOPLIST_DIR}"

git add "${INFOPLIST_DIR}"

This script reads the package.json version, increments the build number, then updates the Info.plist file using PlistBuddy. Finally it stages the Info.plist with the modified package.json.

All together now

Npm version by default will increment the version, commit, and tag that commit with the new version. To hook into this command npm provides three entry points, preversion, version, and postversion.

To execute the version-ios.sh script when we increment the version we can add the following:

“scripts”: {
“version”: “./version-ios.sh”
}

Success!

There you have it, we can now increment our version without the added hassle of updating it three times. Just “npm version [major|minor|patch]”.

Using the hooks for npm version we can do even more! We could update the CHANGELOG.md, create a release branch, and push the branch and tags to remote.

Source code: https://github.com/AndrewJack/versioning-react-native-app