Overview
Large codebases often organize multiple projects in a monorepo. This example shows how to use Ando.Build() to orchestrate builds across sub-projects, each with their own build.csando file.
Project Structure
my-monorepo/
├── packages/
│ ├── core/
│ │ ├── src/Core/Core.csproj
│ │ └── build.csando
│ ├── api/
│ │ ├── src/Api/Api.csproj
│ │ └── build.csando
│ └── web/
│ ├── package.json
│ └── build.csando
├── shared/
│ └── Common/Common.csproj
└── build.csando # Root orchestrator
Basic Monorepo Build
Orchestrate multiple sub-projects from the root:
// Root build.csando
var core = Directory("./packages/core");
var api = Directory("./packages/api");
var web = Directory("./packages/web");
// Build in dependency order
Ando.Build(core);
Ando.Build(api);
Ando.Build(web);
Log.Info("All packages built successfully");
Each sub-project has its own build.csando:
// packages/core/build.csando
var project = Dotnet.Project("./src/Core/Core.csproj");
Dotnet.SdkInstall();
Dotnet.Restore(project);
Dotnet.Build(project);
Dotnet.Test(Dotnet.Project("./tests/Core.Tests/Core.Tests.csproj"));
Pass Profiles to Sub-Builds
Share profiles across the monorepo:
// Root build.csando
var deploy = DefineProfile("deploy");
var production = DefineProfile("production");
var core = Directory("./packages/core");
var api = Directory("./packages/api");
var web = Directory("./packages/web");
// Build all packages
Ando.Build(core);
Ando.Build(api);
Ando.Build(web);
// Deploy all if profile is active
if (deploy)
{
// Profiles are automatically passed to sub-builds
Ando.Build(api, o => o.WithProfiles("deploy"));
Ando.Build(web, o => o.WithProfiles("deploy"));
if (production)
{
Ando.Build(api, o => o.WithProfiles("deploy", "production"));
Ando.Build(web, o => o.WithProfiles("deploy", "production"));
}
Log.Info("All packages deployed");
}
Usage:
# Build only
ando
# Deploy to staging
ando -p deploy
# Deploy to production
ando -p deploy,production
Conditional Sub-Builds
Only build changed packages:
var buildCore = DefineProfile("core");
var buildApi = DefineProfile("api");
var buildWeb = DefineProfile("web");
var buildAll = !buildCore && !buildApi && !buildWeb;
var core = Directory("./packages/core");
var api = Directory("./packages/api");
var web = Directory("./packages/web");
// Build based on profiles
if (buildCore || buildAll)
{
Ando.Build(core);
}
if (buildApi || buildAll)
{
Ando.Build(api);
}
if (buildWeb || buildAll)
{
Ando.Build(web);
}
Usage:
# Build everything
ando
# Build only core
ando -p core
# Build core and api
ando -p core,api
Parallel Sub-Builds
Build independent packages in parallel:
var core = Directory("./packages/core");
var utils = Directory("./packages/utils");
var api = Directory("./packages/api");
var web = Directory("./packages/web");
// Build shared packages first (they have no dependencies)
Ando.BuildParallel(core, utils);
// Then build dependent packages in parallel
Ando.BuildParallel(api, web);
Log.Info("Parallel build complete");
Share Artifacts Between Packages
Use a shared output directory:
// Root build.csando
var artifactsDir = Root / "artifacts";
var core = Directory("./packages/core");
var api = Directory("./packages/api");
// Set shared artifact location via environment
Environment.SetEnvironmentVariable("ARTIFACTS_DIR", artifactsDir.ToString());
// Build core (outputs to artifacts)
Ando.Build(core);
// Build api (depends on core artifacts)
Ando.Build(api);
// packages/core/build.csando
var project = Dotnet.Project("./src/Core/Core.csproj");
var artifactsDir = Env("ARTIFACTS_DIR");
Dotnet.Restore(project);
Dotnet.Build(project);
// Output package to shared location
Dotnet.Pack(project, o => o.Output(artifactsDir));
// packages/api/build.csando
var project = Dotnet.Project("./src/Api/Api.csproj");
var artifactsDir = Env("ARTIFACTS_DIR");
// Restore from shared artifacts
Dotnet.Restore(project, o => o
.WithSource(artifactsDir));
Dotnet.Build(project);
NuGet Package Publishing
Publish multiple packages from a monorepo:
var publish = DefineProfile("publish");
var packages = new[]
{
Directory("./packages/core"),
Directory("./packages/utils"),
Directory("./packages/client"),
};
// Build all packages
foreach (var pkg in packages)
{
Ando.Build(pkg);
}
if (publish)
{
var version = Git.GetVersion();
// Publish each package
foreach (var pkg in packages)
{
Ando.Build(pkg, o => o
.WithProfiles("publish")
.WithEnvironment("VERSION", version));
}
// Tag release
Git.Tag($"v{version}");
Git.PushTags();
Log.Info($"Published all packages at version {version}");
}
Mixed Technology Monorepo
Handle .NET, Node.js, and other technologies:
var deploy = DefineProfile("deploy");
// .NET services
var api = Directory("./services/api");
var worker = Directory("./services/worker");
// Node.js frontend
var web = Directory("./apps/web");
var admin = Directory("./apps/admin");
// Build all services
Ando.Build(api);
Ando.Build(worker);
Ando.Build(web);
Ando.Build(admin);
if (deploy)
{
Azure.EnsureAuthenticated();
Cloudflare.EnsureAuthenticated();
// Deploy .NET to Azure
Ando.Build(api, o => o.WithProfiles("deploy"));
Ando.Build(worker, o => o.WithProfiles("deploy"));
// Deploy frontends to Cloudflare
Ando.Build(web, o => o.WithProfiles("deploy"));
Ando.Build(admin, o => o.WithProfiles("deploy"));
Log.Info("Full monorepo deployed");
}
Key Operations
| Operation | Purpose |
|---|---|
| Ando.Build() | Run a sub-project’s build.csando |
| Ando.BuildParallel() | Build multiple projects in parallel |
| WithProfiles() | Pass profiles to sub-build |
| WithEnvironment() | Pass environment variables |
Tips
- Dependency order - Build shared packages before dependent ones
- Parallel when possible - Independent packages can build in parallel
- Share via environment - Pass paths and config via environment variables
- Consistent profiles - Use the same profile names across sub-projects
See Also
- Ando Provider - Full API reference
- Full Stack Deployment - Multi-tier deployment
- Git Versioning - Consistent versioning