#15042·pnpm

Add a URL rewrite setting for the downloads a per-tool mirror cannot name

Author: zkochanCreated Sep 17, 2026Updated Sep 17, 2026
Labelstype: feature

Summary

tools.<name>.mirror (pnpm/pnpm#15035 follow-up) lets a workspace say where pnpm downloads Node.js, Bun and a Python interpreter from. It cannot cover everything pnpm fetches, and the gaps are not incidental:

what pnpm fetches from can a mirror cover it?
Node.js nodejs.org/download/<channel>/ yes
Bun github.com/oven-sh/bun/releases/download/bun-v<v>/ yes
Python interpreter github.com/astral-sh/python-build-standalone/releases yes
Deno api.github.com/repos/denoland/deno/releases/tags/v<v> no
Yarn (zpm) api.github.com/repos/yarnpkg/zpm/releases no
npm packages registry / registries separate setting
PyPI distributions python.indexUrl separate setting
crates cargo.indexUrl separate setting
git dependencies github.com, gitlab.com, bitbucket.org, codeload nothing

Deno and Yarn read the GitHub API for release metadata and take asset URLs out of the response. A base URL cannot stand in for that without also pinning the response shape. So the two runtimes most in need of a mirror are the two a mirror setting cannot reach, and a user behind a proxy still has four other settings to find.

Proposal

A URL rewrite map, applied to every URL pnpm is about to fetch:

yaml
urlRewrites:
  'https://github.com/': 'https://gh-mirror.corp.example/'
  'https://api.github.com/': 'https://gh-api-mirror.corp.example/'

One rule then covers runtimes, interpreters, package managers, registries, indexes and git hosts at once, including the GitHub API calls no declarative mirror can express.

This is not speculative. Both tools that manage many runtimes converged on it, and both ship it alongside per-tool settings rather than instead of them:

  • mise: url_replacements, "map of URL patterns to replacement URLs… for download mirroring and custom registries", next to node.mirror_url and go.download_mirror
  • proto: settings.url-rewrites, a regex→replacement map covering tool download, checksum, plugin and archive URLs, next to [tools.*] config

The two are complementary. tools.<name>.mirror is declarative: pnpm knows the default, so the user names only the replacement. A rewrite is the escape hatch for every URL pnpm did not model.

Questions to settle

  1. Prefix or regex? proto and mise both use regex. A prefix match is far easier to reason about and to make safe, and covers the mirror case. Regex buys path rewriting.
  2. Which URLs are in scope? proto excludes git repository URLs and its own telemetry. pnpm would need to decide about git remotes, the pnpr server, audit/advisory endpoints, and self-update.
  3. How does it interact with authentication? Rewriting a URL moves it to a different origin, and pnpm resolves credentials by origin. python_index already refuses to let repository-selected indexes pick up user-level npm credentials (crates/python-installer/src/settings.rs). A rewrite must not become a way to redirect a request to a host that then receives a token meant for another. This is the part that needs the most care.
  4. Where can it be set? A repository-controlled pnpm-workspace.yaml redirecting downloads is a different trust proposition from a user-level config doing so.

Question 3 is the reason this is filed rather than built: it is a security design question, not a configuration one.

Out of scope

Anything a tools.<name>.mirror already covers. This is for the URLs that setting cannot name.


Written by an agent (Claude Code, claude-opus-5).