mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
feat: Add GitHub Actions workflows for building and releasing Android, iOS, Linux, and Windows applications
This commit is contained in:
69
.github/workflows/android-build.yml
vendored
Normal file
69
.github/workflows/android-build.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# .github/workflows/release_apk_github.yml
|
||||||
|
name: Build Android Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Android Release for GitHub
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'vleeuwenmenno' && contains(github.server_url, 'github.com')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "17"
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- name: Build release APK
|
||||||
|
run: flutter build apk --release
|
||||||
|
|
||||||
|
- name: Extract version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
APP_VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //g' | sed 's/+.*//g')
|
||||||
|
echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Rename APK
|
||||||
|
run: |
|
||||||
|
mv build/app/outputs/flutter-apk/app-release.apk supplements_${{ env.APP_VERSION }}_android.apk
|
||||||
|
|
||||||
|
- name: Build debug APK
|
||||||
|
run: flutter build apk --debug
|
||||||
|
|
||||||
|
- name: Rename Debug APK
|
||||||
|
run: |
|
||||||
|
mv build/app/outputs/flutter-apk/app-debug.apk supplements_${{ env.APP_VERSION }}_debug.apk
|
||||||
|
|
||||||
|
- name: Debug release information
|
||||||
|
run: |
|
||||||
|
echo "Release tag: ${{ github.event.release.tag_name }}"
|
||||||
|
echo "Release name: ${{ github.event.release.name }}"
|
||||||
|
echo "Release URL: ${{ github.event.release.html_url }}"
|
||||||
|
echo "App version: ${{ env.APP_VERSION }}"
|
||||||
|
|
||||||
|
- name: Upload APKs to GitHub Release
|
||||||
|
run: |
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" "supplements_${{ env.APP_VERSION }}_android.apk" --clobber
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" "supplements_${{ env.APP_VERSION }}_debug.apk" --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
95
.github/workflows/ios-build.yml
vendored
Normal file
95
.github/workflows/ios-build.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# .github/workflows/ios-build-github.yml
|
||||||
|
name: Build iOS Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and Upload iOS Release
|
||||||
|
runs-on: macos-14
|
||||||
|
if: github.repository_owner == 'vleeuwenmenno'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Print Release Info
|
||||||
|
run: |
|
||||||
|
echo "Release Tag: ${{ github.event.release.tag_name }}"
|
||||||
|
echo "Release Name: ${{ github.event.release.name }}"
|
||||||
|
echo "GitHub Token exists: ${{ secrets.GITHUB_TOKEN != '' }}"
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
- name: Enable iOS support
|
||||||
|
run: flutter config --enable-ios
|
||||||
|
|
||||||
|
- name: Cache CocoaPods
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cocoapods/repos
|
||||||
|
ios/Pods
|
||||||
|
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pods-
|
||||||
|
|
||||||
|
- name: Update CocoaPods repository
|
||||||
|
run: |
|
||||||
|
echo "Updating CocoaPods repository..."
|
||||||
|
pod repo update --silent
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "CocoaPods repository updated successfully"
|
||||||
|
else
|
||||||
|
echo "Failed to update CocoaPods repository"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Extract version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
APP_VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //g' | sed 's/+.*//g')
|
||||||
|
echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV
|
||||||
|
echo "App version: $APP_VERSION"
|
||||||
|
|
||||||
|
- name: Set up Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: latest-stable
|
||||||
|
|
||||||
|
# Updated build steps start here
|
||||||
|
- name: Build iOS app (unsigned)
|
||||||
|
run: |
|
||||||
|
flutter build ios --release --no-codesign
|
||||||
|
echo "Built .app location: $(pwd)/build/ios/iphoneos/Runner.app"
|
||||||
|
|
||||||
|
- name: Create unsigned IPA
|
||||||
|
run: |
|
||||||
|
mkdir Payload
|
||||||
|
cp -r build/ios/iphoneos/Runner.app Payload/
|
||||||
|
zip -r supplements_${{ env.APP_VERSION }}_ios_unsigned.ipa Payload
|
||||||
|
echo "Created unsigned IPA: $(pwd)/supplements_${{ env.APP_VERSION }}_ios_unsigned.ipa"
|
||||||
|
|
||||||
|
- name: Upload IPA to GitHub Release
|
||||||
|
run: |
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" \
|
||||||
|
"supplements_${{ env.APP_VERSION }}_ios_unsigned.ipa" \
|
||||||
|
--clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ios-unsigned-ipa
|
||||||
|
path: supplements_${{ env.APP_VERSION }}_ios_unsigned.ipa
|
||||||
|
retention-days: 30
|
184
.github/workflows/linux-build.yml
vendored
Normal file
184
.github/workflows/linux-build.yml
vendored
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# .github/workflows/linux-build-github.yml
|
||||||
|
name: Build Linux Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and Upload Linux Release
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: github.repository_owner == 'vleeuwenmenno' && contains(github.server_url, 'github.com')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
- name: Enable Linux desktop support
|
||||||
|
run: flutter config --enable-linux-desktop
|
||||||
|
|
||||||
|
- name: Install Linux dependencies
|
||||||
|
run: |
|
||||||
|
# Fix GPG key issues
|
||||||
|
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 871920D1991BC93C
|
||||||
|
sudo apt-get update -y
|
||||||
|
# Install dependencies needed for Flutter Linux builds and appimage-builder
|
||||||
|
sudo apt-get install -y clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev \
|
||||||
|
python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace pipx squashfs-tools zsync \
|
||||||
|
python3-venv python3-dev appstream
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build Linux release
|
||||||
|
run: flutter build linux --release
|
||||||
|
|
||||||
|
- name: Extract version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
APP_VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //g' | sed 's/+.*//g')
|
||||||
|
echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Zip bundle directory
|
||||||
|
run: |
|
||||||
|
cd build/linux/x64/release
|
||||||
|
zip -r ../../../../supplements_${{ env.APP_VERSION }}_amd64_linux.zip bundle/*
|
||||||
|
cd ../../../../ # Go back to the root directory
|
||||||
|
|
||||||
|
- name: Download and extract AppImageTool
|
||||||
|
run: |
|
||||||
|
wget -O appimagetool-x86_64.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
|
chmod +x appimagetool-x86_64.AppImage
|
||||||
|
# Extract AppImageTool to avoid FUSE dependency issues in containers
|
||||||
|
./appimagetool-x86_64.AppImage --appimage-extract
|
||||||
|
# The extracted tool will be in squashfs-root/AppRun
|
||||||
|
|
||||||
|
- name: Create AppDir structure using Flutter bundle
|
||||||
|
run: |
|
||||||
|
# Verify Flutter build exists
|
||||||
|
echo "Verifying Flutter build..."
|
||||||
|
if [[ ! -d "build/linux/x64/release/bundle" ]]; then
|
||||||
|
echo "Error: Flutter build directory not found!"
|
||||||
|
ls -la build/linux/x64/release/ || echo "Release directory doesn't exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "build/linux/x64/release/bundle/supplements" ]]; then
|
||||||
|
echo "Error: supplements executable not found!"
|
||||||
|
ls -la build/linux/x64/release/bundle/
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create AppDir and copy the entire Flutter bundle
|
||||||
|
echo "Creating AppDir with Flutter bundle..."
|
||||||
|
mkdir -p AppDir
|
||||||
|
cp -r build/linux/x64/release/bundle AppDir/
|
||||||
|
|
||||||
|
# Create required directories for AppImage structure
|
||||||
|
mkdir -p AppDir/usr/share/applications
|
||||||
|
mkdir -p AppDir/usr/share/icons/hicolor/64x64/apps
|
||||||
|
|
||||||
|
# Copy icon
|
||||||
|
echo "Copying application icon..."
|
||||||
|
if [[ -f "android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png" ]]; then
|
||||||
|
# Copy to standard location
|
||||||
|
cp android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png AppDir/usr/share/icons/hicolor/64x64/apps/
|
||||||
|
# Also copy to AppDir root with the name referenced in desktop file
|
||||||
|
cp android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png AppDir/ic_launcher.png
|
||||||
|
else
|
||||||
|
echo "Warning: Icon not found, trying other locations..."
|
||||||
|
ICON_FILE=$(find android/app/src/main/res/ -name "ic_launcher.png" | head -1)
|
||||||
|
if [[ -n "$ICON_FILE" ]]; then
|
||||||
|
cp "$ICON_FILE" AppDir/usr/share/icons/hicolor/64x64/apps/
|
||||||
|
cp "$ICON_FILE" AppDir/ic_launcher.png
|
||||||
|
else
|
||||||
|
echo "No icon found"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create desktop file (AppImageTool looks for it in the root of AppDir)
|
||||||
|
echo "Creating desktop file..."
|
||||||
|
cat > AppDir/supplements.desktop << 'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=supplements
|
||||||
|
Exec=AppRun
|
||||||
|
Icon=ic_launcher
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Also create it in the standard location for completeness
|
||||||
|
cp AppDir/supplements.desktop AppDir/usr/share/applications/supplements.desktop
|
||||||
|
|
||||||
|
# Create AppRun script that uses the Flutter bundle directly
|
||||||
|
echo "Creating AppRun script..."
|
||||||
|
cat > AppDir/AppRun << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
HERE="$(dirname "$(readlink -f "${0}")")"
|
||||||
|
cd "${HERE}/bundle"
|
||||||
|
exec "./supplements" "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x AppDir/AppRun
|
||||||
|
|
||||||
|
echo "AppDir structure created successfully!"
|
||||||
|
echo "AppDir contents:"
|
||||||
|
ls -la AppDir/
|
||||||
|
echo "Bundle contents:"
|
||||||
|
ls -la AppDir/bundle/
|
||||||
|
echo "Desktop file verification:"
|
||||||
|
ls -la AppDir/*.desktop
|
||||||
|
echo "Icon verification:"
|
||||||
|
ls -la AppDir/usr/share/icons/hicolor/64x64/apps/
|
||||||
|
|
||||||
|
- name: Build AppImage manually
|
||||||
|
run: |
|
||||||
|
# Verify AppDir structure before building
|
||||||
|
echo "Verifying AppDir structure..."
|
||||||
|
if [[ ! -f "AppDir/AppRun" ]]; then
|
||||||
|
echo "Error: AppRun not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "AppDir/bundle/supplements" ]]; then
|
||||||
|
echo "Error: supplements executable not found in AppDir/bundle/!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build AppImage using extracted appimagetool (avoids FUSE issues)
|
||||||
|
echo "Building AppImage..."
|
||||||
|
ARCH=amd64 ./squashfs-root/AppRun AppDir supplements-latest-amd64.AppImage
|
||||||
|
|
||||||
|
# Check if AppImage was created successfully
|
||||||
|
if [[ -f "supplements-latest-amd64.AppImage" ]]; then
|
||||||
|
echo "AppImage created successfully!"
|
||||||
|
ls -la supplements-latest-amd64.AppImage
|
||||||
|
else
|
||||||
|
echo "Error: AppImage creation failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Debug release information
|
||||||
|
run: |
|
||||||
|
echo "Release tag: ${{ github.event.release.tag_name }}"
|
||||||
|
echo "Release name: ${{ github.event.release.name }}"
|
||||||
|
echo "Release URL: ${{ github.event.release.html_url }}"
|
||||||
|
echo "App version: ${{ env.APP_VERSION }}"
|
||||||
|
|
||||||
|
- name: Upload Linux Zip to GitHub Release
|
||||||
|
run: |
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" "supplements_${{ env.APP_VERSION }}_amd64_linux.zip" --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload Linux AppImage to GitHub Release
|
||||||
|
run: |
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" "supplements-latest-amd64.AppImage" --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
206
.github/workflows/windows-build.yml
vendored
Normal file
206
.github/workflows/windows-build.yml
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# .github/workflows/windows-build-github.yml
|
||||||
|
name: Build Windows Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Windows Release
|
||||||
|
runs-on: windows-latest
|
||||||
|
if: github.repository_owner == 'vleeuwenmenno' && contains(github.server_url, 'github.com')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
- name: Enable Windows desktop support
|
||||||
|
run: flutter config --enable-windows-desktop
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build Windows release
|
||||||
|
run: flutter build windows --release
|
||||||
|
|
||||||
|
- name: Extract version
|
||||||
|
id: version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$version = (Get-Content pubspec.yaml | Select-String "version:" | ForEach-Object { $_.ToString().Split(' ')[1] }).Split('+')[0]
|
||||||
|
echo "APP_VERSION=$version" >> $env:GITHUB_ENV
|
||||||
|
echo "Version extracted: $version"
|
||||||
|
|
||||||
|
- name: Create Windows installer directory structure
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# Create installer directory
|
||||||
|
New-Item -ItemType Directory -Force -Path "installer"
|
||||||
|
|
||||||
|
# Copy the built app
|
||||||
|
Copy-Item -Recurse "build\windows\x64\runner\Release\*" "installer\"
|
||||||
|
|
||||||
|
# Verify the executable exists
|
||||||
|
if (Test-Path "installer\supplements.exe") {
|
||||||
|
Write-Host "✓ supplements.exe found in installer directory"
|
||||||
|
} else {
|
||||||
|
Write-Host "✗ supplements.exe not found!"
|
||||||
|
Get-ChildItem "installer" -Recurse
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create portable zip
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "installer\*" -DestinationPath "supplements_${{ env.APP_VERSION }}_windows_x64_portable.zip"
|
||||||
|
|
||||||
|
# Verify zip was created
|
||||||
|
if (Test-Path "supplements_${{ env.APP_VERSION }}_windows_x64_portable.zip") {
|
||||||
|
$size = (Get-Item "supplements_${{ env.APP_VERSION }}_windows_x64_portable.zip").Length / 1MB
|
||||||
|
Write-Host "✓ Portable zip created successfully ($([math]::Round($size, 2)) MB)"
|
||||||
|
} else {
|
||||||
|
Write-Host "✗ Failed to create portable zip"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create NSIS installer script
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$nsisScript = @"
|
||||||
|
!define APP_NAME "supplements"
|
||||||
|
!define APP_VERSION "${{ env.APP_VERSION }}"
|
||||||
|
!define APP_PUBLISHER "vleeuwenmenno"
|
||||||
|
!define APP_DESCRIPTION "A ComfyUI client with sync capabilities"
|
||||||
|
!define APP_EXECUTABLE "supplements.exe"
|
||||||
|
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
|
||||||
|
Name "`${APP_NAME} `${APP_VERSION}"
|
||||||
|
OutFile "supplements_`${APP_VERSION}_windows_x64_installer.exe"
|
||||||
|
InstallDir "`$PROGRAMFILES64\`${APP_NAME}"
|
||||||
|
InstallDirRegKey HKLM "Software\`${APP_NAME}" "InstallDir"
|
||||||
|
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!define MUI_ICON "`${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
||||||
|
!define MUI_UNICON "`${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "installer\LICENSE"
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_WELCOME
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
|
||||||
|
Section "Install"
|
||||||
|
SetOutPath "`$INSTDIR"
|
||||||
|
File /r "installer\*"
|
||||||
|
|
||||||
|
WriteRegStr HKLM "Software\`${APP_NAME}" "InstallDir" "`$INSTDIR"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "DisplayName" "`${APP_NAME}"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "UninstallString" "`$INSTDIR\Uninstall.exe"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "DisplayVersion" "`${APP_VERSION}"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "Publisher" "`${APP_PUBLISHER}"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "DisplayIcon" "`$INSTDIR\`${APP_EXECUTABLE}"
|
||||||
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "NoModify" 1
|
||||||
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}" "NoRepair" 1
|
||||||
|
|
||||||
|
CreateDirectory "`$SMPROGRAMS\`${APP_NAME}"
|
||||||
|
CreateShortcut "`$SMPROGRAMS\`${APP_NAME}\`${APP_NAME}.lnk" "`$INSTDIR\`${APP_EXECUTABLE}"
|
||||||
|
CreateShortcut "`$SMPROGRAMS\`${APP_NAME}\Uninstall.lnk" "`$INSTDIR\Uninstall.exe"
|
||||||
|
CreateShortcut "`$DESKTOP\`${APP_NAME}.lnk" "`$INSTDIR\`${APP_EXECUTABLE}"
|
||||||
|
|
||||||
|
WriteUninstaller "`$INSTDIR\Uninstall.exe"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
Delete "`$INSTDIR\Uninstall.exe"
|
||||||
|
RMDir /r "`$INSTDIR"
|
||||||
|
RMDir /r "`$SMPROGRAMS\`${APP_NAME}"
|
||||||
|
Delete "`$DESKTOP\`${APP_NAME}.lnk"
|
||||||
|
|
||||||
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APP_NAME}"
|
||||||
|
DeleteRegKey HKLM "Software\`${APP_NAME}"
|
||||||
|
SectionEnd
|
||||||
|
"@
|
||||||
|
|
||||||
|
$nsisScript | Out-File -FilePath "installer.nsi" -Encoding UTF8
|
||||||
|
Write-Host "✓ NSIS script created"
|
||||||
|
|
||||||
|
- name: Create dummy LICENSE file
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$license = @"
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 vleeuwenmenno
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
"@
|
||||||
|
|
||||||
|
$license | Out-File -FilePath "installer\LICENSE" -Encoding UTF8
|
||||||
|
Write-Host "✓ LICENSE file created"
|
||||||
|
|
||||||
|
- name: Build NSIS installer
|
||||||
|
uses: joncloud/makensis-action@v4.1
|
||||||
|
with:
|
||||||
|
script-file: "installer.nsi"
|
||||||
|
|
||||||
|
- name: Verify installer creation
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# Verify installer was created
|
||||||
|
if (Test-Path "supplements_${{ env.APP_VERSION }}_windows_x64_installer.exe") {
|
||||||
|
$size = (Get-Item "supplements_${{ env.APP_VERSION }}_windows_x64_installer.exe").Length / 1MB
|
||||||
|
Write-Host "✓ NSIS installer created successfully ($([math]::Round($size, 2)) MB)"
|
||||||
|
} else {
|
||||||
|
Write-Host "✗ Failed to create NSIS installer"
|
||||||
|
Get-ChildItem . -Filter "*.exe" | ForEach-Object { Write-Host "Found: $($_.Name)" }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Debug release information
|
||||||
|
run: |
|
||||||
|
echo "Release tag: ${{ github.event.release.tag_name }}"
|
||||||
|
echo "Release name: ${{ github.event.release.name }}"
|
||||||
|
echo "Release URL: ${{ github.event.release.html_url }}"
|
||||||
|
echo "App version: ${{ env.APP_VERSION }}"
|
||||||
|
|
||||||
|
- name: Upload Windows builds to GitHub Release
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" "supplements_${{ env.APP_VERSION }}_windows_x64_portable.zip" --clobber
|
||||||
|
gh release upload "${{ github.event.release.tag_name }}" "supplements_${{ env.APP_VERSION }}_windows_x64_installer.exe" --clobber
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
573
bin/release.py
Executable file
573
bin/release.py
Executable file
@@ -0,0 +1,573 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import fileinput
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def get_project_root():
|
||||||
|
"""Finds the project root directory containing .git"""
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Go up one level from bin/
|
||||||
|
project_root = os.path.dirname(current_dir)
|
||||||
|
if os.path.isdir(os.path.join(project_root, '.git')):
|
||||||
|
return project_root
|
||||||
|
else:
|
||||||
|
print("Error: Could not find project root (.git directory).", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def get_version(project_root):
|
||||||
|
"""Reads the version from pubspec.yaml"""
|
||||||
|
pubspec_path = os.path.join(project_root, 'pubspec.yaml')
|
||||||
|
try:
|
||||||
|
with open(pubspec_path, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.strip().startswith('version:'):
|
||||||
|
# Extract version string after 'version:'
|
||||||
|
version_match = re.search(r'version:\s*([\w\.\+\-]+)', line)
|
||||||
|
if version_match:
|
||||||
|
return version_match.group(1).strip()
|
||||||
|
print(f"Error: Could not find 'version:' line in {pubspec_path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: {pubspec_path} not found.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading {pubspec_path}: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def check_tag_exists(tag_name, project_root):
|
||||||
|
"""Checks if a git tag already exists"""
|
||||||
|
try:
|
||||||
|
# Use cwd=project_root to run git in the correct directory
|
||||||
|
result = subprocess.run(['git', 'rev-parse', '--verify', '--quiet', tag_name],
|
||||||
|
cwd=project_root,
|
||||||
|
check=False, # Don't raise exception on non-zero exit
|
||||||
|
capture_output=True)
|
||||||
|
return result.returncode == 0
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: 'git' command not found. Make sure Git is installed and in your PATH.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error checking git tag: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def check_git_dirty(project_root):
|
||||||
|
"""Checks if the git working directory is dirty"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['git', 'status', '--porcelain'],
|
||||||
|
cwd=project_root,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True)
|
||||||
|
if result.stdout:
|
||||||
|
print("Warning: Git working directory is dirty:", file=sys.stderr)
|
||||||
|
print(result.stdout.strip(), file=sys.stderr)
|
||||||
|
return True # Dirty
|
||||||
|
return False # Clean
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: 'git' command not found. Make sure Git is installed and in your PATH.", file=sys.stderr)
|
||||||
|
sys.exit(1) # Exit if git isn't found
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error checking git status: {e}", file=sys.stderr)
|
||||||
|
# Decide if we should exit or just warn and continue? Let's exit for safety.
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred checking git status: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def update_pubspec_version(project_root, new_version):
|
||||||
|
"""Updates the version in pubspec.yaml"""
|
||||||
|
pubspec_path = os.path.join(project_root, 'pubspec.yaml')
|
||||||
|
print(f"Updating {pubspec_path} to version: {new_version}")
|
||||||
|
try:
|
||||||
|
# Read lines, update the version line, write back
|
||||||
|
updated = False
|
||||||
|
lines = []
|
||||||
|
with open(pubspec_path, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
with open(pubspec_path, 'w') as f:
|
||||||
|
for line in lines:
|
||||||
|
if line.strip().startswith('version:'):
|
||||||
|
f.write(f"version: {new_version}\n")
|
||||||
|
updated = True
|
||||||
|
else:
|
||||||
|
f.write(line)
|
||||||
|
|
||||||
|
if not updated:
|
||||||
|
print(f"Warning: 'version:' line not found in {pubspec_path}. File not updated.", file=sys.stderr)
|
||||||
|
return updated
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating {pubspec_path}: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(command_list, cwd, error_message_prefix):
|
||||||
|
"""Runs a command and handles errors"""
|
||||||
|
print(f"Running: {' '.join(command_list)}")
|
||||||
|
try:
|
||||||
|
result = subprocess.run(command_list, cwd=cwd, check=True, capture_output=True, text=True)
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr) # Show stderr even on success if present
|
||||||
|
return True
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"\nError: '{command_list[0]}' command not found. Make sure it's installed and in your PATH.", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"\n{error_message_prefix}:", file=sys.stderr)
|
||||||
|
print(f" Command: {' '.join(e.cmd)}", file=sys.stderr)
|
||||||
|
print(f" Return code: {e.returncode}", file=sys.stderr)
|
||||||
|
if e.stdout:
|
||||||
|
print(f" Stdout:\n{e.stdout}", file=sys.stderr)
|
||||||
|
if e.stderr:
|
||||||
|
print(f" Stderr:\n{e.stderr}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nAn unexpected error occurred while running {' '.join(command_list)}: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_release_notes():
|
||||||
|
"""Prompts the user for multi-line release notes"""
|
||||||
|
print("Enter release notes (press Ctrl+D on a new line when finished):")
|
||||||
|
lines = []
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
line = sys.stdin.readline()
|
||||||
|
if not line: # EOF detected (Ctrl+D)
|
||||||
|
break
|
||||||
|
lines.append(line)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nAborted note entry.")
|
||||||
|
return None # Indicate abortion if needed, or handle differently
|
||||||
|
return "".join(lines).strip()
|
||||||
|
|
||||||
|
def get_git_remotes(project_root):
|
||||||
|
"""Gets all git remotes"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['git', 'remote', '-v'],
|
||||||
|
cwd=project_root,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True)
|
||||||
|
remotes = {}
|
||||||
|
for line in result.stdout.strip().split('\n'):
|
||||||
|
if line and '(fetch)' in line:
|
||||||
|
parts = line.split('\t')
|
||||||
|
if len(parts) >= 2:
|
||||||
|
remote_name = parts[0]
|
||||||
|
remote_url = parts[1].replace(' (fetch)', '')
|
||||||
|
remotes[remote_name] = remote_url
|
||||||
|
return remotes
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: 'git' command not found. Make sure Git is installed and in your PATH.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error getting git remotes: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred getting git remotes: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def is_gitea_remote(remote_url):
|
||||||
|
"""Checks if a remote URL appears to be a Gitea instance"""
|
||||||
|
# This is a heuristic - you might want to adjust this based on your setup
|
||||||
|
gitea_indicators = ['gitea', 'tea', 'git.mvl.sh']
|
||||||
|
return any(indicator in remote_url.lower() for indicator in gitea_indicators)
|
||||||
|
|
||||||
|
def is_github_remote(remote_url):
|
||||||
|
"""Checks if a remote URL is GitHub"""
|
||||||
|
return 'github.com' in remote_url.lower()
|
||||||
|
|
||||||
|
def select_remotes_for_push(remotes):
|
||||||
|
"""Allows user to select which remotes to push to"""
|
||||||
|
if not remotes:
|
||||||
|
print("No remotes found.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
print("\nAvailable remotes:")
|
||||||
|
remote_list = list(remotes.keys())
|
||||||
|
for i, remote in enumerate(remote_list, 1):
|
||||||
|
print(f" {i}. {remote} ({remotes[remote]})")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
selection = input(f"\nSelect remotes to push to (comma-separated numbers, 'all', or 'none') [all]: ").strip()
|
||||||
|
|
||||||
|
if not selection or selection.lower() == 'all':
|
||||||
|
return remote_list
|
||||||
|
|
||||||
|
if selection.lower() == 'none':
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
selected_indices = [int(x.strip()) for x in selection.split(',')]
|
||||||
|
selected_remotes = []
|
||||||
|
for idx in selected_indices:
|
||||||
|
if 1 <= idx <= len(remote_list):
|
||||||
|
selected_remotes.append(remote_list[idx - 1])
|
||||||
|
else:
|
||||||
|
print(f"Invalid selection: {idx}. Please try again.")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return selected_remotes
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter numbers separated by commas, 'all', or 'none'.")
|
||||||
|
|
||||||
|
def select_remote_for_release(remotes):
|
||||||
|
"""Allows user to select which remote to create the release on"""
|
||||||
|
if not remotes:
|
||||||
|
print("No remotes found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Filter remotes that might support releases (Gitea or GitHub)
|
||||||
|
release_capable_remotes = {}
|
||||||
|
for name, url in remotes.items():
|
||||||
|
if is_gitea_remote(url) or is_github_remote(url):
|
||||||
|
release_capable_remotes[name] = url
|
||||||
|
|
||||||
|
if not release_capable_remotes:
|
||||||
|
print("No remotes found that appear to support releases (Gitea or GitHub).")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(release_capable_remotes) == 1:
|
||||||
|
remote_name = list(release_capable_remotes.keys())[0]
|
||||||
|
print(f"\nUsing {remote_name} for release creation ({release_capable_remotes[remote_name]})")
|
||||||
|
return remote_name
|
||||||
|
|
||||||
|
print("\nAvailable remotes for release creation:")
|
||||||
|
remote_list = list(release_capable_remotes.keys())
|
||||||
|
for i, remote in enumerate(remote_list, 1):
|
||||||
|
remote_type = "Gitea" if is_gitea_remote(release_capable_remotes[remote]) else "GitHub"
|
||||||
|
print(f" {i}. {remote} ({remote_type}: {release_capable_remotes[remote]})")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
selection = input(f"\nSelect remote for release creation [1]: ").strip()
|
||||||
|
if not selection:
|
||||||
|
return remote_list[0]
|
||||||
|
|
||||||
|
idx = int(selection)
|
||||||
|
if 1 <= idx <= len(remote_list):
|
||||||
|
return remote_list[idx - 1]
|
||||||
|
else:
|
||||||
|
print(f"Invalid selection: {idx}. Please try again.")
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter a number.")
|
||||||
|
|
||||||
|
def create_github_release(tag_name, title, notes, is_prerelease, project_root, remote_name, remotes):
|
||||||
|
"""Creates a GitHub release using gh CLI"""
|
||||||
|
# Get the repository URL from the remote
|
||||||
|
remote_url = remotes[remote_name]
|
||||||
|
|
||||||
|
# Extract owner/repo from GitHub URL
|
||||||
|
repo_info = None
|
||||||
|
if 'github.com' in remote_url:
|
||||||
|
# Handle both SSH and HTTPS URLs
|
||||||
|
if remote_url.startswith('git@github.com:'):
|
||||||
|
# SSH format: git@github.com:owner/repo.git
|
||||||
|
repo_part = remote_url.replace('git@github.com:', '').replace('.git', '')
|
||||||
|
repo_info = repo_part
|
||||||
|
elif 'github.com/' in remote_url:
|
||||||
|
# HTTPS format: https://github.com/owner/repo.git
|
||||||
|
repo_part = remote_url.split('github.com/')[-1].replace('.git', '')
|
||||||
|
repo_info = repo_part
|
||||||
|
|
||||||
|
gh_command = ['gh', 'release', 'create', tag_name, '--title', title]
|
||||||
|
if repo_info:
|
||||||
|
gh_command.extend(['--repo', repo_info])
|
||||||
|
|
||||||
|
if is_prerelease:
|
||||||
|
gh_command.append('--prerelease')
|
||||||
|
if notes:
|
||||||
|
gh_command.extend(['--notes', notes])
|
||||||
|
else:
|
||||||
|
gh_command.append('--generate-notes')
|
||||||
|
|
||||||
|
print(f"\nCreating GitHub release on {remote_name}...")
|
||||||
|
if repo_info:
|
||||||
|
print(f"Repository: {repo_info}")
|
||||||
|
print(f"Running: {' '.join(gh_command)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(gh_command, cwd=project_root, check=True, capture_output=True, text=True)
|
||||||
|
print("\nGitHub release created successfully!")
|
||||||
|
print(result.stdout)
|
||||||
|
return True
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("\nError: 'gh' command not found. Make sure GitHub CLI is installed and authenticated.", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("\nError creating GitHub release:", file=sys.stderr)
|
||||||
|
print(f" Return code: {e.returncode}", file=sys.stderr)
|
||||||
|
if e.stdout:
|
||||||
|
print(f" Stdout:\n{e.stdout}", file=sys.stderr)
|
||||||
|
if e.stderr:
|
||||||
|
print(f" Stderr:\n{e.stderr}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nAn unexpected error occurred creating GitHub release: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_gitea_release(tag_name, title, notes, is_prerelease, project_root, remote_name):
|
||||||
|
"""Creates a Gitea release using tea CLI"""
|
||||||
|
tea_command = ['tea', 'release', 'create', '--tag', tag_name, '--title', title, '--remote', remote_name]
|
||||||
|
if is_prerelease:
|
||||||
|
tea_command.append('--prerelease')
|
||||||
|
if notes:
|
||||||
|
tea_command.extend(['--note', notes])
|
||||||
|
|
||||||
|
print(f"\nCreating Gitea release on {remote_name}...")
|
||||||
|
print(f"Running: {' '.join(tea_command)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(tea_command, cwd=project_root, check=True, capture_output=True, text=True)
|
||||||
|
print("\nGitea release created successfully!")
|
||||||
|
print(result.stdout)
|
||||||
|
return True
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("\nError: 'tea' command not found. Make sure Gitea CLI is installed and configured.", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("\nError creating Gitea release:", file=sys.stderr)
|
||||||
|
print(f" Return code: {e.returncode}", file=sys.stderr)
|
||||||
|
if e.stdout:
|
||||||
|
print(f" Stdout:\n{e.stdout}", file=sys.stderr)
|
||||||
|
if e.stderr:
|
||||||
|
print(f" Stderr:\n{e.stderr}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nAn unexpected error occurred creating Gitea release: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
project_root = get_project_root()
|
||||||
|
|
||||||
|
# --- Get remotes information ---
|
||||||
|
remotes = get_git_remotes(project_root)
|
||||||
|
print(f"Found {len(remotes)} remote(s):")
|
||||||
|
for name, url in remotes.items():
|
||||||
|
print(f" {name}: {url}")
|
||||||
|
|
||||||
|
# --- Check Git Status ---
|
||||||
|
if check_git_dirty(project_root):
|
||||||
|
dirty_confirm = input("Working directory is dirty. Continue anyway? [y/N]: ").strip().lower()
|
||||||
|
if dirty_confirm != 'y':
|
||||||
|
print("Aborted due to dirty working directory.")
|
||||||
|
sys.exit(0)
|
||||||
|
print("Continuing with dirty working directory...")
|
||||||
|
# --- End Check Git Status ---
|
||||||
|
|
||||||
|
original_version = get_version(project_root)
|
||||||
|
tag_name = f"v{original_version}"
|
||||||
|
version_to_use = original_version # This might change if tag exists
|
||||||
|
|
||||||
|
print(f"Detected version: {original_version}")
|
||||||
|
print(f"Tag to create based on pubspec: {tag_name}")
|
||||||
|
|
||||||
|
# Ask if user wants to use detected version or specify a new one
|
||||||
|
use_detected = input(f"\nUse detected version '{original_version}'? [Y/n]: ").strip().lower()
|
||||||
|
if use_detected == 'n':
|
||||||
|
while True:
|
||||||
|
new_base_version_input = input("Enter a new base version tag (e.g., v1.2.0): ").strip()
|
||||||
|
# Validate the base version input (starts with v, numbers, dots)
|
||||||
|
if not re.match(r'^v\d+(\.\d+)*$', new_base_version_input):
|
||||||
|
print("Invalid format. Please use 'v' followed by numbers and dots (e.g., v1.2.0). Try again.", file=sys.stderr)
|
||||||
|
continue # Ask again
|
||||||
|
|
||||||
|
# Get current date in ddMMyyyy format
|
||||||
|
current_date = datetime.now().strftime('%d%m%Y')
|
||||||
|
# Construct the full version string (without leading 'v' for pubspec)
|
||||||
|
version_to_use = f"{new_base_version_input[1:]}+{current_date}" # Remove leading 'v'
|
||||||
|
tag_name = f"v{version_to_use}" # Add 'v' back for the tag
|
||||||
|
print(f"New version string: {version_to_use}")
|
||||||
|
print(f"New tag to check/create: {tag_name}")
|
||||||
|
|
||||||
|
# Check if this new tag already exists
|
||||||
|
if check_tag_exists(tag_name, project_root):
|
||||||
|
print(f"\nWarning: Tag '{tag_name}' already exists.")
|
||||||
|
continue # Ask for a different version
|
||||||
|
else:
|
||||||
|
break # Tag doesn't exist, we can use this version
|
||||||
|
else:
|
||||||
|
# User wants to use detected version, but check if tag already exists
|
||||||
|
if check_tag_exists(tag_name, project_root):
|
||||||
|
print(f"\nWarning: Tag '{tag_name}' already exists.")
|
||||||
|
while True:
|
||||||
|
new_base_version_input = input("Enter a new base version tag (e.g., v1.2.0): ").strip()
|
||||||
|
# Validate the base version input (starts with v, numbers, dots)
|
||||||
|
if not re.match(r'^v\d+(\.\d+)*$', new_base_version_input):
|
||||||
|
print("Invalid format. Please use 'v' followed by numbers and dots (e.g., v1.2.0). Try again.", file=sys.stderr)
|
||||||
|
continue # Ask again
|
||||||
|
|
||||||
|
# Get current date in ddMMyyyy format
|
||||||
|
current_date = datetime.now().strftime('%d%m%Y')
|
||||||
|
# Construct the full version string (without leading 'v' for pubspec)
|
||||||
|
version_to_use = f"{new_base_version_input[1:]}+{current_date}" # Remove leading 'v'
|
||||||
|
tag_name = f"v{version_to_use}" # Add 'v' back for the tag
|
||||||
|
print(f"New version string: {version_to_use}")
|
||||||
|
print(f"New tag to check/create: {tag_name}")
|
||||||
|
|
||||||
|
# Check if this new tag already exists
|
||||||
|
if check_tag_exists(tag_name, project_root):
|
||||||
|
print(f"\nWarning: Tag '{tag_name}' already exists.")
|
||||||
|
continue # Ask for a different version
|
||||||
|
else:
|
||||||
|
break # Tag doesn't exist, we can use this version
|
||||||
|
|
||||||
|
print(f"\nFinal version: {version_to_use}")
|
||||||
|
print(f"Final tag: {tag_name}")
|
||||||
|
|
||||||
|
# --- Optional: Update pubspec, pub get, commit ---
|
||||||
|
commit_changes_input = input(f"\nUpdate pubspec to {version_to_use}, run 'flutter pub get', commit, and push? [y/N]: ").strip().lower()
|
||||||
|
if commit_changes_input == 'y':
|
||||||
|
print("\n--- Starting update, commit, and push process ---")
|
||||||
|
# 1. Update pubspec.yaml
|
||||||
|
if not update_pubspec_version(project_root, version_to_use):
|
||||||
|
print("Aborting due to error updating pubspec.yaml.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 2. Run flutter pub get
|
||||||
|
if not run_command(['flutter', 'pub', 'get'], project_root, "Error running 'flutter pub get'"):
|
||||||
|
print("Aborting due to error running 'flutter pub get'.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 3. Git add
|
||||||
|
if not run_command(['git', 'add', 'pubspec.yaml', 'pubspec.lock'], project_root, "Error running 'git add'"):
|
||||||
|
print("Aborting due to error running 'git add'.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 4. Git commit
|
||||||
|
commit_message = f"chore: bump version to {version_to_use}"
|
||||||
|
if not run_command(['git', 'commit', '-m', commit_message], project_root, "Error running 'git commit'"):
|
||||||
|
print("Aborting due to error running 'git commit'.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 5. Select remotes to push to
|
||||||
|
selected_remotes = select_remotes_for_push(remotes)
|
||||||
|
if not selected_remotes:
|
||||||
|
print("No remotes selected for push. Skipping push step.")
|
||||||
|
else:
|
||||||
|
# Push to selected remotes
|
||||||
|
for remote in selected_remotes:
|
||||||
|
print(f"\nPushing to {remote}...")
|
||||||
|
if not run_command(['git', 'push', remote], project_root, f"Error pushing to {remote}"):
|
||||||
|
print(f"Failed to push to {remote}. Continuing with other remotes...", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f"Successfully pushed to {remote}")
|
||||||
|
|
||||||
|
print("--- Update, commit, and push process finished ---")
|
||||||
|
else:
|
||||||
|
print("Skipping pubspec update, pub get, commit, and push.")
|
||||||
|
# --- End optional update ---
|
||||||
|
|
||||||
|
|
||||||
|
# Ask about pre-release
|
||||||
|
prerelease_input = input("\nMark as pre-release? [Y/n]: ").strip().lower()
|
||||||
|
is_prerelease = prerelease_input == '' or prerelease_input == 'y'
|
||||||
|
|
||||||
|
# Ask about release notes
|
||||||
|
notes = ""
|
||||||
|
notes_input = input("Add release notes? [y/N]: ").strip().lower()
|
||||||
|
if notes_input == 'y':
|
||||||
|
notes = get_release_notes()
|
||||||
|
if notes is None: # Handle potential abortion during note entry
|
||||||
|
print("Release aborted during note entry.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Select remote for release creation
|
||||||
|
release_remote = select_remote_for_release(remotes)
|
||||||
|
if not release_remote:
|
||||||
|
print("No suitable remote found for release creation. Exiting.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
remote_url = remotes[release_remote]
|
||||||
|
is_github = is_github_remote(remote_url)
|
||||||
|
is_gitea = is_gitea_remote(remote_url)
|
||||||
|
|
||||||
|
# Create the git tag locally first
|
||||||
|
print(f"\nCreating git tag '{tag_name}'...")
|
||||||
|
if not run_command(['git', 'tag', tag_name], project_root, f"Error creating tag {tag_name}"):
|
||||||
|
print(f"Failed to create tag {tag_name}.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Push the tag to the release remote
|
||||||
|
print(f"Pushing tag '{tag_name}' to {release_remote}...")
|
||||||
|
if not run_command(['git', 'push', release_remote, tag_name], project_root, f"Error pushing tag to {release_remote}"):
|
||||||
|
print(f"Failed to push tag to {release_remote}. Cannot create release without the tag.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Ask for confirmation
|
||||||
|
platform = "GitHub" if is_github else "Gitea" if is_gitea else "Unknown platform"
|
||||||
|
confirm_input = input(f"\nProceed with creating the release on {release_remote} ({platform})? [Y/n]: ").strip().lower()
|
||||||
|
if confirm_input != '' and confirm_input != 'y':
|
||||||
|
print("Aborted by user.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Create the release
|
||||||
|
success = False
|
||||||
|
if is_github:
|
||||||
|
success = create_github_release(tag_name, tag_name, notes, is_prerelease, project_root, release_remote, remotes)
|
||||||
|
elif is_gitea:
|
||||||
|
success = create_gitea_release(tag_name, tag_name, notes, is_prerelease, project_root, release_remote)
|
||||||
|
else:
|
||||||
|
print(f"Unsupported remote type for {release_remote}. Only GitHub and Gitea are supported.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print("Release creation failed.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Ask about creating releases on other platforms
|
||||||
|
release_capable_remotes = {}
|
||||||
|
for name, url in remotes.items():
|
||||||
|
if is_gitea_remote(url) or is_github_remote(url):
|
||||||
|
release_capable_remotes[name] = url
|
||||||
|
|
||||||
|
print(f"\nAll release-capable remotes found: {release_capable_remotes}")
|
||||||
|
|
||||||
|
# Remove the already used remote
|
||||||
|
remaining_remotes = {k: v for k, v in release_capable_remotes.items() if k != release_remote}
|
||||||
|
|
||||||
|
print(f"Remaining remotes after removing {release_remote}: {remaining_remotes}")
|
||||||
|
|
||||||
|
if remaining_remotes:
|
||||||
|
print(f"\nOther release-capable remotes available:")
|
||||||
|
for name, url in remaining_remotes.items():
|
||||||
|
remote_type = "GitHub" if is_github_remote(url) else "Gitea"
|
||||||
|
print(f" {name} ({remote_type}: {url})")
|
||||||
|
|
||||||
|
create_more = input(f"\nCreate release on other platforms? [y/N]: ").strip().lower()
|
||||||
|
if create_more == 'y':
|
||||||
|
for remote_name, remote_url in remaining_remotes.items():
|
||||||
|
remote_is_github = is_github_remote(remote_url)
|
||||||
|
remote_is_gitea = is_gitea_remote(remote_url)
|
||||||
|
remote_platform = "GitHub" if remote_is_github else "Gitea" if remote_is_gitea else "Unknown"
|
||||||
|
|
||||||
|
create_on_remote = input(f"\nCreate release on {remote_name} ({remote_platform})? [Y/n]: ").strip().lower()
|
||||||
|
if create_on_remote == '' or create_on_remote == 'y':
|
||||||
|
# Push tag to this remote too
|
||||||
|
print(f"Pushing tag '{tag_name}' to {remote_name}...")
|
||||||
|
if not run_command(['git', 'push', remote_name, tag_name], project_root, f"Error pushing tag to {remote_name}"):
|
||||||
|
print(f"Failed to push tag to {remote_name}. Skipping release creation.", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create release on this remote
|
||||||
|
if remote_is_github:
|
||||||
|
create_github_release(tag_name, tag_name, notes, is_prerelease, project_root, remote_name, remotes)
|
||||||
|
elif remote_is_gitea:
|
||||||
|
create_gitea_release(tag_name, tag_name, notes, is_prerelease, project_root, remote_name)
|
||||||
|
else:
|
||||||
|
print(f"Skipping release creation on {remote_name}")
|
||||||
|
else:
|
||||||
|
print("\nNo other release-capable remotes found.")
|
||||||
|
|
||||||
|
print("\nRelease process completed!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Reference in New Issue
Block a user