2018-03-09 22:59:12 +02:00
|
|
|
const fs = require('fs-extra');
|
2019-10-13 01:21:56 +02:00
|
|
|
const { execCommand, execCommandWithPipes, githubRelease, githubOauthToken, fileExists } = require('./tool-utils.js');
|
2018-03-09 22:59:12 +02:00
|
|
|
const path = require('path');
|
|
|
|
const fetch = require('node-fetch');
|
|
|
|
const uriTemplate = require('uri-template');
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-07-28 18:19:41 +02:00
|
|
|
const projectName = 'joplin-android';
|
2019-09-19 23:51:18 +02:00
|
|
|
const rnDir = `${__dirname}/../ReactNativeClient`;
|
2018-01-17 22:16:13 +02:00
|
|
|
const rootDir = path.dirname(__dirname);
|
2019-09-19 23:51:18 +02:00
|
|
|
const releaseDir = `${rootDir}/_releases`;
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-10-13 01:21:56 +02:00
|
|
|
// 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('\\');
|
|
|
|
// }
|
2019-06-15 19:58:09 +02:00
|
|
|
|
2018-01-17 22:16:13 +02:00
|
|
|
function increaseGradleVersionCode(content) {
|
2019-09-13 00:16:42 +02:00
|
|
|
const newContent = content.replace(/versionCode\s+(\d+)/, function(a, versionCode) {
|
2018-01-17 22:16:13 +02:00
|
|
|
const n = Number(versionCode);
|
2019-09-19 23:51:18 +02:00
|
|
|
if (isNaN(n) || !n) throw new Error(`Invalid version code: ${versionCode}`);
|
|
|
|
return `versionCode ${n + 1}`;
|
2018-01-17 22:16:13 +02:00
|
|
|
});
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
if (newContent === content) throw new Error('Could not update version code');
|
2018-01-17 22:16:13 +02:00
|
|
|
|
|
|
|
return newContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
function increaseGradleVersionName(content) {
|
|
|
|
const newContent = content.replace(/(versionName\s+"\d+?\.\d+?\.)(\d+)"/, function(match, prefix, buildNum) {
|
|
|
|
const n = Number(buildNum);
|
2020-09-12 01:10:18 +02:00
|
|
|
if (isNaN(n)) throw new Error(`Invalid version code: ${buildNum}`);
|
2019-09-19 23:51:18 +02:00
|
|
|
return `${prefix + (n + 1)}"`;
|
2018-01-17 22:16:13 +02:00
|
|
|
});
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
if (newContent === content) throw new Error('Could not update version name');
|
2018-01-17 22:16:13 +02:00
|
|
|
|
|
|
|
return newContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateGradleConfig() {
|
2019-09-19 23:51:18 +02:00
|
|
|
let content = fs.readFileSync(`${rnDir}/android/app/build.gradle`, 'utf8');
|
2018-01-17 22:16:13 +02:00
|
|
|
content = increaseGradleVersionCode(content);
|
|
|
|
content = increaseGradleVersionName(content);
|
2019-09-19 23:51:18 +02:00
|
|
|
fs.writeFileSync(`${rnDir}/android/app/build.gradle`, content);
|
2018-01-17 22:16:13 +02:00
|
|
|
return content;
|
|
|
|
}
|
|
|
|
|
|
|
|
function gradleVersionName(content) {
|
|
|
|
const matches = content.match(/versionName\s+"(\d+?\.\d+?\.\d+)"/);
|
2018-03-09 22:59:12 +02:00
|
|
|
if (!matches || matches.length < 1) throw new Error('Cannot get gradle version name');
|
2018-01-17 22:16:13 +02:00
|
|
|
return matches[1];
|
|
|
|
}
|
|
|
|
|
2020-09-04 20:11:46 +02:00
|
|
|
async function createRelease(name, tagName, version) {
|
2019-07-28 18:19:41 +02:00
|
|
|
const originalContents = {};
|
2020-09-04 20:11:46 +02:00
|
|
|
const suffix = version + (name === 'main' ? '' : `-${name}`);
|
2018-12-29 04:12:23 +02:00
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Creating release: ${suffix}`);
|
2019-07-28 18:19:41 +02:00
|
|
|
|
|
|
|
if (name === '32bit') {
|
2020-03-14 01:46:14 +02:00
|
|
|
const filename = `${rnDir}/android/app/build.gradle`;
|
2019-07-28 18:19:41 +02:00
|
|
|
let content = await fs.readFile(filename, 'utf8');
|
|
|
|
originalContents[filename] = content;
|
2019-07-30 09:35:42 +02:00
|
|
|
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"');
|
2019-07-28 18:19:41 +02:00
|
|
|
await fs.writeFile(filename, content);
|
|
|
|
}
|
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
const apkFilename = `joplin-v${suffix}.apk`;
|
|
|
|
const apkFilePath = `${releaseDir}/${apkFilename}`;
|
|
|
|
const downloadUrl = `https://github.com/laurent22/${projectName}/releases/download/${tagName}/${apkFilename}`;
|
2018-01-17 22:16:13 +02:00
|
|
|
|
|
|
|
process.chdir(rootDir);
|
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Running from: ${process.cwd()}`);
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Building APK file v${suffix}...`);
|
2018-10-13 00:25:11 +02:00
|
|
|
|
|
|
|
let restoreDir = null;
|
2019-06-15 19:58:09 +02:00
|
|
|
let apkBuildCmd = 'assembleRelease -PbuildDir=build';
|
2019-06-15 10:44:34 +02:00
|
|
|
if (await fileExists('/mnt/c/Windows/System32/cmd.exe')) {
|
2019-06-15 19:58:09 +02:00
|
|
|
// 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.
|
|
|
|
|
2019-10-13 01:21:56 +02:00
|
|
|
// 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 = '';
|
2019-06-15 10:44:34 +02:00
|
|
|
} else {
|
2019-09-19 23:51:18 +02:00
|
|
|
process.chdir(`${rnDir}/android`);
|
|
|
|
apkBuildCmd = `./gradlew ${apkBuildCmd}`;
|
2019-06-15 10:44:34 +02:00
|
|
|
restoreDir = rootDir;
|
|
|
|
}
|
|
|
|
|
2019-06-15 19:58:09 +02:00
|
|
|
if (apkBuildCmd) {
|
|
|
|
console.info(apkBuildCmd);
|
|
|
|
const output = await execCommand(apkBuildCmd);
|
|
|
|
console.info(output);
|
|
|
|
}
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2018-10-13 00:25:11 +02:00
|
|
|
if (restoreDir) process.chdir(restoreDir);
|
|
|
|
|
2018-01-17 22:16:13 +02:00
|
|
|
await fs.mkdirp(releaseDir);
|
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Copying APK to ${apkFilePath}`);
|
2018-10-02 19:19:27 +02:00
|
|
|
await fs.copy('ReactNativeClient/android/app/build/outputs/apk/release/app-release.apk', apkFilePath);
|
2019-07-28 18:29:07 +02:00
|
|
|
|
|
|
|
if (name === 'main') {
|
2019-09-19 23:51:18 +02:00
|
|
|
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`);
|
2019-07-28 18:29:07 +02:00
|
|
|
}
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
for (const filename in originalContents) {
|
2019-07-28 18:19:41 +02:00
|
|
|
const content = originalContents[filename];
|
|
|
|
await fs.writeFile(filename, content);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
downloadUrl: downloadUrl,
|
2019-07-28 18:31:31 +02:00
|
|
|
apkFilename: apkFilename,
|
2019-07-28 18:33:48 +02:00
|
|
|
apkFilePath: apkFilePath,
|
2019-07-28 18:19:41 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async function main() {
|
2020-03-07 01:45:07 +02:00
|
|
|
const argv = require('yargs').argv;
|
|
|
|
|
2020-09-22 17:36:46 +02:00
|
|
|
if (!['release', 'prerelease'].includes(argv.type)) throw new Error('Must specify release type. Either --type=release or --type=prerelease');
|
|
|
|
|
|
|
|
const isPreRelease = argv.type === 'prerelease';
|
2020-03-07 01:45:07 +02:00
|
|
|
|
2020-09-04 20:11:46 +02:00
|
|
|
if (isPreRelease) console.info('Creating pre-release');
|
2019-07-28 18:19:41 +02:00
|
|
|
console.info('Updating version numbers in build.gradle...');
|
|
|
|
|
|
|
|
const newContent = updateGradleConfig();
|
|
|
|
const version = gradleVersionName(newContent);
|
2020-09-04 20:11:46 +02:00
|
|
|
const tagName = `android-v${version}`;
|
2019-07-28 18:29:07 +02:00
|
|
|
const releaseNames = ['main', '32bit'];
|
|
|
|
const releaseFiles = {};
|
2019-07-28 18:19:41 +02:00
|
|
|
|
2019-07-28 18:29:07 +02:00
|
|
|
for (const releaseName of releaseNames) {
|
2020-09-04 20:11:46 +02:00
|
|
|
releaseFiles[releaseName] = await createRelease(releaseName, tagName, version);
|
2019-07-28 18:29:07 +02:00
|
|
|
}
|
2019-07-28 18:19:41 +02:00
|
|
|
|
2020-09-04 20:11:46 +02:00
|
|
|
if (!isPreRelease) {
|
2020-03-07 01:45:07 +02:00
|
|
|
console.info('Updating Readme URL...');
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2020-03-07 01:45:07 +02:00
|
|
|
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);
|
|
|
|
}
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
console.info(await execCommand('git pull'));
|
|
|
|
console.info(await execCommand('git add -A'));
|
2020-09-04 20:11:46 +02:00
|
|
|
console.info(await execCommand(`git commit -m "Android release v${version}"`));
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(await execCommand(`git tag ${tagName}`));
|
2018-03-09 22:59:12 +02:00
|
|
|
console.info(await execCommand('git push'));
|
|
|
|
console.info(await execCommand('git push --tags'));
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Creating GitHub release ${tagName}...`);
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2020-09-04 20:11:46 +02:00
|
|
|
const releaseOptions = { isPreRelease: isPreRelease };
|
2020-09-04 18:28:18 +02:00
|
|
|
|
2019-07-28 18:33:48 +02:00
|
|
|
const oauthToken = await githubOauthToken();
|
2020-09-04 18:28:18 +02:00
|
|
|
const release = await githubRelease(projectName, tagName, releaseOptions);
|
2019-07-28 18:36:36 +02:00
|
|
|
const uploadUrlTemplate = uriTemplate.parse(release.upload_url);
|
2019-07-28 18:33:48 +02:00
|
|
|
|
2019-07-28 18:31:31 +02:00
|
|
|
for (const releaseFilename in releaseFiles) {
|
|
|
|
const releaseFile = releaseFiles[releaseFilename];
|
|
|
|
const uploadUrl = uploadUrlTemplate.expand({ name: releaseFile.apkFilename });
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-07-28 18:33:48 +02:00
|
|
|
const binaryBody = await fs.readFile(releaseFile.apkFilePath);
|
2018-02-04 19:42:33 +02:00
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Uploading ${releaseFile.apkFilename} to ${uploadUrl}`);
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-07-28 18:29:07 +02:00
|
|
|
const uploadResponse = await fetch(uploadUrl, {
|
2019-07-30 09:35:42 +02:00
|
|
|
method: 'POST',
|
2019-07-28 18:29:07 +02:00
|
|
|
body: binaryBody,
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/vnd.android.package-archive',
|
2019-09-19 23:51:18 +02:00
|
|
|
'Authorization': `token ${oauthToken}`,
|
2019-07-28 18:29:07 +02:00
|
|
|
'Content-Length': binaryBody.length,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const uploadResponseText = await uploadResponse.text();
|
2019-07-28 22:43:33 +02:00
|
|
|
const uploadResponseObject = JSON.parse(uploadResponseText);
|
|
|
|
if (!uploadResponseObject || !uploadResponseObject.browser_download_url) throw new Error('Could not upload file to GitHub');
|
2019-07-28 18:29:07 +02:00
|
|
|
}
|
2018-01-17 22:16:13 +02:00
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
console.info(`Main download URL: ${releaseFiles['main'].downloadUrl}`);
|
2018-01-17 22:16:13 +02:00
|
|
|
}
|
|
|
|
|
2018-03-09 22:59:12 +02:00
|
|
|
main().catch((error) => {
|
|
|
|
console.error('Fatal error');
|
2018-01-17 22:16:13 +02:00
|
|
|
console.error(error);
|
2018-03-23 19:32:29 +02:00
|
|
|
process.exit(1);
|
2019-07-30 09:35:42 +02:00
|
|
|
});
|