param( [string]$ArgusBaseUrl = $(if ($env:ARGUS_BASE_URL) { $env:ARGUS_BASE_URL } else { "https://argus.kusoft.xyz" }), [string]$Username = $env:ARGUS_USERNAME, [string]$Password = $env:ARGUS_PASSWORD, [string]$ApkPath = "keeperApp\build\outputs\apk\debug\keeperApp-debug.apk", [string]$Slug = "keeper-android", [string]$Name = "QKeep", [string]$Summary = "Cloud notes app for QSfera with local storage and sync.", [string]$Description = "QKeep stores notes locally on Android, uses QSfera browser authorization, syncs notes with the Keeper REST service, and installs Argus updates from inside the app.", [string]$AndroidPackageName = "eu.qsfera.keeper", [string]$Version = "0.1.21", [int]$AndroidVersionCode = 22, [string]$Channel = "stable", [string]$Platform = "android", [string]$PackageKind = "apk", [string]$Notes = "Adds Keep-like list behavior settings for new and checked checklist items." ) $ErrorActionPreference = "Stop" if ([string]::IsNullOrWhiteSpace($Username) -or [string]::IsNullOrWhiteSpace($Password)) { throw "Set ARGUS_USERNAME and ARGUS_PASSWORD before publishing." } $resolvedApk = (Resolve-Path -LiteralPath $ApkPath).Path $baseUri = [Uri]($ArgusBaseUrl.TrimEnd("/")) $slugEscaped = [Uri]::EscapeDataString($Slug) Add-Type -AssemblyName System.Net.Http $handler = [System.Net.Http.HttpClientHandler]::new() $handler.CookieContainer = [System.Net.CookieContainer]::new() $client = [System.Net.Http.HttpClient]::new($handler) $client.BaseAddress = $baseUri function New-JsonContent([object]$Value) { $json = $Value | ConvertTo-Json -Depth 8 return [System.Net.Http.StringContent]::new($json, [Text.Encoding]::UTF8, "application/json") } function Read-ResponseBody($Response) { return $Response.Content.ReadAsStringAsync().GetAwaiter().GetResult() } function Assert-Success($Response, [string]$Action) { if (-not $Response.IsSuccessStatusCode) { $body = Read-ResponseBody $Response throw "$Action failed: HTTP $([int]$Response.StatusCode) $body" } } try { $loginPayload = @{ username = $Username password = $Password } $loginResponse = $client.PostAsync("/api/admin/session/login", (New-JsonContent $loginPayload)).GetAwaiter().GetResult() Assert-Success $loginResponse "Argus login" $metadataPayload = @{ name = $Name summary = $Summary description = $Description androidPackageName = $AndroidPackageName repositoryUrl = $null homepageUrl = $null isListed = $true } $metadataResponse = $client.PutAsync("/api/admin/apps/$slugEscaped", (New-JsonContent $metadataPayload)).GetAwaiter().GetResult() Assert-Success $metadataResponse "Argus app metadata update" $form = [System.Net.Http.MultipartFormDataContent]::new() $form.Add([System.Net.Http.StringContent]::new($Version), "Version") $form.Add([System.Net.Http.StringContent]::new($Channel), "Channel") $form.Add([System.Net.Http.StringContent]::new($Platform), "Platform") $form.Add([System.Net.Http.StringContent]::new($PackageKind), "PackageKind") $form.Add([System.Net.Http.StringContent]::new($AndroidVersionCode.ToString()), "AndroidVersionCode") $form.Add([System.Net.Http.StringContent]::new($Notes), "Notes") $fileStream = [IO.File]::OpenRead($resolvedApk) $fileContent = [System.Net.Http.StreamContent]::new($fileStream) $fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/vnd.android.package-archive") $form.Add($fileContent, "Package", [IO.Path]::GetFileName($resolvedApk)) $releaseResponse = $client.PostAsync("/api/admin/apps/$slugEscaped/releases", $form).GetAwaiter().GetResult() Assert-Success $releaseResponse "Argus release upload" $releaseBody = Read-ResponseBody $releaseResponse Write-Output $releaseBody } finally { if ($fileStream) { $fileStream.Dispose() } if ($form) { $form.Dispose() } $client.Dispose() $handler.Dispose() }