name: Build and Deploy LSFE Frontend on: push: branches: [ main ] jobs: build-and-deploy: runs-on: lsfe-server steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: "20" - name: Clean previous build output shell: pwsh run: | Remove-Item -Recurse -Force ".\dist" -ErrorAction SilentlyContinue - name: Install dependencies shell: pwsh run: npm ci # ---- Inject VITE_API_BASE_URL from a Gitea secret so the API URL never has to be # hand-edited/committed again - only this secret needs updating when the API moves. # This OVERWRITES .env.production before the build; Vite bakes it into the bundle # at build time (Vite env vars are compile-time, not runtime), so nothing needs # to change on the IIS/deploy side. - name: Write production env - Frontend shell: pwsh env: API_BASE_URL: ${{ secrets.LSFE_FRONTEND_VITE_API_BASE_URL }} run: | "VITE_API_BASE_URL=$env:API_BASE_URL" | Out-File -FilePath ".\.env.production" -Encoding utf8 -NoNewline Write-Host "Wrote .env.production (value not echoed)" - name: Build (Vite production build) shell: pwsh run: npm run build - name: Verify build output exists shell: pwsh run: | if (-not (Test-Path ".\dist\index.html")) { throw "Vite build did not produce dist\index.html - aborting before touching live site" } # ---- Backup current live deployment before touching anything ---- - name: Backup current live files shell: pwsh run: | $stamp = Get-Date -Format "yyyyMMdd-HHmmss" New-Item -ItemType Directory -Force -Path "C:\backups-frontend\$stamp" | Out-Null if (Test-Path "C:\inetpub\LSFE-Frontend") { robocopy "C:\inetpub\LSFE-Frontend" "C:\backups-frontend\$stamp\frontend" /MIR /R:2 /W:3 | Out-Null } $stamp | Out-File -FilePath "C:\backups-frontend\latest.txt" -Encoding ascii -NoNewline # Keep only the last 5 backups to avoid filling the disk $all = Get-ChildItem "C:\backups-frontend" -Directory | Sort-Object Name -Descending if ($all.Count -gt 5) { $all | Select-Object -Skip 5 | Remove-Item -Recurse -Force } Write-Host "Backed up current frontend deployment to C:\backups-frontend\$stamp" exit 0 - name: Stop app pool shell: pwsh run: | Import-Module WebAdministration Stop-WebAppPool -Name "LSFE-Frontend" -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 - name: Deploy frontend files id: deploy_frontend shell: pwsh run: | # Mirrors the built dist folder into the live IIS site path. # No content/uploads folder to protect here (that's a backend concept) - # if you later add one under the frontend site, add /XD here too. robocopy ".\dist" "C:\inetpub\LSFE-Frontend" /MIR /R:3 /W:5 $rc = $LASTEXITCODE Write-Host "ROBOCOPY EXIT CODE: $rc" if ($rc -ge 8) { throw "robocopy failed for frontend with exit code $rc" } exit 0 - name: Start app pool shell: pwsh run: | Import-Module WebAdministration Start-WebAppPool -Name "LSFE-Frontend" - name: Verify app pool is running shell: pwsh run: | Start-Sleep -Seconds 3 Import-Module WebAdministration $fe = Get-WebAppPoolState -Name "LSFE-Frontend" Write-Host "LSFE-Frontend: $($fe.Value)" if ($fe.Value -ne "Started") { throw "Frontend app pool failed to start" } # ---- Rollback path: only runs if any prior step in this job failed ---- - name: ROLLBACK - restore previous backup if: failure() shell: pwsh run: | $stamp = (Get-Content "C:\backups-frontend\latest.txt" -Raw).Trim() $backupPath = "C:\backups-frontend\$stamp" Write-Host "Deployment failed - rolling back to backup: $backupPath" Import-Module WebAdministration Stop-WebAppPool -Name "LSFE-Frontend" -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 if (Test-Path "$backupPath\frontend") { robocopy "$backupPath\frontend" "C:\inetpub\LSFE-Frontend" /MIR /R:3 /W:5 | Out-Null } Start-WebAppPool -Name "LSFE-Frontend" Write-Host "Rollback complete. Restored from $backupPath" exit 0 - name: ROLLBACK - verify pool after restore if: failure() shell: pwsh run: | Start-Sleep -Seconds 3 Import-Module WebAdministration $fe = Get-WebAppPoolState -Name "LSFE-Frontend" Write-Host "After rollback - LSFE-Frontend: $($fe.Value)" if ($fe.Value -ne "Started") { Write-Host "WARNING: frontend app pool still not running after rollback. Manual intervention needed." }