Finish a Play release from the bundle already uploaded for this version code instead of failing on it
This commit is contained in:
parent
69cca87144
commit
41dfc7dc06
2 changed files with 75 additions and 26 deletions
|
|
@ -33,7 +33,7 @@ const getAccessToken = async ({client_email, private_key}) => {
|
||||||
return body.access_token
|
return body.access_token
|
||||||
}
|
}
|
||||||
|
|
||||||
export const uploadToPlay = async ({credentials, packageName, bundle, track, status, notes}) => {
|
export const play = async ({credentials, packageName}) => {
|
||||||
const accessToken = await getAccessToken(credentials)
|
const accessToken = await getAccessToken(credentials)
|
||||||
const base = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}`
|
const base = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}`
|
||||||
|
|
||||||
|
|
@ -48,6 +48,10 @@ export const uploadToPlay = async ({credentials, packageName, bundle, track, sta
|
||||||
body: binary ? body : body && JSON.stringify(body),
|
body: binary ? body : body && JSON.stringify(body),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const result = await response.json()
|
const result = await response.json()
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -57,25 +61,41 @@ export const uploadToPlay = async ({credentials, packageName, bundle, track, sta
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const edit = await api("POST", `${base}/edits`)
|
return {
|
||||||
const {versionCode} = await api(
|
// Listing bundles needs an edit, which is thrown away so the build can run before the real one
|
||||||
"POST",
|
bundles: async () => {
|
||||||
`https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${packageName}/edits/${edit.id}/bundles?uploadType=media`,
|
const edit = await api("POST", `${base}/edits`)
|
||||||
bundle,
|
|
||||||
)
|
|
||||||
|
|
||||||
await api("PUT", `${base}/edits/${edit.id}/tracks/${track}`, {
|
try {
|
||||||
track,
|
return (await api("GET", `${base}/edits/${edit.id}/bundles`)).bundles ?? []
|
||||||
releases: [
|
} finally {
|
||||||
{
|
await api("DELETE", `${base}/edits/${edit.id}`)
|
||||||
status,
|
}
|
||||||
versionCodes: [String(versionCode)],
|
},
|
||||||
releaseNotes: [{language: "en-US", text: notes}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
await api("POST", `${base}/edits/${edit.id}:commit`)
|
release: async ({bundle, versionCode, track, status, notes}) => {
|
||||||
|
const edit = await api("POST", `${base}/edits`)
|
||||||
|
|
||||||
return versionCode
|
if (bundle) {
|
||||||
|
await api(
|
||||||
|
"POST",
|
||||||
|
`https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${packageName}/edits/${edit.id}/bundles?uploadType=media`,
|
||||||
|
bundle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await api("PUT", `${base}/edits/${edit.id}/tracks/${track}`, {
|
||||||
|
track,
|
||||||
|
releases: [
|
||||||
|
{
|
||||||
|
status,
|
||||||
|
versionCodes: [String(versionCode)],
|
||||||
|
releaseNotes: [{language: "en-US", text: notes}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await api("POST", `${base}/edits/${edit.id}:commit`)
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
|
import {createHash} from "node:crypto"
|
||||||
|
import {existsSync} from "node:fs"
|
||||||
import {readFile} from "node:fs/promises"
|
import {readFile} from "node:fs/promises"
|
||||||
import {join, resolve} from "node:path"
|
import {join, resolve} from "node:path"
|
||||||
|
import {spec} from "@welshman/lib"
|
||||||
import {gradle, keystoreEnv} from "../lib/android.mjs"
|
import {gradle, keystoreEnv} from "../lib/android.mjs"
|
||||||
import {followUps, missingEnv, notes, root, version} from "../lib/context.mjs"
|
import {followUps, missingEnv, notes, root, version} from "../lib/context.mjs"
|
||||||
import {uploadToPlay} from "../lib/play.mjs"
|
import {play} from "../lib/play.mjs"
|
||||||
|
|
||||||
|
const aab = join(root, "android/app/build/outputs/bundle/release/app-release.aab")
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "play",
|
name: "play",
|
||||||
|
|
@ -24,19 +29,43 @@ export default {
|
||||||
"PLAY_TRACK (default production) and PLAY_STATUS (default draft) are optional.",
|
"PLAY_TRACK (default production) and PLAY_STATUS (default draft) are optional.",
|
||||||
],
|
],
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await gradle("bundleRelease", keystoreEnv("PLAY"))
|
|
||||||
|
|
||||||
const gradleConfig = await readFile(join(root, "android/app/build.gradle"), "utf-8")
|
const gradleConfig = await readFile(join(root, "android/app/build.gradle"), "utf-8")
|
||||||
|
const versionCode = Number(gradleConfig.match(/versionCode (\d+)/)[1])
|
||||||
const track = process.env.PLAY_TRACK ?? "production"
|
const track = process.env.PLAY_TRACK ?? "production"
|
||||||
const status = process.env.PLAY_STATUS ?? "draft"
|
const status = process.env.PLAY_STATUS ?? "draft"
|
||||||
const versionCode = await uploadToPlay({
|
const api = await play({
|
||||||
credentials: JSON.parse(
|
credentials: JSON.parse(
|
||||||
await readFile(resolve(root, process.env.PLAY_SERVICE_ACCOUNT), "utf-8"),
|
await readFile(resolve(root, process.env.PLAY_SERVICE_ACCOUNT), "utf-8"),
|
||||||
),
|
),
|
||||||
packageName: gradleConfig.match(/applicationId "(.+)"/)[1],
|
packageName: gradleConfig.match(/applicationId "(.+)"/)[1],
|
||||||
bundle: await readFile(
|
})
|
||||||
join(root, "android/app/build/outputs/bundle/release/app-release.aab"),
|
|
||||||
),
|
// Play never takes a version code twice, so a rerun after an upload goes on to finish the
|
||||||
|
// release with that bundle. Rebuilding would change its bytes, so it only counts as this
|
||||||
|
// build while the aab on disk is the one that went up.
|
||||||
|
const uploaded = (await api.bundles()).find(spec({versionCode}))
|
||||||
|
|
||||||
|
if (uploaded) {
|
||||||
|
const local =
|
||||||
|
existsSync(aab) &&
|
||||||
|
createHash("sha256")
|
||||||
|
.update(await readFile(aab))
|
||||||
|
.digest("hex")
|
||||||
|
|
||||||
|
if (local !== uploaded.sha256) {
|
||||||
|
throw new Error(
|
||||||
|
`Play already has version code ${versionCode} from a build that isn't ${aab}; bump versionCode in android/app/build.gradle`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Play already has version code ${versionCode} from this build, releasing it`)
|
||||||
|
} else {
|
||||||
|
await gradle("bundleRelease", keystoreEnv("PLAY"))
|
||||||
|
}
|
||||||
|
|
||||||
|
await api.release({
|
||||||
|
bundle: uploaded ? undefined : await readFile(aab),
|
||||||
|
versionCode,
|
||||||
track,
|
track,
|
||||||
status,
|
status,
|
||||||
// Play rejects release notes over 500 characters
|
// Play rejects release notes over 500 characters
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue