ci(windows): pwsh -File 명시적 child process 호출로 exit code 격리 #3
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 의 같은 PowerShell process 안에서 .\script.ps1 호출 시 | |
| # 자식의 exit 코드가 부모 $LASTEXITCODE 로 깨끗이 전파되지 않는 케이스가 있어 | |
| # 모든 진입점 검증은 'pwsh -File ...' 명시적 child process 로 격리해 호출. | |
| - 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 } | |
| # ── 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 } | |
| # ── 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 | |
| } | |
| # ── 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 | |
| } | |
| # ── 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" | |
| # ── 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 } |