701

I have a Node.js project that requires Node version 12 or higher. Is there a way to specify this in the packages.json file, so that the installer will automatically check and inform the users if they need to upgrade?

SharpC
  • 6,974
  • 4
  • 45
  • 40
Erel Segal-Halevi
  • 33,955
  • 36
  • 114
  • 183
  • 2
    A similar way to Adam's response, also using node.version: https://stackoverflow.com/a/48691987/3032209 – Yair Kukielka Feb 08 '18 at 18:08
  • Possible duplicate of [How to enforce a specific node.js version to use?](https://stackoverflow.com/questions/28409883/how-to-enforce-a-specific-node-js-version-to-use) – cilap Feb 06 '19 at 15:09
  • Question was already asked here: How to enforce a specific node.js version to use? – cilap Feb 06 '19 at 15:09
  • I wonder if there is any tool that can automatically set this field to an appropriate value by inspecting API usage. – geekley Dec 05 '19 at 05:00
  • NOTE: If using `yarn` version 2+, you will need to install the yarn engines plugin found here: https://github.com/devoto13/yarn-plugin-engines – Joshua Pinter Oct 26 '21 at 16:41

7 Answers7

859

You can set the engines field in your package.json and set requirements for either node or npm versions or both:

  "engines" : { 
    "npm" : ">=8.0.0 <9.0.0",
    "node" : ">=16.0.0 <17.0.0"
  }

To enforce this via npm you need to create an .npmrc file (and commit it to the repository) and set the engines-strict option to true, which will cause npm commands such as npm install to fail if the required engine versions to not match:

# .npmrc
engine-strict=true

Without that file, every developer will need to run npm config set engine-strict true in their local workspace to switch on this option.

Original Answer

As you're saying your code definitely won't work with any lower versions, you probably want the "engineStrict" flag too:

{ "engineStrict" : true }

Documentation for the package.json file can be found on the npmjs site

Update

engineStrict is now deprecated, so this will only give a warning. It's now down to the user to run npm config set engine-strict true if they want this.

Update 2

As ben pointed out below, creating a .npmrc file at the root of your project (the same level as your package.json file) with the text engine-strict=true will force an error during installation if the Node version is not compatible.

Reza
  • 18,865
  • 13
  • 88
  • 163
IBam
  • 10,114
  • 1
  • 24
  • 36
  • 20
    https://github.com/npm/npm/blob/master/CHANGELOG.md#enginestrict "The rarely-used package.json option `engineStrict` has been **deprecated** for several months, producing warnings when it was used. Starting with npm@3, the value of the field is ignored, and engine violations will only produce warnings. If you, as a user, want strict engines field enforcement, just run npm config set engine-strict true" – Mike Dec 24 '15 at 02:22
  • 1
    Remember to `cd .. && npm i ` in order to check for the project itself. However, this will trigger a whole build in it self. – mlunoe Jul 26 '16 at 23:20
  • 11
    why on earth they deprecated that.. it looses all its meaning then – vasilakisfil May 10 '17 at 13:52
  • 39
    Adding `engine-strict=true` to your .npmrc now has the same effect – ben Jun 13 '18 at 05:23
  • 4
    @ben Perfect, thank you! And this can be committed so that at least your entire team is required to adhere to the engine version requirements. – Joshua Pinter Aug 03 '19 at 04:25
  • @MikeStead that deprecation is for the package.json directive `engineStrict` and not the user-configurable `engine-strict` setting for NPM – Joe Feb 08 '20 at 16:27
  • 1
    @Joe erm yep I was replying to this answer (in 2015) which mentioned `engineStrict` specifically and my reply also mentioned the alternative `engine-strict` npm config option. – Mike Feb 10 '20 at 05:01
  • I dont't get a warning on windows when `engine-strict=false` and `{ "engineStrict" : true }` – Ini Dec 14 '20 at 21:40
  • Just to be clear, adding `engine-strict=true` to *your own* .npmrc file does not enforce anything for your end user, it only enforces you to use the right engine for the packages that *you* install. There’s nothing you can do to enforce that *your end users* use the right engine, short of you telling them to add the setting to *their* .npmrc file. – chharvey Dec 27 '20 at 15:29
  • `npm install` respects `engine=strict` but `yarn install` ignores it – rofrol Oct 25 '21 at 18:37
  • 1
    must be `>=12` not `>=0.12` – milahu Nov 10 '21 at 07:06
  • how can i set this with yarn – olawalejuwonm Jan 10 '22 at 10:26
  • But how do I know the minimum required node version of the node proejct I've made? Please let me know if anyone has a clue. thanks. – Mocha Mar 10 '22 at 05:48
  • May I know what is the deal with node version in `package.json` shown as `0.10.0` but in CLI it says `10.0.x`? – Rajesh Swarnkar Mar 24 '22 at 03:55
  • This doesn't work without also adding node-version to your .npmrc. E.g. `node-version = >=14.0.0||<15.0.0`. This will prevent `npm i` from running if the user is not using `node 14` for example. The only downside is npm complains about semver matching: `npm WARN invalid config Must be full valid SemVer string`. Not a major issue, this still works. – FranCarstens Jul 22 '22 at 16:54
337

Add the following to package.json:

  "engines": {
    "npm": ">=8.0.0 <9.0.0",
    "node": ">=16.0.0 <17.0.0"
  },

Add the following to .npmrc (same directory as package.json):

engine-strict=true
Reza
  • 18,865
  • 13
  • 88
  • 163
Mikel
  • 5,902
  • 5
  • 34
  • 49
  • 5
    This is the easiest solution that gives the end user a nice fat error about not having the right version of node when they run `npm install`; works with `yarn` as well – jcollum Jan 04 '19 at 18:35
  • 7
    This seems to have no effect at all. I set up my `package.json` with an "engines" section similar to the above (`11.13.0` and `6.7.0`), and a `.npmrc` with nothing but content specified above. I had nvm switch me to an older node version, then ran `npm install`, but it just installs the dependencies and doesn't even mention the engine version mismatch. – Adrian Apr 02 '19 at 19:03
  • 3
    Adding `engine-strict=true` to *your* .npmrc file only enforces *you* to use the right engine when *you* install packages. **It does not enforce anything for your end user.** If you want your users to use the engines listed under the `"engines: {}"` property in your package.json when they install it, you should tell *them* to add `engine-strict=true` to *their* .npmrc file. – chharvey Dec 27 '20 at 15:34
  • 3
    @chharvey you could add to the `package.json` the script `"preinstall": "echo 'engine-strict=true' >> .npmrc"` – Mikel May 05 '21 at 17:21
  • `engine-strict` usage in `.npmrc` is currently not supported by direnv's `.envrc` https://github.com/direnv/direnv/wiki/Node (Found '.nvmrc' with version `engine-strict=true`N/A: version "engine-strict=true -> N/A" is not yet installed. – svandragt Aug 12 '21 at 09:39
73

Just like said Ibam, engineStrict is now deprecated. But I've found this solution:

check-version.js:

import semver from 'semver';
import { engines } from './package';

const version = engines.node;
if (!semver.satisfies(process.version, version)) {
  console.log(`Required node version ${version} not satisfied with current version ${process.version}.`);
  process.exit(1);
}

package.json:

{
  "name": "my package",
  "engines": {
    "node": ">=50.9" // intentionally so big version number
  },
  "scripts": {
    "requirements-check": "babel-node check-version.js",
    "postinstall": "npm run requirements-check"
  }
}

Find out more here: https://medium.com/@adambisek/how-to-check-minimum-required-node-js-version-4a78a8855a0f#.3oslqmig4

.nvmrc

And one more thing. A dotfile '.nvmrc' can be used for requiring specific node version - https://github.com/creationix/nvm#nvmrc

But, it is only respected by npm scripts (and yarn scripts).

Adam
  • 1,779
  • 2
  • 15
  • 14
62

.nvmrc

If you are using NVM like this, which you likely should, then you can indicate the nodejs version required for given project in a git-tracked .nvmrc file:

node --version > .nvmrc

or:

echo v10.15.1 > .nvmrc

This does not take effect automatically on cd, which is sane: the user must then do a:

nvm use

and now that version of node will be used for the current shell.

You can list the versions of node that you have with:

nvm list

.nvmrc is documented at: https://github.com/creationix/nvm/tree/02997b0753f66c9790c6016ed022ed2072c22603#nvmrc

How to automatically select that node version on cd was asked at: Automatically switch to correct version of Node based on project

Tested with NVM 0.33.11.

.nvmrc vs package.json engines

What you might want to do is:

much like package.json vs package-lock.json.

Heroku does respect package.json engines:

Worth mentioning, as documented here, Heroku does play it nice and obey the engines: entry e.g.:

  "engines": {
    "node": "14.17.0",
    "npm": "6.14.13"
  },

So you should Always, Always set that to what you are using locally.

This had been previously mentioned on this self deleted answer to this thread.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • `nvm use` doesn't pick up .nvmrc for nvm version 1.1.7 – Aakash Verma Jan 07 '21 at 20:58
  • @AakashVerma hmmm, on a quick look nvm only goes to version 0.37.2, and nvmrc is still documented on master: https://github.com/nvm-sh/nvm/tree/436cda247e1f5f96bf20df8d49f968dd4d3a9523#nvmrc let me know if you figure it out. – Ciro Santilli OurBigBook.com Jan 07 '21 at 23:55
  • 2
    @AakashVerma I'm guessing you're using https://github.com/coreybutler/nvm-windows/releases As mentioned on their README "The original nvm is a completely separate project for Mac/Linux only. This project uses an entirely different philosophy and is not just a clone of nvm" so it's not surprising. Consider opening a feature request on their tracker. – Ciro Santilli OurBigBook.com Jan 08 '21 at 08:43
  • 2
    Seems there's a recent PR waiting for this https://github.com/coreybutler/nvm-windows/pull/594 – Aakash Verma Jan 08 '21 at 14:47
17

There's another, simpler way to do this:

  1. npm install Node@8 (saves Node 8 as dependency in package.json)
  2. Your app will run using Node 8 for anyone - even Yarn users!

This works because node is just a package that ships node as its package binary. It just includes as node_module/.bin which means it only makes node available to package scripts. Not main shell.

See discussion on Twitter here: https://twitter.com/housecor/status/962347301456015360

vnglst
  • 622
  • 5
  • 11
  • 7
    I disagree, this would potentially hide the issue and would sideload a different version of node if it wasn't installed. – Brendan Hannemann Mar 14 '18 at 17:44
  • 13
    -1 because this is terrible (really terrible) idea. It's like saying that if you are unemployed you should fund a company first and you can start working there. – ozanmuyes Apr 04 '18 at 17:46
  • 4
    Sounds like a great idea to me. Separate node versions for separate projects. Can safely upgrade one without upgrading the others. Only catch is have to run in .bin `./node node-sass` rather than just `node-sass`. Not sure if same for all .bin files. – Jon Aug 10 '18 at 05:56
  • 4
    This is a simple and elegant solution - as long as the team members working on the product know this is happening, I think it's a great answer. We are using this technique at a large company to deal with the variety of Node versions for a dozen web front-end products. Removes the need for constant switching with nvm when going back and forth between products. – Nathan Bedford Sep 29 '19 at 14:53
  • 5
    This solution has its own pros and cons. Node version encapsulation is potentially its biggest pro. The downside is bloated docker image size if you are going to deploy it this way. – ivosh Oct 02 '19 at 17:59
  • Am I misunderstanding something but, couldn't this be installed as a 'dev' dependency, so `npm I -D Node@8`, I'm guessing it wouldn't (shouldn't) then be bundled into a Docker image (or executable), which would simply provide it's own NodeJS runtime, right? – Big Rich Apr 13 '22 at 16:23
  • @Brendan Hannemann ,why would it not being installed? shouldn't you always run `npm install` at least once before running the app? This is the best solution IMHO as it's basically packaging a virtual environment with the app which means it will always run with exactly the same nodejs environment for everyone as long as `npm install` finished successfully. – hellopeach Apr 27 '22 at 12:03
  • Does this account for node requirements in dependencies? F.e. node-sass and many other libs uses/builds different executables for different node versions, if install performed under node 8 and package.json depends on node 16, would then node-sass be built for node 16, or node 8? If node dependency restriction is accounted in other dependencies, then this might be a convenient trick. – ankhzet Aug 21 '22 at 04:02
