Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/content/docs/changelog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Notable changes to the kit, newest first.
- **Dependencies updated to latest for the v10 release** — .NET **Aspire 13.3.5** (Hosting packages + AppHost SDK), Finbuckle.MultiTenant 10.1.0, MailKit/MimeKit 4.17.0, AWSSDK.S3 4.0.23.4, Scalar.AspNetCore 2.14.14, and SonarAnalyzer 10.27. Builds clean with warnings-as-errors and the full test suite (unit + Testcontainers integration) stays green.
- **Template packaging fixes — scaffolded Dockerfiles and dev-machine packing** — `dotnet new fsh` / `fsh new` packed extensionless files (every `Dockerfile`) to a doubled nested path, so scaffolded projects got a `Dockerfile` *directory* instead of a file and `deploy/docker` (`docker compose up`) was broken. Also made the IDE-cache excludes (`.vs`/`.idea`/`.vscode`) recursive so `dotnet pack` no longer fails (or bundles IDE junk) when packing the template on a developer machine. Scaffolded output now builds and self-hosts cleanly.
- **Scaffolded apps log in out of the box, get isolated data volumes, and start on `main`** — three `fsh new` / Aspire DX fixes: the AppHost migrator now runs `apply --seed`, so the root admin (`admin@root.com`) is seeded automatically — previously a freshly-run app came up with an empty user table and **nobody could log in**; each app's Docker volumes are namespaced by app name (e.g. `myapp-postgres-data`) instead of sharing a literal `postgres-data`, so two FSH-based apps on one machine no longer clobber each other's database; and `fsh new` initializes git on `main` rather than following the machine's git default (often `master`).
- **Demo logins (`acme`/`globex`) work on a fresh Aspire launch** — the dashboard's demo-login panel advertised accounts that were never seeded: the AppHost migrator ran only `apply --seed` (which seeds the root admin), while the `acme`/`globex` demo tenants are created by the dev-only `seed-demo` verb. Aspire now runs `seed-demo` as a dedicated `fsh-demo-seeder` step after migration — so `admin@acme.com` / `Password123!` works the moment the dashboard loads. Also fixes the migrator crashing at startup in Development (its trimmed service graph tripped the DI container's build-time validation) and corrects the verb's environment gate to `DOTNET_ENVIRONMENT` (the migrator is a generic-host console app, not a web host).
- **Demo logins (`acme`/`globex`) work on a fresh Aspire launch** — the dashboard's demo-login panel advertised accounts that were never seeded: the AppHost migrator ran only `apply --seed` (which seeds the root admin), while the `acme`/`globex` demo tenants are created by the dev-only `seed-demo` verb. Aspire now runs `seed-demo` as a dedicated demo-seeder step after migration — so `admin@acme.com` / `Password123!` works the moment the dashboard loads. Also fixes the migrator crashing at startup in Development (its trimmed service graph tripped the DI container's build-time validation) and corrects the verb's environment gate to `DOTNET_ENVIRONMENT` (the migrator is a generic-host console app, not a web host).
- **Aspire resource names are namespaced per app** — the AppHost's resource/container names (API, migrator, demo-seeder, admin, dashboard) now derive from the app's namespace, like the Docker volume names already did. A scaffolded `Acme.Store` shows `acme-store-api` etc. instead of the kit's literal `fsh-*`, so two FSH-based apps on one machine don't collide. (This repo resolves to `fsh-starter-*`; the `postgres`/`redis`/`minio` infra and the `fsh-db` database keep stable names.)
- **Stale sessions resolve cleanly instead of erroring** — both React apps (admin + dashboard) treated an expired token left in `localStorage` as signed-in, firing protected requests that 401'd in a loop (`SecurityTokenExpiredException`). On boot they now attempt one silent token refresh: success restores the session, failure routes to `/login`. Long-lived sessions still refresh transparently mid-use.
- **CI split into path-scoped backend + frontend pipelines** — the single `ci.yml` is replaced by `backend.yml` (runs only on `src/**` changes) and `frontend.yml` (runs only on `clients/**`), so a client-only change never builds or tests the API, and vice versa. The SDK is pinned to the .NET 10 **GA** release via a root `global.json` (no more preview channel). Unit and integration tests each run **once**, and the coverage gate merges their results instead of re-running the whole solution. The React apps get real CI for the first time — ESLint, `tsc`/Vite build, and the Playwright E2E suites (admin + dashboard) on Node 22. Branch protection requires the always-resolving `Backend CI` / `Frontend CI` gate jobs. See [CI/CD](/docs/deployment/ci-cd/).
- **Consolidated to a single `main` branch** — the repo now uses one long-lived default branch, `main`; the `develop` branch is retired. Branch from and target `main`; stable releases are cut from `v*` tags. See [Contributing](/docs/contributing/).
Expand Down
25 changes: 17 additions & 8 deletions src/content/docs/deployment/aspire.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,26 @@ documents what it starts and **why** it's wired the way it is.
| RedisInsight | *(sidecar)* | Key browser auto-connected to the Valkey instance for inspecting cache keys in dev | Persistent |
| MinIO | `minio` | S3-compatible object storage, **:9000** (API) / **:9001** (console) | Persistent (data volume) |
| MinIO init | `minio-init` | One-shot: creates the `fsh-uploads` bucket + download policy, then exits | Run-once |
| DB migrator | `fsh-db-migrator` | One-shot: applies migrations across the tenant catalog + every tenant's module DBs (`apply --seed`, so the root admin exists), then exits | Run-once |
| Demo seeder | `fsh-demo-seeder` | One-shot, **dev-only**: runs `seed-demo` after the migrator to provision the `acme`/`globex` demo tenants + users, then exits | Run-once |
| API | `fsh-api` | The ASP.NET Core API (`net10.0`) | Long-running |
| Admin app | `fsh-admin` | Operator React + Vite SPA on **:5173** | Long-running |
| Dashboard app | `fsh-dashboard` | Tenant React + Vite SPA on **:5174** (with SSE live feed) | Long-running |
| DB migrator | `fsh-starter-db-migrator` | One-shot: applies migrations across the tenant catalog + every tenant's module DBs (`apply --seed`, so the root admin exists), then exits | Run-once |
| Demo seeder | `fsh-starter-demo-seeder` | One-shot, **dev-only**: runs `seed-demo` after the migrator to provision the `acme`/`globex` demo tenants + users, then exits | Run-once |
| API | `fsh-starter-api` | The ASP.NET Core API (`net10.0`) | Long-running |
| Admin app | `fsh-starter-admin` | Operator React + Vite SPA on **:5173** | Long-running |
| Dashboard app | `fsh-starter-dashboard` | Tenant React + Vite SPA on **:5174** (with SSE live feed) | Long-running |

