Skip to content

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 (devappsettings.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:

abp create-migration-and-run-migrator src/Cargonerds.EntityFrameworkCore

Apply pending migrations (and seed) by running the migrator console app — or let Aspire / CI run it:

dotnet run --project src/Cargonerds.DbMigrator

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:

src/Cargonerds.Domain.Shared/Cargonerds.Domain.Shared.csproj
<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:

cd frontend && npx prettier --write .

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

pwsh .githooks/install.ps1

.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:

src/Cargonerds.AppHost/azure.yaml
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