Package Manager Development: Performance, UX, the Ecosystem Comparison, and Governance¶
Part 4 of 5 of the Package Manager Development reference (plugin
package-manager-development), covering §11–§14. Sibling skills:package-manager-versioning-and-resolution(§0–§4),package-manager-registries-and-installation(§5–§7),package-manager-supply-chain-and-workspaces(§8–§10),package-manager-reference(§15–§20). Section numbers are shared across the set; a reference written as §N →skillpoints into that sibling skill.Currency: Verified August 2026. See §17 →
package-manager-referencefor the currency snapshot and what goes stale first.How to read this. Reference, not tutorial. Sections are independent. Three markers: - [DURABLE] — computer science, or a lesson every ecosystem has independently learned. Does not expire. - [ECOSYSTEM] — specific to npm, PyPI, crates.io, Go, etc. Verify against that ecosystem's current docs. - [CONTESTED] — the ecosystems have made genuinely different, defensible choices, or the practitioners disagree. Both cases given.
⚠️ GOTCHA boxes mark the design mistakes that produce unreproducible builds, wedged caches, exponential resolutions, or supply-chain compromise.
The framing that organizes everything below: a package manager is a distributed systems problem wearing a CLI. It is a cache-coherency problem, a constraint-solving problem, a trust-and-identity problem, and a compatibility-contract problem, all of which happen to be invoked by typing
install. Every serious difficulty in this document is one of those four, and treating it as "just downloading files" is the root of most bad designs.
§11. Performance¶
[DURABLE] The rough cost order, for a cold install:
The corollary: most package managers that are "slow" are network-bound and serially-bound, not CPU-bound. uv, Bun, and pnpm's speed comes overwhelmingly from attacking the first two.The techniques that actually matter:
1. Parallelism everywhere — metadata fetch, download, extraction, linking. Bound
concurrency per-host to avoid being rate-limited.
2. Metadata-only fetches — never download an artifact to learn its dependencies (§3.6 → package-manager-versioning-and-resolution).
3. Aggressive, immutable caching (§5.2 → package-manager-registries-and-installation makes this safe) with a global store shared
across projects.
4. Hardlink or reflink instead of copy when populating a project from the store. This
is often the difference between seconds and minutes for large trees.
5. Streaming decompression — extract while downloading.
6. Skip work: if the lockfile and the installed tree already agree, do nothing. Fast
install on an up-to-date project should be near-instant.
7. Write the resolver in a fast language, last. It's the smallest term.
⚠️ On benchmarks. Published package-manager benchmarks (the widely-circulated 2026 npm/pnpm/Yarn/Bun comparisons showing e.g. sub-second cold installs for Bun versus ~14 s for npm on a 50-dependency project) are single-machine, single-project, and highly sensitive to cache state, network, and dependency shape. The direction is reliable; the multipliers are not. Benchmark your own workload before making an architectural claim.
§12. UX¶
12.1 Error messages are the product¶
[DURABLE] A resolver's error message is its most-used feature after install. Compare:
BAD: ERROR: Could not find a version that satisfies the requirement foo
BAD: error: failed to select a version for `foo`
GOOD: Because myapp depends on foo ^1.0.0 and bar ^3.0.0,
and every version of foo requires bar ^2.0.0,
version solving failed.
Try: relaxing myapp's constraint on bar, or upgrading foo to 2.x
which requires bar ^3.0.0.
package-manager-versioning-and-resolution). Design for that
from the start — it cannot be bolted on to a resolver that discards its reasoning.
12.2 The rest of the CLI¶
- Distinguish "resolve and install" from "install exactly the lockfile." Two commands, clearly named. CI uses the second, always.
--dry-runthat actually works — possible only if resolution is separable (§0.1 →package-manager-versioning-and-resolution).why/explain— "why is this package in my tree?" is the single most-requested diagnostic.npm ls,cargo tree -i,go mod why,pnpm why.- Progress that means something — which package, how many remain, and never a spinner during a multi-minute backtracking search without saying what's thrashing.
- Machine-readable output (
--json) for every command, from day one. - Exit codes that distinguish resolution failure, network failure, integrity failure, and build failure. Tooling depends on this.
- Never mutate the lockfile as a side effect of a read-only command.
- Deprecation warnings that are actionable: what's deprecated, what replaces it, and which of your dependencies pulled it in.
§13. The Ecosystem Comparison¶
| npm | pip / uv | Cargo | Go modules | Maven | apt/dnf | Nix | |
|---|---|---|---|---|---|---|---|
| Manifest | package.json |
pyproject.toml |
Cargo.toml |
go.mod |
pom.xml |
control/spec | .nix |
| Lockfile | package-lock.json |
uv.lock / pylock.toml |
Cargo.lock |
go.sum (hashes; go.mod pins) |
none native | none | flake.lock |
| Resolution | backtracking, npm semantics | pip: backtracking; uv: PubGrub + forking | backtracking (PubGrub designated as replacement) | MVS | nearest-wins mediation | SAT (libsolv) | none — exact inputs |
| Ranges | yes | yes | yes | no | yes | yes | n/a |
| Multiple versions | yes | no | yes (semver-incompatible) | yes (via /vN import paths) |
no | no | yes (by hash) |
| Layout | hoisted/isolated/PnP | flat venv | compiler --extern |
module cache | classpath | system-wide | content-addressed store |
| Install-time code | yes (scripts) | wheels: no; sdists: yes | build.rs |
no | plugins | maintainer scripts | sandboxed builds |
| Namespacing | flat + @scope |
flat | flat | domain-derived | reverse-DNS | flat | flat |
| Provenance | Sigstore/SLSA, GA | PEP 740 attestations | trusted publishing GA; signing proposed | sum.golang.org (integrity, not provenance) |
PGP; Sigstore emerging | distro signing | hashes |
| Signature model | keyless (Sigstore) | keyless (Sigstore) | — | transparency log | PGP web-of-trust | distro keyring | — |
Reading this table is the fastest way to see that there are no universal answers, only consistent trade-off sets. Go trades expressiveness for determinism at every single row. npm trades strictness for compatibility. Nix trades familiarity for correctness.
§14. Governance and Sustainability¶
14.1 The registry is critical infrastructure¶
[DURABLE] Once an ecosystem depends on you, you cannot go down and you cannot break compatibility. Design for: read-path availability via CDN (the registry API being down should not stop installs of already-known versions), mirrorability, an incremental change feed, and a documented disaster-recovery story. PyPI's reliance on donated CDN capacity (Fastly) is typical and worth understanding as a structural fact about how these are funded.
14.2 The policies you will need, whether or not you planned them¶
- Name disputes, trademark claims, and ownership transfer.
- Abandoned packages — archival/status markers (PyPI's PEP 792 project-status work) are better than silence.
- Deprecation — a first-class, machine-readable signal, not a README note.
- Yank vs. delete (§5.2 →
package-manager-registries-and-installation) — and the very narrow circumstances for actual removal (malware, secrets, illegal content). - Malware response — who can quarantine, how fast, and what users are told.
- Maintainer burnout and single-maintainer critical packages — the underlying condition behind most account-takeover incidents.
14.3 Regulation now reaches package managers¶
The EU Cyber Resilience Act makes vulnerability handling a legal duty for anyone
shipping products with digital elements into the EU — reporting obligations begin
11 September 2026, full obligations 11 December 2027. Practical consequences for this
domain:
- Every dependency is a component in your SBOM, and its vulnerabilities are your duty
to handle.
- Package managers are increasingly expected to emit SBOMs (npm sbom --sbom-format
cyclonedx) and to make provenance verifiable.
- CISA guidance requires machine-readable SBOM formats — SPDX or CycloneDX.
- If you are building a registry or package manager, "we just host files" is no longer a
tenable position on where your responsibility ends.
Plugin: package-manager-development · View SKILL.md on GitHub