5

Here's my complete ready-to-use script, based on Adam's answer.

check-version.js :

/* eslint-disable no-console */
const fs = require('fs');
const semver = require('semver');
const childProcess = require('child_process');

// checks that current node and npm versions satisfies requirements in package.json
// to run manually:   node check-version.js [verbose]

const VERBOSE_FORCED = false;    
const args = process.argv.slice(2);
const VERBOSE = VERBOSE_FORCED || (args.length > 0 && args[0] === 'verbose');

const printErrAndExit = (x) => {
  console.error(x);
  console.error('Aborting');
  process.exit(1);
};

const checkNpmVersion = (npmVersionRequired) => {
  if (!npmVersionRequired) {
    console.log('No required npm version specified');
    return;
  }
  const npmVersion = `${childProcess.execSync('npm -v')}`.trim();
  if (VERBOSE) console.log(`npm required: '${npmVersionRequired}' - current: '${npmVersion}'`);
  if (!semver.satisfies(npmVersion, npmVersionRequired)) {
    printErrAndExit(`Required npm version '${npmVersionRequired}' not satisfied. Current: '${npmVersion}'.`);
  }
};

const checkNodeVersion = (nodeVersionRequired) => {
  if (!nodeVersionRequired) {
    console.log('No required node version specified');
    return;
  }
  const nodeVersion = process.version;
  if (VERBOSE) console.log(`node required: '${nodeVersionRequired}' - current: '${nodeVersion}'`);
  if (!semver.satisfies(nodeVersion, nodeVersionRequired)) {
    printErrAndExit(`Required node version '${nodeVersionRequired}' not satisfied. Current: '${nodeVersion}'.`);
  }
};

