#!/usr/bin/env node /** * play-submit.mjs — Google Play Console AAB/APK Upload * * Nutzt die Google Play Developer API (v3) via googleapis package. * Lädt AAB/APK direkt in einen Track (internal/alpha/beta/production). * * Usage: * node scripts/play-submit.mjs \ * --package org.rebreak.app \ * --aab android/app/build/outputs/bundle/release/app-release.aab \ * --track internal \ * --service-account ~/secrets/rebreak-play-service-account.json * * Env-Variablen (Fallback): * PLAY_SERVICE_ACCOUNT_JSON — Pfad zum Service-Account-JSON * * Service-Account-Setup: * 1. Google Cloud Console → IAM & Admin → Service Accounts * 2. Create Service Account → JSON-Key downloaden * 3. Play Console → Setup → API-Access → Service-Account verlinken * 4. Permissions: "Releases" (Edit + Read) * * Exit-Codes: * 0 — Upload erfolgreich * 1 — Fehler (File nicht gefunden, Auth-Fehler, API-Fehler) */ import { readFile } from 'fs/promises'; import { createReadStream } from 'fs'; import { resolve } from 'path'; import { google } from 'googleapis'; // ─── CLI-Args ──────────────────────────────────────────────────────────────── const args = process.argv.slice(2); const getArg = (flag) => { const idx = args.indexOf(flag); return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null; }; const packageName = getArg('--package'); const aabPath = getArg('--aab'); const track = getArg('--track') || 'internal'; const serviceAccountPath = getArg('--service-account') || process.env.PLAY_SERVICE_ACCOUNT_JSON || `${process.env.HOME}/secrets/rebreak-play-service-account.json`; if (!packageName || !aabPath) { console.error(`ERROR: Fehlende Args Usage: node scripts/play-submit.mjs \\ --package \\ --aab \\ [--track ] \\ [--service-account ] Env-Variablen (Fallback): PLAY_SERVICE_ACCOUNT_JSON — Service-Account-JSON-Pfad `); process.exit(1); } // ─── Auth ──────────────────────────────────────────────────────────────────── const expandTilde = (p) => p.replace(/^~/, process.env.HOME); const serviceAccountFullPath = resolve(expandTilde(serviceAccountPath)); const aabFullPath = resolve(expandTilde(aabPath)); let auth; try { const keyFile = await readFile(serviceAccountFullPath, 'utf-8'); const credentials = JSON.parse(keyFile); auth = new google.auth.GoogleAuth({ credentials, scopes: ['https://www.googleapis.com/auth/androidpublisher'], }); console.log(`[play-submit] Auth: ${serviceAccountFullPath}`); } catch (err) { console.error(`ERROR: Service-Account-JSON konnte nicht geladen werden Pfad: ${serviceAccountFullPath} Fehler: ${err.message} Setup: 1. Google Cloud Console → Service Accounts → Create → JSON-Key 2. Play Console → Setup → API-Access → Service-Account linken 3. Permissions: "Releases" (Edit + Read) 4. JSON-Key ablegen in ~/secrets/ (NICHT committen) `); process.exit(1); } // ─── Upload ───────────────────────────────────────────────────────────────── const androidpublisher = google.androidpublisher({ version: 'v3', auth }); try { console.log(`[play-submit] Package : ${packageName}`); console.log(`[play-submit] AAB : ${aabFullPath}`); console.log(`[play-submit] Track : ${track}`); console.log(''); // 1. Edit erstellen (neue Transaction) console.log('[play-submit] → Creating edit...'); const editRes = await androidpublisher.edits.insert({ packageName, }); const editId = editRes.data.id; console.log(`[play-submit] Edit-ID: ${editId}`); // 2. AAB/APK hochladen console.log('[play-submit] → Uploading AAB...'); const uploadRes = await androidpublisher.edits.bundles.upload({ packageName, editId, media: { mimeType: 'application/octet-stream', body: createReadStream(aabFullPath), }, }); const versionCode = uploadRes.data.versionCode; console.log(`[play-submit] Version-Code: ${versionCode}`); // 3. Track-Assignment (Bundle in Track packen) console.log(`[play-submit] → Assigning to track '${track}'...`); await androidpublisher.edits.tracks.update({ packageName, editId, track, requestBody: { track, releases: [ { versionCodes: [String(versionCode)], status: 'completed', // Draft = 'draft', Live = 'completed' }, ], }, }); // 4. Edit committen (Transaction abschließen) console.log('[play-submit] → Committing edit...'); await androidpublisher.edits.commit({ packageName, editId, }); console.log(''); console.log(`✓ Upload erfolgreich!`); console.log(` Version-Code: ${versionCode}`); console.log(` Track : ${track}`); console.log(` Play-Console: https://play.google.com/console/u/0/developers/${packageName.split('.')[1]}/app/${packageName}/tracks/${track}`); console.log(''); console.log('Status-Check:'); console.log(` Release erscheint in ~10-30min im Play-Console-Dashboard`); } catch (err) { console.error(''); console.error('ERROR: Upload fehlgeschlagen'); console.error(''); console.error(`Fehler: ${err.message}`); if (err.response?.data) { console.error('API-Response:', JSON.stringify(err.response.data, null, 2)); } console.error(''); console.error('Mögliche Ursachen:'); console.error(' - Service-Account hat keine "Releases"-Permission im Play-Console'); console.error(' - Package-Name stimmt nicht mit dem im Play-Console überein'); console.error(' - Version-Code existiert bereits (muss unique + monoton steigend sein)'); console.error(' - AAB ist nicht korrekt signiert (Keystore-Hash muss in Play-Console registriert sein)'); console.error(''); process.exit(1); }