Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaFmzMN5i7qyZihKBsksxz
206 lines
9.9 KiB
PowerShell
206 lines
9.9 KiB
PowerShell
<#
|
|
Zet de nieuwste AAB in de Google Play Console (Android Publisher API), met versiecontrole.
|
|
Voor SideKick: onderdeel mijnbesluit-playstore, Sleutel playstore. NIET GETEST tegen Google.
|
|
|
|
Wat het doet
|
|
1. Leest ApplicationVersion uit MijnBesluit.App.csproj en vraagt aan Google welke versiecodes
|
|
al op de tracks en in de bundels staan. Is de versie niet hoger dan de hoogste: stoppen.
|
|
2. Uploadt de nieuwste *.aab uit -AabMap.
|
|
3. Zet hem op de track (standaard intern testen) als CONCEPT: er gaat niets live. Publiceren
|
|
doe je zelf in de Play Console.
|
|
4. Schrijft playstore-upload.txt in -Uitvoer (zo ziet SideKick dat het gelukt is).
|
|
|
|
Eenmalig regelen (zie docs\Android-app.md)
|
|
* De app staat al in de Play Console en de eerste AAB is met de hand geupload.
|
|
* Een serviceaccount met sleutelbestand (JSON) en recht om releases te beheren.
|
|
* Gebruikersvariabele MIJNBESLUIT_PLAY_SLEUTEL = pad naar dat JSON-bestand (buiten de repo).
|
|
SideKick opnieuw starten na het zetten van de variabele.
|
|
|
|
Het sleutelbestand wordt alleen gelezen en komt nooit in een log of de repo.
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string] $Configuratie = 'Release',
|
|
|
|
# Waar playstore-upload.txt komt (SideKick: <publicatiemap>\playstore).
|
|
[string] $Uitvoer,
|
|
|
|
# De map met de AAB van Bouwen-android-aab.ps1.
|
|
[string] $AabMap = 'D:\MijnBesluit VS\MijnBesluitpublish\android-aab',
|
|
|
|
[string] $Pakket = 'nl.mijnbesluit.app',
|
|
|
|
# internal = intern testen, alpha = gesloten testen, production = productie.
|
|
[ValidateSet('internal', 'alpha', 'beta', 'production')]
|
|
[string] $Track = 'internal',
|
|
|
|
# draft = concept, nog te publiceren in de Play Console. completed = meteen uitrollen
|
|
# (bij een app die nog nooit is gepubliceerd weigert Google dat).
|
|
[ValidateSet('draft', 'completed')]
|
|
[string] $Status = 'draft',
|
|
|
|
[string] $Notities = 'Nieuwe versie.',
|
|
[string] $Taal = 'nl-NL',
|
|
|
|
# Alleen controleren (versies ophalen en vergelijken); niets uploaden.
|
|
[switch] $Controle
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
|
|
$csproj = Join-Path $PSScriptRoot '..\src\MijnBesluit.App\MijnBesluit.App.csproj' | Resolve-Path
|
|
|
|
# ------------------------------------------------------------------ versie uit de csproj
|
|
|
|
$tekst = Get-Content $csproj -Raw
|
|
if ($tekst -notmatch '<ApplicationVersion>\s*(\d+)\s*</ApplicationVersion>') {
|
|
throw "Geen ApplicationVersion (geheel getal) gevonden in $csproj."
|
|
}
|
|
$nieuweVersie = [int]$Matches[1]
|
|
$weergave = if ($tekst -match '<ApplicationDisplayVersion>\s*([^<]+?)\s*</ApplicationDisplayVersion>') { $Matches[1] } else { '?' }
|
|
Write-Host "Versie in de csproj: $weergave (versiecode $nieuweVersie)"
|
|
|
|
# ------------------------------------------------------------------ welke AAB
|
|
|
|
if (-not (Test-Path $AabMap)) { throw "De AAB-map bestaat niet: $AabMap. Bouw eerst de AAB (mijnbesluit-aab)." }
|
|
$aab = Get-ChildItem $AabMap -Filter '*.aab' | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
|
if (-not $aab) { throw "Geen .aab gevonden in $AabMap. Bouw eerst de AAB (mijnbesluit-aab)." }
|
|
Write-Host "AAB: $($aab.FullName) ($([math]::Round($aab.Length / 1MB, 1)) MB, $($aab.LastWriteTime))"
|
|
|
|
if ($aab.LastWriteTime -lt (Get-Item $csproj).LastWriteTime) {
|
|
throw "De AAB is ouder dan de csproj. Bouw de AAB opnieuw (mijnbesluit-aab), anders zit er een andere versiecode in dan hierboven."
|
|
}
|
|
|
|
# ------------------------------------------------------------------ sleutelbestand en token
|
|
|
|
$sleutelPad = $env:MIJNBESLUIT_PLAY_SLEUTEL
|
|
if (-not $sleutelPad) {
|
|
throw @"
|
|
De gebruikersvariabele MIJNBESLUIT_PLAY_SLEUTEL ontbreekt.
|
|
Zet hem op het pad van het JSON-sleutelbestand van het serviceaccount (buiten de repo):
|
|
[Environment]::SetEnvironmentVariable('MIJNBESLUIT_PLAY_SLEUTEL', 'D:\pad\naar\sleutel.json', 'User')
|
|
Start SideKick daarna opnieuw.
|
|
"@
|
|
}
|
|
if (-not (Test-Path $sleutelPad)) { throw "Het sleutelbestand bestaat niet: $sleutelPad" }
|
|
$sa = Get-Content $sleutelPad -Raw | ConvertFrom-Json
|
|
if (-not $sa.client_email -or -not $sa.private_key) { throw "Dit is geen sleutelbestand van een serviceaccount (client_email/private_key ontbreken)." }
|
|
|
|
function ConvertTo-Base64Url([byte[]] $bytes) {
|
|
[Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_')
|
|
}
|
|
|
|
function Get-Token {
|
|
$nu = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
|
|
$kop = ConvertTo-Base64Url ([Text.Encoding]::UTF8.GetBytes('{"alg":"RS256","typ":"JWT"}'))
|
|
$aud = if ($sa.token_uri) { $sa.token_uri } else { 'https://oauth2.googleapis.com/token' }
|
|
$claims = @{ iss = $sa.client_email; scope = 'https://www.googleapis.com/auth/androidpublisher'; aud = $aud; iat = $nu; exp = $nu + 3000 } | ConvertTo-Json -Compress
|
|
$inhoud = ConvertTo-Base64Url ([Text.Encoding]::UTF8.GetBytes($claims))
|
|
$teTekenen = [Text.Encoding]::ASCII.GetBytes("$kop.$inhoud")
|
|
|
|
$pem = ($sa.private_key -replace '-----[A-Z ]+-----', '' -replace '\s', '')
|
|
$sleutel = [Security.Cryptography.CngKey]::Import([Convert]::FromBase64String($pem), [Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
|
|
$rsa = New-Object Security.Cryptography.RSACng($sleutel)
|
|
$handtekening = $rsa.SignData($teTekenen, [Security.Cryptography.HashAlgorithmName]::SHA256, [Security.Cryptography.RSASignaturePadding]::Pkcs1)
|
|
$jwt = "$kop.$inhoud." + (ConvertTo-Base64Url $handtekening)
|
|
|
|
$antwoord = Invoke-RestMethod -Method Post -Uri $aud -ContentType 'application/x-www-form-urlencoded' `
|
|
-Body "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=$jwt"
|
|
return $antwoord.access_token
|
|
}
|
|
|
|
function Invoke-Play([string] $methode, [string] $url, $body = $null, [string] $infile = $null) {
|
|
$kopjes = @{ Authorization = "Bearer $script:token" }
|
|
try {
|
|
if ($infile) {
|
|
return Invoke-RestMethod -Method $methode -Uri $url -Headers $kopjes -ContentType 'application/octet-stream' -InFile $infile -TimeoutSec 900
|
|
}
|
|
if ($null -ne $body) {
|
|
return Invoke-RestMethod -Method $methode -Uri $url -Headers $kopjes -ContentType 'application/json' -Body ($body | ConvertTo-Json -Depth 10)
|
|
}
|
|
return Invoke-RestMethod -Method $methode -Uri $url -Headers $kopjes
|
|
}
|
|
catch {
|
|
$detail = $_.ErrorDetails.Message
|
|
if (-not $detail -and $_.Exception.Response) {
|
|
try { $detail = (New-Object IO.StreamReader($_.Exception.Response.GetResponseStream())).ReadToEnd() } catch { }
|
|
}
|
|
throw "Google Play weigerde $methode $url : $($_.Exception.Message) $detail"
|
|
}
|
|
}
|
|
|
|
Write-Host "Inloggen bij Google Play als $($sa.client_email)..."
|
|
$token = Get-Token
|
|
|
|
$basis = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/$Pakket"
|
|
$edit = Invoke-Play 'POST' "$basis/edits" @{}
|
|
$editId = $edit.id
|
|
Write-Host "Bewerking $editId geopend."
|
|
|
|
try {
|
|
# -------------------------------------------------------------- versiecontrole
|
|
$hoogste = 0
|
|
$gevonden = @()
|
|
$tracks = Invoke-Play 'GET' "$basis/edits/$editId/tracks"
|
|
foreach ($t in @($tracks.tracks)) {
|
|
foreach ($r in @($t.releases)) {
|
|
foreach ($v in @($r.versionCodes)) {
|
|
$gevonden += "$($t.track): $v ($($r.status))"
|
|
if ([int]$v -gt $hoogste) { $hoogste = [int]$v }
|
|
}
|
|
}
|
|
}
|
|
$bundels = Invoke-Play 'GET' "$basis/edits/$editId/bundles"
|
|
foreach ($b in @($bundels.bundles)) {
|
|
if ([int]$b.versionCode -gt $hoogste) { $hoogste = [int]$b.versionCode }
|
|
}
|
|
if ($gevonden.Count) { Write-Host "Al bij Google: $($gevonden -join '; ')" } else { Write-Host "Nog geen versies bij Google gevonden." }
|
|
Write-Host "Hoogste versiecode bij Google: $hoogste; nieuwe versiecode: $nieuweVersie"
|
|
|
|
if ($nieuweVersie -le $hoogste) {
|
|
throw "De versiecode in de csproj ($nieuweVersie) is niet hoger dan wat Google al heeft ($hoogste). Verhoog ApplicationVersion in MijnBesluit.App.csproj en bouw de AAB opnieuw."
|
|
}
|
|
|
|
if ($Controle) {
|
|
Write-Host "Controle gelukt: versiecode $nieuweVersie mag geupload worden. Er is niets geupload." -ForegroundColor Green
|
|
Invoke-Play 'DELETE' "$basis/edits/$editId" | Out-Null
|
|
return
|
|
}
|
|
|
|
# -------------------------------------------------------------- uploaden
|
|
Write-Host "AAB uploaden..."
|
|
$bundel = Invoke-Play 'POST' "https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/$Pakket/edits/$editId/bundles?uploadType=media" -infile $aab.FullName
|
|
$code = [int]$bundel.versionCode
|
|
Write-Host "Geupload als versiecode $code."
|
|
if ($code -ne $nieuweVersie) {
|
|
Write-Host "Let op: de AAB heeft versiecode $code, de csproj zegt $nieuweVersie. Bouw de AAB opnieuw als dat niet de bedoeling is." -ForegroundColor Yellow
|
|
}
|
|
|
|
# -------------------------------------------------------------- op de track zetten
|
|
$release = @{
|
|
name = "$weergave ($code)"
|
|
versionCodes = @("$code")
|
|
status = $Status
|
|
releaseNotes = @(@{ language = $Taal; text = $Notities })
|
|
}
|
|
Invoke-Play 'PUT' "$basis/edits/$editId/tracks/$Track" @{ track = $Track; releases = @($release) } | Out-Null
|
|
Write-Host "Op track '$Track' gezet (status: $Status)."
|
|
|
|
Invoke-Play 'POST' "$basis/edits/$editId`:commit" @{} | Out-Null
|
|
Write-Host "Vastgelegd bij Google Play." -ForegroundColor Green
|
|
}
|
|
catch {
|
|
try { Invoke-Play 'DELETE' "$basis/edits/$editId" | Out-Null } catch { }
|
|
throw
|
|
}
|
|
|
|
$samenvatting = "Geupload naar Google Play ($Pakket): versie $weergave, versiecode $code, track $Track, status $Status, op $(Get-Date -Format 'yyyy-MM-dd HH:mm')."
|
|
if ($Uitvoer) {
|
|
New-Item -ItemType Directory -Path $Uitvoer -Force | Out-Null
|
|
Set-Content -Path (Join-Path $Uitvoer 'playstore-upload.txt') -Value $samenvatting -Encoding UTF8
|
|
}
|
|
Write-Host $samenvatting
|
|
if ($Status -eq 'draft') { Write-Host "Het is een concept: publiceer de release zelf in de Play Console." }
|