Skip to content

Redesign Foglight as an intelligence console #36

Redesign Foglight as an intelligence console

Redesign Foglight as an intelligence console #36

Workflow file for this run

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