This will probably be the last v7 release with the “beta” tag. Starting a week from now, we’ll be moving into release candidates, using the “rc” prerelease tag.
This release refactors some of the dependency-explaning code used to give helpful errors in ERESOLVE conflicts, and turns it into a new top-level command npm explain. We hope you find it useful!
npm i -g npm@next-7
Heading into the holiday (in the USA at least) weekend with a fresh new npm v7 beta for you all. You can get it in the traditional way:
npm i -g npm@next-7
This adds explanatory text for ERESOLVE errors, because “unable to resolve dependency tree” was just not as helpful as we aim to be. 😅
It also adds the ability to try to do the right thing in most cases for conflicting peerDependencies when you set --force, and fixes up several other bugs reported throughout our beta process.
Thank you all for your continued help with reporting issues on the beta! We expect to have a release candidate for you soon.
ef8f5676b #1757 view: always fetch fullMetadata, and preferOnlineac5aa709a #1758 fix scope configa36e2537f outdated: don’t throw on non-version/tag/range dep371f0f062 @npmcli/arborist@0.0.20
ERESOLVE errorsERESOLVE errors with --force8e3e83bd4 @npmcli/arborist@0.0.21
d6b134fd9 #1738 #1734 fix package spec parsing during cache add process (@mjeanroy)
f105eb833 npm-audit-report@2.1.4:
03a9f569b opener@1.5.2
5616a23b4 @npmcli/git@2.0.4
.git files, so that git worktrees are respectedIt’s time for a new npm7 beta pre-release 😄
If you’re not running the beta yet and want to give it a try or in case you’re running it and want to update to the latest beta version: npm i -g npm@next-7
This release fixes some regressions in the config refactor from last week, also fixes an issue with npm ls not properly handling workspaces and properly forward install options in npx.
834e62a0e@npmcli/arborist@0.0.19758b02358 #1739 add full install options to npm exec (@ruyadorno)2ee7c8a98 @npmcli/config@1.1.7 (@ruyadorno)Another week, another round of releases for the npm7 beta 😄
If you’re not running the beta yet and want to give it a try or in case you’re running it and want to update to the latest beta version:
npm i -g npm@next-7
This brings a major refactor to the config system that now lives at https://www.npmjs.com/package/@npmcli/config along with many fixes to bug reports from the community.
b38f68acd
ensure npm-command HTTP header is sent properly9f200abb9
Properly exit with error status codeaa0152b58#1719 Detect CI properly50f9740ca#1717 fund with multiple funding
sources (@ruyadorno)3a63ecb6f#1718RFC-0029
add ability to skip pre/post hooks to npm run-script by using
--ignore-scripts (@ruyadorno)707207bdd
add @npmcli/config dependency
5cb9a1d4d#1688 use @npmcli/config for
configuration (@isaacs)
a4295f5dbnpm-registry-fetch@8.1.4:
a5a6a516djson-parse-even-better-errors@2.3.0:
a14054558read-package-json-fast@1.2.1:
f8603c8aflibnpmversion@1.0.4:
9891fa71cread-package-json@2.1.2:
b44768aac#1662#1693#1690@npmcli/arborist@0.0.17:
package.json when running loadVirtual.package.json and package-lock.json formatting in all
places where these files are written.281da6fdctar@6.0.5
88e4241c5#1698 add lib/logout.js unit
tests (@ruyadorno)
As foretold by the prophesies, another weekly npm v7 beta is now available.
tl;dr - run npm i -g npm@next-7 right now, and tell us about any problems you encounter with it.
This fixes the npm view --json output, adds test coverage, and switches us from meant to leven for the “Did you mean…?” message.
It does not yet fix some of the other issues that early beta testers have helpfully reported to us:
/usr/local/lib/node_modules/npm/npmrc. (Which npm v6 puts there when it installs npm v7.)package.json sets indentation style to two-spaces when saving dependencies.We expect these issues to be fixed in upcoming beta updates soon.
As always, please report any problems you encounter! We are grateful for your help in npm’s ongoing improvement.
b718b0e28 #1657 display multiple versions when using --json with npm view (@claudiahdz)9e7cc42f6 #1071 migrate from meant to leven (@jamesgeorge007)85027f40c #1664 refactor and add tests for npm adduser (@ruyadorno)6e03e5583 #1672 refactor and add tests for npm audit (@claudiahdz)Replace some environment variables that were excluded. This implements the amendment to RFC0021.
Bring back support for npm audit --production, fix a minor npm version annoyance, and track down a very serious issue where a project could be blown away when it matches a meta-dep in the tree.
5fb217701 #1641 @npmcli/arborist@0.0.153598fe1f2 @npmcli/arborist@0.0.16 Add support for npm audit --production8ba2aeaee libnpmversion@1.0.3New notification style for updates, and a working doctor.
cf2819210 #1622 Improve abbrevs for install and helpd062b2c02 new npm-specific update-notifier implementationf6d468a3b update doctor commandb8b4d77af #1638 Direct users to our GitHub issues instead of npm.communityFix some issues found in the beta pubish process, and initial attempts to use npm v7 with citgm.
Happy Monday everyone! 😊
A new v6 patch release is out with a few bug fixes from the community and dependency updates. To get it:
npm install -g npm@latest
9262e8c88 #1575 npm install –dev deprecation message (@sandratatarevicova)765cfe0bc #1658 remove unused broken require (@aduh95)4e28de79a #1663 Do not send user secret in the referer header (@assapir)8abdf30c9 #1572 docs: add missing metadata in semver page (@tripu)8cedcca46 #1614 Node-gyp supports both Python and legacy Python (@cclauss)<< Why keep package-lock.json?
A new beta version of npm appears!
tl;dr - Run npm i -g npm@next-7 right now, and tell us about any problems you encounter with it.
This is a big one, you’re going to want to check it out. As with any beta software, it’s likely to still have a few rough edges, but we’re confident that those will be polished down very quickly. Your help finding them will make that process go much faster.
This post outlines the major-version updates (aka “Breaking Changes” in SemVer lingo) in this release. If you’re going to start using npm v7, it would be good to take a look over this list and make sure nothing is going to ruin your day.
Despite a massive overhaul of most of npm’s functionality, we are endeavoring to make npm v7 as low-risk and low-effort as possible, for as many users of npm as we possibly can. Nevertheless, this list is fairly long, because we endeavor to call out a change even if we think the impact is likely minor.
For the massive majority of npm users, where your experience is “install my stuff, do the right thing, let me get back to work”, we are confident this update will be entirely an improvement to your development experience.
The Semantic Versioning specification precisely defines what constitutes a “breaking” change. In a nutshell, it’s any change that causes a you to change your code in order to start using our code. We hasten to point this out, because a “breaking change” does not mean that something about the update is “broken”, necessarily.
We’re sure that some things likely are broken in this beta, because it’s best to be pessimistic about beta software. But nothing is “broken” on purpose here, and if you find a bug, we’d love for you to let us know so we can get it fixed before the beta tag falls off.
We have identified automatic peerDependencies installation as a potentially disruptive change for many users (albeit one that we are confident is the correct behavior for a package manager), we have some tools to minimize this disruption, based on the feedback we get.
We are confident that resolving package trees such that peerDependencies are properly accounted for is the right thing to do. After all, an error here can result in a production issue that’s very difficult to debug later, especially if it occurs deep in a node_modules tree. However, years of not resolving peerDependencies has allowed many projects to fail to notice these problems.
In order to get unblocked and install your project in spite of peerDependencies conflicts, you can use the --legacy-peer-deps flag at install time. It may be that the disruption is too great to take all at once, and we have to have this flag enabled by default for a while as projects gradually update their conflicting dependencies. Our intent is to let the beta give us some more data points to help make that decision carefully.
Regardless of where we land on installs, the npm ls command will always run with --legacy-peer-deps set to false, so that missing peer dependencies will be highlighted when looking at the package tree. We hope that this will be useful encouragement to get projects to update out of a broken state (or worse, a “works by accident” state).
Beta versions of v7 will go out weekly on Tuesdays until everything looks good. We’ll decide to cut a GA release based on stability and getting the last bits of housecleaning done.
We have not yet gotten to 100% test coverage of the npm CLI codebase. As such, there are almost certainly bugs lying in wait. We do have 100% test coverage of most of the commands, and all recently-updated dependencies in the npm stack, so it’s certainly more well-tested than any version of npm before.
There’s still lots of work we need to do in updating the documentation. Prior to a GA release, we’ll be going through all of our documentation with a fine-toothed comb to make sure it’s as accurate and helpful as possible.
That’s about it! It’s ready to use for most purposes, and you should try it out.
Now on to the list of changes.
figgy-pudding library for configs. Configuration is done using a flat plain old JavaScript object.lib/fetch-package-metadata.js module is removed. Use pacote to fetch package metadata.@npmcli/arborist should be used to do most things programmatically involving dependency trees.onload-script option is no longer supported.log-stream option is no longer supported.npm.load() MUST be called with two arguments (the parsed cli options and a callback).npm.root alias for npm.dir removed.package.json in npm now defines an exports field, making it no longer possible to require() npm’s internal modules. (This was always a bad idea, but now it won’t work.)The following affect all commands that contact the npm registry.
referer header no longer sentnpm-command header addedThe environment for lifecycle scripts (eg, build scripts, npm test, etc.) has changed.
RFC 21 Environment no longer includes npm_package_* fields, or npm_config_* fields for default configs. npm_package_json, npm_package_integrity, npm_package_resolved, and npm_command environment variables added.
RFC 22 Scripts run during the normal course of installation are silenced unless they exit in error (ie, with a signal or non-zero exit status code), and are for a non-optional dependency.
RFC 24 PATH environment variable includes all node_modules/.bin folders, even if found outside of an existing node_modules folder hierarchy.
The user, group, uid, gid, and unsafe-perms configurations are no longer relevant. When npm is run as root, scripts are always run with the effective uid and gid of the working directory owner.
Commands that just run a single script (npm test, npm start, npm stop, and npm restart) will now run their script even if --ignore-scripts is set. Prior to the GA v7.0.0 release, they will not run the pre/post scripts, however. (So, it’ll be possible to run npm test --ignore-scripts to run your test but not your linter, for example.)
The npx binary was rewritten in npm v7, and the standalone npx package deprecated when v7.0.0 hits GA. npx uses the new npm exec command instead of a separate argument parser and install process, with some affordances to maintain backwards compatibility with the arguments it accepted in previous versions.
This resulted in some shifts in its functionality:
npm config value may be provided.npx prompts before installing anything. Suppress this prompt with the -y or --yes option.--no-install option is deprecated, and will be converted to --no.-p argument is a shorthand for --parseable in npm, but shorthand for --package in npx. This is maintained, but only for the npx executable. (Ie, running npm exec -p foo will be different from running npx -p foo.)--ignore-existing option is removed. Locally installed bins are always present in the executed process PATH.--npm option is removed. npx will always use the npm it ships with.--node-arg and -n options are removed.--always-spawn option is redundant, and thus removed.--shell option is replaced with --script-shell, but maintained in the npx executable for backwards compatibility.We do intend to continue supporting the npx that npm ships; just not the npm install -g npx library that is out in the wild today.
package.json files no longer are mutated to include extra metadata. (This extra metadata is stored in the lockfile.)package-lock.json is updated to a newer format, using "lockfileVersion": 2. This format is backwards-compatible with npm CLI versions using "lockfileVersion": 1, but older npm clients will print a warning about the version mismatch.yarn.lock files used as source of package metadata and resolution guidance, if available. (Prior to v7, they were ignored.)These changes affect install, ci, install-test, install-ci-test, update, prune, dedupe, uninstall, link, and audit fix.
RFC 25 peerDependencies are installed by default. This behavior can be disabled by setting the legacy-peer-deps configuration flag.
BREAKING CHANGE: this can cause some packages to not be installable, if they have unresolveable peer dependency conflicts. While the correct solution is arguably to fix the conflict, this was not forced upon users for several years, and some have come to rely on this lack of correctness. Use the --legacy-peer-deps config flag if impacted.
See the note above about --legacy-peer-deps.
RFC 23 Support for acceptDependencies is added. This can result in dependency resolutions that previous versions of npm will incorrectly flag as invalid.
Git dependencies on known git hosts (GitHub, BitBucket, etc.) will always attempt to fetch package contents from the relevant tarball CDNs if possible, falling back to git+ssh for private packages. resolved value in package-lock.json will always reflect the git+ssh url value. Saved value in package.json dependencies will always reflect the canonical shorthand value.
Support for the --link flag (to install a link to a globall-installed copy of a module if present, otherwise install locally) has been removed. Local installs are always local, and npm link <pkg> must be used explicitly if desired.
Installing a dependency with the same name as the root project no longer requires --force. (That is, the ENOSELF error is removed.)
workspaces support is added. This changes npm’s behavior when a root project’s package.json file contains a workspaces field.npm updatenpm update is run without any arguments. As it is no longer relevant, --depth config flag removed from npm update.npm outdated--depth config from npm outdated. Only top-level dependencies are shown, unless --all config option is set.npm adduser, npm login--sso options are deprecated, and will print a warning.npm auditOutput and data structure is significantly refactored to call attention to issues, identify classes of fixes not previously available, and remove extraneous data not used for any purpose.
BREAKING CHANGE: Any tools consuming the output of npm audit will almost certainly need to be updated, as this has changed significantly, both in the readable and --json output styles.
npm dedupePerforms a full dependency tree reification to disk. As a result, npm dedupe can cause missing or invalid packages to be installed or updated, though it will only do this if required by the stated dependency semantics.
Note that the --prefer-dedupe flag has been added, so that you may install in a maximally deduplicated state from the outset.
npm fundnpm lsnode_modules tree.npm ls only prints the first level of dependencies by default. You can make it print more of the tree by using --depth=<n> to set a specific depth, or --all to print all of them.npm pack, npm publishnpm rebuild--ignore-scripts and --bin-links=false configuration options.npm build, npm unbuildnpm testmissing script: test rather than injecting a synthetic echo 'Error: no test specified' test script into the package.json data.Huge thanks to the people who wrote code for this update, as well as our group of dedicated Open RFC call participants. Your participation has contributed immeasurably to the quality and design of npm.
A wild new npm version appears! This patch release includes some bug fixes and dependencies updates.
To get the latest npm, run:
$ npm install -g npm@latest
de5108836 #784 npm explore spawn shell correctly (@jasisk)36e6c01d3 git tag handling regression on shrinkwrap (@claudiahdz)1961c9369 #288 Fix package id in shrinkwrap lifecycle step output (@bz2)87888892a #1009 gracefully handle error during npm install (@danielleadams)6fe2bdc25 #1547 npm ls –parseable –long output (@ruyadorno)2d78481c7 update mkdirp on tacks (@claudiahdz)4e129d105 uninstall npm-registry-couchapp (@claudiahdz)8e1869e27 update marked dev dep (@claudiahdz)6a6151f37 libnpx@10.2.4 (@claudiahdz)dc21422eb bin-links@1.1.8 (@claudiahdz)d341f88ce gentle-fs@2.3.1 (@claudiahdz)3e168d49b libcipm@4.0.8 (@claudiahdz)6ae942a51 npm-audit-report@1.3.3 (@claudiahdz)6a35e3dee npm-lifecycle@3.1.5 (@claudiahdz)A new npm version has been shipped! This release includes some minor changes and dependencies updates.
To get the latest npm, run:
$ npm install -g npm@latest
BUG FIXES
DEPENDENCIES
DOCUMENTATION
2e052984b#1459
chore(docs): fixed links to cli commands (@claudiahdz)0ca3509ca#1283 Update npm-link.md (@peterfich)3dd429e9a#1377
Add note about dropped * filenames (@maxwellgerber)9a2e2e797#1429 Fix typo (@seanpoulter)<< Arborist Deep Dive >> Beta Release!
One common question we’ve gotten a few times now, once we announce that npm v7 will include support for yarn.lock files, is “Why keep package-lock.json at all, then? Why not just use yarn.lock only?”
The simple answer is: because yarn.lock doesn’t fully address npm’s needs, and relying on it exclusively would limit our ability to produce optimal package installs or add features in the future.
yarn.lock FileA yarn.lock file is a map of requested dependency specifiers to metadata describing their resolution. For example:
mkdirp@1.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.2.tgz#5ccd93437619ca7050b538573fc918327eba98fb"
integrity sha512-N2REVrJ/X/jGPfit2d7zea2J1pf7EAR5chIUcfHffAZ7gmlam5U65sAm76+o4ntQbSRdTjYf7qZz3chuHlwXEA==
This says “Any dependency on mkdirp@1.x should resolve to this exact thing”. If multiple packages depend on mkdirp@1.x, they’ll all get the same resolution.
In npm v7, if a yarn.lock file exists, npm will use the metadata it contains. The resolved values will tell it where to fetch packages from, and the integrity will be used to check that the result matches expectations. If packages are added or removed, then the yarn.lock file will be updated.
npm will still create a package-lock.json file, and if a package-lock.json file is present, it’ll be used as the authoritative definition of the tree shape to create.
So if it’s good enough for Yarn, why doesn’t npm just use that?
Yarn installs are guaranteed to be deterministic given a single combination of yarn.lock and Yarn version. It is possible that a different version of Yarn will result in a different tree layout on disk.
A yarn.lock file does guarantee deterministic resolutions of dependencies. For example, if foo@1.x resolves to foo@1.2.3, it’ll continue to resolve to that version number in subsequent installs for all Yarn versions, given a consistent yarn.lock file. But that (at least, in itself) is not equivalent to guaranteeing a deterministic tree shape!
Consider this dependency graph:
root -> (foo@1, bar@1)
foo -> (baz@1)
bar -> (baz@2)
Either of these package trees would be just as correct as the other:
root
+-- foo
+-- bar
| +-- baz@2
+-- baz@1
~~ OR ~~
+-- foo
| +-- baz@1
+-- bar
+-- baz@2
A yarn.lock file can’t tell you which one to use. If the root package (incorrectly, as it’s an unlisted dep) does require("baz"), the result would not be guaranteed by the yarn.lock file. This is a form of determinism that the package-lock.json file can provide, and a yarn.lock file cannot.
In practice, of course, since Yarn has all the required information in the yarn.lock file to make this choice, it is deterministic as long as everyone is using the same version of Yarn, so that the choice is being made in exactly the same way. Code doesn’t change unless someone changes it. To its credit, Yarn is smart enough to not be subject to discrepancies in package manifest load times when building the tree, or else determinism would not be guaranteed.
As this is defined by the particulars of Yarn’s algorithm rather than by the data structure on disk (which does not identify the algorithm to be used), that determinism guarantee is fundamentally weaker than what a package-lock.json provides by fully specifying the shape of the package tree on disk.
In other words, the Yarn tree building contract is split between the yarn.lock file and the implementation of Yarn itself. The npm tree building contract is entirely specified by the package-lock.json file. This makes it much harder for us to break by accident across npm versions, and if we do (whether by mistake or on purpose), the change will be reflected in the file in source control.
Furthermore, there is a class of nesting and deduplication cases where the yarn.lock file does not accurately reflect the resolutions that will be used by npm in practice, even when npm does use it as a source of metadata. While npm uses the yarn.lock file as a reliable source of information, it does not treat it as an authoritative set of constraints.
In some cases Yarn produces a tree with excessive duplication, which we don’t want to do. So, following the Yarn algorithm exactly isn’t ideal in these cases.
Consider this dependency graph:
root -> (x@1.x, y@1.x, z@1.x)
x@1.1.0 -> ()
x@1.2.0 -> ()
y@1.0.0 -> (x@1.1, z@2.x)
z@1.0.0 -> ()
z@2.0.0 -> (x@1.x)
The root project depends on version 1.x of x, y, and z. The y package depends on x@1.1 and z@2. z at version 1 has no dependencies, but z at version 2 depends on x@1.x.
The resulting tree shape that npm produces looks like this:
root (x@1.x, y@1.x, z@1.x) <-- x@1.x dep here
+-- x 1.2.0 <-- x@1.x resolves to 1.2.0
+-- y (x@1.1, z@2.x)
| +-- x 1.1.0 <-- x@1.x resolves to 1.1.0
| +-- z 2.0.0 (x@1.x) <-- x@1.x dep here
+-- z 1.0.0
z@2.0.0 depends on x@1.x, and so does the root project. The yarn lock file maps x@1.x to 1.2.0. However, the dependency from the z package, which also specifies x@1.x, will get x@1.1.0 instead.
That is, even though the x@1.x dependency has a resolution in the yarn.lock file stipulating that it should resolve to version 1.2.0, there is a second x@1.x resolution which instead resolves to 1.1.0.
If run with the --prefer-dedupe flag on npm, it’d go a step further, and only install a single instance of x, like this:
root (x@1.x, y@1.x, z@1.x)
+-- x 1.1.0 <-- x@1.x resolves to 1.1.0 for everyone
+-- y (x@1.1, z@2.x)
| +-- z 2.0.0 (x@1.x)
+-- z 1.0.0
This minimizes duplication, and the resulting package tree is captured in the package-lock.json file.
Because yarn.lock only locks down resolutions instead of locking down the resulting package tree, Yarn produces this tree instead:
root (x@1.x, y@1.x, z@1.x) <-- x@1.x dep here
+-- x 1.2.0 <-- x@1.x resolves to 1.2.0
+-- y (x@1.1, z@2.x)
| +-- x 1.1.0 <-- x@1.x resolves to 1.1.0
| +-- z 2.0.0 (x@1.x) <-- x@1.1.0 would be fine, but...
| +-- x 1.2.0 <-- Yarn dupes to satisfy yarn.lock resolution
+-- z 1.0.0
The x package appears three times in the Yarn implementation, twice in the default npm implementation, and only once (albeit, not the latest and greatest version) in npm’s --prefer-dedupe algorithm.
All three resulting trees are “correct”, in the sense that every package is getting a version of their dependencies that matches their stated requirements. But, we do not want to create package trees with excessive duplication. Consider what would happen if x was a large package with a lot of dependencies of its own!
So, the only way that npm can optimize a package tree, while maintaining deterministic reproducible builds, is to use a fundamentally different sort of lock file.
As mentioned above, in npm v7, a user can use --prefer-dedupe to have the tree generation algorithm prefer deduplication rather than always updating to latest. This is usually best in any scenario where duplication should be minimized.
If that config flag is set, then the resulting tree for the example above would look like this:
root (x@1.x, y@1.x, z@1.x) <-- x@1.x dep here
+-- x 1.1.0 <-- x@1.x resolves to 1.1.0 for everyone
+-- y (x@1.1, z@2.x)
| +-- z 2.0.0 (x@1.x) <-- x@1.x dep here
+-- z 1.0.0
In this case, npm sees that, even though x@1.2.0 is the latest package version that satisfies the x@1.x requirement, choosing x@1.1.0 instead would still be acceptable, and would result in less duplication.
Without capturing the tree shape in the lockfile, every user working on the project would have to configure their client exactly the same way to get the same results. When the “implementation” can be changed by the user in this way, this gives them a lot of power to optimize for their specific conditions. But, it also makes deterministic builds impossible if the contract is implementation-dependent, which yarn.lock is.
Other examples where the algorithm would be different are:
--legacy-peer-deps, which tells npm to completely ignore peerDependencies--legacy-bundling, which tells npm to not even try to flatten the tree--global-style, which installs all transitive dependencies nested under their top-level dependentsCapturing the result of resolutions, and relying on the algorithm to be consistent, doesn’t work when we give the user the ability to tweak the package installation algorithm in use.
Locking down the resulting tree shape allows us to ship features like this without breaking our contract to provide deterministic reproducible builds.
The package-lock.json file is not only useful for ensuring deterministically reproducible builds. We also lean on it to track and store package metadata, saving considerably on package.json reads and requests to the registry. Since the yarn.lock file is so limited, it doesn’t have the metadata that we need to load on a regular basis.
In npm v7, the package-lock.json file contains everything npm will need to fully build the package tree. (This data is spread out in npm v6, so when we see an older lockfile, we have to do a bit of extra digging up front, but that’s a one-time hit.)
So, even if it did capture tree shape, we’d still have to use a file other than yarn.lock to track this extra metadata.
Approaches to package dependency layout on disk such as pnpm, yarn 2/berry, and Yarn’s PnP, can change the context of this calculation considerably.
We intend to explore a virtual file system approach in npm v8, modeled on Tink, the proof of concept Kat Marchán wrote in 2019. We’ve also talked about migrating to something like pnpm’s layout structure, though this is in some ways an even bigger breaking change than Tink would be.
If all dependencies are stored in a central location, and only simulated in their nested locations via symbolic links or a virtual filesystem, then modeling the tree shape is far less of a concern. However, we’d still need more metadata than the yarn.lock file provides, and thus, it would make more sense to update and streamline our existing package-lock.json format rather than rely on yarn.lock.
I want to be very clear that, as far as I’ve ever been able to determine, Yarn reliably produces correct package dependency resolutions. And, for a given Yarn version (all recent Yarn versions, as of this writing), it is fully deterministic, just like npm.
While it is good that the yarn.lock file is sufficient for a specific version of Yarn to generate deterministic builds, relying on an implementation-dependent contract is not acceptable for use across multiple tools. This is all the more true by virtue of the fact that the implementation and yarn.lock format are not documented or specified in any formal way. (This isn’t a dig on Yarn; npm’s aren’t either. Doing so will be quite a bit of work.)
The best way to fully ensure build reliability and strict determinism for the long term is to lock down the results of the build process in the contract itself, rather than naively trusting that future implementations will continue to make the same choices, and effectively limiting our ability to design an optimized package tree.
Deviations from that contract must be a result of explicit user intent, and self-documenting by virtue of updating the saved contract on completion.
Only package-lock.json or something like it can provide this functionality for npm.