Monorepo Build

Orchestrate builds across multiple projects in a monorepo using Ando.Build.

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

OperationPurpose
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