- .NET SDK (see
global.jsonor.csprojfiles for version) - Node.js 22+
- Docker Desktop
- Aspire CLI: install via
curl -sSL https://aspire.dev/install.sh | bash(macOS/Linux) orirm https://aspire.dev/install.ps1 | iex(Windows)
git clone <repo-url>
cd AspireAcademy
# Set the OpenAI connection string (required for AI Tutor; optional for everything else)
aspire secret set ConnectionStrings:openai "Key=sk-your-openai-api-key"
# Start the full stack
aspire runThis starts PostgreSQL, Redis, the .NET API, and the React frontend. The Aspire Dashboard opens automatically.
cd AspireAcademy.Web
npm installAspireAcademy/
├── apphost.cs # Aspire AppHost — orchestrates all services
├── aspire.config.json # Aspire configuration & profiles
├── AspireAcademy.Api/ # .NET API (C# Minimal APIs)
│ ├── Program.cs # Entry point, middleware, DI
│ ├── Endpoints/ # Endpoint groups (Auth, Curriculum, Quiz, etc.)
│ ├── Models/ # EF Core entity models
│ ├── Data/AcademyDbContext.cs # Database context
│ ├── Services/ # Business logic (Gamification, AI Tutor, etc.)
│ ├── Curriculum/ # Content files (loaded at startup)
│ │ ├── worlds.yaml # Curriculum structure (source of truth)
│ │ ├── achievements.yaml # Achievement definitions
│ │ ├── content/ # Lesson prose (Markdown)
│ │ ├── quizzes/ # Quiz questions (YAML)
│ │ └── challenges/ # Code challenges (YAML)
│ └── Migrations/ # EF Core migrations
├── AspireAcademy.Web/ # React frontend (Vite + Chakra UI)
│ └── src/
│ ├── pages/ # Route pages
│ ├── components/ # Shared components
│ ├── store/ # Zustand state stores
│ └── services/ # API client layer (apiClient.ts)
├── AspireAcademy.Api.Tests/ # Backend tests (xUnit)
│ ├── *EndpointsTests.cs # API endpoint integration tests
│ ├── *ServiceTests.cs # Service unit tests
│ ├── E2E/ # Playwright E2E tests (C# harness)
│ └── Fixtures/ # Test data & shared fixtures
├── AspireAcademy.ServiceDefaults/ # OpenTelemetry, health checks, resilience
└── AspireAcademy.TestAppHost/ # Minimal AppHost for test isolation
# Run all backend tests
dotnet test AspireAcademy.Api.Tests/
# Run a specific test class
dotnet test AspireAcademy.Api.Tests/ --filter "FullyQualifiedName~AuthEndpointsTests"
# Run with verbose output
dotnet test AspireAcademy.Api.Tests/ -v normalBackend tests use AcademyApiFactory (a WebApplicationFactory<Program>) with an in-memory SQLite database and fake Redis. No Docker required.
cd AspireAcademy.Web
# Run all tests
npm test
# Run in watch mode
npm test -- --watch
# Run a specific file
npx vitest run src/store/__tests__/authStore.test.tscd AspireAcademy.Web
npm run type-checkE2E tests require the full app to be running:
# Terminal 1: Start the app
aspire run
# Terminal 2: Run E2E tests
cd AspireAcademy.Web
npm run test:e2ecd AspireAcademy.Web
npm run lint- Edit endpoint files in
AspireAcademy.Api/Endpoints/ - If you change the database model, add a migration:
cd AspireAcademy.Api dotnet ef migrations add YourMigrationName - Run the relevant tests:
dotnet test AspireAcademy.Api.Tests/ --filter "FullyQualifiedName~YourEndpointTests"
- The API hot-reloads during
aspire run— changes are picked up automatically.
- Edit components in
AspireAcademy.Web/src/ - Vite provides instant HMR — changes appear immediately in the browser
- Run type checking:
npm run type-check - Run tests:
npm test
Curriculum is loaded from files at app startup. No migrations needed.
- Lessons: Add/edit Markdown files in
Curriculum/content/world-N/ - Quizzes: Add/edit YAML files in
Curriculum/quizzes/ - Challenges: Add/edit YAML files in
Curriculum/challenges/ - Structure: Edit
Curriculum/worlds.yamlto add worlds, modules, or lessons - Achievements: Edit
Curriculum/achievements.yaml
After editing, click "Reload Curriculum" in the Aspire Dashboard (on the API resource), or restart the app.
-
Create
AspireAcademy.Api/Endpoints/YourEndpoints.cs:namespace AspireAcademy.Api.Endpoints; public static class YourEndpoints { public static void MapYourEndpoints(this WebApplication app) { var group = app.MapGroup("/api/your-feature") .RequireAuthorization(); group.MapGet("/", async (AcademyDbContext db) => { // ... }); } }
-
Register it in
Program.cs:app.MapYourEndpoints();
-
Add test file
AspireAcademy.Api.Tests/YourEndpointsTests.cs.
- Aspire orchestration: All services (API, frontend, PostgreSQL, Redis) are defined in
apphost.csand started together - Minimal APIs: No controllers — all endpoints use
MapGroup+MapGet/MapPost - SQLite for tests:
AcademyApiFactoryswaps PostgreSQL for in-memory SQLite so tests run without Docker - JWT authentication: Stateless auth tokens, no session state
- Curriculum as files: Markdown + YAML on disk, loaded into the database at startup. Easy to edit, diff, and review
- Azure deployment:
aspire deployprovisions everything via Bicep. See DEPLOYMENT.md
- C#: Default .NET conventions,
nullableenabled,ImplicitUsingsenabled - TypeScript: ESLint config in
eslint.config.js, strict mode enabled - No unnecessary abstractions: Keep endpoints and services simple and direct
- Tests are required for new API endpoints
Releases are cut manually when you're ready to ship. The scripts/release.sh script handles the full flow:
Before releasing, add an entry to AspireAcademy.Web/src/data/changelog.ts at the top of the array:
{
version: '1.4.0',
date: '2026-04-01',
title: '🎯 Your Release Title',
highlights: ['Feature A', 'Feature B'],
entries: [
{ type: 'feature', text: 'Description of new feature' },
{ type: 'improvement', text: 'Description of improvement' },
{ type: 'fix', text: 'Description of bug fix' },
]
},This powers the What's New page in the app. The release script and CI will fail if the changelog entry is missing.
git add -A && git commit -m "Prepare release 1.4.0"
./scripts/release.sh 1.4.0The script will:
- Verify the changelog has an entry for this version
- Check for uncommitted changes
- Run backend tests and frontend checks
- Create a git tag
v1.4.0and push it
Pushing the tag triggers the Release GitHub Actions workflow, which runs full CI and creates a GitHub Release with auto-extracted release notes.
When the release workflow passes all tests, it automatically deploys to Azure:
- Installs the Aspire CLI and logs in to Azure via OIDC
- Runs
aspire deploytargeting the production resource group - Runs a smoke test against
/health - Updates the GitHub Release with the deployed URL
You can also trigger a deploy-only run (skip tests) from the Actions tab → Release & Deploy → Run workflow → check "Skip tests".
For manual deployments or first-time setup, see DEPLOYMENT.md.
| Workflow | Trigger | What it does |
|---|---|---|
CI (.github/workflows/ci.yml) |
Push to main, PRs |
Unit tests, integration tests, frontend type-check/lint/build (3 parallel jobs) |
Release & Deploy (.github/workflows/release.yml) |
v* tags or manual dispatch |
Validates changelog → runs full CI → creates GitHub Release → deploys to Azure |
The deploy job uses OIDC federated credentials (no stored passwords). You need to configure:
- GitHub Environment: Create a
productionenvironment in repo Settings → Environments - Azure App Registration: Create an app with federated credential for
repo:adamint/AspireAcademy:environment:production - Repository Secrets:
AZURE_CLIENT_ID— App registration client IDAZURE_TENANT_ID— Entra ID tenant IDAZURE_SUBSCRIPTION_ID— Target subscription
- Repository Variables:
AZURE_RESOURCE_GROUP— Target resource group nameAZURE_LOCATION— Azure region (default:eastus)
See DEPLOYMENT.md for detailed Azure setup.
| Command | Description |
|---|---|
aspire run |
Start the full stack locally |
aspire run --watch |
Start with file watching (auto-restart on changes) |
dotnet test AspireAcademy.Api.Tests/ |
Run all backend tests |
cd AspireAcademy.Web && npm test |
Run frontend unit tests |
cd AspireAcademy.Web && npm run test:e2e |
Run Playwright E2E tests |
cd AspireAcademy.Web && npm run type-check |
TypeScript type checking |
cd AspireAcademy.Web && npm run lint |
ESLint |
./scripts/release.sh <version> |
Tag & push a release (runs checks first) |
aspire publish -o ./aspire-output |
Generate deployment artifacts (Bicep) |
aspire deploy |
Deploy to Azure |