name: GUI Version Release on: push: branches: [ master, develop ] # Trigger on master and develop branches jobs: gui-version-release: runs-on: windows defaults: run: working-directory: E:\shuidrop_gui steps: # Step 1: Pull latest code - name: Pull latest code shell: powershell run: | Write-Host "=========================================="; Write-Host "Pulling latest code"; Write-Host "=========================================="; Write-Host "Working directory: $(Get-Location)"; Write-Host "Branch: ${{ github.ref_name }}"; Write-Host "Commit: ${{ github.sha }}"; Write-Host ""; git config core.autocrlf false; git config core.longpaths true; Write-Host "Fetching from origin..."; git fetch origin; Write-Host "Checking out branch: ${{ github.ref_name }}"; git checkout ${{ github.ref_name }}; Write-Host "Pulling latest changes..."; git reset --hard origin/${{ github.ref_name }}; Write-Host ""; Write-Host "Current status:"; git log -1 --oneline; Write-Host "=========================================="; # Step 2: Check Python environment - name: Check Python environment shell: powershell run: | Write-Host "Python version:" try { python --version } catch { Write-Host "Python not installed" } Write-Host "Pip version:" try { pip --version } catch { Write-Host "Pip not installed" } # Step 3: Install dependencies - name: Install dependencies shell: powershell run: | Write-Host "Installing psycopg2-binary..." python -m pip install --upgrade pip if ($LASTEXITCODE -ne 0) { Write-Host "Failed to upgrade pip" exit 1 } pip install psycopg2-binary if ($LASTEXITCODE -ne 0) { Write-Host "Failed to install psycopg2-binary" exit 1 } pip install py-mini-racer if ($LASTEXITCODE -ne 0) { Write-Host "WARNING: Failed to install py-mini-racer (DY platform may not work)" } else { Write-Host "OK: py-mini-racer installed" } Write-Host "Dependencies installed successfully" # Step 4: Create GUI version record - name: Create version record id: create_version shell: powershell run: | Write-Host "Starting GUI version release process..." Write-Host "Commit hash: ${{ github.sha }}" Write-Host "Commit author: ${{ github.actor }}" Write-Host "Branch: ${{ github.ref_name }}" # Retry mechanism: maximum 3 attempts $SUCCESS = $false for ($i = 1; $i -le 3; $i++) { Write-Host "Attempt $i to create version record..." python .gitea/scripts/gui_version_creator.py if ($LASTEXITCODE -eq 0) { Write-Host "Version record created successfully" $SUCCESS = $true break } else { Write-Host "Attempt $i failed" if ($i -eq 3) { Write-Host "All 3 attempts failed" } else { Write-Host "Waiting 5 seconds before retry..." Start-Sleep -Seconds 5 } } } if (-not $SUCCESS) { Write-Host "Version creation failed" exit 1 } # Verify version was updated if (Test-Path "config.py") { $configContent = Get-Content "config.py" -Raw if ($configContent -match 'APP_VERSION\s*=\s*"([\d.]+)"') { $VERSION = $matches[1] Write-Host "Version updated successfully: $VERSION" } } env: DB_HOST: 8.155.9.53 DB_NAME: ai_web DB_USER: user_emKCAb DB_PASSWORD: password_ee2iQ3 DB_PORT: 5400 # Step 4.5: Build production executable - name: Build production executable if: success() shell: powershell env: PYTHONIOENCODING: utf-8 run: | Write-Host "=========================================="; Write-Host "Step 4.5: Build production executable"; Write-Host "=========================================="; # Check and install dependencies only if needed Write-Host "Checking dependencies..."; # Check PyInstaller $pyinstallerInstalled = python -c "try: import PyInstaller; print('installed')`nexcept: print('not-installed')" 2>$null; if ($pyinstallerInstalled -ne "installed") { Write-Host "Installing PyInstaller..."; python -m pip install pyinstaller --quiet; } else { Write-Host "PyInstaller: already installed"; } # Check Pillow $pillowInstalled = python -c "try: from PIL import Image; print('installed')`nexcept: print('not-installed')" 2>$null; if ($pillowInstalled -ne "installed") { Write-Host "Installing Pillow..."; python -m pip install Pillow --quiet; } else { Write-Host "Pillow: already installed"; } Write-Host ""; Write-Host "Environment ready:"; Write-Host " Python:" (python --version); Write-Host " PyInstaller:" (python -m PyInstaller --version); Write-Host " Pillow:" (python -c "from PIL import Image; print(Image.__version__)"); Write-Host ""; python build_production.py; if ($LASTEXITCODE -ne 0) { Write-Host "Build failed"; exit 1; } Write-Host "Production build completed successfully"; Write-Host ""; # Step 4.6: Check or Install NSIS - name: Check or Install NSIS if: success() shell: powershell run: | Write-Host "=========================================="; Write-Host "Step 4.6: Check or Install NSIS"; Write-Host "=========================================="; # Step 1: Check if NSIS is already installed Write-Host "Checking if NSIS is already installed..."; $nsisPaths = @( "C:\Program Files (x86)\NSIS", "C:\Program Files\NSIS", "C:\Tools\NSIS", "$env:ProgramFiles\NSIS", "$env:ProgramFiles(x86)\NSIS" ); $nsisFound = $false; $nsisPath = ""; foreach ($path in $nsisPaths) { if (Test-Path $path) { $makensisPath = Join-Path $path "makensis.exe"; if (Test-Path $makensisPath) { $nsisPath = $path; $nsisFound = $true; Write-Host "Found NSIS at: $nsisPath"; break; } } } # Also check if makensis is in PATH if (-not $nsisFound) { $makensisInPath = Get-Command makensis -ErrorAction SilentlyContinue; if ($makensisInPath) { $nsisPath = Split-Path $makensisInPath.Source; $nsisFound = $true; Write-Host "Found makensis in PATH: $nsisPath"; } } if ($nsisFound) { $env:Path = "$nsisPath;$env:Path"; Write-Host "Using existing NSIS installation"; & makensis /VERSION; Write-Host ""; exit 0; } Write-Host "NSIS not found, attempting installation..."; Write-Host ""; # Step 2: Try Chocolatey Write-Host "Method 1: Trying Chocolatey..."; $chocoInstalled = Get-Command choco -ErrorAction SilentlyContinue; if ($chocoInstalled) { try { choco install nsis -y --no-progress --limit-output; if ($LASTEXITCODE -eq 0) { Start-Sleep -Seconds 3; # Refresh PATH $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User"); $nsisPath = "C:\Program Files (x86)\NSIS"; if (-not (Test-Path $nsisPath)) { $nsisPath = "C:\Program Files\NSIS"; } if (Test-Path (Join-Path $nsisPath "makensis.exe")) { $env:Path = "$nsisPath;$env:Path"; & makensis /VERSION; Write-Host "NSIS installed via Chocolatey"; Write-Host ""; exit 0; } } } catch { Write-Host "Chocolatey installation failed: $_"; } } else { Write-Host "Chocolatey not available"; } # Step 3: Try winget (Windows Package Manager) Write-Host ""; Write-Host "Method 2: Trying winget..."; $wingetInstalled = Get-Command winget -ErrorAction SilentlyContinue; if ($wingetInstalled) { try { winget install --id=NSIS.NSIS -e --silent --accept-package-agreements --accept-source-agreements; if ($LASTEXITCODE -eq 0) { Start-Sleep -Seconds 3; $nsisPath = "C:\Program Files (x86)\NSIS"; if (-not (Test-Path $nsisPath)) { $nsisPath = "C:\Program Files\NSIS"; } if (Test-Path (Join-Path $nsisPath "makensis.exe")) { $env:Path = "$nsisPath;$env:Path"; & makensis /VERSION; Write-Host "NSIS installed via winget"; Write-Host ""; exit 0; } } } catch { Write-Host "winget installation failed: $_"; } } else { Write-Host "winget not available"; } # Step 4: Manual installation is not possible due to network restrictions Write-Host ""; Write-Host "========================================"; Write-Host "ERROR: NSIS is not installed and automatic installation failed"; Write-Host ""; Write-Host "Please install NSIS manually on the CI/CD runner:"; Write-Host "1. Download from: https://nsis.sourceforge.io/Download"; Write-Host "2. Install to: C:\Program Files (x86)\NSIS"; Write-Host "3. Or use: choco install nsis"; Write-Host "4. Or use: winget install NSIS.NSIS"; Write-Host "========================================"; exit 1; # Step 4.7: Build NSIS installer - name: Build NSIS installer if: success() shell: powershell run: | Write-Host "=========================================="; Write-Host "Step 4.7: Build NSIS installer"; Write-Host "=========================================="; # Ensure NSIS is in PATH $nsisPath = "C:\Program Files (x86)\NSIS"; if (-not (Test-Path $nsisPath)) { $nsisPath = "C:\Program Files\NSIS"; } $env:Path = "$nsisPath;$env:Path"; Write-Host "Using NSIS from: $nsisPath"; cd installer; python build_installer.py; if ($LASTEXITCODE -ne 0) { Write-Host "Installer build failed"; exit 1; } $installers = Get-ChildItem -Path "output" -Filter "*.exe" -ErrorAction SilentlyContinue; if (-not $installers -or $installers.Count -eq 0) { Write-Host "No installer file found"; exit 1; } $installerName = $installers[0].Name; $installerSize = [math]::Round($installers[0].Length / 1MB, 2); Write-Host "Installer built successfully"; Write-Host " Filename: $installerName"; Write-Host " Size: $installerSize MB"; Write-Host ""; # Step 4.8: Upload installer to KS3 - name: Upload installer to KS3 if: success() shell: powershell timeout-minutes: 30 run: | Write-Host "=========================================="; Write-Host "Step 4.8: Upload installer to KS3"; Write-Host "=========================================="; Write-Host "NOTE: Large file upload may take 5-10 minutes"; Write-Host ""; # Install KS3 SDK Write-Host "Installing KS3 SDK..."; python -m pip install ks3sdk --quiet; if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: Failed to install ks3sdk"; Write-Host "Skipping KS3 upload (version release continues)"; exit 0; } Write-Host "Starting upload process..."; Write-Host "This may take several minutes, please be patient..."; Write-Host ""; # Run upload script python .gitea/scripts/upload_installer_to_ks3.py; if ($LASTEXITCODE -eq 0) { Write-Host ""; Write-Host "OK: Installer uploaded to KS3 successfully"; } else { Write-Host ""; Write-Host "WARNING: KS3 upload failed (exit code: $LASTEXITCODE)"; Write-Host "NOTICE: Version release continues"; Write-Host "NOTICE: You can manually upload the installer later"; Write-Host ""; Write-Host "Manual upload steps:"; Write-Host " 1. Find installer in: installer/output/"; Write-Host " 2. Upload to KS3 bucket: shuidrop-chat-server"; Write-Host " 3. Target path: installers/"; } Write-Host ""; # Step 5: Commit version changes - name: Commit version changes if: success() shell: powershell run: | Write-Host "========================================"; Write-Host "Step 5: Commit version changes"; Write-Host "========================================"; # Read new version number $VERSION = ""; if (Test-Path "config.py") { $configContent = Get-Content "config.py" -Raw; if ($configContent -match 'APP_VERSION\s*=\s*"([\d.]+)"') { $VERSION = $matches[1]; Write-Host "New version: $VERSION"; } } # Configure Git git config user.name "Gitea Actions Bot"; git config user.email "bot@gitea.local"; # Ensure we are on the correct branch (not detached HEAD) $BRANCH = "${{ github.ref_name }}"; $currentBranch = git rev-parse --abbrev-ref HEAD; Write-Host "Current branch: $currentBranch"; Write-Host "Target branch: $BRANCH"; if ($currentBranch -ne $BRANCH) { Write-Host "WARNING: Not on target branch, checking out..."; git checkout $BRANCH; Write-Host "OK: Checked out to $BRANCH"; } # Clean up any existing rebase state before starting $rebaseExists = (Test-Path ".git/rebase-merge") -or (Test-Path ".git/rebase-apply"); if ($rebaseExists) { Write-Host "WARNING: Found existing rebase state, cleaning up..."; git rebase --abort 2>$null; Remove-Item -Path ".git/rebase-merge" -Recurse -Force -ErrorAction SilentlyContinue; Remove-Item -Path ".git/rebase-apply" -Recurse -Force -ErrorAction SilentlyContinue; Write-Host "OK: Rebase state cleaned"; } # Check for changes git add config.py version_history.json; $hasChanges = git diff --staged --quiet; if ($LASTEXITCODE -ne 0) { Write-Host "Detected changes in version files"; Write-Host ""; # KEY CHANGE: Pull first, then commit Write-Host "Step 5.1: Pulling latest changes first..."; git fetch origin $BRANCH; # Check if remote has updates $LOCAL = git rev-parse HEAD; $REMOTE = git rev-parse origin/$BRANCH; if ($LOCAL -ne $REMOTE) { Write-Host "Remote has new commits, need to merge..."; Write-Host "Local: $LOCAL"; Write-Host "Remote: $REMOTE"; Write-Host ""; # FIX: Stash local changes before pull to avoid conflicts Write-Host "Stashing local version changes..."; git stash push -m "CI-CD-temp-stash" config.py version_history.json; Write-Host "Pulling remote changes..."; # Pull remote changes (use merge strategy to avoid rebase conflicts) git pull origin $BRANCH --no-rebase; # Pop stashed changes back Write-Host "Restoring version changes..."; $stashPopResult = git stash pop 2>&1; $stashPopExitCode = $LASTEXITCODE; if ($stashPopExitCode -ne 0) { Write-Host "WARNING: Stash pop encountered conflicts, resolving..."; Write-Host "$stashPopResult"; # Check conflict files $conflicts = git diff --name-only --diff-filter=U; Write-Host "Conflict files: $conflicts"; # FIX: Smart conflict resolution to preserve user code # For config.py: use remote version (user code), then re-apply version update if ($conflicts -match "config.py") { Write-Host "Resolving config.py conflict..."; # Use remote version (preserve user code) git checkout --theirs config.py; # Re-apply version update only python .gitea/scripts/gui_version_creator.py; git add config.py; Write-Host "OK: Resolved config.py (preserved user code + updated version)"; } # For version_history.json: use CI/CD version (append record) if ($conflicts -match "version_history.json") { Write-Host "Resolving version_history.json conflict..."; git checkout --ours version_history.json; git add version_history.json; Write-Host "OK: Resolved version_history.json (using CI/CD version)"; } # Drop the stash after resolving git stash drop 2>$null; } else { Write-Host "OK: Stash pop successful"; # FIX: After stash pop, files are automatically merged # config.py now contains: # 1. User's code from remote (from pull) # 2. Version update from stash (from gui_version_creator.py) Write-Host "Checking if version update is preserved..."; # Verify version number is correct $configContent = Get-Content "config.py" -Raw; if ($configContent -match 'APP_VERSION\s*=\s*"([\d.]+)"') { $currentVersion = $matches[1]; Write-Host "Current APP_VERSION in config.py: $currentVersion"; Write-Host "Expected version: $VERSION"; if ($currentVersion -ne $VERSION) { Write-Host "WARNING: Version mismatch, re-applying version update..."; # Re-execute version update (only modify APP_VERSION line) python .gitea/scripts/gui_version_creator.py; } else { Write-Host "OK: Version is correct"; } } # Add current files (includes user code + version update) git add config.py version_history.json; Write-Host "Files staged successfully"; } } else { Write-Host "No remote changes, proceeding with commit..."; # FIX: Ensure files are staged even if no remote changes git add config.py version_history.json; Write-Host "Files staged for commit"; } Write-Host ""; Write-Host "Step 5.2: Committing version changes..."; # Check if there are changes to commit $hasUncommitted = git diff --quiet; $diffExitCode1 = $LASTEXITCODE; $hasStagedChanges = git diff --staged --quiet; $diffExitCode2 = $LASTEXITCODE; if (($diffExitCode1 -ne 0) -or ($diffExitCode2 -ne 0)) { Write-Host "Detected uncommitted changes, creating commit..."; git commit -m "[skip ci] Update version to v$VERSION" --no-verify; if ($LASTEXITCODE -eq 0) { Write-Host "OK: Commit successful"; } else { Write-Host "ERROR: Commit failed"; } } else { Write-Host "No changes to commit (already committed in merge)"; Write-Host "Skipping commit step"; $LASTEXITCODE = 0; } if ($LASTEXITCODE -eq 0) { Write-Host ""; Write-Host "Step 5.3: Pushing to remote..."; # Push to remote (retry up to 3 times with smart strategy) $pushSuccess = $false; for ($i = 1; $i -le 3; $i++) { Write-Host "Push attempt $i/3..."; # Before each attempt, ensure clean state if ($i -gt 1) { Write-Host "Preparing for retry $i..."; # Step 1: Clean up any rebase state $rebaseCheck = (Test-Path ".git/rebase-merge") -or (Test-Path ".git/rebase-apply"); if ($rebaseCheck) { Write-Host "Cleaning up rebase state..."; git rebase --abort 2>$null; Remove-Item -Path ".git/rebase-merge" -Recurse -Force -ErrorAction SilentlyContinue; Remove-Item -Path ".git/rebase-apply" -Recurse -Force -ErrorAction SilentlyContinue; } # Step 2: Ensure on correct branch $currentBranchCheck = git rev-parse --abbrev-ref HEAD; if ($currentBranchCheck -ne $BRANCH) { Write-Host "Checking out to branch $BRANCH..."; git checkout $BRANCH; } # Step 3: Fetch latest remote state Write-Host "Fetching latest remote state..."; git fetch origin $BRANCH; # Step 4: Record current version number $currentVersionMatch = (Get-Content "config.py" -Raw) -match 'APP_VERSION\s*=\s*"([\d.]+)"'; $targetVersion = $matches[1]; Write-Host "Target version to apply: $targetVersion"; # Step 5: Reset to remote state Write-Host "Resetting to remote state..."; git reset --hard origin/$BRANCH; # Step 6: FIX - Re-apply version update only, do not overwrite entire file Write-Host "Re-applying version update only..."; python .gitea/scripts/gui_version_creator.py; # Step 7: Stage and commit again git add config.py version_history.json; git commit -m "[skip ci] Update version to v$VERSION" --no-verify; Write-Host "Retry preparation complete"; Start-Sleep -Seconds 1; } # Attempt to push git push origin $BRANCH; if ($LASTEXITCODE -eq 0) { Write-Host "OK: Push successful on attempt $i"; $pushSuccess = $true; break; } else { Write-Host "WARNING: Push attempt $i failed"; } } if (-not $pushSuccess) { Write-Host "ERROR: Push failed after 3 attempts"; Write-Host "NOTICE: This is not critical - version is already in database and KS3"; Write-Host "NOTICE: Git repository sync can be done manually later"; } } else { Write-Host "ERROR: Commit failed"; } } else { Write-Host "No changes to commit"; } Write-Host "========================================"; Write-Host ""; # Step 6: Display summary - name: Display summary if: always() shell: powershell run: | $VERSION = "Unknown"; if (Test-Path "config.py") { $configContent = Get-Content "config.py" -Raw; if ($configContent -match 'APP_VERSION\s*=\s*"([\d.]+)"') { $VERSION = $matches[1]; } } Write-Host "=========================================="; Write-Host "GUI Version Release Summary"; Write-Host "=========================================="; Write-Host "Author: ${{ github.actor }}"; Write-Host "Branch: ${{ github.ref_name }}"; Write-Host "Version: $VERSION"; Write-Host "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"; Write-Host "Commit: ${{ github.sha }}"; Write-Host "==========================================";