smalltalk-matrix/tools/beta-release/release.js

202 lines
6.1 KiB
JavaScript
Raw Normal View History

import * as google from '@googleapis/androidpublisher';
import * as fs from "fs";
import * as http from 'https';
import * as url from 'url';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const auth = new google.auth.GoogleAuth({
keyFile: '.secrets/service-account.json',
scopes: ['https://www.googleapis.com/auth/androidpublisher'],
})
const androidPublisher = google.androidpublisher({
version: 'v3',
auth: auth,
})
const universalApkPath = `${__dirname}/universal.apk`
export const release = async (github, version, applicationId, artifacts, config) => {
const appEditId = await startPlayRelease(applicationId)
console.log("Uploading bundle...")
await uploadBundle(appEditId, applicationId, artifacts.bundle)
console.log("Uploading mapping...")
await uploadMappingFile(appEditId, version.code, applicationId, artifacts.mapping)
console.log("Assign artifacts to beta release...")
await addReleaseToTrack(appEditId, version, applicationId)
console.log("Commiting draft release...")
await androidPublisher.edits.commit({
editId: appEditId,
packageName: applicationId,
}).catch((error) => Promise.reject(error.response.data))
console.log("Downloading generated universal apk...")
await dowloadSignedUniversalApk(
version,
applicationId,
await auth.getAccessToken(),
universalApkPath
)
const releaseResult = await github.rest.repos.createRelease({
owner: config.owner,
repo: config.repo,
tag_name: version.name,
prerelease: true,
generate_release_notes: true,
})
console.log(releaseResult.data.id)
await github.rest.repos.uploadReleaseAsset({
owner: config.owner,
repo: config.repo,
release_id: releaseResult.data.id,
name: `universal-${version.name}.apk`,
data: fs.readFileSync(universalApkPath)
})
console.log("Promoting beta draft release to live...")
await promoteDraftToLive(applicationId)
}
const startPlayRelease = async (applicationId) => {
const result = await androidPublisher.edits.insert({
packageName: applicationId
}).catch((error) => Promise.reject(error.response.data))
return result.data.id
}
const uploadBundle = async (appEditId, applicationId, bundleReleaseFile) => {
const res = await androidPublisher.edits.bundles.upload({
packageName: applicationId,
editId: appEditId,
media: {
mimeType: 'application/octet-stream',
body: fs.createReadStream(bundleReleaseFile)
}
}).catch((error) => Promise.reject(error.response.data))
return res.data
}
const uploadMappingFile = async (appEditId, versionCode, applicationId, mappingFilePath) => {
await androidPublisher.edits.deobfuscationfiles.upload({
packageName: applicationId,
editId: appEditId,
apkVersionCode: versionCode,
deobfuscationFileType: 'proguard',
media: {
mimeType: 'application/octet-stream',
body: fs.createReadStream(mappingFilePath)
}
}).catch((error) => Promise.reject(error.response.data))
}
const addReleaseToTrack = async (appEditId, version, applicationId) => {
const result = await androidPublisher.edits.tracks
.update({
editId: appEditId,
packageName: applicationId,
track: "beta",
requestBody: {
track: "beta",
releases: [
{
name: version.name,
status: "draft",
releaseNotes: {
language: "en-GB",
text: "Bug fixes and improvments - See https://github.com/ouchadam/small-talk/releases for more details",
},
versionCodes: [version.code]
}
]
}
})
.catch((error) => Promise.reject(error.response.data))
return result.data;
}
const dowloadSignedUniversalApk = async (version, applicationId, authToken, outputFile) => {
console.log("fetching universal apk")
const apkRes = await androidPublisher.generatedapks.list({
packageName: applicationId,
versionCode: version.code,
})
const apks = apkRes.data.generatedApks
console.log(`found ${apks.length} apks`)
apks.forEach((apk) => {
console.log(apk)
})
const id = apks[0].generatedUniversalApk.downloadId
console.log(`downloading: ${id}`)
const downloadUrl = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${applicationId}/generatedApks/${version.code}/downloads/${id}:download?alt=media`
const options = {
headers: {
"Authorization": `Bearer ${authToken}`
}
}
await downloadToFile(downloadUrl, options, outputFile)
}
const downloadToFile = async (url, options, outputFile) => {
return new Promise((resolve, error) => {
http.get(url, options, (response) => {
const file = fs.createWriteStream(outputFile)
response.pipe(file)
file.on("finish", () => {
file.close()
resolve()
})
file.on("error", (cause) => {
error(cause)
})
}).on("error", (cause) => {
error(cause)
})
})
}
const promoteDraftToLive = async (applicationId) => {
const editId = await startPlayRelease(applicationId)
await androidPublisher.edits.tracks
.update({
editId: editId,
packageName: applicationId,
track: "beta",
requestBody: {
track: "beta",
releases: [
{
status: "completed",
}
]
}
})
.catch((error) => Promise.reject(error.response.data))
await androidPublisher.edits.commit({
editId: editId,
packageName: applicationId,
}).catch((error) => Promise.reject(error.response.data))
}