May 06, 2019

I’m a fan of React Native. It allows me to build native mobile apps using JavaScript. That’s no small feat, but no tool is perfect. React Native has its frustrating quirks and time-consuming challenges, as any library as heavy as React Native might. Automating deployment has been one of those challenges. I’ve fiddled with deployment processes across several projects over the years and I was never really satisfied with the results. It took many iterations: adding new tools, removing stale ones, using old tools in new ways, but I recently realized that I reached my goal.

I should point out that I didn’t create some silver bullet to the deployment problem. I am simply using existing tools in intended, documented ways. Look at any individual piece, and there’s nothing original. What I believe is special, and what I am excited to share, is the way the tools are combined to form a seamless, single-command, automated deployment process from commit to app store.

Part 1 will establish some project assumptions that are required for this process to work, and then walk through the first half of the setup. This is meant to be an example of what is possible. Some of these tools have alternatives that would be relatively easy to swap–such as Circle CI with pretty much any other CI–but I’m going to stick with what I used and maybe you can take it from there.

To see everything put together, check out the working example repo with all code samples.

Bird’s eye view

When this series is complete, you should have a working deployment flow that runs with one command: yarn release (or npm run release ). Here’s an overview of the whole process:

Note: e2e testing is not part of this flow. It assumes the previous commit has already passed all tests. Setting up testing for supported devices in CI is another beast. I may share that in a later post.

You know what happens when you assume

This workflow assumes some things about the project. It assumes you have a project structure similar to mine. That is, similar to the one generated by react-native init . Most of this should work in an expo project, but I haven’t used expo that much so I can’t say for sure.

Commit message guidelines

The critical assumption is that all commits for the release follow some commit message guidelines. This is necessary for determining a proper semantic version and generating release notes from commit messages.

You’re not tied to the format I use, but each commit should at least have a scope, a subject, and some keyword to denote a breaking change. There are several possible presets. Have a look at @semantic-release/commit-analyzer for the possible configs. I also recommend checking out commitlint and husky for enforcing commit message guidelines on commit. commitizen is nice if you’d like a prompt to use when committing. The config I use for this blog is available on my GitHub. Check out the package.json and commitlint.config.js

[TypeScript]

It’s not necessary that you use TypeScript, but a few of my code examples will use the TypeScript compiler and tslint. You could swap those with tools such as eslint. prettier works with both TypeScript and Vanilla JavaScript.

Dependencies for part 1

$ yarn add --dev \ semantic-release \ @semantic-release/git \ react-native-version

Setting up semantic-release

Add this script to your package.json :

"scripts" : { "release" : "semantic-release" ... }

Next, double check git authentication, which involves setting some environment variables. Then create a GitHub token for semantic-release-bot. The @semantic-release/github docs explain how to do this. Specifically, follow this guide and set GH_TOKEN in your environment. The token will allow the semantic-release-bot to use the GitHub API to push release notes.

Here’s an example config to use for semantic-release. Replace $PROJECTNAME with your own project name.

"release" : { "branch" : "master" , "npmPublish" : false , "ci" : false , "plugins" : [ "@semantic-release/commit-analyzer" , "@semantic-release/release-notes-generator" , "@semantic-release/npm" , [ "@semantic-release/git" , { "assets" : [ "ios/$PROJECTNAME*/Info.plist" , "ios/$PROJECTNAME.xcodeproj/project.pbxproj" , "android/app/build.gradle" , "package.json" ] , "message" : "chore(release): ${nextRelease.version}



${nextRelease.notes}" } ] , "@semantic-release/github" ] }

Updating the version

There’s an important package that works in conjunction with @semantic-release/npm that makes releasing to the app store possible. If we stopped here, only the version in package.json would get updated, resulting in errors later on CI when deploying to the app stores. react-native-version is needed to update the versions in android and ios.1 It piggybacks off of the command used to update the package.json version and uses the same version to update android and ios files.

Make sure it’s installed and add this to your package.json :

"scripts" : { "postversion" : "react-native-version --never-amend" ... }

Side note: you can add pre or post to any script and it will run before or after that script. That includes custom scripts, by the way.

The git commit

After updating the version, @semantic-release/git is responsible for staging the changed assets, committing those assests, tagging the commit, and pushing it to GitHub. But you have to be explicit about which files to add. The files listed above are all of the files that @semantic-release/npm and react-native-version changes.

The message is mostly default with one important difference. It removes [skip ci] from the end of the message. We do not want to skip CI.

Signing commits with GPG

If you already have GPG set up for signing commits 2, you may also want to enable signing commits and tags made by semantic-release. This is easy to do in our case since we’re committing from our local machine. The fix is to commit using your own name and email rather than than the default name and email used for the bot. 3 Make sure these environment variables are set to the ones matching your GPG signature:

GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL

If you opted to run semantic-release in CI, the @semantic-release/git docs explain how to sign commits on Travis.

Conclusion

Try running yarn release -d or npm run release -- -d to do a dry run of what you have so far. If everything is set up correctly, you’re halfway to single-command deployment. In the next part, we’ll discuss setting up fastlane and Circle CI, which will take care of linting, building, and actually deploying to Testflight and Google Play, as well as posting a message to Slack.