feat(windows): add install.ps1 / run.ps1 + Windows CI smoke #23
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: | |
| branches: [main] | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| backend: | |
| name: Backend tests (pytest) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: | | |
| backend/requirements.txt | |
| backend/requirements-dev.txt | |
| - name: Install backend dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements-dev.txt | |
| - name: Run pytest | |
| working-directory: backend | |
| run: pytest -q | |
| frontend: | |
| name: Frontend JS syntax check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Syntax-check frontend JS | |
| run: | | |
| set -e | |
| for f in frontend/*.js; do | |
| echo "node --check $f" | |
| node --check "$f" | |
| done | |
| - name: Validate JSON content | |
| run: | | |
| set -e | |
| python3 -c " | |
| import json, pathlib, sys | |
| errors = 0 | |
| for p in list(pathlib.Path('frontend').rglob('*.json')) + list(pathlib.Path('curriculum').rglob('*.json')): | |
| try: | |
| json.loads(p.read_text()) | |
| print(f'ok {p}') | |
| except Exception as e: | |
| print(f'ERR {p}: {e}', file=sys.stderr) | |
| errors += 1 | |
| sys.exit(1 if errors else 0) | |
| " | |
| - name: Check hero landing site | |
| run: | | |
| if [ -f scripts/check_site.sh ]; then | |
| chmod +x scripts/check_site.sh | |
| ./scripts/check_site.sh | |
| else | |
| echo "scripts/check_site.sh missing; skipping" | |
| fi | |
| scripts: | |
| name: Shell script lint + smoke | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: shellcheck install/run scripts | |
| run: | | |
| if [ -f install.sh ] || [ -f run.sh ]; then | |
| sudo apt-get update -y | |
| sudo apt-get install -y shellcheck | |
| for f in install.sh run.sh; do | |
| [ -f "$f" ] && shellcheck -x "$f" | |
| done | |
| else | |
| echo "no install.sh / run.sh yet; skipping" | |
| fi | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Smoke-test install.sh (skip model pull, skip Ollama) | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| TUTOR_SKIP_MODEL_PULL: "1" | |
| TUTOR_NONINTERACTIVE: "1" | |
| run: | | |
| if [ -f install.sh ]; then | |
| chmod +x install.sh run.sh scripts/smoke_run.sh | |
| ./install.sh | |
| test -d backend/.venv | |
| backend/.venv/bin/python -c "import fastapi, uvicorn, httpx, pydantic" | |
| else | |
| echo "install.sh missing; skipping smoke test" | |
| fi | |
| - name: Smoke-test run.sh (no Ollama; check /api/health and /) | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| TUTOR_PORT: "8801" | |
| run: | | |
| if [ -f scripts/smoke_run.sh ]; then | |
| chmod +x scripts/smoke_run.sh | |
| ./scripts/smoke_run.sh | |
| else | |
| echo "scripts/smoke_run.sh missing; skipping run.sh smoke" | |
| fi | |
| - name: Smoke-test install.sh y/N prompts (noninteractive) | |
| run: | | |
| if [ -f scripts/smoke_prompts.sh ]; then | |
| chmod +x scripts/smoke_prompts.sh | |
| ./scripts/smoke_prompts.sh | |
| else | |
| echo "scripts/smoke_prompts.sh missing; skipping prompt smoke" | |
| fi | |
| - name: Smoke-test CLI flags (--help, --no-launch, port-in-use) | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| run: | | |
| if [ -f scripts/smoke_flags.sh ]; then | |
| chmod +x scripts/smoke_flags.sh | |
| ./scripts/smoke_flags.sh | |
| else | |
| echo "scripts/smoke_flags.sh missing; skipping flags smoke" | |
| fi | |
| windows: | |
| name: Windows PowerShell install + run smoke | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: PowerShell syntax check (install.ps1 / run.ps1) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| foreach ($f in @('install.ps1','run.ps1')) { | |
| if (-not (Test-Path $f)) { throw "missing $f" } | |
| $tokens = $null; $parseErrors = $null | |
| [System.Management.Automation.Language.Parser]::ParseFile( | |
| (Resolve-Path $f), [ref]$tokens, [ref]$parseErrors) | Out-Null | |
| if ($parseErrors -and $parseErrors.Count -gt 0) { | |
| $parseErrors | ForEach-Object { Write-Host $_ } | |
| throw "parse errors in $f" | |
| } | |
| Write-Host "ok $f" | |
| } | |
| - name: Help text (-Help) for both scripts | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| .\install.ps1 -Help | Select-Object -First 5 | |
| .\run.ps1 -Help | Select-Object -First 5 | |
| - name: Smoke-test install.ps1 (noninteractive, skip Ollama, skip pull) | |
| shell: pwsh | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| TUTOR_SKIP_MODEL_PULL: "1" | |
| TUTOR_NONINTERACTIVE: "1" | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| .\install.ps1 -NoLaunch | |
| if (-not (Test-Path 'backend\.venv\Scripts\python.exe')) { | |
| throw 'venv was not created' | |
| } | |
| & 'backend\.venv\Scripts\python.exe' -c "import fastapi, uvicorn, httpx, pydantic; print('imports ok')" | |
| - name: Smoke-test run.ps1 -NoLaunch (preflight only, skip Ollama) | |
| shell: pwsh | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| TUTOR_PORT: "8801" | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| .\run.ps1 -NoLaunch -SkipOllama -Port 8801 | |
| - name: Smoke-test run.ps1 actually serves /api/health (no Ollama) | |
| shell: pwsh | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $job = Start-Job -ScriptBlock { | |
| param($root) | |
| Set-Location $root | |
| $env:TUTOR_SKIP_OLLAMA = '1' | |
| & .\run.ps1 -SkipOllama -Port 8802 | |
| } -ArgumentList (Get-Location).Path | |
| try { | |
| $ok = $false | |
| for ($i = 0; $i -lt 60; $i++) { | |
| Start-Sleep -Seconds 1 | |
| try { | |
| $r = Invoke-WebRequest -Uri 'http://127.0.0.1:8802/api/health' ` | |
| -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop | |
| if ($r.StatusCode -eq 200) { $ok = $true; break } | |
| } catch { } | |
| } | |
| if (-not $ok) { | |
| Write-Host '--- background job output ---' | |
| Receive-Job $job | |
| throw '/api/health did not return 200 within 60s' | |
| } | |
| Write-Host 'ok /api/health -> 200' | |
| } finally { | |
| Stop-Job $job -ErrorAction SilentlyContinue | |
| Remove-Job $job -Force -ErrorAction SilentlyContinue | |
| } | |
| - name: Reject unknown parameter | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Continue' | |
| $err = $null | |
| try { | |
| & .\install.ps1 -DoesNotExist 2>&1 | Out-Null | |
| } catch { | |
| $err = $_ | |
| } | |
| # PowerShell raises a ParameterBindingException before the script | |
| # body runs; either $err is set or the call wrote a non-terminating | |
| # error to $Error[0]. | |
| if (-not $err -and -not $Error[0]) { | |
| throw 'unknown parameter should have been rejected' | |
| } | |
| Write-Host 'ok unknown parameter rejected' |