Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 54 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,35 @@ no-op.

## Usage

Run it once with npx:
Without arguments, the command compares the native files with the version in
`package.json` and exits with status 1 when a platform is out of sync, which
makes it usable as a CI check:

```bash
npx rn-version-sync
```
$ npx rn-version-sync
PLATFORM APP ID VERSION STATUS
js example-app 1.2.3
android com.example.app 1.2.3 (10203) ok
ios com.example.app 1.2.2 (10202) outdated
Run with --write to update the native files.
```

Every row shows what its file says: `name` and `version` from `package.json`
in the `js` row, and app id, version name and version code from the native
files. When `--version-name` or `--version-code` replace the `package.json`
values, the `js` row's status names the target, for example
`overridden 2.0.0 (20000)`. Add `--write` to update the native files:

```
$ npx rn-version-sync --write
PLATFORM APP ID VERSION STATUS
js example-app 1.2.3
android com.example.app 1.2.3 (10203) unchanged
ios com.example.app 1.2.3 (10203) updated
```

Or install it as a dev dependency and hook it into the `npm version` lifecycle:
To keep the native files in step with `npm version`, install it as a dev
dependency and hook it into the `version` lifecycle:

```bash
npm install --save-dev rn-version-sync
Expand All @@ -53,31 +75,31 @@ yarn add -D rn-version-sync
```json
{
"scripts": {
"version": "rn-version-sync && git add -u"
"version": "rn-version-sync --write && git add -u"
}
}
```

Now `npm version patch|minor|major` bumps `package.json` and the native files
together, in one commit.

A platform whose native file cannot be found is skipped with a warning (pass
`--skip-android` or `--skip-ios` to silence it), and the command fails when
nothing could be synced.
A platform whose native file cannot be found is listed as `not found` (pass
`--skip-android` or `--skip-ios` to leave it out), and the command fails when
no native file is left.

### Options

| Option | Description |
| ------ | ----------- |
| `--version-name <name>` | Write this version name instead of the `package.json` version. Requires `--version-code` unless it is semver. |
| `--version-code <code>` | Write this version code instead of the calculated one |
| `--write` | Update the native files instead of only comparing them |
| `--version-name <name>` | Use this version name instead of the `package.json` version. Requires `--version-code` unless it is semver. |
| `--version-code <code>` | Use this version code instead of the calculated one |
| `--reserve-builds <n>` | Multiply the calculated version code by `n` so each version owns `n` consecutive codes, e.g. for CI builds that bump the code per build (`100` turns `10203` into `1020300`). Ignored with `--version-code`. |
| `--skip-android`, `--skip-ios` | Leave that platform untouched |
| `--skip-android`, `--skip-ios` | Ignore that platform |
| `--project-dir <dir>` | Project root (default: current directory) |
| `--gradle-path <path>` | Android `build.gradle` to update, instead of `android/app/build.gradle` |
| `--pbxproj-path <path>` | iOS `project.pbxproj` to update, instead of the first `.xcodeproj` in `ios/` |
| `--dry-run` | Print the resolved version name and code without writing anything |
| `-v, --verbose` | Log every value and file as it is updated |
| `--gradle-path <path>` | Android `build.gradle` to use, instead of `android/app/build.gradle` |
| `--pbxproj-path <path>` | iOS `project.pbxproj` to use, instead of the first `.xcodeproj` in `ios/` |
| `--configuration <name>` | Xcode build configuration to read the iOS values from, in the table and with the `--print*` flags (default `Release`) |

Relative `--gradle-path` and `--pbxproj-path` values are resolved against the
project directory. Version codes must be positive integers up to
Expand All @@ -86,7 +108,7 @@ project directory. Version codes must be positive integers up to
## Reading values from native files

The `--print*` flags read what is actually written in the native files instead
of syncing. They print the requested value to stdout and nothing else, which
of comparing. They print the requested value to stdout and nothing else, which
makes them handy for CI scripts that tag releases in Sentry, GitHub, and the
like. `--gradle-path` and `--pbxproj-path` apply here as well.

