Skip to content

Commit ed8e262

Browse files
committed
Switched to agvtool, fixed increment build inconsistency issue, added programmatic interface
1 parent 366291f commit ed8e262

File tree

5 files changed

+184
-100
lines changed

5 files changed

+184
-100
lines changed

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
Seamlessly shadows the behaviour of [`npm version`](https://docs.npmjs.com/cli/version).
44

5+
## Prerequisites (iOS only)
6+
7+
- Xcode Command Line Tools (`xcode-select --install`)
8+
9+
## Project setup (iOS only)
10+
11+
Open your Xcode project and under "Build Settings -> Versioning -> Current Project Version", set the value to your current `CFBundleVersion` ("General -> Identity -> Build").
12+
13+
---
14+
515
## npm-scripts hook (automatic method)
616

717
### Setup
@@ -39,7 +49,7 @@ react-native-version will then update your `android/` and `ios/` code and automa
3949
npm install -g react-native-version
4050
```
4151

42-
### Example
52+
### Example usage
4353

4454
```shell
4555
cd AwesomeProject/
@@ -54,8 +64,9 @@ react-native-version
5464
-a, --amend Amend the previous commit. This is done automatically when react-native-version is run from the "postversion" npm script. Use "--never-amend" if you never want to amend.
5565
-A, --never-amend Never amend the previous commit
5666
-b, --increment-build Only increment build number
57-
-d, --android [path] Path to your "app/build.gradle" file
58-
-i, --ios [path] Path to your "Info.plist" file
67+
-d, --android [path] Path to your "android/app/build.gradle" file
68+
-i, --ios [path] Path to your "ios/" folder
69+
-r, --reset-build Reset build number back to "1" (iOS only)
5970
-t, --target <platforms> Only version specified platforms, eg. "--target android,ios"
6071
```
6172

@@ -90,5 +101,6 @@ RNV=android react-native-version --target ios
90101

91102
## See also
92103

104+
- [agvtool](https://developer.apple.com/library/content/qa/qa1827/_index.html)
93105
- [npm-version](https://docs.npmjs.com/cli/version)
94106
- [Semantic Versioning (semver)](http://semver.org/)

cli.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env node
2+
const list = require('./utils').list;
3+
const pkg = require('./package');
4+
const program = require('commander');
5+
const rnv = require('./');
6+
7+
const defaults = rnv.getDefaults();
8+
9+
program
10+
/* eslint-disable max-len */
11+
.option('-a, --amend', 'Amend the previous commit. This is done automatically when ' + pkg.name + ' is run from the "postversion" npm script. Use "--never-amend" if you never want to amend.')
12+
.option('-A, --never-amend', 'Never amend the previous commit')
13+
.option('-b, --increment-build', 'Only increment build number')
14+
.option('-d, --android [path]', 'Path to your "android/app/build.gradle" file', defaults.android)
15+
.option('-i, --ios [path]', 'Path to your "ios/" folder', defaults.ios)
16+
.option('-r, --reset-build', 'Reset build number back to "1" (iOS only)')
17+
.option('-t, --target <platforms>', 'Only version specified platforms, eg. "--target android,ios"', list)
18+
/* eslint-enable max-len */
19+
.parse(process.argv);
20+
21+
rnv.version(program);

index.js

Lines changed: 134 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,151 @@
1-
#!/usr/bin/env node
2-
31
const chalk = require('chalk');
42
const child = require('child_process');
53
const fs = require('fs');
4+
const list = require('./utils').list;
65
const path = require('path');
7-
const pkg = require('./package');
8-
const plist = require('simple-plist');
9-
const program = require('commander');
106

117
const cwd = process.cwd();
128

139
const appPkg = require(path.join(cwd, 'package.json'));
1410

15-
/**
16-
* Splits list items in comma-separated lists
17-
* @param {string} val comma-separated list
18-
* @return {array} list items
19-
*/
20-
function list(val) {
21-
return val.split(',');
22-
}
23-
24-
program
25-
/* eslint-disable max-len */
26-
.option('-a, --amend', 'Amend the previous commit. This is done automatically when ' + pkg.name + ' is run from the "postversion" npm script. Use "--never-amend" if you never want to amend.')
27-
.option('-A, --never-amend', 'Never amend the previous commit')
28-
.option('-b, --increment-build', 'Only increment build number')
29-
.option('-d, --android [path]', 'Path to your "app/build.gradle" file', path.join(cwd, 'android/app/build.gradle'))
30-
.option('-i, --ios [path]', 'Path to your "Info.plist" file', path.join(cwd, 'ios', appPkg.name, 'Info.plist'))
31-
.option('-t, --target <platforms>', 'Only version specified platforms, eg. "--target android,ios"', list)
32-
/* eslint-enable max-len */
33-
.parse(process.argv);
34-
35-
/**
36-
* Amends previous commit with changed gradle and plist files
37-
*/
38-
function amend() {
39-
if (
40-
program.amend
41-
|| process.env.npm_lifecycle_event === 'postversion' && !program.neverAmend
42-
|| !program.incrementBuild
43-
) {
44-
child.spawnSync('git', ['add', program.android, program.ios]);
45-
child.execSync('git commit --amend --no-edit');
46-
}
47-
}
48-
4911
const env = {
5012
target: process.env.RNV && list(process.env.RNV)
5113
};
5214

53-
const targets = []
54-
.concat(program.target, env.target)
55-
.filter(function(target) {
56-
return typeof target !== 'undefined';
57-
});
58-
59-
if (!targets.length || targets.indexOf('android') > -1) {
60-
fs.stat(program.android, function(err, stats) {
61-
if (err) {
62-
console.log(chalk.red('No file found at ' + program.android));
63-
console.log(chalk.yellow('Use the "--android" option to specify the path manually'));
64-
program.outputHelp();
65-
} else {
66-
const androidFile = fs.readFileSync(program.android, 'utf8');
67-
var newAndroidFile = androidFile;
68-
69-
if (!program.incrementBuild) {
70-
newAndroidFile = newAndroidFile.replace(
71-
/versionName "(.*)"/, 'versionName "' + appPkg.version + '"'
72-
);
73-
}
15+
const defaults = {
16+
android: path.join(cwd, 'android/app/build.gradle'),
17+
ios: path.join(cwd, 'ios')
18+
};
7419

75-
newAndroidFile = newAndroidFile.replace(/versionCode (\d+)/, function(match, cg1) {
76-
const newVersionCodeNumber = parseInt(cg1, 10) + 1;
77-
return 'versionCode ' + newVersionCodeNumber;
78-
});
79-
80-
fs.writeFileSync(program.android, newAndroidFile);
81-
amend();
82-
}
83-
});
84-
}
85-
86-
if (!targets.length || targets.indexOf('ios') > -1) {
87-
fs.stat(program.ios, function(err, stats) {
88-
if (err) {
89-
console.log(chalk.red('No file found at ' + program.ios));
90-
console.log(chalk.yellow('Use the "--ios" option to specify the path manually'));
91-
program.outputHelp();
92-
} else {
93-
const iosFile = plist.readFileSync(program.ios);
94-
95-
if (program.incrementBuild) {
96-
iosFile.CFBundleVersion = String(parseInt(iosFile.CFBundleVersion, 10) + 1);
97-
} else {
98-
iosFile.CFBundleShortVersionString = appPkg.version;
99-
iosFile.CFBundleVersion = String(1);
20+
module.exports = {
21+
22+
/**
23+
* Returns default values for some options, namely android/ios file/folder paths
24+
* @return {object} defaults
25+
*/
26+
getDefaults: function() {
27+
return defaults;
28+
},
29+
30+
/**
31+
* Version your app. Pass an object of the same options as for the CLI, except camelCased.
32+
* @param {object} program commander-style options, camelCased
33+
*/
34+
version: function(program) {
35+
program.android = program.android || defaults.android;
36+
program.ios = program.ios || defaults.ios;
37+
38+
const targets = []
39+
.concat(program.target, env.target)
40+
.filter(function(target) {
41+
return typeof target !== 'undefined';
42+
});
43+
44+
const android = new Promise(function(resolve) {
45+
if (!targets.length || targets.indexOf('android') > -1) {
46+
fs.stat(program.android, function(err) {
47+
if (err) {
48+
console.log(chalk.red('No gradle file found at ' + program.android));
49+
50+
console.log(chalk.yellow(
51+
'Use the "--android" option to specify the path manually'
52+
));
53+
54+
program.outputHelp();
55+
} else {
56+
const androidFile = fs.readFileSync(program.android, 'utf8');
57+
var newAndroidFile = androidFile;
58+
59+
if (!program.incrementBuild) {
60+
newAndroidFile = newAndroidFile.replace(
61+
/versionName "(.*)"/, 'versionName "' + appPkg.version + '"'
62+
);
63+
}
64+
65+
newAndroidFile = newAndroidFile
66+
.replace(/versionCode (\d+)/, function(match, cg1) {
67+
const newVersionCodeNumber = parseInt(cg1, 10) + 1;
68+
return 'versionCode ' + newVersionCodeNumber;
69+
});
70+
71+
fs.writeFileSync(program.android, newAndroidFile);
72+
resolve();
73+
}
74+
});
10075
}
101-
102-
plist.writeFileSync(program.ios, iosFile);
103-
104-
if (process.platform === 'darwin') {
105-
child.execSync('plutil -convert xml1 ' + program.ios);
76+
});
77+
78+
const ios = new Promise(function(resolve) {
79+
if (!targets.length || targets.indexOf('ios') > -1) {
80+
fs.stat(program.ios, function(err) {
81+
if (err) {
82+
console.log(chalk.red('No project folder found at ' + program.ios));
83+
84+
console.log(chalk.yellow(
85+
'Use the "--ios" option to specify the path manually'
86+
));
87+
88+
program.outputHelp();
89+
} else {
90+
try {
91+
child.execSync('xcode-select --print-path', {
92+
stdio: ['ignore', 'ignore', 'pipe']
93+
});
94+
} catch (err) {
95+
console.log(chalk.red(err));
96+
97+
console.log(chalk.yellow(
98+
'Looks like Xcode Command Line Tools aren\'t installed'
99+
));
100+
101+
console.log('');
102+
console.log(' Install:');
103+
console.log('');
104+
console.log(' $ xcode-select --install');
105+
console.log('');
106+
process.exit(1);
107+
}
108+
109+
const agvtoolOpts = {
110+
cwd: program.ios
111+
};
112+
113+
try {
114+
child.execSync('agvtool what-version', agvtoolOpts);
115+
} catch (err) {
116+
console.log(chalk.red(err.stdout));
117+
process.exit(1);
118+
}
119+
120+
if (!program.incrementBuild) {
121+
child.spawnSync(
122+
'agvtool', ['new-marketing-version', appPkg.version], agvtoolOpts
123+
);
124+
}
125+
126+
if (program.resetBuild) {
127+
child.execSync('agvtool new-version -all 1', agvtoolOpts);
128+
} else {
129+
child.execSync('agvtool next-version -all', agvtoolOpts);
130+
}
131+
132+
resolve();
133+
}
134+
});
135+
}
136+
});
137+
138+
Promise
139+
.all([android, ios])
140+
.then(function() {
141+
if (
142+
program.amend
143+
|| process.env.npm_lifecycle_event === 'postversion' && !program.neverAmend
144+
) {
145+
child.spawnSync('git', ['add', program.android, program.ios]);
146+
child.execSync('git commit --amend --no-edit');
106147
}
148+
});
149+
}
107150

108-
amend();
109-
}
110-
});
111-
}
151+
};

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
"license": "MIT",
77
"main": "index.js",
88
"bin": {
9-
"react-native-version": "index.js"
9+
"react-native-version": "cli.js"
1010
},
1111
"dependencies": {
1212
"chalk": "^1.1.3",
13-
"commander": "^2.9.0",
14-
"simple-plist": "^0.1.4"
13+
"commander": "^2.9.0"
1514
},
1615
"devDependencies": {
1716
"eslint": "^3.4.0",

utils.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
3+
/**
4+
* Splits list items in comma-separated lists
5+
* @param {string} val comma-separated list
6+
* @return {array} list items
7+
*/
8+
list: function(val) {
9+
return val.split(',');
10+
}
11+
12+
};

0 commit comments

Comments
 (0)