en

Monorepos

Share dependencies across many projects in one repository, with one lockfile and one store.

If you have a .NET monorepo (one repo, many .csproj files, one solution or many), PCPM treats it as one workspace: one pcpm.lock, one store, and one Directory.Packages.props for the whole tree.

This page covers the practical side: how to lay out your repo so PCPM sees it, and what to expect from pcpm install at the monorepo scale.

PCPM doesn’t care about layout, but the following is a convention that works well:

monorepo/
├── pcpm-workspace.yaml          # project globs
├── Directory.Packages.props     # central version manifest
├── pcpm.json                    # workspace config
├── pcpm.lock                    # generated by pcpm install
├── apps/
│   ├── web/                     # ASP.NET project
│   │   └── web.csproj
│   └── worker/                  # background worker
│       └── worker.csproj
├── libs/
│   ├── shared/                  # internal library
│   │   └── shared.csproj
│   └── contracts/
│       └── contracts.csproj
└── tools/
    └── migrations/
        └── migrations.csproj

The matching pcpm-workspace.yaml:

packages:
  - "apps/*"
  - "libs/*"
  - "tools/*"

Every .csproj under apps/, libs/, and tools/ is part of the workspace. The lockfile covers them all.

Adding a dependency

By default, pcpm add writes the <PackageReference /> to every project in the workspace. If you only want one:

pcpm add Serilog --project apps/web/web.csproj

If you want a subset:

pcpm add Microsoft.Extensions.Hosting \
  --project apps/web/web.csproj \
  --project apps/worker/worker.csproj

Versioning

Central Package Management gives you one place to pin versions:

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Serilog" Version="3.1.1" />
    <PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
  </ItemGroup>
</Project>

PCPM writes these <PackageVersion /> entries for you. The <PackageReference /> in each .csproj references the same id without a version:

<ItemGroup>
  <PackageReference Include="Serilog" />
</ItemGroup>

Transitive resolution

A monorepo with N projects has a single dependency graph. PCPM resolves it as a whole, which means:

  • A package required by two projects is downloaded once.
  • Conflicts are resolved globally — there’s only one Newtonsoft.Json version on disk, even if apps/web and libs/contracts ask for different ranges.
  • pcpm.lock contains every direct and transitive dependency, so any re-run (CI, new dev, Docker build) produces the same restore.

Sharing internal projects

The projects inside a monorepo reference each other with normal <ProjectReference /> entries. PCPM doesn’t need to know about them — it sees the resolved .csproj graph and just resolves the NuGet dependencies.

If you want to enforce that all projects reference the same version of an internal library, use a <PackageVersion /> in Directory.Packages.props and reference the project by id only.

When the monorepo gets large

On a monorepo with hundreds of projects, the lockfile can get big (think tens of thousands of transitive entries). PCPM handles this — resolution is O(V + E) in the worst case, with caching that makes re-runs essentially free — but the lockfile diff can get noisy.

Practical tips:

  • Use pcpm outdated and pcpm update <pkg> to keep version bumps intentional. Avoid letting every CI run resolve against a wide range.
  • Keep pcpm-workspace.yaml narrow. The wider the project globs, the bigger the lockfile, and the slower pcpm list becomes (it pretty- prints the whole graph).
  • If you have truly independent slices (e.g. mobile + backend in the same repo), consider multiple workspaces — one pcpm-workspace.yaml per slice, each with its own lockfile.

When not to use a monorepo

PCPM is happy to run against a single .sln with two .csproj files. You don’t need a pcpm-workspace.yaml for that. Reach for the monorepo setup when your repo has 10+ projects, or when you want a shared lockfile across multiple solutions.