Expand Down Expand Up @@ -183,16 +205,29 @@ steps:
The same functionality is available as a module, with type definitions:

```js
const { syncVersions, readNativeValues } = require('rn-version-sync');
const { checkVersions, syncVersions, readNativeValues } = require('rn-version-sync');

checkVersions(process.cwd());
// {
// packageName: 'example-app',
// packageVersion: '1.2.3',
// target: { versionName: '1.2.3', versionCode: 10203 },
// overridden: false,
// platforms: [
// { platform: 'android', path: '/app/android/app/build.gradle', appId: 'com.example.app', versionName: '1.2.3', versionCode: '10203', inSync: true },
// { platform: 'ios', path: '/app/ios/App.xcodeproj/project.pbxproj', appId: 'com.example.app', versionName: '1.2.2', versionCode: '10202', inSync: false },
// ],
// missing: [],
// }

syncVersions(process.cwd(), { reserveBuilds: 100 });
// { android: '/app/android/app/build.gradle', ios: '/app/ios/App.xcodeproj/project.pbxproj' }
// same shape, read back after writing, with an `updated` flag per platform

readNativeValues(process.cwd(), 'ios', { configuration: 'Staging' });
// { appId: 'com.example.app.staging', versionName: '1.2.3', versionCode: '1020300' }
```

`resolveVersions`, `formatTemplate` and `formatEnv` back `--dry-run`,
`resolveVersions`, `formatTemplate` and `formatEnv` back the target row,
`--format` and `--print-env` in the same way.

## Requirements
Expand Down
25 changes: 8 additions & 17 deletions src/__tests__/android.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('updateAndroidVersion', () => {
it('updates versionName and versionCode', () => {
project = new TestProject({ ios: false });

const result = updateAndroidVersion(project.root, '2.3.4', 20304, false);
const result = updateAndroidVersion(project.root, '2.3.4', 20304);

expect(result).toBe(project.gradlePath());
const content = project.readGradle();
Expand All @@ -32,26 +32,24 @@ describe('updateAndroidVersion', () => {
});

const before = fs.statSync(project.gradlePath()).mtimeMs;
updateAndroidVersion(project.root, '1.2.3', 10203, false);
updateAndroidVersion(project.root, '1.2.3', 10203);
const after = fs.statSync(project.gradlePath()).mtimeMs;
expect(after).toBe(before);
});

it('returns null when build.gradle is missing', () => {
project = new TestProject({ android: false, ios: false });

expect(
updateAndroidVersion(project.root, '1.0.0', 10000, false),
).toBeNull();
expect(updateAndroidVersion(project.root, '1.0.0', 10000)).toBeNull();
});

it('throws when build.gradle has no version settings', () => {
project = new TestProject({ ios: false });
fs.writeFileSync(project.gradlePath(), 'android {\n}\n');

expect(() =>
updateAndroidVersion(project.root, '1.0.0', 10000, false),
).toThrow('No versionName found');
expect(() => updateAndroidVersion(project.root, '1.0.0', 10000)).toThrow(
'No versionName found',
);
});

it('handles single-quoted versionName', () => {
Expand All @@ -60,7 +58,7 @@ describe('updateAndroidVersion', () => {
ios: false,
});

updateAndroidVersion(project.root, '2.0.0', 20000, false);
updateAndroidVersion(project.root, '2.0.0', 20000);

const content = project.readGradle();
expect(content).toContain("versionName '2.0.0'");
Expand All @@ -69,13 +67,7 @@ describe('updateAndroidVersion', () => {
it('uses explicit gradlePath when provided', () => {
project = new TestProject({ ios: false });

updateAndroidVersion(
project.root,
'5.0.0',
50000,
false,
project.gradlePath(),
);
updateAndroidVersion(project.root, '5.0.0', 50000, project.gradlePath());

const content = project.readGradle();
expect(content).toContain('versionName "5.0.0"');
Expand All @@ -89,7 +81,6 @@ describe('updateAndroidVersion', () => {
project.root,
'1.0.0',
10000,
false,
'/nonexistent/build.gradle',
),
).toThrow('build.gradle not found at specified path');
Expand Down
Loading