<# .SYNOPSIS Run Gitea CI workflows locally using act. .DESCRIPTION PowerShell wrapper around nektos/act for local CI execution. Builds the CI Docker image if needed, ensures .env.local exists, and invokes act with the correct arguments. .PARAMETER Workflow Workflow file name (with or without .yml) under .gitea/workflows/. .PARAMETER Job Run only a specific job within the workflow. .PARAMETER List List all available workflow jobs without running them. .PARAMETER DryRun Dry-run mode (-n). Shows what would execute without running. .PARAMETER Event Event type to simulate: pull_request (default) or push. .PARAMETER Rebuild Force rebuild of the CI Docker image. .PARAMETER ActVerbose Enable verbose act output. .EXAMPLE .\run-act.ps1 -List .\run-act.ps1 -Workflow test-matrix -DryRun .\run-act.ps1 -Workflow build-test-deploy -Job build .\run-act.ps1 -Event push -Workflow release-validation #> [CmdletBinding()] param( [string]$Workflow, [string]$Job, [switch]$List, [switch]$DryRun, [ValidateSet('pull_request', 'push')] [string]$Event = 'pull_request', [switch]$Rebuild, [switch]$ActVerbose ) $ErrorActionPreference = 'Stop' # Resolve paths $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $RepoRoot = (Resolve-Path "$ScriptDir\..\..").Path $CiLocalDir = $ScriptDir $CiImage = 'stellaops-ci:local' $Dockerfile = Join-Path $RepoRoot 'devops\docker\Dockerfile.ci' # ---- Prerequisite checks ---- # Docker if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { Write-Error "Docker is not installed or not in PATH." exit 1 } $ErrorActionPreference = 'Continue' $null = docker info 2>$null $ErrorActionPreference = 'Stop' if ($LASTEXITCODE -ne 0) { Write-Error "Docker is not running. Start Docker Desktop and try again." exit 1 } # act if (-not (Get-Command act -ErrorAction SilentlyContinue)) { Write-Error "act is not installed. Install with: choco install act-cli" exit 1 } # ---- Build CI image if needed ---- docker image inspect $CiImage 2>&1 | Out-Null $imageExists = $LASTEXITCODE -eq 0 if ($Rebuild -or -not $imageExists) { Write-Host "[ci-local] Building CI image $CiImage ..." -ForegroundColor Cyan docker build -t $CiImage -f $Dockerfile $RepoRoot if ($LASTEXITCODE -ne 0) { Write-Error "CI image build failed." exit 1 } } # ---- Ensure .env.local exists ---- $envLocal = Join-Path $CiLocalDir '.env.local' $envTemplate = Join-Path $CiLocalDir '.env.local.template' if (-not (Test-Path $envLocal)) { Write-Host "[ci-local] Creating .env.local from template ..." -ForegroundColor Yellow Copy-Item $envTemplate $envLocal } # ---- Assemble act arguments ---- $actArgs = @() # List mode if ($List) { $actArgs += '-l' Push-Location $RepoRoot try { act @actArgs } finally { Pop-Location } exit $LASTEXITCODE } # Workflow if ($Workflow) { $wfFile = $Workflow if (-not $wfFile.EndsWith('.yml')) { $wfFile = "$wfFile.yml" } $wfPath = Join-Path $RepoRoot ".gitea\workflows\$wfFile" if (-not (Test-Path $wfPath)) { Write-Error "Workflow not found: $wfPath" exit 1 } $actArgs += '-W', $wfPath } # Job filter if ($Job) { $actArgs += '-j', $Job } # Event file $eventMap = @{ 'pull_request' = Join-Path $CiLocalDir 'events\pull-request.json' 'push' = Join-Path $CiLocalDir 'events\push.json' } $eventFile = $eventMap[$Event] if (Test-Path $eventFile) { $actArgs += '--eventpath', $eventFile } # Dry run if ($DryRun) { $actArgs += '-n' } # Verbose if ($ActVerbose) { $actArgs += '--verbose' } # ---- Run act ---- Write-Host "[ci-local] act $($actArgs -join ' ')" -ForegroundColor Cyan Push-Location $RepoRoot try { act @actArgs exit $LASTEXITCODE } finally { Pop-Location }