param( [switch]$SkipTests, [switch]$SkipUpload ) $ErrorActionPreference = 'Stop' Set-Location $PSScriptRoot $updateServer = 'https://updater.kooltap.com' $pubspecPath = Join-Path $PSScriptRoot 'pubspec.yaml' $apkPath = Join-Path $PSScriptRoot 'build\app\outputs\flutter-apk\app-release.apk' function Fail([string]$message) { Write-Host $message -ForegroundColor Red exit 1 } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $pubspec = [IO.File]::ReadAllText($pubspecPath) $match = [regex]::Match( $pubspec, '(?m)^version:[ \t]*([0-9]+\.[0-9]+\.[0-9]+)\+([0-9]+)' ) if (-not $match.Success) { Fail 'Could not find a version like 1.2.3+4 in pubspec.yaml.' } $currentName = $match.Groups[1].Value $currentBuild = [int]$match.Groups[2].Value Write-Host "Current version: $currentName+$currentBuild" # The server picks the latest release by Android version code, so every release # needs a higher one than anything already published $publishedName = $null $publishedCode = 0 try { $releases = Invoke-RestMethod "$updateServer/api/releases" -UseBasicParsing -TimeoutSec 15 foreach ($release in $releases) { if ($release.VersionCode -gt $publishedCode) { $publishedName = $release.Version $publishedCode = [int]$release.VersionCode } } if ($publishedName) { Write-Host "Latest published: $publishedName (version code $publishedCode)" } } catch { Write-Host "Could not reach ${updateServer}: $($_.Exception.Message)" -ForegroundColor Yellow Write-Host 'Using pubspec.yaml only; the upload is refused if the version is too low.' -ForegroundColor Yellow } $name = Read-Host 'Release version (for example 1.2.2)' if ($name -notmatch '\A[0-9]+\.[0-9]+\.[0-9]+\z') { Fail 'Use a version like 1.2.2.' } if ($publishedName -and [version]$name -le [version]$publishedName) { Fail "$name must be higher than the published $publishedName, or tablets will not see it." } # The build number only counts builds of the same version and starts at 0 for # a new one. Android refuses updates with a lower version code, so that code is # derived from the whole version instead: 1.2.5+3 becomes 1020503 $build = if ($name -eq $currentName) { $currentBuild + 1 } else { 0 } $major, $minor, $patch = $name.Split('.') | ForEach-Object { [int]$_ } if ($minor -ge 100 -or $patch -ge 100 -or $build -ge 100) { Fail 'Minor, patch and build numbers must stay below 100.' } $versionCode = $major * 1000000 + $minor * 10000 + $patch * 100 + $build if ($versionCode -le $publishedCode) { Fail "Version code $versionCode is not higher than the published $publishedCode." } $version = "$name+$build" $versionStart = $match.Groups[1].Index $versionEnd = $match.Groups[2].Index + $match.Groups[2].Length $pubspec = $pubspec.Substring(0, $versionStart) + $version + $pubspec.Substring($versionEnd) [IO.File]::WriteAllText($pubspecPath, $pubspec) Write-Host "Updated pubspec.yaml to version $version (version code $versionCode)." if (-not $SkipTests) { Write-Host 'Running tests...' flutter test if ($LASTEXITCODE -ne 0) { Fail 'Tests failed. Release cancelled.' } } Write-Host 'Building release APK...' flutter build apk --release --build-number=$versionCode if ($LASTEXITCODE -ne 0) { Fail 'Release APK build failed.' } Write-Host "Release APK created at $apkPath" if ($SkipUpload) { exit 0 } $token = $env:KOOLTAP_ADMIN_TOKEN if (-not $token) { Write-Host 'KOOLTAP_ADMIN_TOKEN is not set, skipping the upload.' -ForegroundColor Yellow exit 0 } $notes = Read-Host 'Release notes' $mandatory = if ((Read-Host 'Mandatory update? (y/N)') -match '\A[yY]') { 'true' } else { 'false' } Write-Host "Uploading $version to $updateServer..." Add-Type -AssemblyName System.Net.Http $client = [Net.Http.HttpClient]::new() $client.Timeout = [TimeSpan]::FromMinutes(10) $client.DefaultRequestHeaders.Add('X-Admin-Token', $token) $apk = [IO.File]::OpenRead($apkPath) $form = [Net.Http.MultipartFormDataContent]::new() $form.Add([Net.Http.StreamContent]::new($apk), 'apk', 'app-release.apk') $form.Add([Net.Http.StringContent]::new($notes), 'notes') $form.Add([Net.Http.StringContent]::new($mandatory), 'mandatory') try { $response = $client.PostAsync("$updateServer/admin/releases", $form).GetAwaiter().GetResult() $body = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() } finally { $form.Dispose() $apk.Dispose() $client.Dispose() } if (-not $response.IsSuccessStatusCode) { Fail "Upload failed (HTTP $([int]$response.StatusCode)): $body`nThe APK is still at $apkPath" } $release = $body | ConvertFrom-Json Write-Host "Published $($release.Version) (build $($release.VersionCode)), sha256 $($release.SHA256)" -ForegroundColor Green Write-Host "Don't forget to commit pubspec.yaml and tag the release: git tag v$version"