Skip to content

Implements support for a Node.js integration#158

Merged
arcanis merged 5 commits into
mainfrom
mael/nodejs-integration
Dec 26, 2025
Merged

Implements support for a Node.js integration#158
arcanis merged 5 commits into
mainfrom
mael/nodejs-integration

Conversation

@arcanis

@arcanis arcanis commented Dec 23, 2025

Copy link
Copy Markdown
Member

This PR adds preliminary support for managing Node.js versions in Yarn. The implementation is a little complicated has I needed a couple new primitives. Details:

  • The Cpu / Os / Libc / Requirement / System utilities were moved to zpm-utils. It's kind of an accident: I initially implemented a variant range that used Requirement, and it needed to be in zpm-primitives, so I had to move Requirement in zpm-utils, but eventually I redesigned that part and that requirement wasn't needed anymore. It still makes sense and I don't want to spend time reverting it so I'm ok with leaving it that way.

  • The yarn node and yarn exec commands weren't attaching the package's binaries into the env they were creating. This sounds like an oversight, so I fixed it (otherwise yarn node --version wouldn't use the Node.js binary from our dependencies).

  • New primitives have been added: Range::Builtin and Reference::Builtin. Those primitives refer to packages whose resolution and fetch is entirely up to the package manager. In our case we're pulling the versions and packages directly from the Node.js website.

    • The packages are trimmed to only contain the one binary we care about. We don't provide npm.

    • The packages are cached, and we support supportedArchitectures to only download the versions we care about.

    • For convenience the package manager automatically assumes that @builtin/ descriptors use builtin ranges when the range is an anonymous semver. This means we can write "@builtin/node": "^22.10.0" rather than "@builtin/node": "builtin:^22.10.0".

  • The Resolution struct may now contain a new field called variants. When the tree resolver runs, it will replace all instances of a package listing variants by the first variant matching the current system. In the case of Node.js, it means we have the @builtin/node package whose Resolution contains a variant array pointing to @builtin/node-linux-x64, @builtin/node-darwin-arm64, etc.

  • Because packages with variants are replaced by the tree resolver and not part of the final resolution tree, it wouldn't make sense for them to have a package content (since they're never materialized, and should never be directly accessed for data). To represent that, the PackageData enum now has a new Abstract value that means "this package doesn't have associated data".

  • Because the @builtin/node package gets dynamically replaced in the resolution tree by one of the @builtin/node-<platform> during install, the .pnp.cjs becomes architecture-dependant. It means that this feature, under its current design, is fundamentally incompatible with zero-installs.

    • The only alternative I see would be to make the .pnp.cjs perform dynamic dispatch based on the platform, but I don't think it'd be reasonable considering that third-party tools (Esbuild etc) would have to implement that logic as well. Since zpm supports lazy installs, it seems an acceptable limitation.
  • Just like other dependencies, @builtin/node has to be listed in all workspaces if you want the version to be applied globally. I have ideas to address this, but this will be for a different PR.

Fixes #106


Note

Adds first-class Node.js version management by treating Node as a dependency that Yarn resolves, caches, and wires into scripts and yarn node/yarn exec.

  • New primitives: Range::Builtin and Reference::Builtin; Resolution.variants for platform-specific packages selected at install time; PackageData::Abstract for non-materialized entries
  • Built-in Node resolver/fetcher (@builtin/node + @builtin/node-<platform>) pulling releases from nodejs.org, trimming to the node binary, and honoring supportedArchitectures; adds nodeDistUrl setting
  • System/requirements refactor: move Cpu/Os/Libc/System/Requirements to zpm-utils; config schema updated to use zpm_utils::*; SupportedArchitectures::to_systems() added
  • Install/tree resolution/linking updated to handle variants and mock fetches; improved checksum/lockfile plumbing; ScriptEnvironment now includes package bins so yarn node/yarn exec use the managed Node
  • Parser/utilities: JsonSource<T> for CLI JSON args; semver Range::exact_version; better script file detection
  • Docs: new concept page on Node versioning; migration guide mentions native Node management; acceptance tests cover resolution, profiles, multi-arch caching, and lockfile stability

Written by Cursor Bugbot for commit abb2390. This will update automatically on new commits. Configure here.

@netlify

netlify Bot commented Dec 23, 2025

Copy link
Copy Markdown

Deploy Preview for yarn6 ready!

Name Link
🔨 Latest commit abb2390
🔍 Latest deploy log https://app.netlify.com/projects/yarn6/deploys/694ef8126d87100008600366
😎 Deploy Preview https://deploy-preview-158--yarn6.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@arcanis

arcanis commented Dec 24, 2025

Copy link
Copy Markdown
Member Author

@BugBot review

Comment thread packages/zpm/src/tree_resolver.rs
Comment thread packages/zpm/src/resolvers/builtin.rs Outdated
= BuiltinReference {version: highest_match_release.version.clone()};

Variant {
requirements: system.to_requirements(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Musl Linux systems receive incompatible glibc Node.js binaries

The variant generation calls system.without_libc() which strips libc information before creating the variant name and requirements. This causes musl-based Linux systems (like Alpine) to match the @builtin/node-linux-x64 variant, which downloads glibc-compiled Node.js binaries. These binaries are incompatible with musl and will fail to execute. Node.js provides separate musl builds (node-v{version}-linux-x64-musl.tar.gz) but the code doesn't create or fetch musl-specific variants. Users on Alpine or other musl distributions would get silently broken Node.js installations.

Additional Locations (1)

Fix in Cursor Fix in Web

@arcanis

arcanis commented Dec 24, 2025

Copy link
Copy Markdown
Member Author

@BugBot review

Comment thread packages/zpm/src/commands/exec.rs
@arcanis

arcanis commented Dec 26, 2025

Copy link
Copy Markdown
Member Author

@BugBot review

match locator.ident.as_str() {
_
=> Err(Error::Unsupported)?,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing handler for abstract builtin package in locator resolver

The resolve_builtin_locator function has a match statement with only a catch-all that returns Unsupported, but it doesn't handle the @builtin/node package. In contrast, fetch_builtin_locator correctly handles @builtin/node by returning PackageData::Abstract. When the lockfile needs to be refreshed (e.g., after a Yarn version upgrade that changes lockfile format), the Refresh operation calls resolve_locator for locators from the lockfile. For @builtin/node@builtin:X.X.X, this would return Unsupported and fail the install. The match should include a case for "@builtin/node" that creates a resolution with the variants, similar to what resolve_nodejs_descriptor does.

Additional Locations (1)

Fix in Cursor Fix in Web

@arcanis arcanis merged commit 2ba6195 into main Dec 26, 2025
13 checks passed
@arcanis arcanis deleted the mael/nodejs-integration branch December 26, 2025 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Node.js as a development dependency

1 participant