From 73603ac6607b135ef6b08f123931b3d91ac7913a Mon Sep 17 00:00:00 2001 From: Shalvah Date: Wed, 30 Jun 2021 15:23:41 +0100 Subject: [PATCH 1/3] Create 000-package-resolution-nodejs-version.md --- .../000-package-resolution-nodejs-version.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 accepted/000-package-resolution-nodejs-version.md diff --git a/accepted/000-package-resolution-nodejs-version.md b/accepted/000-package-resolution-nodejs-version.md new file mode 100644 index 000000000..278f00ac3 --- /dev/null +++ b/accepted/000-package-resolution-nodejs-version.md @@ -0,0 +1,84 @@ +# Resolve packages based on Node.js version + +## Summary + +npm should be able to accept a semver range of a package and install the highest available version that supports the current Node.js version. + +## Motivation + +Developers often make apps/libraries that need to support multiple versions of the same package, which have different minimum Node.js versions. + +A good use case for this is to allow "progressive upgrades": +- my library supports Node.js 10.x and above +- package X releases `newVersion`, which supports Node.js 12.x and above +- I change my package X constraint from `"oldVersion"` to `"oldVersion || newVersion"` +- My code will then use `newVersion` when my code is running on 12.x, and fallback to `oldVersion` when running on 10.x +- This gives several benefits: + - I can test out `newVersion` locally over time before upgrading the production server's Node.js version + - I can support older Node.js versions while benefitting from package X's newer versions on newer Node.js versions + +Right now, npm always installs the highest available version of the package, even if it isn't supported on my Node.js version, making this hard. + +For instance, Mocha v9 [dropped support for Node.js 10.x](https://github.com/mochajs/mocha/pull/4633) (using the `engines` field). However, in my library, Node.js 10.x is still supported for end users, so I'd like to use Mocha v8 to run tests on Node.js 10.x and v9 on 12.x. There are no major API changes, so this should be easy to do. + +However, specifying a semver range like `"mocha": "^8.0.0 || ^9.0.0"` doesn't work. npm happily installs v9 even on Node.js 10.x (although it outputs a warning): + +``` +❯ npm i +npm WARN EBADENGINE Unsupported engine { package: 'mocha@9.0.1', +npm WARN EBADENGINE required: { node: '>= 12.0.0' }, +npm WARN EBADENGINE current: { node: 'v10.24.1', npm: '7.17.0' } } + +added 92 packages, and audited 93 packages in 1s + +❯ npm list mocha +in@ C:\Users\shalvah\ +`-- mocha@9.0.1 +``` + +With the `--engine-strict` flag, the install is stopped: + +``` +❯ npm i --engine-strict +npm ERR! code EBADENGINE +npm ERR! engine Unsupported engine +npm ERR! engine Not compatible with your version of node/npm: mocha@9.0.1 +npm ERR! notsup Not compatible with your version of node/npm: mocha@9.0.1 +npm ERR! notsup Required: {"node":">= 12.0.0"} +npm ERR! notsup Actual: {"npm":"7.17.0","node":"v10.24.1"} +``` + +## Detailed Explanation + +npm should take the current Node.js version into consideration when determining valid package versions to install. In the example above, npm should install Mocha v8 when on Node.js 10.x, and Mocha v9 when on Node.js 12.x. + +## Rationale and Alternatives + +Current workarounds: +1. Maintain separate copies of `package.json` for different Node.js versions (such as a `package.json` and a `package.10.x.json`, with the latter referencing the older version of the package), and switch between them when on a different Node.js version +2. Manually replace the package version in `package.json` when on a different Node.js version. + +Both these options are clunky, as they involve either manual work or additional scripting. + +## Implementation + +During install: +- Fetch package versions satisfying the semver range +- Get the current Node.js version (`process.version`). If there is an `engines.node` field in the root package, this overrides the value from `process.version`. +- Find the highest which supports the current Node.js version. + - If there is none, fallbback to the current behaviour (install the highest available, or fail if `--engine-strict` is set). + - If there are valid versions for this Node.js version, install the highest of those. + + +During `npm ci`, the current behaviour should be retained (install exactly from lockfile). The engine-aware behaviour will only be activated when using `npm install`. + +## Prior Art + +- In the PHP world, Composer will not install any packages that have their `config.platform.php` set to a higher range than your PHP version. + +## Unresolved Questions and Bikeshedding + +- I think this behaviour should be the default, with or without `engine-strict`. As a user, running `npm install` and getting package versions that will crash on my machine is counter-intuitive. +- I'm not sure if this should also apply to the current npm version as well. + +{{THIS SECTION SHOULD BE REMOVED BEFORE RATIFICATION}} From e0386f1d8a4aa9087acd8f931a780724cb94374d Mon Sep 17 00:00:00 2001 From: Shalvah Date: Wed, 30 Jun 2021 15:43:58 +0100 Subject: [PATCH 2/3] Update 000-package-resolution-nodejs-version.md --- accepted/000-package-resolution-nodejs-version.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/accepted/000-package-resolution-nodejs-version.md b/accepted/000-package-resolution-nodejs-version.md index 278f00ac3..502124db2 100644 --- a/accepted/000-package-resolution-nodejs-version.md +++ b/accepted/000-package-resolution-nodejs-version.md @@ -19,9 +19,13 @@ A good use case for this is to allow "progressive upgrades": Right now, npm always installs the highest available version of the package, even if it isn't supported on my Node.js version, making this hard. +## Detailed Explanation + +npm should take the current Node.js version into consideration when determining valid package versions to install. + For instance, Mocha v9 [dropped support for Node.js 10.x](https://github.com/mochajs/mocha/pull/4633) (using the `engines` field). However, in my library, Node.js 10.x is still supported for end users, so I'd like to use Mocha v8 to run tests on Node.js 10.x and v9 on 12.x. There are no major API changes, so this should be easy to do. -However, specifying a semver range like `"mocha": "^8.0.0 || ^9.0.0"` doesn't work. npm happily installs v9 even on Node.js 10.x (although it outputs a warning): +Currently, specifying a semver range like `"mocha": "^8.0.0 || ^9.0.0"` doesn't work. npm happily installs v9 even on Node.js 10.x (although it outputs a warning): ``` ❯ npm i @@ -48,9 +52,7 @@ npm ERR! notsup Required: {"node":">= 12.0.0"} npm ERR! notsup Actual: {"npm":"7.17.0","node":"v10.24.1"} ``` -## Detailed Explanation - -npm should take the current Node.js version into consideration when determining valid package versions to install. In the example above, npm should install Mocha v8 when on Node.js 10.x, and Mocha v9 when on Node.js 12.x. +The goal is to change this so that, on Node.js 10.x, version 8 is installed instead in both cases. ## Rationale and Alternatives @@ -62,13 +64,14 @@ Both these options are clunky, as they involve either manual work or additional ## Implementation -During install: +During `npm install`: - Fetch package versions satisfying the semver range - Get the current Node.js version (`process.version`). If there is an `engines.node` field in the root package, this overrides the value from `process.version`. - Find the highest which supports the current Node.js version. - If there is none, fallbback to the current behaviour (install the highest available, or fail if `--engine-strict` is set). - If there are valid versions for this Node.js version, install the highest of those. +This will apply all the way down the tree. During `npm ci`, the current behaviour should be retained (install exactly from lockfile). The engine-aware behaviour will only be activated when using `npm install`. From 6488fa8988c8a44652989b6994b848b44471c3c6 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Wed, 30 Jun 2021 16:34:47 +0100 Subject: [PATCH 3/3] Update 000-package-resolution-nodejs-version.md --- accepted/000-package-resolution-nodejs-version.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/accepted/000-package-resolution-nodejs-version.md b/accepted/000-package-resolution-nodejs-version.md index 502124db2..c27673b32 100644 --- a/accepted/000-package-resolution-nodejs-version.md +++ b/accepted/000-package-resolution-nodejs-version.md @@ -68,16 +68,18 @@ During `npm install`: - Fetch package versions satisfying the semver range - Get the current Node.js version (`process.version`). If there is an `engines.node` field in the root package, this overrides the value from `process.version`. - Find the highest which supports the current Node.js version. - - If there is none, fallbback to the current behaviour (install the highest available, or fail if `--engine-strict` is set). + - If there is none, fallback to the current behaviour (install the highest available, or fail if `--engine-strict` is set). - If there are valid versions for this Node.js version, install the highest of those. This will apply all the way down the tree. +A package is defined as supporting a Node.js version if it does not define any `engines.node` constraint, or defines an `engines.node` constraint that is satisfied by that version. + During `npm ci`, the current behaviour should be retained (install exactly from lockfile). The engine-aware behaviour will only be activated when using `npm install`. ## Prior Art -- In the PHP world, Composer will not install any packages that have their `config.platform.php` set to a higher range than your PHP version. +- In the PHP world, Composer does this—it will look for package versions with a declared `php` constraint that match your local version, or with no `php` constraints at all. You can override your "local version" with the `config.platform.php` field, similar to `engins.node`. ## Unresolved Questions and Bikeshedding