<Callout type="note" title="Resource names are namespaced per app">
The `fsh-starter-*` resource names — and the Docker volume names — are derived
from the AppHost's assembly name. A CLI-scaffolded app (say `Acme.Store`) gets
`acme-store-api`, `acme-store-admin`, and so on, so two FSH-based apps on one
machine never collide on container or volume names. The third-party infra
(`postgres`, `redis`, `minio`) and the `fsh-db` database keep stable names.
</Callout>

The Aspire dashboard opens automatically and shows every resource's state,
logs, traces, and endpoints. The API's Scalar UI is at `/scalar`.

<Callout type="note" title="Startup is ordered, not racy">
The API waits for Postgres and Valkey to be healthy **and** for the one-shot
jobs (`minio-init`, `fsh-db-migrator`, `fsh-demo-seeder`) to finish before it starts. So the API
jobs (`minio-init`, `fsh-starter-db-migrator`, `fsh-starter-demo-seeder`) to finish before it starts. So the API
never boots against an unmigrated database or a missing bucket — no retry loops,
no first-request 500s.
</Callout>
Expand All @@ -69,8 +77,9 @@ browser PUTs from the admin (`:5173`) and dashboard (`:5174`) origins via

### The migrator is the production deploy step too

`fsh-db-migrator` is the same `FSH.Starter.DbMigrator` project you run as an
explicit step in production (`dotnet run --project ... -- apply`). The database
The `fsh-starter-db-migrator` resource runs the same `FSH.Starter.DbMigrator`
project you run as an explicit step in production (published as the
`fsh-db-migrator` image; `dotnet run --project ... -- apply`). The database
is **never** migrated at API startup — locally or in the cloud. One mechanism,
two contexts.

Expand Down
4 changes: 2 additions & 2 deletions src/content/docs/deployment/database-migrations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --catalog-only
## Local development

When you run the stack via Aspire, the migrator runs **for you**: the
`fsh-db-migrator` resource executes `apply --seed` on each AppHost launch (so the
root admin `admin@root.com` is seeded), then a dev-only `fsh-demo-seeder` resource
`fsh-starter-db-migrator` resource executes `apply --seed` on each AppHost launch (so the
root admin `admin@root.com` is seeded), then a dev-only `fsh-starter-demo-seeder` resource
runs `seed-demo` to provision the `acme`/`globex` demo tenants and their users. The
API is gated on both (`WaitForCompletion`), so it never starts against an
unmigrated database and the demo logins work the moment the dashboard loads. See
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/frontend/admin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ The dev server proxies API calls to the backend's URL configured in `clients/adm
VITE_API_BASE_URL=http://localhost:5000
```

The kit's Aspire AppHost wires this automatically when you run via `dotnet run --project src/Host/FSH.Starter.AppHost` — both `fsh-api` and `fsh-admin` come up, the admin app discovers the API's URL via Aspire's service discovery.
The kit's Aspire AppHost wires this automatically when you run via `dotnet run --project src/Host/FSH.Starter.AppHost` — both `fsh-starter-api` and `fsh-starter-admin` come up, the admin app discovers the API's URL via Aspire's service discovery.

## Build + deploy

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/frontend/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ npm run dev # http://localhost:5174 with HMR

The dev server proxies API calls to the backend's URL configured in `clients/dashboard/.env`.

When run via the kit's Aspire AppHost, the dashboard wires automatically — `fsh-api`, `fsh-admin`, `fsh-dashboard` all come up with one command, and service discovery handles the URLs.
When run via the kit's Aspire AppHost, the dashboard wires automatically — `fsh-starter-api`, `fsh-starter-admin`, `fsh-starter-dashboard` all come up with one command, and service discovery handles the URLs.

## Related

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/getting-started/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ app.UseHeroMultiTenantDatabases();
app.UseHeroPlatform(p => { p.MapModules = true; /* ... */ });
```

The API never mutates data on startup. Demo data (acme/globex tenants, sample catalog/tickets/chat) is provisioned by `FSH.Starter.DbMigrator seed-demo`, which Aspire runs automatically on launch (the `fsh-demo-seeder` resource) — so the demo logins work out of the box.
The API never mutates data on startup. Demo data (acme/globex tenants, sample catalog/tickets/chat) is provisioned by `FSH.Starter.DbMigrator seed-demo`, which Aspire runs automatically on launch (the `fsh-starter-demo-seeder` resource) — so the demo logins work out of the box.

## Stack

Expand Down