Skip to content

Instantly share code, notes, and snippets.

@BransonGitomeh
Last active January 19, 2022 10:27
Show Gist options
  • Save BransonGitomeh/4f9f04c23e0f413e9c2f38d38697ed20 to your computer and use it in GitHub Desktop.
Save BransonGitomeh/4f9f04c23e0f413e9c2f38d38697ed20 to your computer and use it in GitHub Desktop.
Script to upload apk to playstore, can be added to CI step to automate publications
#!/usr/bin/env NODE_NO_WARNINGS=1 node
const { google } = require('googleapis');
const Promise = require('bluebird');
const fs = require('fs');
const settings = require('./config/settings.json');
const { version } = require('./package.json');
// Enable API access into the Developer Console: https://play.google.com/apps/publish/?account=7639196906174529268#ApiAccessPlace
// Create a service account
// Download the JSON and save it here
// Make sure the email of the JSON is added to the apps for release manager role:
// https://play.google.com/apps/publish/?account=7639196906174529268#AdminPlace
const key = require('./secrets.json');
// editing "scope" allowed for OAuth2
const scopes = ['https://www.googleapis.com/auth/androidpublisher'];
process.exitCode = 1;
const { OAuth2 } = google.auth;
const oauth2Client = new OAuth2();
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
scopes,
null,
);
const play = google.androidpublisher({
version: 'v3',
auth: oauth2Client,
params: {
// default options
// this is the package name for your initial app you've already set up on the Play Store
packageName: settings.app.id,
},
});
google.options({ auth: oauth2Client });
/**
* Sets our authorization token and begins an edit transaction.
*/
function startEdit() {
return new Promise(((resolve, reject) => {
jwtClient.authorize((err, tokens) => {
if (err) {
console.log('authorize error', err);
process.exit(1);
return;
}
// Set the credentials before we doing anything.
oauth2Client.setCredentials(tokens);
play.edits.insert(
{
packageName: settings.app.id,
},
(insertError, edit) => {
if (insertError || !edit) {
console.log('Insert errors', insertError);
reject(err);
}
resolve({
edit: edit.data,
});
},
);
});
}));
}
const start = async () => {
const { packageName = 'io.braiven.databank' } = process.env;
console.log(`Upload started for ${packageName} for version ${version}`);
const {
edit: { id: editId },
} = await startEdit();
const builds = ['app-armeabi-v7a-release.apk', 'app-x86-release.apk'];
const availableOutputFiles = fs.readdirSync('./android/app/build/outputs/apk/release');
console.log('Available builds are', availableOutputFiles.join(', '));
const uploadRes = await Promise.all(
builds.map(async (build) => {
try {
// dont try read a build thats not on the FS
if (!availableOutputFiles.includes(build)) { return; }
const apk = fs.readFileSync(`./android/app/build/outputs/apk/release/${build}`);
console.log(`Attempting Upload for cpu build ${build}`);
const {
data: {
versionCode: uploadedVersionCode,
binary: { sha256 },
},
} = await play.edits.apks.upload({
editId,
packageName,
media: {
mimeType: 'application/vnd.android.package-archive',
body: apk,
},
});
console.log(`Successfully uploaded version:${uploadedVersionCode}, build:${build}, sha256:${sha256}`);
return { versionCode: uploadedVersionCode, sha256 };
} catch (err) {
console.log(`Upload for ${build} failed`, err.message);
throw err;
}
}),
);
console.log(JSON.stringify({ uploadRes }, null, '\t'));
// Assign apk to beta track.
const { data: trackRes } = await play.edits.tracks.update({
editId,
track: 'beta',
packageName,
releases: [
{
versionCodes: [uploadRes[0].versionCode],
status: 'completed',
},
],
});
console.log('Placing edit on beta track', trackRes);
const { data: lists } = await play.edits.apks.list({ editId });
console.log('Edits apk list', lists.apks.map(apk => apk.versionCode));
// const { data: lists } = await play.edits.apks.u;
const { data: commit } = await play.edits.commit({
editId,
packageName,
});
console.log({ commit });
};
start().catch((err) => {
const errString = 'Upload failed with the following errors';
if (err.errors) {
console.error(`${errString} ${JSON.stringify(err.errors, null, '\t')}`);
} else {
console.error(`${errString} ${err}`);
}
process.exit(1);
});
@BransonGitomeh
Copy link
Author

BransonGitomeh commented Nov 19, 2018

important notes

  • Create service account on google api console, export key JSON (secrets.json)
  • Create tole for service account for android developer release manager
  • Create role on play console permissions for this service account email (i couldnt find this step documented by google as of today(19 nov 2019))
  • Add it as a yarn script so you can do something like yarn publish-release

@BransonGitomeh
Copy link
Author

After you do this, users on each track will be given the updated version of the APK. (As with all edits, it can take several hours for the changes to take effect.)

@BransonGitomeh
Copy link
Author

has served my team for quite some time now, added some improvements to exit the process with clear logs incase of failures

@woodcockjosh
Copy link

Is this up to date? I'm getting invalid request errors. Also how is this different / better than the apkup package?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment