forked from laurent22/joplin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
release-android.js
225 lines (173 loc) · 8.22 KB
/
release-android.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
const fs = require('fs-extra');
const { execCommand, execCommandWithPipes, githubRelease, githubOauthToken, fileExists } = require('./tool-utils.js');
const path = require('path');
const fetch = require('node-fetch');
const uriTemplate = require('uri-template');
const projectName = 'joplin-android';
const rnDir = `${__dirname}/../ReactNativeClient`;
const rootDir = path.dirname(__dirname);
const releaseDir = `${rootDir}/_releases`;
// function wslToWinPath(wslPath) {
// const s = wslPath.split('/');
// if (s.length < 3) return s.join('\\');
// s.splice(0, 1);
// if (s[0] !== 'mnt' || s[1].length !== 1) return s.join('\\');
// s.splice(0, 1);
// s[0] = `${s[0].toUpperCase()}:`;
// while (s.length && !s[s.length - 1]) s.pop();
// return s.join('\\');
// }
function increaseGradleVersionCode(content) {
const newContent = content.replace(/versionCode\s+(\d+)/, function(a, versionCode) {
const n = Number(versionCode);
if (isNaN(n) || !n) throw new Error(`Invalid version code: ${versionCode}`);
return `versionCode ${n + 1}`;
});
if (newContent === content) throw new Error('Could not update version code');
return newContent;
}
function increaseGradleVersionName(content) {
const newContent = content.replace(/(versionName\s+"\d+?\.\d+?\.)(\d+)"/, function(match, prefix, buildNum) {
const n = Number(buildNum);
if (isNaN(n)) throw new Error(`Invalid version code: ${buildNum}`);
return `${prefix + (n + 1)}"`;
});
if (newContent === content) throw new Error('Could not update version name');
return newContent;
}
function updateGradleConfig() {
let content = fs.readFileSync(`${rnDir}/android/app/build.gradle`, 'utf8');
content = increaseGradleVersionCode(content);
content = increaseGradleVersionName(content);
fs.writeFileSync(`${rnDir}/android/app/build.gradle`, content);
return content;
}
function gradleVersionName(content) {
const matches = content.match(/versionName\s+"(\d+?\.\d+?\.\d+)"/);
if (!matches || matches.length < 1) throw new Error('Cannot get gradle version name');
return matches[1];
}
async function createRelease(name, tagName, version) {
const originalContents = {};
const suffix = version + (name === 'main' ? '' : `-${name}`);
console.info(`Creating release: ${suffix}`);
if (name === '32bit') {
const filename = `${rnDir}/android/app/build.gradle`;
let content = await fs.readFile(filename, 'utf8');
originalContents[filename] = content;
content = content.replace(/abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"/, 'abiFilters "armeabi-v7a", "x86"');
content = content.replace(/include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"/, 'include "armeabi-v7a", "x86"');
await fs.writeFile(filename, content);
}
const apkFilename = `joplin-v${suffix}.apk`;
const apkFilePath = `${releaseDir}/${apkFilename}`;
const downloadUrl = `https://github.com/laurent22/${projectName}/releases/download/${tagName}/${apkFilename}`;
process.chdir(rootDir);
console.info(`Running from: ${process.cwd()}`);
console.info(`Building APK file v${suffix}...`);
let restoreDir = null;
let apkBuildCmd = 'assembleRelease -PbuildDir=build';
if (await fileExists('/mnt/c/Windows/System32/cmd.exe')) {
// In recent versions (of Gradle? React Native?), running gradlew.bat from WSL throws the following error:
// Error: Command failed: /mnt/c/Windows/System32/cmd.exe /c "cd ReactNativeClient\android && gradlew.bat assembleRelease -PbuildDir=build"
// FAILURE: Build failed with an exception.
// * What went wrong:
// Could not determine if Stdout is a console: could not get handle file information (errno 1)
// So we need to manually run the command from DOS, and then coming back here to finish the process once it's done.
// console.info('Run this command from DOS:');
// console.info('');
// console.info(`cd "${wslToWinPath(rootDir)}\\ReactNativeClient\\android" && gradlew.bat ${apkBuildCmd}"`);
// console.info('');
// await readline('Press Enter when done:');
// apkBuildCmd = ''; // Clear the command because we've already ran it
// process.chdir(`${rnDir}/android`);
// apkBuildCmd = `/mnt/c/Windows/System32/cmd.exe /c "cd ReactNativeClient\\android && gradlew.bat ${apkBuildCmd}"`;
// restoreDir = rootDir;
// apkBuildCmd = `/mnt/c/Windows/System32/cmd.exe /c "cd ReactNativeClient\\android && gradlew.bat ${apkBuildCmd}"`;
await execCommandWithPipes('/mnt/c/Windows/System32/cmd.exe', ['/c', `cd ReactNativeClient\\android && gradlew.bat ${apkBuildCmd}`]);
apkBuildCmd = '';
} else {
process.chdir(`${rnDir}/android`);
apkBuildCmd = `./gradlew ${apkBuildCmd}`;
restoreDir = rootDir;
}
if (apkBuildCmd) {
console.info(apkBuildCmd);
const output = await execCommand(apkBuildCmd);
console.info(output);
}
if (restoreDir) process.chdir(restoreDir);
await fs.mkdirp(releaseDir);
console.info(`Copying APK to ${apkFilePath}`);
await fs.copy('ReactNativeClient/android/app/build/outputs/apk/release/app-release.apk', apkFilePath);
if (name === 'main') {
console.info(`Copying APK to ${releaseDir}/joplin-latest.apk`);
await fs.copy('ReactNativeClient/android/app/build/outputs/apk/release/app-release.apk', `${releaseDir}/joplin-latest.apk`);
}
for (const filename in originalContents) {
const content = originalContents[filename];
await fs.writeFile(filename, content);
}
return {
downloadUrl: downloadUrl,
apkFilename: apkFilename,
apkFilePath: apkFilePath,
};
}
async function main() {
const argv = require('yargs').argv;
if (!['release', 'prerelease'].includes(argv.type)) throw new Error('Must specify release type. Either --type=release or --type=prerelease');
const isPreRelease = argv.type === 'prerelease';
if (isPreRelease) console.info('Creating pre-release');
console.info('Updating version numbers in build.gradle...');
const newContent = updateGradleConfig();
const version = gradleVersionName(newContent);
const tagName = `android-v${version}`;
const releaseNames = ['main', '32bit'];
const releaseFiles = {};
for (const releaseName of releaseNames) {
releaseFiles[releaseName] = await createRelease(releaseName, tagName, version);
}
if (!isPreRelease) {
console.info('Updating Readme URL...');
let readmeContent = await fs.readFile('README.md', 'utf8');
readmeContent = readmeContent.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+\.apk)/, releaseFiles['main'].downloadUrl);
readmeContent = readmeContent.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+-32bit\.apk)/, releaseFiles['32bit'].downloadUrl);
await fs.writeFile('README.md', readmeContent);
}
console.info(await execCommand('git pull'));
console.info(await execCommand('git add -A'));
console.info(await execCommand(`git commit -m "Android release v${version}"`));
console.info(await execCommand(`git tag ${tagName}`));
console.info(await execCommand('git push'));
console.info(await execCommand('git push --tags'));
console.info(`Creating GitHub release ${tagName}...`);
const releaseOptions = { isPreRelease: isPreRelease };
const oauthToken = await githubOauthToken();
const release = await githubRelease(projectName, tagName, releaseOptions);
const uploadUrlTemplate = uriTemplate.parse(release.upload_url);
for (const releaseFilename in releaseFiles) {
const releaseFile = releaseFiles[releaseFilename];
const uploadUrl = uploadUrlTemplate.expand({ name: releaseFile.apkFilename });
const binaryBody = await fs.readFile(releaseFile.apkFilePath);
console.info(`Uploading ${releaseFile.apkFilename} to ${uploadUrl}`);
const uploadResponse = await fetch(uploadUrl, {
method: 'POST',
body: binaryBody,
headers: {
'Content-Type': 'application/vnd.android.package-archive',
'Authorization': `token ${oauthToken}`,
'Content-Length': binaryBody.length,
},
});
const uploadResponseText = await uploadResponse.text();
const uploadResponseObject = JSON.parse(uploadResponseText);
if (!uploadResponseObject || !uploadResponseObject.browser_download_url) throw new Error('Could not upload file to GitHub');
}
console.info(`Main download URL: ${releaseFiles['main'].downloadUrl}`);
}
main().catch((error) => {
console.error('Fatal error');
console.error(error);
process.exit(1);
});