const json = JSON.parse(fs.readFileSync('./package.json'));
if (!json.engines) printErrAndExit('no engines entry in package json?');
checkNodeVersion(json.engines.node);
checkNpmVersion(json.engines.npm);

It should be placed in the root project directory.

It checks node and/or npm version, as specified in package.json (engines entry), eg

  "engines": {
    "node": ">=16.0.0 <17.0.0",
    "npm": ">=8.0.0 <9.0.0"
  },

You can invoke it manually as

node check-version.js [verbose]

or include it as script inside package json, either as standalone script or as a prerequisite for other scripts, eg

"scripts" : {
  "start": "node check-version.js && vite",
  "build": "node check-version.js && vite build",
  "lint": "node check-version.js && eslint .",
  "check-version": "node check-version.js verbose"
},
leonbloy
  • 73,180
  • 20
  • 142
  • 190
0

A Mocha test case example:

describe('Check version of node', function () {
    it('Should test version assert', async function () {

            var version = process.version;
            var check = parseFloat(version.substr(1,version.length)) > 12.0;
            console.log("version: "+version);
            console.log("check: " +check);         
            assert.equal(check, true);
    });});
  • 1
    Should not be a unit test, use package.json /dotfiles – Ben G Dec 10 '19 at 06:36
  • 2
    But whhhhhhhy, a unit test is designed for this >.- – Jamie Nicholl-Shelley Mar 31 '20 at 13:21
  • 1
    Because you need Node to run a unit test. If the node version present is too outdated, the tests will simply not run or they'll fail with syntax error or smth. similar, which defeats the point of unit testing. It's like hiding a password reset form behind an authorization form. If you can't remember the password, you need to use reset password function, but now you can't use it, because you don't remember the password. – ankhzet Jun 15 '20 at 08:34
  • My assumption being there is at least a minimal packages installed. why else enforce a specific one. – Jamie Nicholl-Shelley Mar 20 '21 at 16:40
  • @JamieNicholl-Shelley Nooooo! unit-test not designed for that! Did you see how go.mod specify the version of go, ... pom.xml specify the version of java! we need the saaaame! got it? – Abdennour TOUMI Jan 10 '22 at 22:36
  • How so? It needs to execute and blocks the runtime if fail. PLus, I mean it's a node project, if you haven't got node installed.. well good luck to you – Jamie Nicholl-Shelley Feb 07 '22 at 15:49
  • Sorry, I down voted this answer, because a unit test doesn't make any sense for this. – Jose4Linux Aug 19 '22 at 00:41
  • @Jose4Linux I don't see how condisering node checks are always done on build time with includes unit tests, but ok. – Jamie Nicholl-Shelley Aug 19 '22 at 11:16
  • @ankhzet 's explanation is pretty clear. – Jose4Linux Aug 19 '22 at 22:02
  • unit tests can be invoked by any number of ways, plus node version control is different to check if it is installd completely - fairly obvious if you don't. So no, it is not clear. – Jamie Nicholl-Shelley Aug 19 '22 at 22:21
  • 1
    As a number of node runtimes grows (deno.js, bun.js, ts-node etc.) this "unit" test becomes even more useless =D And again, OP asked about a fail-safe for an _installer_. Tests are run _after_ an installation already completed, at best. You have a chicken and an egg problem here. – ankhzet Aug 21 '22 at 03:48