setup-node icon indicating copy to clipboard operation
setup-node copied to clipboard

Enforce `engineStrict`

Open piranna opened this issue 10 months ago • 8 comments

Description:

Add a new value engineStrict for the node-version config field, so it will pick the Node.js version number from package.json file engineStrict field. This will require that the repository source code is checkout before using setup-node action, but can be defined in the documentation, or exit with an error if the package.json file is not found. If package.json file is found but the engineStrict field is not available, then we can use the default Node.js version, showing a warning instead.

Justification:

To have a single place to config the Node.js version value instead of two (package.json and GitHub Action yaml file).

Are you willing to submit a PR?

Yes, I can do it, if there's interest on merging it.

piranna avatar Apr 24 '25 08:04 piranna

Hi @piranna, Thank you for creating this feature request. We will investigate it and provide feedback as soon as we have some updates.

mahabaleshwars avatar Apr 25 '25 10:04 mahabaleshwars

engines and engineStrict might not be the correct fields—they specify the runtime environment of the package and not the runtime for the development of the package …

npm engine-strict package.json - engines

I think you might want to look at https://github.com/actions/setup-node/issues/1255; then close this one and upvote the other (or its PR https://github.com/actions/setup-node/pull/1283).

sdavids avatar Jul 11 '25 09:07 sdavids

they specify the runtime environment of the package and not the runtime for the development of the package

Why somebody would want to have a different version of Node.js at development from the one at runtime? Yes, that's possible, but also a source of problems...

piranna avatar Jul 11 '25 09:07 piranna

devEngines doesn't have any way to define the actual versions, or at least it's not clearly defined. It's not a "dev only" replacement for engines and engineStrict fields.

Also, package.json file engineStrict have been deprecated (I don't understand why) in benefit of the engineStrict field from .npmrc file, so we should look for both, having .npmrc higher precedence.

piranna avatar Jul 11 '25 09:07 piranna

Why somebody would want to have a different version of Node.js at development from the one at runtime? Yes, that's possible, but also a source of problems...

E.g. support wide number of runtime engines, e.g. Node 20, 22, and 24. But enforce developers to use only one of them, e.g. Node 24. So that development environment is always the same for all.

susnux avatar Jul 11 '25 10:07 susnux

devEngines doesn't have any way to define the actual versions, or at least it's not clearly defined. It's not a "dev only" replacement for engines and engineStrict fields.

Why not? You can define versions like:

"engines": {
    "node": "^20.11.0 || ^22 || ^24",
  },
  "devEngines": {
    "packageManager": {
      "name": "npm",
      "version": "^10",
      "onFail": "error"
    },
    "runtime": {
      "name": "node",
      "version": "^22",
      "onFail": "error"
    }
  }

There you also can define how strict this is by setting onFail to either warning, error or ignore.

So basically it is that: A dev only replacement for engines and engineStrict as with onFail you set how strict you want that to be enforced.

susnux avatar Jul 11 '25 10:07 susnux

devEngines doesn't have any way to define the actual versions

Please read more closely …

https://docs.npmjs.com/cli/v11/configuring-npm/package-json#devengines

The supported keys under the devEngines property are [...] runtime [...] optionally can specify version

The version is defined like the other versions in packages.json, i.e. it can be as "strict" as you need it to be.

Their example could be improved though …


Why somebody would want to have a different version of Node.js at development from the one at runtime?

This more common than you think.

Take lodash for example

lodash/package.json at 8a26eb42adb303f4adc7ef56e300f14c5992aa68 · lodash/lodash

"engines": {
  "node": ">=4.0.0"
},

Let’s say they want to use Vitest as their testing framework

vitest/package.json at c1f78d2adc78ef08ef8b61b0dd6a925fb08f20b6 · vitest-dev/vitest

"engines": {
  "node": "^18.0.0 || >=20.0.0"
},

Should lodash now bump their own engines to be able to use vitest? — No!

That is why there is devEngines in addition to engines:

devEngines defines what node version you need to build/test the package. engines defines what node version the consumer of your package needs.


Yes, that's possible, but also a source of problems

You could use TypeScript to check if you are using only compatible JavaScript in your project; something along the lines:

package.json

"engines": {
  "node": ">=8.0.0"
},
"devEngines": {
  "runtime": {
    "name": "node",
    "version": "22.17.0"
  },
},

jsconfig.json

{
  "compilerOptions": {
    "include": [/* tailored to only include your sources, i.e. not tests, config, etc. */],
    "lib": ["es2017"],
    "module": "commonjs",
    "target": "es2017"
  }
}

Node Target Mapping

This does not help with the Node APIs themselves though; unfortunately, I am not aware of a project like Animal Sniffer for the Node ecosystem.

You could do something like the following in CI though:

  1. Build the package with devEngine and store the package as artifact(s) in one step.
  2. Load the artifact(s) and run integration tests with engine in another step.
  3. Load the artifact(s) and publish the package in another step.

"Integration test" would mean:

Define a new package.json with your package as a local path dependency.

Then test the API through this consumer package.

sdavids avatar Jul 11 '25 12:07 sdavids

Their example could be improved though …

Definitely yes, thank you for point it out.

devEngines defines what node version you need to build/test the package. engines defines what node version the consumer of your package needs.

If we consider transpiling code with Babel or alike, then yes, it makes sense to have different versions, but still, you can still code with Node.js v20 for example... Not sure how much common is to support obsolete versions like v4... personally long time ago I stopped giving support for not maintained versions of Node.js, and still struggle to support maintained versions that are not latest LTS. Definitely I would love to see some usage stats of each Node.js version (not for this topic, but in general).

You could do something like the following in CI though:

  1. Build the package with devEngine and store the package as artifact(s) in one step.
  2. Load the artifact(s) and run integration tests with engine in another step.
  3. Load the artifact(s) and publish the package in another step.

"Integration test" would mean:

Define a new package.json with your package as a local path dependency.

Then test the API through this consumer package.

That looks like an interesting approach, thank you for the idea.


Retaking the topic of the issue, what fields would be good to look for and where to get them, instead of hardcoding a value on the GitHub workflow that later needs to be synchronized?

piranna avatar Jul 20 '25 18:07 piranna

Hello Everyone, Thanks for raising this. The intent here overlaps with #1255, which focuses on supporting engines / devEngines in package.json when using node-version-file, so users don’t have to duplicate version config in their workflow.

Introducing a separate engineStrict mode for node-version could be useful, but it also adds complexity around precedence, error behavior, and consistency with existing version resolution logic. From a maintainer perspective, it may make sense to first support reading engines / devEngines more broadly as part of the default behavior, and then evaluate whether stricter enforcement is needed based on real-world demand.

Please feel to reach us to discuss further if there are specific workflows where strict enforcement provides clear additional value.

aparnajyothi-y avatar Feb 06 '26 05:02 aparnajyothi-y