Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add native version bumpers #17

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -32,6 +32,6 @@
${{ runner.os }}-build-
${{ runner.os }}-
- run: npm ci
- run: npm run build
- run: npm test
- run: npm run lint
- run: npm run build
51 changes: 51 additions & 0 deletions README.md
Expand Up @@ -61,6 +61,34 @@ module.exports = {
};
```

If you're using the [bare workflow][link-bare-workflow], you'll need a couple
more bumpers to keep your native project config files in sync:

```js
// .versionrc.js
const sdkVersion = '37.0.0'; // or pull from app.json

module.exports = [
// ...
{
filename: 'ios/<YourAppName>/Info.plist',
updater: require.resolve('standard-version-expo/ios/native/app-version'),
},
{
filename: 'ios/<YourAppName>/Info.plist',
updater: require.resolve('standard-version-expo/ios/native/buildnum/increment'),
},
{
filename: 'android/app/build.gradle',
updater: require.resolve('standard-version-expo/android/native/app-version'),
},
{
filename: 'android/app/build.gradle',
updater: require.resolve('standard-version-expo/android/native/buildnum/code')(sdkVersion),
brettdh marked this conversation as resolved.
Show resolved Hide resolved
},
];
```

To test if your configuration works as expected, you can run standard version in dry mode.
This shows you what will happen, without actually applying the versions and tags.

Expand All @@ -87,6 +115,28 @@ updater | example | description
`ios/increment` | `9` | Replace `expo.ios.buildNumber` with an incremental version.
`ios/version` | `3.2.1` | Replace `expo.ios.buildNumber` with the exact calculated semver. (**recommended**)

And for the native build config files:

updater | example | file path | description
--- | --- | --- | ---
`native/ios/app-version` | `3.2.1` | `ios/<YourAppName>/Info.plist` | Replace `CFBundleShortVersionString` with the exact calculated semver.
`native/ios/buildnum/code` | `36030201` | `ios/<YourAppName>/Info.plist` | Replace `CFBundleVersion` with the [method described by Maxi Rosson][link-version-code].
`native/ios/buildnum/increment` | `8` | `ios/<YourAppName>/Info.plist` | Replace `CFBundleVersion` with an incremental version.
`native/ios/buildnum/version` | `3.2.1` | `ios/<YourAppName>/Info.plist` | Replace `CFBundleVersion` with the exact calculated semver. (**recommended**)
`native/android/app-version` | `3.2.1` | `android/app/build.gradle` | Replace `versionName` with the exact calculated semver.
`native/android/buildnum/code` | `36030201` | `android/app/build.gradle` | Replace `versionCode` with the [method described by Maxi Rosson][link-version-code]. (**recommended**)
`native/android/buildnum/increment` | `8` | `android/app/build.gradle` | Replace `versionCode` with an incremental version.

Note that the `native/{ios,android}/buildnum/code` bumpers are only supported
in `.versionrc.js` file, not in `.versionrc` or `.versionrc.json` files.
Since a bumper only operates on one file, the Expo manifest is unavailable to
the bumper when it's operating on a native build config file. Because of this,
you must provide the Expo SDK version via javascript (see example above).

However, this means that you can also use these bumpers with non-Expo React
Native projects, and even plain Android projects, simply by supplying the
minimum Android API level rather than the Expo SDK version.

### Version code

Semver is one of the most popular versioning methods; it generates a string with a syntax that even humans can read.
Expand All @@ -108,3 +158,4 @@ It's a deterministic solution that removes some of the ambiguity of incremental
[link-expo-version]: https://docs.expo.io/versions/latest/workflow/configuration#version
[link-standard-version]: https://github.com/conventional-changelog/standard-version#configuration
[link-version-code]: https://medium.com/@maxirosson/versioning-android-apps-d6ec171cfd82
[link-bare-workflow]: https://docs.expo.io/introduction/managed-vs-bare/
1 change: 1 addition & 0 deletions android/native/app-version.js
@@ -0,0 +1 @@
module.exports = require('../../build/bumpers/native/android-app-version');
1 change: 1 addition & 0 deletions android/native/buildnum/code.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/android-code').default;
1 change: 1 addition & 0 deletions android/native/buildnum/increment.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/android-increment');
1 change: 1 addition & 0 deletions android/native/buildnum/index.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/android-code').default;
1 change: 1 addition & 0 deletions ios/native/app-version.js
@@ -0,0 +1 @@
module.exports = require('../../build/bumpers/native/ios-app-version');
1 change: 1 addition & 0 deletions ios/native/buildnum/code.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/ios-code').default;
1 change: 1 addition & 0 deletions ios/native/buildnum/increment.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/ios-increment');
1 change: 1 addition & 0 deletions ios/native/buildnum/index.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/ios-version');
1 change: 1 addition & 0 deletions ios/native/buildnum/version.js
@@ -0,0 +1 @@
module.exports = require('../../../build/bumpers/native/buildnum/ios-version');
12 changes: 10 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -37,9 +37,11 @@
"dependencies": {
"@expo/config": "^3.1.2",
"@expo/json-file": "^8.2.10",
"@types/plist": "^3.0.2",
"detect-indent": "^6.0.0",
"detect-newline": "^3.1.0",
"globby": "^11.0.0",
"plist": "^3.0.1",
"semver": "^7.3.2"
},
"devDependencies": {
Expand Down
11 changes: 11 additions & 0 deletions src/bumpers/native/android-app-version.ts
@@ -0,0 +1,11 @@
import { androidAppVersionReader, androidAppVersionWriter } from './helpers';

/**
* Read the app version from the `versionName` build.gradle property.
*/
export const readVersion = androidAppVersionReader;

/**
* Write the app version to the `versionName` build.gradle property.
*/
export const writeVersion = androidAppVersionWriter;
42 changes: 42 additions & 0 deletions src/bumpers/native/buildnum/android-code.ts
@@ -0,0 +1,42 @@
import { androidBuildnumReader, androidBuildnumWriter } from '../helpers';
import { getVersionCodeFromSdkVersion } from '../../../versions';

/**
* Since a standard-version bumper only receives the contents of a single file,
* we add a layer of indirection here and ask the user to supply the sdkVersion
* directly. Note that they can choose to pull this from app.json, or even supply
* the Android min SDK version if they're not using Expo.
*
* Configuration example in .versionrc.js:
*
* const sdkVersion = '37.0.0'; // or pull from app.json
* module.exports = [
* ...
* {
* filename: 'android/app/build.gradle',
* updater: require.resolve('standard-version-expo/android/native/code')(sdkVersion),
* },
* ...
* ];
*
* This does add the requirement that they use .versionrc.js, not the other formats.
*/
export default (sdkVersion: string) => ({
/**
* Read the build code from the `versionCode` property.
*/
readVersion: androidBuildnumReader,

/**
* Write the manifest version to the `versionCode` property.
* This uses the Android version code approach of Maxi Rosson.
*
* @see https://medium.com/@maxirosson/versioning-android-apps-d6ec171cfd82
*/
writeVersion: (contents: string, version: string) => androidBuildnumWriter(
contents,
String(
getVersionCodeFromSdkVersion(sdkVersion, version),
),
),
});
24 changes: 24 additions & 0 deletions src/bumpers/native/buildnum/android-increment.ts
@@ -0,0 +1,24 @@
import { androidBuildnumReader, androidBuildnumWriter } from '../helpers';
import { VersionWriter } from '../../../types';

/**
* Read the buildnum stored at versionCode in the build.gradle.
*/
export const readVersion = androidBuildnumReader;

/**
* Increment the buildnum stored at versionCode in the build.gradle.
* This ignores the provided version.
*/
export const writeVersion: VersionWriter = (contents, _version) => {
const buildNumStr = androidBuildnumReader(contents);
const buildNumber = buildNumStr != ''
? Number(buildNumStr)
: 0;

if (Number.isNaN(buildNumber)) {
throw new Error('Could not parse number from `versionCode`.');
}

return androidBuildnumWriter(contents, String(buildNumber + 1));
};
42 changes: 42 additions & 0 deletions src/bumpers/native/buildnum/ios-code.ts
@@ -0,0 +1,42 @@
import { iosBuildnumReader, iosBuildnumWriter } from '../helpers';
import { getVersionCodeFromSdkVersion } from '../../../versions';

/**
* Since a standard-version bumper only receives the contents of a single file,
* we add a layer of indirection here and ask the user to supply the sdkVersion
* directly. Note that they can choose to pull this from app.json, or even supply
* the Android min SDK version if they're not using Expo.
*
* Configuration example in .versionrc.js:
*
* const sdkVersion = '37.0.0'; // or pull from app.json
* module.exports = [
* ...
* {
* filename: 'ios/MyApp/Info.plist',
* updater: require.resolve('standard-version-expo/ios/native/code')(sdkVersion),
* },
* ...
* ];
*
* This does add the requirement that they use .versionrc.js, not the other formats.
*/
export default (sdkVersion: string) => ({
/**
* Read the build code from the `CFBundleVersion` property.
*/
readVersion: iosBuildnumReader,

/**
* Write the manifest version to the `CFBundleVersion` property.
* This uses the Android version code approach of Maxi Rosson.
*
* @see https://medium.com/@maxirosson/versioning-android-apps-d6ec171cfd82
*/
writeVersion: (contents: string, version: string) => iosBuildnumWriter(
contents,
String(
getVersionCodeFromSdkVersion(sdkVersion, version),
),
),
});
24 changes: 24 additions & 0 deletions src/bumpers/native/buildnum/ios-increment.ts
@@ -0,0 +1,24 @@
import { iosBuildnumReader, iosBuildnumWriter } from '../helpers';
import { VersionWriter } from '../../../types';

/**
* Read the buildnum stored at CFBundleVersion in the Info.plist.
*/
export const readVersion = iosBuildnumReader;

/**
* Increment the buildnum stored at CFBundleVersion in the Info.plist.
* This ignores the provided version.
*/
export const writeVersion: VersionWriter = (contents, _version) => {
const buildNumStr = iosBuildnumReader(contents);
const buildNumber = buildNumStr != ''
? Number(buildNumStr)
: 0;

if (Number.isNaN(buildNumber)) {
throw new Error('Could not parse number from `CFBundleVersion`.');
}

return iosBuildnumWriter(contents, String(buildNumber + 1));
};
11 changes: 11 additions & 0 deletions src/bumpers/native/buildnum/ios-version.ts
@@ -0,0 +1,11 @@
import { iosBuildnumReader, iosBuildnumWriter } from '../helpers';

/**
* Read the build version stored at CFBundleVersion in the Info.plist.
*/
export const readVersion = iosBuildnumReader;

/**
* Write the manifest version at CFBundleVersion in the Info.plist.
*/
export const writeVersion = iosBuildnumWriter;