Skip to content

Merge pull request #17 from StewAlexander-com/Windows-install #28

Merge pull request #17 from StewAlexander-com/Windows-install

Merge pull request #17 from StewAlexander-com/Windows-install #28

Workflow file for this run

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'