CLI Commands Reference¶
A task-oriented reference of every command line used across the Cargonerds repository: the .NET SDK
(dotnet build/run/test and dotnet ef), the ABP CLI,
azd, MkDocs, the ResourceTranslator.CLI localization tool, the npm scripts under frontend/,
the PowerShell deployment scripts in .scripts/, and the local Docker helpers under etc/.
Where to run things
Most .NET commands run from the repo root (C:/dev/Cargonerds/github/CargonerdsApp). The
exceptions are deliberately called out per section: the npm scripts run from frontend/ (or a
workspace under it), the PowerShell deploy scripts run from .scripts/, and all MkDocs
commands run from docs/ (that is where mkdocs.yml lives). The solution targets .NET 10
(net10.0); CI installs SDK 10.x.x via actions/setup-dotnet.
Command cheat sheet¶
| Task | Command |
|---|---|
| Run the whole stack (Aspire) | dotnet run --project src/Cargonerds.AppHost |
| Build everything | dotnet build Cargonerds.All.sln |
| Run all tests (CI form) | dotnet test "Cargonerds.All.sln" --no-build --nologo -- --ignore-exit-code 8 |
| Apply DB migrations + seed | dotnet run --project src/Cargonerds.DbMigrator |
| Add an EF migration | dotnet ef migrations add <Name> --project src/Cargonerds.EntityFrameworkCore --startup-project src/Cargonerds.EntityFrameworkCore |
| Restore client-side libs | abp install-libs |
| Refresh C# / TS API proxies | abp generate-proxy -t csharp -u https://localhost:44354 |
| Format all C# | dotnet csharpier format . |
| Run the docs site locally | cd docs && python -m mkdocs serve |
| Run the Next.js frontend | cd frontend/realtime && npm run dev |
| Deploy an ephemeral env | .scripts/DeployToAzureContainerApps.ps1 -EnvironmentName <branch> |
.NET SDK¶
Build¶
# App-only solution (src/ + test/ of the main app + Aspire projects)
dotnet build Cargonerds.sln
# Everything (also Hub + Pricing module projects) — the solution CI builds
dotnet build Cargonerds.All.sln
# A single project
dotnet build src/Cargonerds.HttpApi.Host
Which solution file?
Cargonerds.sln is the app-only solution; Cargonerds.All.sln (and its modern XML twin
Cargonerds.All.slnx) is the superset that also contains the modules/hub/** and
modules/pricing/** projects. CI and the test suite use Cargonerds.All.sln. The per-module
solutions modules/hub/Hub.sln and modules/pricing/Pricing.sln build a single module in
isolation. See Solution & Build.
A .csproj never specifies package versions on its <PackageReference> entries — versions come
from Directory.Packages.props
(Central Package Management), and the ABP/Aspire/LeptonX version variables live in
common.props.
Run¶
# Run the whole distributed app via .NET Aspire (the recommended local entrypoint).
# Boots SQL Server, Redis, RabbitMQ, the AuthServer, API host, Blazor admin and the
# Next.js frontend, plus a DbMigrator run, all wired together.
dotnet run --project src/Cargonerds.AppHost
# Run an individual host directly (without Aspire orchestration)
dotnet run --project src/Cargonerds.AuthServer # OpenIddict auth server (https://localhost:44345)
dotnet run --project src/Cargonerds.HttpApi.Host # REST / OData API host (https://localhost:44354)
dotnet run --project src/Cargonerds.Blazor # Blazor WASM admin host (https://localhost:44381)
dotnet run --project src/Cargonerds.Web.Public # Public web host
dotnet run --project src/Cargonerds.DbMigrator # apply migrations + seed, then exit
The fixed local ports (used in run mode) are defined in
src/Cargonerds.AppHost/RunModeServicePorts.cs: AuthServer 44345, API 44354, Blazor admin
44381, Realtime frontend 4200, local SQL 53938.
Environment selection — SPARK_ENVIRONMENT
Hosts pick an appsettings.spark.<env>.json from the SPARK_ENVIRONMENT variable
(dev → appsettings.spark.dev.json, plus test / prod / local). For local secret
overrides create the git-ignored src/Cargonerds.HttpApi.Host/appsettings.spark.local.user.json.
See Running Locally and
Configuration Settings.
Test¶
The integration tests boot the real ABP application against real infrastructure (SQL Server, Redis, RabbitMQ, Azurite) started in Docker via Testcontainers, so a running Docker daemon is required. The ABP test projects use xunit.v3 under Microsoft.Testing.Platform (MTP).
# Whole suite — the exact command CI runs (pr-validation.yml)
dotnet test "Cargonerds.All.sln" --configuration Release --no-build --nologo -- --ignore-exit-code 8
# Whole suite locally, with quiet output
dotnet build "Cargonerds.All.sln" > /dev/null 2>&1 \
&& dotnet test "Cargonerds.All.sln" --no-build --nologo -- --ignore-exit-code 8 | grep -e "Duration:"
# A single test project
dotnet test test/Cargonerds.Application.Tests
# Filter by test name
dotnet test --filter "FullyQualifiedName~SampleRepositoryTests"
-- --ignore-exit-code 8 is required, and is passed after --
Everything after -- goes to the MTP test executable, not to dotnet test. MTP exit code
8 means "no tests ran"; Cargonerds.All.sln contains projects with no tests (and the
Cargonerds.HttpApi.Client.ConsoleTestApp), so without this flag the run would fail spuriously.
Don't remove it blindly. See Testing.
EF Core migrations¶
Migrations target CargonerdsDbContext (the Default / "Spark" database). The design-time
factory (CargonerdsDbContextFactory, an IDesignTimeDbContextFactory) lives in the EF Core
project and reads ConnectionStrings:Default, so the EF Core tooling runs with that project as
both --project and --startup-project:
# Scaffold a new migration into src/Cargonerds.EntityFrameworkCore/Migrations/
dotnet ef migrations add <Name> \
--project src/Cargonerds.EntityFrameworkCore \
--startup-project src/Cargonerds.EntityFrameworkCore
The ABP CLI offers a one-step helper that scaffolds the migration and runs the DbMigrator:
Apply pending migrations (and seed) by running the migrator console app — or let Aspire / CI run it:
HubDbContext and PricingDbContext have no migrations
Only CargonerdsDbContext has a Migrations/ folder. HubDbContext (the Hub database) is
filtered out of CargonerdsDbContext.OnModelCreating, so changing a Hub entity has no
dotnet ef migrations add to run — its schema is created via EnsureCreatedAsync() elsewhere.
Keep model and migrations in sync: a model change without a matching migration throws at
migrate time. Full detail in Database Migrations.
Local .NET tools¶
These are pinned in .config/dotnet-tools.json
and restored by dotnet tool restore (the git-hook installer does this for you):
| Tool | Version | Purpose |
|---|---|---|
csharpier |
1.2.6 (rollForward: false) |
Format all C# |
husky |
0.7.2 |
Run git-hook task groups |
dotnet tool restore # restore the manifest tools (run once after clone)
dotnet csharpier format . # CSharpier — format all C#
dotnet husky run --group pre-commit # run the pre-commit task group manually
CSharpier is pinned for reproducibility
rollForward: false keeps formatting byte-stable across machines. .csharpierignore skips
Migrations/, *.csproj, *.props, *.xml and *.config. The pre-commit hook re-stages the
formatted bytes automatically, so the committed code is always the formatted code.
ABP CLI¶
The ABP CLI (Volo.Abp.Cli) is a global tool. Every Aspire / App Service build in CI installs it
and runs abp install-libs first.
dotnet tool install -g Volo.Abp.Cli # install (CI does this in each deploy workflow)
abp install-libs # restore client-side libraries (run after every clone)
# Refresh the dynamic C# / TypeScript API client proxies against the running API host
abp generate-proxy -t csharp -u https://localhost:44354
abp generate-proxy -t ts -u https://localhost:44354
Run the API host before generating proxies
abp generate-proxy reads the live application configuration from the API host
(https://localhost:44354); start Cargonerds.HttpApi.Host (or the AppHost) first. The
Swagger document is at https://localhost:44354/swagger/v1/swagger.json. ABP's auto-generated
REST controllers serialize enums as integers by default. See
Code Generation and the ABP docs on
Auto API Controllers
and Dynamic C# API client proxies.
The repo also keeps local checkouts of ABP framework source under abpSrc/ (git-ignored) via
abp get-source, driven by abpSrc/abp-get-all-src.ps1. That tree is for reading framework
internals only — no project references it.
Localization — ResourceTranslator.CLI¶
Non-English localization JSON is generated by the ResourceTranslator.CLI global tool (it calls
Azure Cognitive Services Translator). It is wired into a Translate MSBuild target that runs
only on Debug builds of each *.Domain.Shared project — you normally never invoke it by hand:
<Target Name="Translate" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<Exec IgnoreExitCode="true" Command="dotnet tool install --global ResourceTranslator.CLI" />
<Exec IgnoreExitCode="true" Command="dotnet tool update --global ResourceTranslator.CLI" />
<Exec IgnoreExitCode="true"
Command="resourceTranslator --optionsfile $(ProjectDir)translation.options.json -f $(ProjectDir)Localization\Cargonerds\en.json" />
</Target>
To run it manually, point it at a resource folder's en.json and that project's
translation.options.json:
resourceTranslator --optionsfile <project>/translation.options.json -f <project>/Localization/<Resource>/en.json
Translate is Debug-only, self-installing and failure-silent
Every <Exec> uses IgnoreExitCode="true", so a translator or network failure leaves
translations stale without breaking the build. If a new key only appears in en, suspect a
failed translate step. Note zh-Hans / zh-Hant are not in the translator's
TargetCultures and must be filled in by hand. The Hub project owns three resources and so has
three resourceTranslator <Exec> lines. See Localization.
Frontend (npm)¶
frontend/ is an npm workspaces root
(frontend/package.json:
"workspaces": ["realtime", "ui-library"]); it is independent of the .NET build. realtime is the
Next.js app, ui-library a shared TypeScript component library.
realtime app scripts¶
Run from frontend/realtime (defined in its package.json; Node 22.x):
npm install
npm run dev # next dev --turbopack -p 4200
npm run aspire-dev # npm i --force && next dev --turbopack --hostname 0.0.0.0 (the script Aspire invokes; reinstalls deps first)
npm run build # next build --turbopack
npm run start # next start
npm run test # vitest run
npm run lint # eslint .
npm run type:check # tsc --pretty --noEmit --project tsconfig.json
npm run prettier:check # prettier . --check
npm run prettier:fix # prettier --write .
Format the whole frontend workspace from the frontend/ root:
Workspace install
Because frontend/ is a workspaces root, running npm install from frontend/ installs
dependencies for both realtime and ui-library. The git-hook installer runs npm i in
frontend/ for you. See Realtime (Next.js) Frontend.
Git hooks setup¶
.githooks/install.ps1
sets core.hooksPath to .githooks, restores the .NET tools (dotnet tool restore), runs
npm i in frontend/, and excludes the local worktree dirs (/.claude, .worktrees) via
.git/info/exclude.
Create worktrees only under .claude/worktrees/ or .worktrees/
Those paths are excluded via .git/info/exclude (written by the installer), never via
.gitignore. CSharpier honors .gitignore but ignores .git/info/exclude, so a worktree
placed elsewhere — or git-ignored — becomes invisible to CSharpier and breaks the CI format
check. See AGENTS.md.
The .githooks/pre-commit hook runs dotnet husky run --group pre-commit (CSharpier over .cs,
Prettier over staged frontend/ files) and re-stages the originally staged paths, so the
committed bytes are the formatted ones. It is a sh script and prepends common dotnet PATHs so GUI
git clients (Rider, Fork) can find dotnet.
Documentation (MkDocs)¶
The docs site is built with MkDocs + the Material theme. The pinned
toolchain is in docs/requirements-docs.txt (mkdocs==1.6.1, mkdocs-material==9.6.23,
mkdocs-gen-files==0.5.0).
Run MkDocs from docs/
mkdocs.yml lives in docs/ and the Markdown pages are under docs/docs/. All mkdocs
commands must run from inside docs/.
cd docs
pip install -r requirements-docs.txt # install the pinned toolchain (once)
python -m mkdocs serve # live-reload dev server on http://localhost:8000
python -m mkdocs build --strict # static build into docs/site/ (git-ignored)
--strict matches CI
docs.yml builds with mkdocs build --strict, which fails on broken links. Fix link
warnings before pushing. The Releases page is generated at build time by gen_releases.py
(the mkdocs-gen-files plugin); to preview it with live data, authenticate gh or set
GITHUB_TOKEN. There is also a docs/Dockerfile (python:3.12-slim) that serves the docs on
port 8000 and is wired into the Aspire graph as the documentation resource.
Deployment scripts (.scripts/)¶
All deploy scripts are PowerShell 7. Run them from the .scripts/ folder. The two deployment
paths (Aspire → Azure Container Apps, and zip → Azure App Service) are covered in detail under
Deployment Overview, Azure Container Apps
and Azure App Service.
flowchart TD
subgraph ACA["Aspire → Azure Container Apps (ephemeral + stage)"]
D1["DeployToAzureContainerApps.ps1"] -->|azd up| AZD["azd (src/Cargonerds.AppHost)"]
D1 --> DNS["SetupDnsAndCertificates.ps1"]
T1["TeardownAzureContainerApps.ps1"] -->|azd down| AZD
end
subgraph AS["dotnet publish → Azure App Service (production)"]
P1["PublishApp.ps1"] -->|zips| DEP["DeployToAppServices.ps1"]
DEP -->|dot-sources| MIG["MigrateAppServiceDb.ps1"]
DEP --> SWAP["SwapAppServiceSlots.ps1"]
end
UV["UpdateVersion.ps1"] -->|bumps Version| CP["common.props"]
Azure Container Apps (Aspire path)¶
# Deploy an ephemeral per-branch environment (resolves env name from the current branch if omitted).
# Sets AZD_UP_CONCURRENCY=1 and runs `azd up` against src/Cargonerds.AppHost.
./DeployToAzureContainerApps.ps1 -EnvironmentName 'my-feature' -SparkEnvironment Default
# DNS + managed TLS for the deployed Container Apps (asuid TXT / CNAME / cert bind)
./SetupDnsAndCertificates.ps1 -SubDomain 'my-feature' -ResourceGroup 'rg-ABP-my-feature'
# Tear it all down: azd down --force + DNS cleanup + AAD redirect-URI removal
./TeardownAzureContainerApps.ps1 -EnvironmentName 'my-feature'
-SparkEnvironment accepts Default or Test. The environment name maps to
azdEnvName = "ABP-<env>", resourceGroup = "rg-ABP-<env>" and fullDomain = "<env>.cargonerds.dev".
Azure App Service (zip / slot path)¶
# 1. Publish each .NET host (-c Release -r linux-x64) + the Next.js standalone bundle into ./publish/*.zip
# Optional -version stamps the zip names; PowerShell 7 is required (PS5 zips are rejected by Azure).
./PublishApp.ps1
./PublishApp.ps1 -version 3.1.4.0
# 2. Deploy the zips into the slots of an environment (slot-aware az webapp deploy + DB migration handshake)
./DeployToAppServices.ps1 -AzureEnvironment Dev-HubTest # Prod-Staging | Dev-HubProd | Dev-HubTest
./DeployToAppServices.ps1 -AzureEnvironment Dev-HubTest -SkipDbMigration # skip the migrator step
# 3. Warm up + swap a staging/test slot into its production slot
./SwapAppServiceSlots.ps1 -AzureEnvironment Prod-Staging # Prod-Staging | Dev-HubTest only
-AzureEnvironment value sets differ per script
DeployToAppServices.ps1 accepts Prod-Staging, Dev-HubProd and Dev-HubTest, but
SwapAppServiceSlots.ps1 only accepts Prod-Staging and Dev-HubTest (there is no
Dev-HubProd swap). -SkipDbMigration on DeployToAppServices.ps1 is a switch — present it
with no value.
MigrateAppServiceDb.ps1 is not run directly
It is dot-sourced by the deploy/swap scripts and exposes functions only (migrateDb,
VerifyDeploymentFromScmLogs) — it has no param() block, so there are no command-line
arguments. The DB migration runs the DbMigrator slot and polls
https://<migrator>/migration/status?token=... until Completed, with a one-time
PIPELINE_AUTH_TOKEN.
Versioning, DNS upkeep, RBAC and local helpers¶
# Bump <Version> in common.props. Use -incVersion (build|patch|minor|major) OR -version x.y.z.w.
# -Commit also commits + pushes "chore: bump version to ...". CI uses the build increment.
./UpdateVersion.ps1 -incVersion build -Commit
./UpdateVersion.ps1 -version 3.1.4.0 -Commit
# Run the Aspire AppHost for a chosen git branch (interactive: stash/checkout, optional Azure DB run mode)
./Start-AppHostFromBranch.ps1
# App Service custom domains (A records + slot SSL bindings; uses Az.* PowerShell, not the az CLI)
./SetupAppServiceCustomDomains.ps1
# Add / remove the per-env signin-oidc redirect URI on the AAD login app
./ConfigureAzureApplication.ps1
# Delete dangling CNAMEs whose targets no longer resolve
./CleanupDnsEntries.ps1
# RBAC: grant the Aspire Dashboard Owners group rights on the ACA managed environment
./GrantAspireDashboardOwner.ps1
# RBAC: grant User Access Administrator (optionally Contributor) on a RG so a dev can run azd
./Grant-PreProvision-OnRG.ps1
# Generate a FileZilla XML profile with SFTP creds for every App Service + slot in a resource group
./Generate-FileZillaConfig.ps1 -ResourceGroupName rg-spark-dev
UpdateVersion.ps1 uses parameter sets
-incVersion and -version are mutually exclusive (you must pass exactly one), and the commit
switch is -Commit (capital C). This script is what produced the chore: bump version to ...
commits, and it edits the root
common.props <Version>
only — the Hub/Pricing module common.props keep an independent 1.0.0.
Azure Developer CLI (azd)¶
azd is invoked under the hood by DeployToAzureContainerApps.ps1 against src/Cargonerds.AppHost.
The azd service manifest (src/Cargonerds.AppHost/azure.yaml) declares a single dotnet service
named app with host: containerapp, so azd up always publishes to Azure Container Apps:
name: cargonerds-apphost
services:
app:
language: dotnet
project: ./Cargonerds.AppHost.csproj
host: containerapp
azd auth login
azd env new ABP-<env> --location germanywestcentral
azd up --environment ABP-<env>
azd down --environment ABP-<env> --force
Two azd gotchas
The wrapper script sets AZD_UP_CONCURRENCY=1 because parallel dotnet publish runs
collide (a regression since azure-dev-cli 1.25.0). It also exports DEPLOYMENT_DOMAIN
(<env>.cargonerds.dev), which the AppHost requires in publish mode — a bare azd up
without the wrapper will throw. Despite referencing Aspire.Hosting.Azure.AppService, the
AppHost never deploys to App Service via Aspire; App Service is reached only through the
zip/slot path above.
Docker / infrastructure (local only)¶
These helpers are local-only (Docker Desktop) and not used by CI.
# Infra-only dependencies (RabbitMQ + Redis on a `cargonerds` docker network)
pwsh etc/docker/up.ps1
pwsh etc/docker/down.ps1
# Full local compose stack (api/auth/web/blazor/migrator + azure-sql-edge + redis).
# run-docker.ps1 also generates a dev HTTPS cert (localhost.pfx) on first run.
pwsh etc/docker-compose/run-docker.ps1
pwsh etc/docker-compose/stop-docker.ps1
There is also an ABP-Studio-generated umbrella Helm chart under etc/helm/cargonerds/ for local
Kubernetes (Docker Desktop). See Local Deployment and
Helm & Kubernetes.
See also¶
- Development Workflow — day-to-day loop and code-style rules
- Code Generation — proxies, DTOs and Mapperly in depth
- Testing — the Testcontainers / xunit.v3 harness
- Database Migrations — DbMigrator and the EF Core flow
- Configuration Settings and appsettings Reference
- Deployment Overview, CI/CD Pipelines