pnpm downloads a Node.js runtime, a Bun runtime, a Deno runtime, a Yarn
release and, since this week, a Python interpreter. Two of those could be
pointed at a mirror, through two settings that shared neither a name nor a
shape: `node-mirror:<channel>` and `python.downloadUrl`. Bun and Deno had
none at all.
`tools` names them, keyed by the tool, with `mirror` saying where its
builds come from:
tools:
node:
mirror: https://mirror.example.com/node/download
bun:
mirror: https://mirror.example.com/bun
python:
mirror: https://mirror.example.com/python-build-standalone/releases
The key is the tool itself, with no category above it. Every taxonomy
this could have used breaks on its next entry: Node.js is a runtime, Yarn
is not, a Python interpreter arguably is, a Rust toolchain would not be,
and a cargo subcommand certainly is not. `tools` inherits no such split
and needs no rename when the next one arrives. It is also the word both
tools that manage many runtimes use, in proto's `[tools.*]` and mise's
tool-namespaced settings.
A mirror carries the project's own layout below it, so only the host
above it differs. That is what lets one string serve tools whose trees
are otherwise nothing alike, and why `tools.node.mirror` is the base its
release channels hang off, exactly as nodejs.org lays them out.
This is the tool an ecosystem runs on, not where its packages come from.
`registry`, `python.indexUrl` and `cargo.indexUrl` keep answering that,
which is why `tools.python` and the `python` section can mean different
things without competing.
`python.downloadUrl` has not been released, so it is gone rather than
deprecated, and its pending changeset now names the setting that replaces
it. `node-mirror:<channel>` has shipped and stays: it names one release
channel where the base names them all, so it decides the channel it names
and leaves the rest to the base.
Deno and Yarn are left out. Both read the GitHub API for release metadata
and take asset URLs out of the response, which a base URL cannot stand in
for. pnpm/pnpm#15042 covers the URL rewriting that would reach them.
`pnpm pack-app` also downloads the Node.js it embeds through `tools.node`
now. It passed no mirror at all, under a comment saying pacquet had no
such setting, which stopped being true when `nodeDownloadMirrors`
landed. It reads only `tools.node`: that runtime runs as the builder and
is kept in the shared pack-app cache, so a repository naming
`node-mirror:<channel>` would be choosing the program that packs
everyone's app.
Related to pnpm/pnpm#14945
python.downloads answered the same question runtimeOnFail already answers
for the Node.js, Deno and Bun runtimes: what an install does when the
runtime a project needs is not on this machine. Neither the setting nor
the Python support around it has shipped, so the two merge before the
first release rather than after it.
requires-python is to an interpreter what engines.runtime is to a Node.js
runtime, so every runtimeOnFail mode now has a meaning for Python.
download, which is also the unset default, installs an interpreter. error
reports the project. warn and ignore install with an interpreter the
machine has that requires-python rejects, which python.downloads had no
spelling for.
A declared environment matrix outside requires-python stays an error
under every mode: no interpreter choice can satisfy it, so it is a
configuration error rather than a runtime mismatch.
Related to pnpm/pnpm#14945.
A project whose `requires-python` no interpreter on the machine satisfied
was reported and left uninstalled, and a `.python-version` naming a version
the machine did not have was warned about. Four of the eight repositories in
pnpm/pnpm#14945 pin an interpreter that way, so the install stopped before
resolving anything.
pnpm now installs one. The builds are python-build-standalone's, which uv,
rye, hatch and mise install too, so the interpreter a Python developer
expects is the one that arrives. A release holds an interpreter of every
version line it supports, and its `SHA256SUMS` is both the list of what pnpm
can install and the digest every download is checked against; it is cached
for a day. The newest build the project accepts is the one installed, so a
range takes the newest release inside it and a pin takes the version it
names.
An interpreter lands under `<store>/python/`, shared by every project and
repository on the machine, and is found there afterwards like any other
interpreter: a later install uses it without reading the release at all, an
offline one included. It is unpacked beside where it belongs and moved
there, so a directory under `python` is one an install can use and never a
download that stopped halfway, and two installs racing for one build end
with the interpreter either of them unpacked.
`downloads: never` keeps pnpm from installing any, and an offline install
installs none. Both report the project, saying which of the two refused.
`downloadUrl` names a mirror of the releases.
Related to pnpm/pnpm#14945.