Merge pull request #17 from StewAlexander-com/Windows-install #28
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: Verify FastAPI app imports cleanly (no Ollama) | |
| shell: pwsh | |
| env: | |
| TUTOR_SKIP_OLLAMA: "1" | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| # Lightweight check: the same app the server would serve must | |
| # import without errors from inside the venv. Avoids actually | |
| # binding a port on the Windows runner (HTTP localhost probes | |
| # under nested pwsh + Start-Process have proven flaky in CI). | |
| $py = Join-Path (Get-Location) 'backend\.venv\Scripts\python.exe' | |
| if (-not (Test-Path $py)) { throw "missing venv python: $py" } | |
| Push-Location backend | |
| try { | |
| & $py -c "import app.main; assert hasattr(app.main, 'app'), 'app.main.app missing'; print('ok app.main imports')" | |
| if ($LASTEXITCODE -ne 0) { throw "app.main import failed (exit $LASTEXITCODE)" } | |
| } finally { | |
| Pop-Location | |
| } | |
| - 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' |