param( [switch]$SkipTests, [switch]$SkipUpload ) $ErrorActionPreference = 'Stop' Set-Location $PSScriptRoot $updateServer = 'https://updater.kooltap.com' $apkPath = '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('pubspec.yaml') $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 build number, so every release needs # a higher one than anything already published $publishedName = $null $publishedBuild = 0 try { foreach ($release in Invoke-RestMethod "$updateServer/api/releases") { if ($release.VersionCode -gt $publishedBuild) { $publishedName = $release.Version $publishedBuild = [int]$release.VersionCode } } if ($publishedName) { Write-Host "Latest published: $publishedName+$publishedBuild" } } catch { Write-Host "Could not reach $updateServer, using pubspec.yaml only." -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." } $build = [Math]::Max($currentBuild, $publishedBuild) + 1 $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((Resolve-Path 'pubspec.yaml'), $pubspec) Write-Host "Updated pubspec.yaml to version $version." 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 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(5) $client.DefaultRequestHeaders.Add('X-Admin-Token', $token) $apk = [IO.File]::OpenRead((Resolve-Path $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"