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
This commit is contained in:
Mike Kell 2026-10-18 08:11:23 -04:00
parent 3d5135c1c5
commit a40589cc89
9 changed files with 450 additions and 17 deletions

View File

@ -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."

3
.gitignore vendored
View File

@ -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

View File

@ -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.
- [ ] 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-<tag>.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-<tag>.msix` and `GLP1Companion-Windows-x64-<tag>-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 <x.y.z> [-ForgejoToken <token>]` (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<version>/`.
- [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).

View File

@ -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")
}
}

23
android/app/proguard-rules.pro vendored Normal file
View File

@ -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.**

View File

@ -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:

View File

@ -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:

92
scripts/publish_alpha.ps1 Normal file
View File

@ -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 <token> 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

52
scripts/test.ps1 Normal file
View File

@ -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