Implements support for a Node.js integration#158
Conversation
✅ Deploy Preview for yarn6 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@BugBot review |
| = BuiltinReference {version: highest_match_release.version.clone()}; | ||
|
|
||
| Variant { | ||
| requirements: system.to_requirements(), |
There was a problem hiding this comment.
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)
|
@BugBot review |
|
@BugBot review |
| match locator.ident.as_str() { | ||
| _ | ||
| => Err(Error::Unsupported)?, | ||
| } |
There was a problem hiding this comment.
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.
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 inzpm-primitives, so I had to move Requirement inzpm-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 nodeandyarn execcommands weren't attaching the package's binaries into the env they were creating. This sounds like an oversight, so I fixed it (otherwiseyarn node --versionwouldn't use the Node.js binary from our dependencies).New primitives have been added:
Range::BuiltinandReference::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
supportedArchitecturesto 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/nodepackage 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/nodepackage gets dynamically replaced in the resolution tree by one of the@builtin/node-<platform>during install, the.pnp.cjsbecomes architecture-dependant. It means that this feature, under its current design, is fundamentally incompatible with zero-installs..pnp.cjsperform 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/nodehas 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.Range::BuiltinandReference::Builtin;Resolution.variantsfor platform-specific packages selected at install time;PackageData::Abstractfor non-materialized entries@builtin/node+@builtin/node-<platform>) pulling releases fromnodejs.org, trimming to thenodebinary, and honoringsupportedArchitectures; addsnodeDistUrlsettingCpu/Os/Libc/System/Requirementstozpm-utils; config schema updated to usezpm_utils::*;SupportedArchitectures::to_systems()addedScriptEnvironmentnow includes package bins soyarn node/yarn execuse the managed NodeJsonSource<T>for CLI JSON args; semverRange::exact_version; better script file detectionWritten by Cursor Bugbot for commit abb2390. This will update automatically on new commits. Configure here.