diff --git a/.github/scripts/upload_google_play_draft.js b/.github/scripts/upload_google_play_draft.js new file mode 100644 index 00000000..29655140 --- /dev/null +++ b/.github/scripts/upload_google_play_draft.js @@ -0,0 +1,120 @@ +const fs = require('fs'); +const { google } = require('googleapis'); + +const requiredEnv = (name) => { + const value = process.env[name]; + if (!value) { + throw new Error(`${name} is required.`); + } + return value; +}; + +const readReleaseNotes = (filePath) => { + if (!filePath || !fs.existsSync(filePath)) { + return undefined; + } + + const text = fs.readFileSync(filePath, 'utf8').trimEnd(); + if (!text) { + return undefined; + } + + return [{ language: 'en-US', text }]; +}; + +const deleteEdit = async (publisher, packageName, editId) => { + try { + await publisher.edits.delete({ packageName, editId }); + } catch (error) { + console.warn(`Unable to delete failed edit ${editId}: ${error.message}`); + } +}; + +const main = async () => { + const packageName = requiredEnv('PACKAGE_NAME'); + const track = requiredEnv('PLAY_TRACK'); + const releaseFile = requiredEnv('RELEASE_FILE'); + const serviceAccount = JSON.parse(requiredEnv('GOOGLE_PLAY_SERVICE_ACCOUNT_JSON')); + + if (!fs.existsSync(releaseFile)) { + throw new Error(`Release file does not exist: ${releaseFile}`); + } + + const auth = new google.auth.GoogleAuth({ + credentials: serviceAccount, + scopes: ['https://www.googleapis.com/auth/androidpublisher'], + }); + const publisher = google.androidpublisher({ version: 'v3', auth }); + const insertResponse = await publisher.edits.insert({ packageName }); + const editId = insertResponse.data.id; + + if (!editId) { + throw new Error('New Google Play edit has no id.'); + } + + console.log(`Created Google Play edit ${editId}`); + + try { + const tracksResponse = await publisher.edits.tracks.list({ + packageName, + editId, + }); + const availableTracks = (tracksResponse.data.tracks || []).map((item) => item.track); + if (!availableTracks.includes(track)) { + throw new Error( + `Track "${track}" is not available. Available tracks: ${availableTracks.join(', ')}` + ); + } + + console.log(`Uploading ${releaseFile} to track "${track}"`); + const bundleResponse = await publisher.edits.bundles.upload({ + packageName, + editId, + media: { + mimeType: 'application/octet-stream', + body: fs.createReadStream(releaseFile), + }, + }); + const versionCode = bundleResponse.data.versionCode; + + if (!versionCode) { + throw new Error('Uploaded Android App Bundle did not return a versionCode.'); + } + + const release = { + status: 'draft', + versionCodes: [String(versionCode)], + }; + const releaseNotes = readReleaseNotes(process.env.RELEASE_NOTES_FILE); + if (releaseNotes) { + release.releaseNotes = releaseNotes; + } + + await publisher.edits.tracks.update({ + packageName, + editId, + track, + requestBody: { + track, + releases: [release], + }, + }); + + const commitResponse = await publisher.edits.commit({ + packageName, + editId, + }); + + console.log(`Committed Google Play edit ${commitResponse.data.id}`); + console.log(`Uploaded draft release versionCode ${versionCode}`); + } catch (error) { + await deleteEdit(publisher, packageName, editId); + const details = error.response?.data ? JSON.stringify(error.response.data) : error.message; + throw new Error(details); + } +}; + +main().catch((error) => { + console.error(error.message); + process.exit(1); +}); diff --git a/.github/workflows/android-play-closed.yml b/.github/workflows/android-play-closed.yml index 730f9ce7..3059f38a 100644 --- a/.github/workflows/android-play-closed.yml +++ b/.github/workflows/android-play-closed.yml @@ -149,25 +149,14 @@ jobs: if-no-files-found: error retention-days: 14 - - name: Upload closed testing draft with release notes - if: ${{ inputs.release_notes != '' }} - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} - packageName: club.devkor.ontime - releaseFiles: build/app/outputs/bundle/release/app-release.aab - tracks: alpha - status: draft - changesNotSentForReview: true - whatsNewDirectory: distribution/whatsnew + - name: Install Google Play upload dependencies + run: npm install --no-save --no-package-lock googleapis - name: Upload closed testing draft - if: ${{ inputs.release_notes == '' }} - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} - packageName: club.devkor.ontime - releaseFiles: build/app/outputs/bundle/release/app-release.aab - tracks: alpha - status: draft - changesNotSentForReview: true + env: + GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} + PACKAGE_NAME: club.devkor.ontime + PLAY_TRACK: alpha + RELEASE_FILE: build/app/outputs/bundle/release/app-release.aab + RELEASE_NOTES_FILE: distribution/whatsnew/whatsnew-en-US + run: node .github/scripts/upload_google_play_draft.js diff --git a/.github/workflows/android-play-internal.yml b/.github/workflows/android-play-internal.yml index 7ad82872..e2b89c8a 100644 --- a/.github/workflows/android-play-internal.yml +++ b/.github/workflows/android-play-internal.yml @@ -145,25 +145,14 @@ jobs: if-no-files-found: error retention-days: 14 - - name: Upload internal testing draft with release notes - if: ${{ inputs.release_notes != '' }} - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} - packageName: club.devkor.ontime - releaseFiles: build/app/outputs/bundle/release/app-release.aab - tracks: internal - status: draft - changesNotSentForReview: true - whatsNewDirectory: distribution/whatsnew + - name: Install Google Play upload dependencies + run: npm install --no-save --no-package-lock googleapis - name: Upload internal testing draft - if: ${{ inputs.release_notes == '' }} - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} - packageName: club.devkor.ontime - releaseFiles: build/app/outputs/bundle/release/app-release.aab - tracks: internal - status: draft - changesNotSentForReview: true + env: + GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} + PACKAGE_NAME: club.devkor.ontime + PLAY_TRACK: internal + RELEASE_FILE: build/app/outputs/bundle/release/app-release.aab + RELEASE_NOTES_FILE: distribution/whatsnew/whatsnew-en-US + run: node .github/scripts/upload_google_play_draft.js