From a40589cc8937447ff7b5e2a5f0d519b3afe5d04d Mon Sep 17 00:00:00 2001 From: Mike Kell Date: Sun, 18 Oct 2026 08:11:23 -0400 Subject: [PATCH] ci(release): add alpha release pipeline, signing config, and publish tooling - .forgejo/workflows/release.yaml: tag-triggered (v*) pipeline that builds a split-per-abi Android arm64 APK (signed via ANDROID_KEYSTORE_BASE64 / ANDROID_KEY_PROPERTIES secrets), a Windows MSIX plus portable zip, and attaches every asset to a Forgejo prerelease through the releases API; Flutter pinned to 3.47.x because pubspec.lock requires flutter >=3.44.0 - scripts/publish_alpha.ps1: local one-shot build+publish equivalent (codegen, APK, portable zip, MSIX, release + asset upload under dist/v*) - android: release signing wired to key.properties with minify, resource shrinking, and new proguard-rules.pro (Flutter embedding, SQLCipher, and JSON keep rules) - pubspec: msix ^3.16.7 dev dependency and msix_config publisher identity - scripts/test.ps1: Forgejo asset-upload probe reading the token from env; the hardcoded token was removed and its rotation is tracked as a BUILD_STATE.md verification gate - gitignore: /dist/, **/android/build/, and android/key.properties --- .forgejo/workflows/release.yaml | 167 ++++++++++++++++++++++++++++++++ .gitignore | 3 + BUILD_STATE.md | 27 +++++- android/app/build.gradle.kts | 44 ++++++--- android/app/proguard-rules.pro | 23 +++++ pubspec.lock | 48 +++++++++ pubspec.yaml | 11 +++ scripts/publish_alpha.ps1 | 92 ++++++++++++++++++ scripts/test.ps1 | 52 ++++++++++ 9 files changed, 450 insertions(+), 17 deletions(-) create mode 100644 .forgejo/workflows/release.yaml create mode 100644 android/app/proguard-rules.pro create mode 100644 scripts/publish_alpha.ps1 create mode 100644 scripts/test.ps1 diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml new file mode 100644 index 0000000..8896bbd --- /dev/null +++ b/.forgejo/workflows/release.yaml @@ -0,0 +1,167 @@ +name: Build and Publish Alpha Releases + +on: + push: + tags: + - 'v*' + +jobs: + build-android: + name: Build Android APK + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Java 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.47.x' + channel: 'stable' + cache: true + + - name: Install Dependencies + run: flutter pub get + + - name: Run Code Generation + run: dart run build_runner build --delete-conflicting-outputs + + - name: Decode Android Keystore & Key Properties + env: + KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + KEY_PROPERTIES: ${{ secrets.ANDROID_KEY_PROPERTIES }} + run: | + if [ -n "$KEYSTORE_BASE64" ]; then + echo "$KEYSTORE_BASE64" | base64 -d > android/app/upload-keystore.jks + echo "$KEY_PROPERTIES" > android/key.properties + fi + + - name: Build Split APKs + run: flutter build apk --release --split-per-abi + + - name: Rename and Stage APK + run: | + mkdir -p dist + TAG_NAME=${GITHUB_REF#refs/tags/} + cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk "dist/GLP1Companion-Android-arm64-${TAG_NAME}.apk" + + - name: Upload Artifact for Release + uses: actions/upload-artifact@v4 + with: + name: android-release-apk + path: dist/*.apk + retention-days: 1 + + build-windows: + name: Build Windows MSIX & Portable + runs-on: windows-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.47.x' + channel: 'stable' + cache: true + + - name: Install Dependencies + shell: pwsh + run: flutter pub get + + - name: Run Code Generation + shell: pwsh + run: dart run build_runner build --delete-conflicting-outputs + + - name: Build Windows Native Release + shell: pwsh + run: flutter build windows --release + + - name: Generate MSIX Installer + shell: pwsh + run: dart run msix:create --install-certificate false + + - name: Package Portable Zip & Stage MSIX + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path dist | Out-Null + $tag = $env:GITHUB_REF -replace 'refs/tags/', '' + + # Copy MSIX + Copy-Item "build\windows\x64\runner\Release\*.msix" "dist\GLP1Companion-Windows-${tag}.msix" -Force + + # Compress standalone runner folder + Compress-Archive -Path "build\windows\x64\runner\Release\*" -DestinationPath "dist\GLP1Companion-Windows-x64-${tag}-Portable.zip" -Force + + - name: Upload Artifacts for Release + uses: actions/upload-artifact@v4 + with: + name: windows-release-packages + path: dist/* + retention-days: 1 + + publish-release: + name: Attach Assets to Forgejo Release + needs: [build-android, build-windows] + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Download Staged Artifacts + uses: actions/download-artifact@v4 + with: + path: staged-assets + + - name: Create Forgejo Release and Upload Assets + env: + FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + REPO="${{ github.repository }}" + SERVER="${{ github.server_url }}" + + echo "Target Release: $TAG_NAME on $REPO" + + # 1. Fetch or Create Release + RELEASE_JSON=$(curl -s -X POST "$SERVER/api/v1/repos/$REPO/releases" \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"$TAG_NAME\", + \"name\": \"GLP-1 Companion $TAG_NAME\", + \"body\": \"Automated alpha release build for $TAG_NAME.\", + \"draft\": false, + \"prerelease\": true + }") + + RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2) + + if [ -z "$RELEASE_ID" ]; then + echo "Release may already exist, fetching existing release ID..." + RELEASE_JSON=$(curl -s "$SERVER/api/v1/repos/$REPO/releases/tags/$TAG_NAME" \ + -H "Authorization: token $FORGEJO_TOKEN") + RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2) + fi + + echo "Release ID: $RELEASE_ID" + + # 2. Upload All Staged Artifacts to Release + find staged-assets -type f \( -name "*.apk" -o -name "*.msix" -o -name "*.zip" \) | while read -r FILE; do + FILENAME=$(basename "$FILE") + echo "Uploading $FILENAME to release $RELEASE_ID..." + + curl -s -X POST "$SERVER/api/v1/repos/$REPO/releases/$RELEASE_ID/assets?name=$FILENAME" \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"$FILE" > /dev/null + done + + echo "All release assets uploaded successfully." \ No newline at end of file diff --git a/.gitignore b/.gitignore index 80971b1..eddc30b 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ +/dist/ # Web specific @@ -54,6 +55,8 @@ macos/Flutter/ephemeral/ **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +**/android/build/ **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside diff --git a/BUILD_STATE.md b/BUILD_STATE.md index cabced5..851504a 100644 --- a/BUILD_STATE.md +++ b/BUILD_STATE.md @@ -2,8 +2,8 @@ ## Active Target - **Current Milestone:** Phase 7: Navigation & Domain Expansion (Exercise, Recipe Vault, Settings) — COMPLETE -- **Status:** Phase 7 Complete (release-ready: all phases 1–7 done) -- **Latest Addendum:** Phase 4B — Private Recipe Sources & Mealie Integration (2026-10-17) +- **Status:** Phase 7 Complete (all phases 1–7 done); alpha release pipeline landed 2026-10-18 +- **Latest Addendum:** Alpha Release Pipeline & Scripting (2026-10-18) ## Phase Checklist - [x] Phase 1: Foundation & Identity (Passwordless SSO & Encrypted Drift DB) @@ -196,4 +196,25 @@ - [x] `flutter analyze` — No issues found. - [x] Codegen: `dart run build_runner build` (`flutter pub run` is retired on current Flutter; `--delete-conflicting-outputs` is now ignored by build_runner) — drift re-emits `app_database.g.dart` byte-identically (no schema change). - [x] `flutter test` — 184/184 green (170 pre-existing + 14 new Mealie tests), including the 390 dp mobile navigation suite. -- [ ] Manual Mealie round trip on a reachable instance: Settings → Private Recipe Integrations → Connect Mealie (URL + API token) → Test & Save → badge flips to Connected → Sync Now (or the Recipe Vault AppBar sync action) → vault shows the Mealie badge and the Mealie/Web Scraped filter chips isolate each source. \ No newline at end of file +- [ ] Manual Mealie round trip on a reachable instance: Settings → Private Recipe Integrations → Connect Mealie (URL + API token) → Test & Save → badge flips to Connected → Sync Now (or the Recipe Vault AppBar sync action) → vault shows the Mealie badge and the Mealie/Web Scraped filter chips isolate each source. + +## Alpha Release Pipeline & Tooling (2026-10-18) +Release engineering for alpha (pre-release) builds across Android and Windows, driven either locally (one-shot PowerShell script) or by CI when a version tag is pushed. + +- [x] CI: `.forgejo/workflows/release.yaml` — "Build and Publish Alpha Releases", triggered on `push` of tags matching `v*`. Three jobs: + - `build-android` (ubuntu): Java 17 (Temurin) + Flutter 3.47.x → `flutter pub get` → `dart run build_runner build --delete-conflicting-outputs` → decode `ANDROID_KEYSTORE_BASE64` / `ANDROID_KEY_PROPERTIES` repo secrets into `android/app/upload-keystore.jks` + `android/key.properties` (step skipped when secrets are absent) → `flutter build apk --release --split-per-abi` → stages `GLP1Companion-Android-arm64-.apk` under `dist/`. + - `build-windows` (windows): Flutter 3.47.x → `flutter pub get` + codegen → `flutter build windows --release` → `dart run msix:create --install-certificate false` → stages `GLP1Companion-Windows-.msix` and `GLP1Companion-Windows-x64--Portable.zip` (runner Release folder zipped via `Compress-Archive`). + - `publish-release` (needs both build jobs): downloads the staged artifacts and drives the Forgejo REST API (`POST /api/v1/repos/{owner}/{repo}/releases`, then `/releases/{id}/assets?name=`) with `GITHUB_TOKEN` to create the release with `prerelease: true` — falling back to the existing release for the tag if creation fails — and uploads every `.apk` / `.msix` / `.zip`. + - Flutter pinned to 3.47.x to match the dev toolchain: `pubspec.lock` declares `flutter: ">=3.44.0"`, so an older CI toolchain (e.g. the originally drafted 3.24.x) fails at `flutter pub get`. +- [x] Local script: `scripts/publish_alpha.ps1 -Version [-ForgejoToken ]` (token defaults to `$env:FORGEJO_TOKEN`; server `https://git.kellsupport.com`, repo `mtkell/glp-companion`) — same flow end to end: codegen → arm64 APK → portable zip → MSIX → Forgejo release (`prerelease: true`) with asset upload; artifacts land in `dist/v/`. +- [x] Forgejo API probe: `scripts/test.ps1` verifies release lookup/creation and `multipart/form-data` asset upload against a throwaway draft release (`v0.0.1-test`) with a dummy text asset; the token is read from `$env:FORGEJO_TOKEN` only (the previously hardcoded token was removed — rotation tracked in the gates below). +- [x] Android release signing: `android/app/build.gradle.kts` loads `android/key.properties` from the project root when present and wires a `release` signing config (alias / password / store) with `isMinifyEnabled` + `isShrinkResources` and `proguard-rules.pro`; when the file is absent the properties are empty (CI decodes them from secrets; local release builds need their own `key.properties`). +- [x] ProGuard rules (`android/app/proguard-rules.pro`): keep Flutter embedding/plugin/util/view classes, `net.sqlcipher.**` + `net.sqlcipher.database.**` (SQLCipher backing the encrypted drift DB), annotation/enum/signature metadata for JSON serialization, plus `-dontwarn` suppressions for Play Core deferred components, conscrypt, and javax.annotation. +- [x] MSIX packaging: `msix: ^3.16.7` dev dependency (resolved 3.18.0) + `msix_config` block in `pubspec.yaml` — display name "GLP-1 Companion", publisher "Kell Engineering LLC" (`CN=Kell Engineering LLC, O=..., L=Valrico, S=Florida, C=US`), identity `KellEngineering.GLP1Companion`, `msix_version 0.1.0.0`, `logo_path: web/icons/Icon-512.png`, `internetClient` capability, execution alias `glp-companion`. +- [x] Secret & artifact hygiene: `/dist/` (local release artifacts, ~107 MB), `**/android/build/` (Gradle problems report), and `**/android/key.properties` added to `.gitignore`; the hardcoded Forgejo token stripped from `scripts/test.ps1` before it could ever reach git history. + +## Alpha Release Verification Gates (2026-10-18, to run) +- [ ] **Rotate the Forgejo token** that was hardcoded in `scripts/test.ps1`: Forgejo → Settings → Applications → delete the exposed token, mint a new one, export it as `FORGEJO_TOKEN` locally, and store it as a repo secret. (The old token never entered git history, but it existed in plaintext on disk.) +- [ ] Configure Forgejo Actions secrets on `mtkell/glp-companion`: `ANDROID_KEYSTORE_BASE64` (base64 of `upload-keystore.jks`) and `ANDROID_KEY_PROPERTIES` (keyAlias / keyPassword / storeFile=`upload-keystore.jks` / storePassword). Without them the Android job's release build fails at signing. +- [ ] Push a `v*` tag (e.g. `git tag v0.1.0 && git push origin v0.1.0`) and confirm all three workflow jobs go green and the prerelease at `https://git.kellsupport.com/mtkell/glp-companion/releases` carries the APK, MSIX, and portable zip assets. +- [ ] Install-gate: sideload the arm64 APK on an Android device and install the MSIX on Windows; launch both and confirm the encrypted drift DB opens (the minify/shrink build is exactly why the SQLCipher keep-rules exist). diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 752b5c2..78d9dc2 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,3 +1,12 @@ +import java.io.FileInputStream +import java.util.Properties + +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) +} + plugins { id("com.android.application") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. @@ -19,36 +28,43 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.companionglp.glpcompanion" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - // // API 26 (Android 8.0) floor required by the `health` plugin (Health // Connect / HealthKit bridge), which declares minSdk 26 in its // manifest; Health Connect itself only ships on Android 8.0+. minSdk = maxOf(flutter.minSdkVersion, 26) targetSdk = flutter.targetSdkVersion - // Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION - // is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions) - // You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true` - // flag during build. versionCode = flutter.versionCode versionName = flutter.versionName } + signingConfigs { + create("release") { + if (keystorePropertiesFile.exists()) { + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + storeFile = file(keystoreProperties.getProperty("storeFile")) + storePassword = keystoreProperties.getProperty("storePassword") + } + } + } + buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + getByName("release") { + signingConfig = signingConfigs.getByName("release") + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } } kotlin { compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } @@ -58,4 +74,4 @@ flutter { dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") -} +} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..809aa8f --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Flutter Wrapper +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } + +# Drift / SQLCipher +-keep class net.sqlcipher.** { *; } +-keep class net.sqlcipher.database.** { *; } + +# OpenFoodFacts & JSON Serialization +-keepattributes *Annotation*,EnclosingMethod,Signature +-keepclassmembers enum * { *; } + +# Flutter Play Store Split / Deferred Components (referenced by engine, not used here) +-dontwarn com.google.android.play.core.** + +# Additional standard Flutter & third-party suppressions +-dontwarn io.flutter.embedding.engine.deferredcomponents.** +-dontwarn javax.annotation.** +-dontwarn org.conscrypt.** \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index eb99c8e..9e12a95 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + archive: + dependency: transitive + description: + name: archive + sha256: "0d58d1fa1fddf6588251ccf2819f3fb3b6d0452c1b2813ac7413a05592bfbefe" + url: "https://pub.dev" + source: hosted + version: "4.5.2" args: dependency: transitive description: @@ -185,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + console: + dependency: transitive + description: + name: console + sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a + url: "https://pub.dev" + source: hosted + version: "4.1.0" convert: dependency: transitive description: @@ -437,6 +453,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.1" + get_it: + dependency: transitive + description: + name: get_it + sha256: "2b5c6b077aad30bba9782178baff6076a2044141f1d5f2f15b43bca3fbe74874" + url: "https://pub.dev" + source: hosted + version: "9.3.0" glob: dependency: transitive description: @@ -517,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a" + url: "https://pub.dev" + source: hosted + version: "4.12.0" intl: dependency: transitive description: @@ -653,6 +685,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.3" + msix: + dependency: "direct dev" + description: + name: msix + sha256: "61415c352e8aea084332b8a5514e6408c961c8cdeca202804fff1040eb555ac7" + url: "https://pub.dev" + source: hosted + version: "3.18.0" native_toolchain_c: dependency: transitive description: @@ -789,6 +829,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.3" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" postgrest: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a43c35e..bd12245 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,7 +67,18 @@ dev_dependencies: # Riverpod 3 migration. build_runner: ^2.16.0 drift_dev: ^2.35.1 + msix: ^3.16.7 +msix_config: + display_name: GLP-1 Companion + publisher_display_name: Kell Engineering LLC + identity_name: KellEngineering.GLP1Companion + publisher: CN=Kell Engineering LLC, O=Kell Engineering LLC, L=Valrico, S=Florida, C=US + msix_version: 0.1.0.0 + logo_path: web/icons/Icon-512.png + capabilities: internetClient + execution_alias: glp-companion + # Compile SQLCipher into the SQLite engine bundled by package:sqlite3's build # hooks (the sqlcipher_flutter_libs plugin is retired on sqlite3 3.x). hooks: diff --git a/scripts/publish_alpha.ps1 b/scripts/publish_alpha.ps1 new file mode 100644 index 0000000..7a9ff59 --- /dev/null +++ b/scripts/publish_alpha.ps1 @@ -0,0 +1,92 @@ +param( + [Parameter(Mandatory=$true)] + [string]$Version, + + [Parameter(Mandatory=$false)] + [string]$ForgejoToken =$env:FORGEJO_TOKEN, + + [string]$Server = "https://git.kellsupport.com", + [string]$Owner = "mtkell", + [string]$Repo = "glp-companion" +) + +$ErrorActionPreference = "Stop" + +if (-not $ForgejoToken) { + Write-Error "Forgejo Token missing! Pass -ForgejoToken or set `$env:FORGEJO_TOKEN." +} + +$DistDir = "dist\v$Version" +$Tag = "v$Version" + +Write-Host "==================================================" -ForegroundColor Cyan +Write-Host " Building & Publishing Alpha Release: $Tag" -ForegroundColor Cyan +Write-Host " Target: $Server/$Owner/$Repo" -ForegroundColor Cyan +Write-Host "==================================================" -ForegroundColor Cyan + +New-Item -ItemType Directory -Force -Path $DistDir | Out-Null + +Write-Host "`n[1/5] Running Drift build_runner..." -ForegroundColor Yellow +dart run build_runner build --delete-conflicting-outputs + +Write-Host "`n[2/5] Building Android arm64 APK..." -ForegroundColor Yellow +flutter build apk --release --split-per-abi +$ApkSrc = "build\app\outputs\flutter-apk\app-arm64-v8a-release.apk" +$ApkDest = "$DistDir\GLP1Companion-Android-arm64-$Tag.apk" +Copy-Item $ApkSrc $ApkDest -Force + +Write-Host "`n[3/5] Building Windows Release & Packaging Standalone Zip..." -ForegroundColor Yellow +flutter build windows --release +$ZipDest = "$DistDir\GLP1Companion-Windows-x64-$Tag-Portable.zip" +Compress-Archive -Path "build\windows\x64\runner\Release\*" -DestinationPath $ZipDest -Force + +Write-Host "`n[4/5] Building Windows MSIX..." -ForegroundColor Yellow +dart run msix:create --install-certificate false +$MsixSrc = Get-ChildItem "build\windows\x64\runner\Release\*.msix" | Select-Object -First 1 +$MsixDest = "$DistDir\GLP1Companion-Windows-$Tag.msix" +Copy-Item $MsixSrc.FullName $MsixDest -Force + +Write-Host "`n[5/5] Publishing Release & Assets to Forgejo..." -ForegroundColor Yellow +$Headers = @{ + "Authorization" = "token $ForgejoToken" + "Accept" = "application/json" +} + +$ReleaseBody = @{ + tag_name = $Tag + name = "GLP-1 Companion $Tag" + body = "Automated alpha release build for $Tag containing Android APK and Windows packages." + draft = $false + prerelease = $true +} | ConvertTo-Json + +$ReleaseUrl = "$Server/api/v1/repos/$Owner/$Repo/releases" + +try { + $ReleaseResp = Invoke-RestMethod -Uri $ReleaseUrl -Method Post -Headers $Headers -Body $ReleaseBody -ContentType "application/json" + $ReleaseId = $ReleaseResp.id + Write-Host "Created release $ReleaseId for$Tag" -ForegroundColor Green +} catch { + Write-Host "Release for $Tag may already exist, fetching existing ID..." -ForegroundColor Gray + $Existing = Invoke-RestMethod -Uri "$ReleaseUrl/tags/$Tag" -Method Get -Headers $Headers + $ReleaseId = $Existing.id + Write-Host "Using existing release ID: $ReleaseId" -ForegroundColor Green +} + +$Assets = @($ApkDest, $ZipDest,$MsixDest) + +foreach ($FilePath in $Assets) { + if (Test-Path $FilePath) { + $FileName = [System.IO.Path]::GetFileName($FilePath) + Write-Host "Uploading $FileName..." -ForegroundColor Cyan + + & curl.exe -s -X POST "$Server/api/v1/repos/$Owner/$Repo/releases/$ReleaseId/assets?name=$FileName" ` + -H "Authorization: token $ForgejoToken" ` + -H "Content-Type: multipart/form-data" ` + -F "attachment=@$FilePath" | Out-Null + + Write-Host " -> Uploaded $FileName" -ForegroundColor Green + } +} + +Write-Host "`nSuccessfully released $Tag to$Server/$Owner/$Repo/releases!" -ForegroundColor Green \ No newline at end of file diff --git a/scripts/test.ps1 b/scripts/test.ps1 new file mode 100644 index 0000000..71545b9 --- /dev/null +++ b/scripts/test.ps1 @@ -0,0 +1,52 @@ +$Server = "https://git.kellsupport.com" +$Owner = "mtkell" +$Repo = "glp-companion" +$Tag = "v0.0.1-test" + +# Token is read from the environment only — never hardcode credentials here. +$Token = $env:FORGEJO_TOKEN +if (-not $Token) { + Write-Error "FORGEJO_TOKEN environment variable is not set. Export it before running this script." +} + +$DummyFile = "dummy-asset-test.txt" +"Forgejo API asset upload verification payload - $(Get-Date)" | Set-Content -Path $DummyFile -Encoding utf8 + +$Headers = @{ + "Authorization" = "token $Token" + "Accept" = "application/json" +} + +Write-Host "Checking for release for tag: $Tag..." -ForegroundColor Cyan +$ReleaseUrl = "$Server/api/v1/repos/$Owner/$Repo/releases" + +try { + $Release = Invoke-RestMethod -Uri "$ReleaseUrl/tags/$Tag" -Headers $Headers -Method Get + $ReleaseId =$Release.id + Write-Host "Found existing release ID: $ReleaseId" -ForegroundColor Green +} catch { + Write-Host "Creating new draft test release for tag $Tag..." -ForegroundColor Yellow + $Release = Invoke-RestMethod -Uri $ReleaseUrl -Headers $Headers -Method Post -ContentType "application/json" -Body (@{ + tag_name = $Tag + name = "Test Asset Upload Release" + body = "Testing Forgejo attachment upload endpoint." + draft = $true + prerelease = $true + } | ConvertTo-Json) + $ReleaseId =$Release.id + Write-Host "Created test release ID: $ReleaseId" -ForegroundColor Green +} + +Write-Host "Uploading $DummyFile to release ID$ReleaseId via curl..." -ForegroundColor Cyan +$UploadUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/$ReleaseId/assets?name=$DummyFile" + +& curl.exe -s -X POST $UploadUrl ` + -H "Authorization: token $Token" ` + -F "attachment=@$DummyFile" ` + -o "curl_response.json" + +Write-Host "`nServer Response:" -ForegroundColor Gray +Get-Content -Path "curl_response.json" + +Remove-Item -Path $DummyFile -Force -ErrorAction SilentlyContinue +Remove-Item -Path "curl_response.json" -Force -ErrorAction SilentlyContinue \ No newline at end of file