Introduction
Large enterprise monorepos quickly suffer from slow CI/CD pipelines. A full build of every Angular app, library and .NET service on every commit wastes CPU, increases feedback time, and costs money. An Intelligent Build System detects which modules are impacted by a change and builds only those — plus their dependents — while ensuring correctness.
This article explains how to design, implement and operate an intelligent build system for hybrid stacks: Angular (frontend) and .NET (backend). You will get:
High-level architecture and workflows (block diagrams).
File-to-module dependency analysis techniques.
Practical detection scripts and CI pipeline examples.
Strategies for correctness (tests, contracts, change validation).
Caching, distributed execution, and observability.
Operational guidance and common pitfalls.
The goal: faster developer feedback, lower CI cost, and reliable builds for production deployment.
Problem Statement
In a monorepo with many frontend libs, multiple Angular apps, and several .NET microservices:
Small UI change can re-trigger tens of builds.
Backend interface change requires rebuilding many services.
CI queues balloon and developer feedback becomes slow.
We want a system that:
Determines the minimal set of modules needing rebuild for a given commit (impacted set).
Builds and tests only that set (and safe dependents).
Preserves correctness (no missing rebuilds).
Integrates with existing CI/CD and developer workflows.
High-Level Architecture
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ Developer │ ---> │ CI Trigger │ ---> │ Impact Analyzer│
└─────────────┘ └──────┬───────┘ └──────┬────────┘
│ │
▼ │
┌───────────┐ │
│ Git Diff │<-------------┘
└────┬──────┘
│
▼
┌───────────────────────┐
│ Module Graph Service │
│ (Angular TS + .NET) │
└─────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ Build Orchestrator (CI) │
│ (local / remote cache / │
│ distributed executors) │
└─────────┬────────────────┘
│
▼
┌─────────────────┐
│ Artifact Store │
└─────────────────┘
Key Concepts
Module: logical unit — Angular app/lib, .NET project, shared library.
Dependency Graph: directed graph where an edge A→B means A depends on B (if B changes, A may need rebuild).
Impacted Set: the minimal set of modules that must be rebuilt because they were changed or they transitively depend on changed modules.
Safe Dependents: set of modules that may be rebuilt to be conservative (e.g., if interfaces changed).
Change Contract: explicit markers that indicate whether a change is backwards-compatible or breaking (e.g., API version bump, public API changes).
Build Cache: store of previous build outputs keyed by input hash to skip rebuilds when possible.
How Impact Detection Works
1. File Diff → Affected Files
Get changed files for the commit/PR:
# changed files vs main
git fetch origin main
git diff --name-only origin/main...HEAD
2. File → Module Mapping
Map each changed file to a module. Techniques:
Path-based mapping: repo layout uses folders per project:
libs/ui/button/**→libs/ui/buttonmodule.tsconfig / project.json: read
angular.jsonortsconfig.jsonprojectssection to know Angular projects and their root..csproj mapping: each .NET project lives in a folder; map changed
.cs/.csprojfile to that project.Custom manifest: maintain
module-manifest.jsonmapping globs → module name.
Example module-manifest.json:
{
"modules": [
{ "name": "ui-button", "globs": ["libs/ui/button/**"] },
{ "name": "orders-api", "globs": ["src/Services/Orders/**"] }
]
}
Apply with minimatch or gitignore-style matching.
3. Module Graph Construction
Two build-time graphs required:
Angular graph: use
ng/nxor thetsconfigproject references. Tools:nx graph --file=graph.json(Nx)ng v8+withng lava? (nx recommended)For manual approach: parse
tsconfigreferencesandpackage.jsonpaths.
.NET graph: use MSBuild to list project references:
dotnet msbuild -nologo -t:GenerateRestoreGraphFile -p:RestoreGraphOutputPath=graph.dg
# or use dotnet list <proj> reference -- include-transitive
dotnet list src/Services/Orders/Orders.csproj reference
Store combined graph in a canonical format (JSON adjacency list). This graph is cached and updated when projects change.
4. Transitive Closure → Impacted Modules
For each module representing a changed file, compute all modules that depend on it (reverse graph traversal). That union is the impacted set.
If module A depends on B and B changed → A is impacted.
Optionally include only direct dependents or include transitive layers; generally transitive closure is safe.
Dealing With APIs And Contracts
Not all changes require rebuilding dependents:
Internal implementation change only: downstream contracts unchanged.
Public API change: downstream must rebuild & test.
Approaches
Conservative (safe): always rebuild dependents. Simple but might rebuild too much.
Contract Aware (preferred)
Use public API extractor (TypeScript
api-extractorfor libs;.NET PublicApiCompatorApiPort) to compute API surface hash.If API hash unchanged, skip dependents. If changed, include dependents.
For .NET, use
dotnet format? Better: usePublicApiAnalyzersor extract public API via reflection/unit tests and compare snapshots.
Example flow:
On every merge to main, extract API snapshot per project and store in artifacts.
On PR, run API extractor for changed module; if snapshot differs → mark breaking change.

Join the conversation! Your thoughts help the community grow.