lock line endings and expand Windows lint to .ps1 #10
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 smoke test | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| smoke: | |
| name: start.bat end-to-end on Windows | |
| runs-on: windows-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Install dependencies | |
| shell: pwsh | |
| run: npm ci | |
| - name: Lint Windows launchers (.bat / .ps1) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $targets = @( | |
| "start.bat", "stop.bat", | |
| "scripts/dev/start-local-agent.ps1", | |
| "scripts/dev/stop-local-agent.ps1", | |
| "scripts/windows/capture-screen.ps1" | |
| ) | |
| $failed = $false | |
| foreach ($target in $targets) { | |
| if (-not (Test-Path $target)) { | |
| Write-Host "skip missing: $target" | |
| continue | |
| } | |
| $bytes = [System.IO.File]::ReadAllBytes($target) | |
| $text = [System.Text.Encoding]::ASCII.GetString($bytes) | |
| # 1. Pure ASCII. cmd.exe on zh-CN runs code page 936 and will | |
| # misdecode UTF-8 multi-byte sequences in .bat files, | |
| # producing '文件名、目录名或卷标语法不正确'. Applying the | |
| # same rule to .ps1 keeps us consistent and protects | |
| # Windows PowerShell 5.1 hosts that can't read BOM-less | |
| # UTF-8 reliably. | |
| foreach ($b in $bytes) { | |
| if ($b -gt 0x7f) { | |
| Write-Error "$target contains non-ASCII byte 0x$("{0:X2}" -f $b) - use ASCII-only for Windows compatibility" | |
| $failed = $true | |
| break | |
| } | |
| } | |
| # 2. CRLF line endings. LF-only .bat files confuse some cmd.exe | |
| # versions, and Windows PowerShell 5.1 parses CRLF more | |
| # reliably than LF. | |
| if ($text -notmatch "`r`n") { | |
| Write-Error "$target has no CRLF line endings" | |
| $failed = $true | |
| } | |
| # 3. No Unix-style /dev/null redirection. cmd.exe would create | |
| # an actual file named 'null' under a 'dev' directory. | |
| if ($text -match "/dev/null") { | |
| Write-Error "$target contains Unix-style '/dev/null' - use 'NUL' instead" | |
| $failed = $true | |
| } | |
| } | |
| if ($failed) { exit 1 } | |
| Write-Host "All Windows launchers passed ASCII / CRLF / NUL lint." | |
| - name: Run start.bat end-to-end | |
| shell: cmd | |
| run: | | |
| start.bat | |
| - name: Stop agent started by start.bat | |
| shell: pwsh | |
| run: | | |
| $pidFile = "runtime/agent/agent.pid" | |
| if (Test-Path $pidFile) { | |
| $agentPid = (Get-Content $pidFile -Raw).Trim() | |
| try { | |
| Stop-Process -Id $agentPid -Force -ErrorAction Stop | |
| Write-Host "Stopped start.bat agent PID $agentPid" | |
| } catch { | |
| Write-Host "start.bat agent PID $agentPid already gone" | |
| } | |
| Remove-Item -Force -ErrorAction SilentlyContinue $pidFile, "runtime/agent/agent.port" | |
| } | |
| - name: Start agent via start-local-agent.ps1 (foreground) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Continue" | |
| Write-Host "Running start-local-agent.ps1 in foreground to surface errors..." | |
| & "$env:GITHUB_WORKSPACE\scripts\dev\start-local-agent.ps1" | |
| Write-Host "start-local-agent.ps1 exit code: $LASTEXITCODE" | |
| - name: Wait for agent to be ready | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $portFile = "runtime/agent/agent.port" | |
| $port = $null | |
| for ($i = 0; $i -lt 90; $i++) { | |
| if (Test-Path $portFile) { | |
| $port = (Get-Content $portFile -Raw).Trim() | |
| if ($port) { break } | |
| } | |
| Start-Sleep -Seconds 2 | |
| } | |
| if (-not $port) { | |
| Write-Error "agent.port file never appeared — start.bat failed to start the agent" | |
| exit 1 | |
| } | |
| Write-Host "Agent port: $port" | |
| "AGENT_PORT=$port" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| $url = "http://127.0.0.1:$port/api/config" | |
| for ($i = 0; $i -lt 60; $i++) { | |
| try { | |
| $resp = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 3 | |
| if ($resp.StatusCode -eq 200) { | |
| Write-Host "GET /api/config -> 200 OK" | |
| Write-Host $resp.Content | |
| exit 0 | |
| } | |
| } catch { | |
| Start-Sleep -Seconds 2 | |
| } | |
| } | |
| Write-Error "Agent never responded on /api/config within 2 minutes" | |
| exit 1 | |
| - name: Smoke test /desktop route | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $url = "http://127.0.0.1:$env:AGENT_PORT/desktop" | |
| $resp = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 10 | |
| if ($resp.StatusCode -ne 200) { | |
| Write-Error "/desktop returned $($resp.StatusCode)" | |
| exit 1 | |
| } | |
| Write-Host "/desktop -> 200 OK ($($resp.Content.Length) bytes)" | |
| - name: Smoke test login (POST /api/pair) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $tokenFile = "runtime/agent/app_data/pairing-token.txt" | |
| if (-not (Test-Path $tokenFile)) { | |
| Write-Error "pairing-token.txt missing — agent never initialized session store" | |
| exit 1 | |
| } | |
| $token = (Get-Content $tokenFile -Raw).Trim() | |
| Write-Host "Pairing token: $token" | |
| $url = "http://127.0.0.1:$env:AGENT_PORT/api/pair" | |
| # 1. Wrong token -> 401 | |
| try { | |
| $bad = Invoke-WebRequest -UseBasicParsing -Uri $url -Method POST ` | |
| -ContentType "application/json" ` | |
| -Body '{"token":"wrong-token"}' -TimeoutSec 5 | |
| Write-Error "Wrong token should have failed, got $($bad.StatusCode)" | |
| exit 1 | |
| } catch { | |
| if ($_.Exception.Response.StatusCode.value__ -ne 401) { | |
| Write-Error "Wrong token expected 401, got $($_.Exception.Response.StatusCode.value__)" | |
| exit 1 | |
| } | |
| Write-Host "Wrong token -> 401 (as expected)" | |
| } | |
| # 2. Correct token -> 200 { ok: true } | |
| $body = @{ token = $token } | ConvertTo-Json -Compress | |
| $ok = Invoke-WebRequest -UseBasicParsing -Uri $url -Method POST ` | |
| -ContentType "application/json" ` | |
| -Body $body -TimeoutSec 5 | |
| if ($ok.StatusCode -ne 200) { | |
| Write-Error "/api/pair returned $($ok.StatusCode)" | |
| exit 1 | |
| } | |
| Write-Host "/api/pair -> 200 OK: $($ok.Content)" | |
| - name: Run unit tests | |
| shell: pwsh | |
| run: npm test | |
| - name: Stop agent | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $pidFile = "runtime/agent/agent.pid" | |
| if (Test-Path $pidFile) { | |
| $agentPid = (Get-Content $pidFile -Raw).Trim() | |
| try { | |
| Stop-Process -Id $agentPid -Force -ErrorAction Stop | |
| Write-Host "Stopped agent PID $agentPid" | |
| } catch { | |
| Write-Host "Agent PID $agentPid already gone" | |
| } | |
| } | |
| - name: Upload agent log | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: agent-log | |
| path: | | |
| runtime/agent/agent.log | |
| runtime/agent/agent.port | |
| runtime/agent/agent.pid | |
| if-no-files-found: warn |