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

212 lines
6.6 KiB
JavaScript

import * as google from '@googleapis/androidpublisher'
import * as fs from "fs"
import * as http from 'https'
import matrixcs, * as matrix from 'matrix-js-sdk'
import request from 'request'
import * as url from 'url'
matrixcs.request(request)
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)
console.log("Sending message to room...")
await sendReleaseMessage(releaseResult.data, config)
}
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
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))
}
const sendReleaseMessage = async (release, config) => {
const matrixAuth = JSON.parse(fs.readFileSync('.secrets/matrix.json'))
const client = matrix.createClient(matrixAuth)
const content = {
"body": `New release`,
"format": "org.matrix.custom.html",
"formatted_body": `New release rolling out <a href="${release.url}">${release.tag_name}</a>`,
"msgtype": "m.text"
}
await client.sendEvent(config.matrixRoomId, "m.room.message", content, "")
}