fix(install): validate_state must always return 0 (set -e silent-exit) #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: windows-validation | |
| # Windows 측 PowerShell 진입점·헬퍼 스크립트의 문법·호환성·동작 검증. | |
| # 모든 매 push / PR 마다 windows-latest runner 에서 자동 실행. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| ps-validation: | |
| name: PowerShell (windows-latest) | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| # actions/checkout@v4 | |
| uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 | |
| # ── 1. Parser (PowerShell 7) ────────────────────────────────────────── | |
| - name: Parser check (pwsh 7) | |
| shell: pwsh | |
| run: | | |
| $files = Get-ChildItem -Recurse -Filter *.ps1 -Path openclaw-mgr | |
| $bad = 0 | |
| foreach ($f in $files) { | |
| $errs = $null | |
| $null = [System.Management.Automation.Language.Parser]::ParseFile($f.FullName, [ref]$null, [ref]$errs) | |
| if ($errs -and $errs.Count -gt 0) { | |
| $bad++ | |
| foreach ($e in $errs) { | |
| Write-Host ("::error file={0},line={1}::{2}" -f $f.FullName, $e.Extent.StartLineNumber, $e.Message) | |
| } | |
| } else { | |
| Write-Host "OK $($f.FullName)" | |
| } | |
| } | |
| if ($bad -gt 0) { exit 1 } | |
| # ── 2. PSScriptAnalyzer (Error / Warning) ──────────────────────────── | |
| - name: PSScriptAnalyzer | |
| shell: pwsh | |
| run: | | |
| $ProgressPreference = 'SilentlyContinue' | |
| Install-Module PSScriptAnalyzer -Force -Scope CurrentUser -SkipPublisherCheck | |
| Import-Module PSScriptAnalyzer | |
| $r = Invoke-ScriptAnalyzer -Path openclaw-mgr -Recurse -Severity Error,Warning -ExcludeRule @( | |
| 'PSAvoidUsingWriteHost', | |
| 'PSUseShouldProcessForStateChangingFunctions', | |
| 'PSAvoidGlobalAliases', | |
| 'PSUseSingularNouns' | |
| ) | |
| if ($null -eq $r -or @($r).Count -eq 0) { | |
| Write-Host 'PSScriptAnalyzer: 0 issues' | |
| exit 0 | |
| } | |
| $r | Format-Table -AutoSize | |
| foreach ($i in $r) { | |
| $sev = if ($i.Severity -eq 'Error') { 'error' } else { 'warning' } | |
| Write-Host ("::{0} file={1},line={2}::[{3}] {4}" -f $sev, $i.ScriptPath, $i.Line, $i.RuleName, $i.Message) | |
| } | |
| $errs = @($r | Where-Object Severity -EQ 'Error').Count | |
| if ($errs -gt 0) { exit 1 } | |
| # ── 3. PS 5.1 / 7.0 / 7.4 호환 구문 ────────────────────────────────── | |
| - name: Compatibility (PSUseCompatibleSyntax) | |
| shell: pwsh | |
| run: | | |
| $ProgressPreference = 'SilentlyContinue' | |
| $settings = @{ | |
| IncludeRules = @('PSUseCompatibleSyntax') | |
| Rules = @{ | |
| PSUseCompatibleSyntax = @{ | |
| Enable = $true | |
| TargetVersions = @('5.1', '7.0', '7.4') | |
| } | |
| } | |
| } | |
| $r = Invoke-ScriptAnalyzer -Path openclaw-mgr -Recurse -Settings $settings | |
| if ($null -eq $r -or @($r).Count -eq 0) { | |
| Write-Host 'Compatibility OK (5.1 / 7.0 / 7.4)' | |
| exit 0 | |
| } | |
| $r | Format-Table -AutoSize | |
| foreach ($i in $r) { | |
| Write-Host ("::error file={0},line={1}::[{2}] {3}" -f $i.ScriptPath, $i.Line, $i.RuleName, $i.Message) | |
| } | |
| exit 1 | |
| # ── 4. Smoke: help / version (OS 무관 동작) ────────────────────────── | |
| # 핵심: GHA 의 'shell: pwsh' wrapper 는 step 종료 시 trailing $LASTEXITCODE | |
| # 를 그대로 step exit code 로 사용한다. child .ps1 호출 후 $LASTEXITCODE 가 | |
| # non-zero 로 남아있으면 if 블록이 false 여도 step 이 fail 로 보고된다. | |
| # → 모든 Smoke step 마지막에 명시적 'exit 0' 으로 깨끗하게 종료한다. | |
| - name: Smoke — help / version (pwsh 7) | |
| shell: pwsh | |
| run: | | |
| & pwsh -NoProfile -File .\openclaw-mgr\openclaw.ps1 help | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured help exit=$rc" | |
| if ($rc -ne 0) { Write-Host "::error::help exit=$rc"; exit 1 } | |
| & pwsh -NoProfile -File .\openclaw-mgr\openclaw.ps1 version | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured version exit=$rc" | |
| if ($rc -ne 0) { Write-Host "::error::version exit=$rc"; exit 1 } | |
| exit 0 | |
| # ── 5. Smoke: 알 수 없는 명령은 exit 2 ──────────────────────────────── | |
| - name: Smoke — unknown command (exit 2) | |
| shell: pwsh | |
| run: | | |
| & pwsh -NoProfile -File .\openclaw-mgr\openclaw.ps1 unknownXYZ | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured unknown exit=$rc" | |
| if ($rc -ne 2) { Write-Host "::error::expected exit 2, got $rc"; exit 1 } | |
| exit 0 | |
| # ── 6. Smoke: install-bootstrap -DryRun (실제 winget 호출 없음) ─────── | |
| - name: Smoke — install-bootstrap -DryRun | |
| shell: pwsh | |
| run: | | |
| & pwsh -NoProfile -File .\openclaw-mgr\cmd-win\install-bootstrap.ps1 -DryRun | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured bootstrap exit=$rc" | |
| # bare runner 에는 winget/git/docker 가 일부 없을 수 있음 → exit code 0 이어도 정상. | |
| if ($rc -ne 0 -and $null -ne $rc) { | |
| Write-Host "::error::install-bootstrap exit=$rc" | |
| exit 1 | |
| } | |
| exit 0 | |
| # ── 7. Smoke: doctor (Windows 측 진단) ─────────────────────────────── | |
| - name: Smoke — doctor.ps1 | |
| shell: pwsh | |
| run: | | |
| & pwsh -NoProfile -File .\openclaw-mgr\cmd-win\doctor.ps1 | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured doctor exit=$rc" | |
| # docker/WSL 미설치라 ✗ 행이 나오는 건 정상. 실행 자체가 끝나면 OK. | |
| if ($rc -ne 0 -and $null -ne $rc) { | |
| Write-Host "::error::doctor crashed exit=$rc" | |
| exit 1 | |
| } | |
| exit 0 | |
| # ── 8. Smoke: schedule status (미등록 → warn 메시지, 정상 종료) ────── | |
| - name: Smoke — schedule status | |
| shell: pwsh | |
| run: | | |
| & pwsh -NoProfile -File .\openclaw-mgr\cmd-win\schedule.ps1 status | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured schedule exit=$rc" | |
| exit 0 | |
| # ── 9. PS 5.1 (Windows PowerShell) 호환 — 진입점 호출 ──────────────── | |
| - name: Smoke — Windows PowerShell 5.1 | |
| shell: powershell | |
| run: | | |
| # 5.1 진입점이 도움말/버전을 정상 출력하는지 — child process 로 격리 | |
| & powershell -NoProfile -File .\openclaw-mgr\openclaw.ps1 help | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured PS5.1 help exit=$rc" | |
| if ($rc -ne 0) { Write-Host "::error::PS 5.1 help exit=$rc"; exit 1 } | |
| & powershell -NoProfile -File .\openclaw-mgr\openclaw.ps1 version | |
| $rc = $LASTEXITCODE | |
| Write-Host "captured PS5.1 version exit=$rc" | |
| if ($rc -ne 0) { Write-Host "::error::PS 5.1 version exit=$rc"; exit 1 } | |
| # 5.1 진입점에서 모든 .ps1 을 파싱 | |
| $files = Get-ChildItem -Recurse -Filter *.ps1 -Path openclaw-mgr | |
| $bad = 0 | |
| foreach ($f in $files) { | |
| $errs = $null | |
| $null = [System.Management.Automation.Language.Parser]::ParseFile($f.FullName, [ref]$null, [ref]$errs) | |
| if ($errs -and $errs.Count -gt 0) { | |
| $bad++ | |
| foreach ($e in $errs) { | |
| Write-Host ("::error file={0},line={1}::PS 5.1: {2}" -f $f.FullName, $e.Extent.StartLineNumber, $e.Message) | |
| } | |
| } | |
| } | |
| if ($bad -gt 0) { exit 1 } | |
| exit 0 |