Redesign Foglight as an intelligence console #36
Workflow file for this run
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| verify-windows-release: | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.13" | |
| cache: pip | |
| cache-dependency-path: | | |
| requirements-build.txt | |
| requirements-dev.txt | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "24" | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install -r requirements-build.txt -r requirements-dev.txt | |
| npm ci | |
| npx playwright install chromium | |
| - name: Static checks | |
| run: | | |
| python -m ruff check . | |
| node --check .\web\app.js | |
| - name: Unit tests | |
| run: | | |
| python -m pytest -q | |
| npm run test:js | |
| - name: Deterministic browser tests | |
| run: npm run test:browser | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: failure() | |
| with: | |
| name: browser-test-results | |
| path: test-results/ | |
| if-no-files-found: ignore | |
| - name: Dependency audit | |
| run: | | |
| python -m pip_audit -r requirements-build.txt | |
| npm audit --audit-level=high | |
| - name: Secret scan | |
| run: python .\scripts\scan_secrets.py --json | |
| - name: Build release executable | |
| run: python .\build_windows.py | |
| - name: Packaged smoke test | |
| shell: pwsh | |
| run: | | |
| $env:FOGLIGHT_NO_BROWSER = "1" | |
| $env:FOGLIGHT_PORT = "19877" | |
| $worldPath = Join-Path $env:RUNNER_TEMP "foglight-world-$PID.geojson" | |
| $p = Start-Process -FilePath ".\dist\Foglight.exe" -WindowStyle Hidden -PassThru | |
| try { | |
| $ready = $false | |
| for ($i = 0; $i -lt 100; $i++) { | |
| try { | |
| $ping = Invoke-WebRequest -UseBasicParsing -TimeoutSec 1 -Uri "http://127.0.0.1:19877/api/ping" | |
| $ready = $true | |
| break | |
| } catch { | |
| Start-Sleep -Milliseconds 200 | |
| } | |
| } | |
| if (-not $ready) { throw "Foglight.exe did not become ready" } | |
| $root = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:19877/" | |
| $settings = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:19877/api/settings" | |
| $leaflet = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:19877/vendor/leaflet/leaflet.js" | |
| & curl.exe --fail --silent --show-error ` | |
| --output $worldPath ` | |
| "http://127.0.0.1:19877/assets/natural-earth-110m-countries.v5.1.1.geojson" | |
| if ($LASTEXITCODE -ne 0) { throw "Packaged world asset download failed" } | |
| if ($ping.StatusCode -ne 200 -or $root.StatusCode -ne 200 -or $settings.StatusCode -ne 200 ` | |
| -or $leaflet.StatusCode -ne 200) { | |
| throw "Packaged smoke test returned a non-200 response" | |
| } | |
| $csp = [string]$root.Headers["Content-Security-Policy"] | |
| if (-not $csp) { | |
| throw "Packaged response is missing Content-Security-Policy" | |
| } | |
| if ($csp -match "unpkg|cartocdn") { throw "Packaged CSP retains a removed map CDN" } | |
| $worldHash = (Get-FileHash -Algorithm SHA256 $worldPath).Hash.ToLowerInvariant() | |
| if ($worldHash -ne "b853e8ab6412d655dbe2fe8719d7cfde24e266db347eeb694b4df0f627a2fdb8") { | |
| $worldBytes = (Get-Item -LiteralPath $worldPath).Length | |
| throw "Packaged world asset failed SHA-256: $worldHash ($worldBytes bytes)" | |
| } | |
| $session = Invoke-RestMethod -Uri "http://127.0.0.1:19877/api/session" | |
| try { | |
| Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:19877/api/settings" ` | |
| -Method POST -ContentType "application/json" -Body "{}" | |
| throw "Unauthenticated settings update unexpectedly succeeded" | |
| } catch { | |
| if ([int]$_.Exception.Response.StatusCode -ne 403) { throw } | |
| } | |
| $authorized = Invoke-WebRequest -UseBasicParsing ` | |
| -Uri "http://127.0.0.1:19877/api/settings" -Method POST ` | |
| -ContentType "application/json" ` | |
| -Headers @{"X-Foglight-Token" = $session.token} -Body "{}" | |
| if ($authorized.StatusCode -ne 200) { throw "Authorized settings update failed" } | |
| try { | |
| Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:19877/api/ping" ` | |
| -Headers @{Host = "invalid.example"} | |
| throw "Invalid Host header unexpectedly succeeded" | |
| } catch { | |
| if ([int]$_.Exception.Response.StatusCode -ne 421) { throw } | |
| } | |
| $actualHash = (Get-FileHash -Algorithm SHA256 ".\dist\Foglight.exe").Hash.ToLowerInvariant() | |
| $manifestHash = ((Get-Content ".\dist\SHA256SUMS.txt") -split "\s+")[0].ToLowerInvariant() | |
| if ($actualHash -ne $manifestHash) { throw "SHA256SUMS.txt does not match Foglight.exe" } | |
| } finally { | |
| if (Test-Path -LiteralPath $worldPath) { Remove-Item -LiteralPath $worldPath -Force } | |
| $conn = Get-NetTCPConnection -LocalPort 19877 -State Listen -ErrorAction SilentlyContinue | |
| if ($conn) { Stop-Process -Id $conn.OwningProcess -Force } | |
| if ($p -and -not $p.HasExited) { Stop-Process -Id $p.Id -Force } | |
| } | |
| - name: Packaged release profiles | |
| run: | | |
| python .\scripts\smoke_packaged_release.py --exe .\dist\Foglight.exe --port 19880 | |
| python .\scripts\smoke_packaged_offline.py --exe .\dist\Foglight.exe --port 19879 | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: foglight-windows-unsigned-qa | |
| path: | | |
| dist/Foglight.exe | |
| dist/SHA256SUMS.txt | |
| if-no-files-found: error |