Prevent npm start if node version mismatch - node.js

I have a project that needs Node >= V14 and would like to prevent npm script execution if node version does not match.
Using .npmrc and engines in package.json, I can prevent npm install from running if node version does not match.
However, is there any way to prevent npm start from executing if appropriate node version is not found?

Short answer: NPM does not provide a built-in feature to achieve this.
Solution:
However your requirement can be met by utilizing a custom node.js helper script:
Save the following check-version.js script in the root of your project directory, i.e. save it at the same level where package.json resides
check-version.js
const MIN_VERSION = 14;
const nodeVersion = process.version.replace(/^v/, '');
const [ nodeMajorVersion ] = nodeVersion.split('.');
if (nodeMajorVersion < MIN_VERSION) {
console.warn(`node version ${nodeVersion} is incompatible with this module. ` +
`Expected version >=${MIN_VERSION}`);
process.exit(1);
}
In the scripts section of your package.json define your start script as follows:
package.json
...
"scripts": {
"start": "node check-version && echo \"Running npm start\""
},
....
Note Replace the echo \"Running npm start\" part (above) with whatever your current start command is.
Explanation:
In check-version.js we obtain the Node.js version string via process.version and remove the v prefix using the replace() method.
Note: You may prefer to use process.versions.node instead of replace to obtain the version string without the prepended v.
Next we obtain the Major version only from the version string and assign it to the nodeMajorVersion variable.
Finally in the if statement we check whether the nodeMajorVersion is less than the expected minimum node.js version (MIN_VERSION). If it is less than the expected version we warn the user and call the process.exit() method with the exit code as 1.

Depends on what your start does, but if it's your code:
if (process.versions.node.split('.')[0] < 14) process.exit(1)

Related

Install different packages depending on node version

I have a project that depends on the websocket package. However, for node 10.x, the latest version (1.0.31) of websocket works, while on node 4.x version 10.0.24 works but the 10.0.31 does not. Is it possible to specify different package (versions) per nodejs version to handle cases like this, e.g. like so
node 4.x and older -> websockets 1.0.24
all other node versions -> websockets 1.0.31
Preferable it should work in both npm and yarn, but if it only works in either that's fine as well.
The node 10 version is used in dev setups, while the node 4.x is used in a legacy embedded platform that cannot run docker or be upgraded.
Consider utilizing a postinstall script in the scripts section of your projects package.json. For instance:
package.json
"scripts": {
"postinstall": "node install-websocket"
},
As you can see, the postinstall script invokes a nodejs script, arbitrarily named install-websocket.js.
install-websocket.js
const execSync = require('child_process').execSync;
const nodeMajorVersion = process.version.replace(/^v/, '').split('.')[0];
const websocketVersion = nodeMajorVersion <= '4' ? '1.0.24' : '1.0.31';
execSync('npm install websocket#' + websocketVersion, {
cwd: __dirname,
stdio: 'inherit'
});
The install-websocket.js script essentially performs the following tasks:
Gets the version of node.js using process.version which returns a string, e.g. v13.10.1
To obtain the Major version from that string (i.e. 13 in that aforementioned example) we use a combination of the replace() and split() methods.
The conditional (ternary) operator ascertains
which version of websocket to subsequently install - based on whether the value of nodeMajorVersion is <= 4.
Finally we "shell out" the appropriate npm install websocket#x.x.x command using execSync.
Note: If you're concerned about execSync being synchronous, then utilize the asynchronous exec instead.
Additional Notes:
Given the code shown above it assumes the install-websocket.js file resides in the root of your project directory, i.e. at the same level as package.json.
my-project
├── package.json
├── install-websocket.js
└── ...
It's important for the install-websocket.js file to exist at this location for following two reasons:
Primarily, and most importantly, because you'll have noticed that we specify __dirname for the value of execSync's cwd option. The value of __dirname in this context is the pathame to the parent directory of wherever the install-websocket.js file resides.
Essentially by setting the cwd option to this specific pathname, (i.e. the path to the project directory), we ensure that when the npm install websocket#x.x.x command is run it gets installed in the same location as where your project resides - regardless of whether it's installed locally or globally.
The postinstall script in package.json expects the install-websocket.js file to reside there too. Note how it currently runs; node install-websocket, and doesn't assume the file exists elsewhere, i.e. it's not running something like: node ./some/path/to/install-websocket
If consumers of your package have npm configured to ignore-scripts then websocket's simply will not be installed because the postinstall script will not be invoked.

Force yarn install instead of npm install for Node module?

I want to force using yarn install instead of npm install. I want to raise an error in npm install. What should I do in package.json?
UPDATE: Alexander's answer is the better solution and uses the same technique I describe here. I am leaving my answer in tact for posterity. The original point of my answer was to show that you can execute a small node script which should work on all platforms.
In your preinstall script you can run a mini node script which should work on all platforms, whereas things like pgrep (and other common *nix commands and operators) won't work on Windows until Windows 10 has received widespread adoption.
I tested the below script on Node v4.7.0 (npm v2.15.11) and Node v7.2.1 (npm v3.10.10). I assume it works on everything in between. It works by checking the environment variables on the currently running process - the npm_execpath is the path to the currently running "npm" script. In the case of yarn, it should point to /path/to/yarn/on/your/machine/yarn.js.
"scripts": {
"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\""
}
You can read more about npm scripts here: https://docs.npmjs.com/misc/scripts
As far as the npm_execpath environment variable, while not documented I doubt that it will ever change. It's been around for multiple major releases of npm and it doesn't really pass the "there's a better name for this" test.
Most of the answers here involve hacky scripts but there's a built in way to achieve this which I posted over on the Yarn github issue. Unlike soe of the other ways, this works for any and all NPM commands -- actually a bug in npm means it blocks npm install but not npm install <package>. Hopefully though the developers suspicions would already be raised from doing an npm install.
You add a fake engine version like so in package.json (you may want to tweak the yarn and node entries):
"engines": {
"npm": "please-use-yarn",
"yarn": ">= 1.17.3",
"node": ">= 12.5.0"
}
Then you add an .npmrc file to the project root with this:
engine-strict = true
Running NPM then raises an error:
npm ERR! code ENOTSUP
npm ERR! notsup Unsupported engine for root#: wanted: {"npm":"please-use-yarn","yarn":">= 1.17.3","node":">= 12.5.0"} (current: {"node":"12.9.1","npm":"6.10.2"})
npm ERR! notsup Not compatible with your version of node/npm: root#
Like the other answers, I'd recommend using a preinstall script and checking your environment. For a portable solution that won't have false-positives if another npm process happens to be running, using node -e 'JS_CODE' is probably the best option.
In that JS code, you can check the package manager's path using the following:
process.env.npm_execpath
Yarn's binary is yarn.js, compared to npm-cli.js used by NPM. We can use a regex like the following to check that this string ends with yarn.js.
/yarn\.js$/
By using this regex, we can be sure it won't accidentally match somewhere earlier in the file system. Most-likely yarn won't appear in the file path, but you can never be too sure.
Here's a minimal example:
{
"name": "test",
"version": "1.0.0",
"scripts": {
"preinstall": "node -e 'if(!/yarn\\.js$/.test(process.env.npm_execpath))throw new Error(\"Use yarn\")'"
}
}
Of course, the user will still be able to get around this check be editing the JSON or using the --ignore-scripts options:
npm install --ignore-scripts
After trying these options and not being very satisfied, I recommend only-allow.
Just add:
{
"scripts": {
"preinstall": "npx only-allow yarn"
}
}
I like that it provides a clear warning message, and instructions how to install yarn:
Credit to Adam Thomas' answer for providing the thread recommending this.
You can use the preinstall hook along with some shell script to achieve this.
sample package.json:
"scripts": {
"preinstall": "pgrep npm && exit 1"
}
I've just released a module that includes a CLI for this (useful for npm preinstall scripts): https://github.com/adjohnson916/use-yarn
Also, I've just released a helper for Danger to check for missing yarn.lock changes on CI:
https://github.com/adjohnson916/danger-yarn-lock
See also discussion here:
https://github.com/yarnpkg/yarn/issues/1732
https://github.com/alexanderwallin/use-yarn-instead/issues/1
If you want to simply test whether packages are being installed under yarn or npm, I tweaked Alexander O'Mara's answer slightly since it worked for me on OS X:
"scripts": {
"preinstall": "if node -e \"process.exitCode=!/yarn\\.js$/.test(process.env.npm_execpath)\" ; then echo yarn ; else echo npm ; fi",
"postinstall": ""
}
There are quite a few concepts happening in this short snippet:
The \\. portion is escaped so that \\ becomes \ and results in a properly escaped \. to detect a period in the regex.
process.exitCode= can be used to set the process's exit code and is safer than calling process.exit(N) due to the asynchronous nature of Node.js.
In Alexander's example, throw new Error(\"Use yarn\") caused node to exit with code 1 and print the stack trace to stderr. You can try running these on the console to see how that works: node -e 'throw new Error("Oops")' and node -e 'throw new Error("Oops")' 2> /dev/null (which directs the stderr stream to /dev/null). Then you can verify that the exit code was 1 with echo $? (which prints the last exit code).
The shell's if XXXX ; then YYYY ; else ZZZZ ; fi conditional logic checks the exit code of XXXX and goes to the then case for 0 (any other value goes to the else case). So if the regex detects yarn.js at the end of process.env.npm_execpath then it returns true. This must be negated so that the node process exits with code 0 and satisfies the if.
You could also console.log() the regex result and compare the output in the shell (this is just a little more verbose). Here are some examples of how to do that: https://unix.stackexchange.com/a/52801 and https://superuser.com/a/688902
You can append true ; or false ; to any shell statement to set the exit code manually. For example you can try true ; echo $? or false ; echo $?.
You can also leave off the else echo npm ; portion entirely if you don't need it.
With all of that out of the way, you can substitute the echo yarn and echo npm portions with other commands. For example, you could put multiple commands in a subshell like (echo yarn) or echo $(echo yarn).
In my case, I needed to work around an issue where one of the packages installed but had bugs under yarn so I had to run an npm install --ignore-scripts in the success case. Note that this should probably never be done in production, but can be a lifesaver if you just need to get something done or don't have control over which package manager will be used down the road.
I haven't tried this on Windows, so if someone can test the syntax there I will update my answer with what works. It would be best if the preinstall script is identical under both Windows and the Mac/Linux shell.
Found an alternate solution on Reddit. I added this to the end of my .zshenv file:
NPM_PATH=$(which npm)
npm () {
if [ -e yarn.lock ]
then
echo "Please use yarn with this project"
else
$NPM_PATH "$#"
fi
}
It now stops me from absentmindedly running commands like npm i on any yarn project on my Mac.
As some answers have already showed, you can use the only-allow package like so:
{
"scripts": {
"preinstall": "npx only-allow [npm|cnpm|pnpm|yarn]"
}
}
However, NodeJS v16.9.0 and v14.19.0 support a new experimental packageManager field in the package.json file.
Type: <string>
{
"packageManager": "<package manager name>#<version>"
}
The "packageManager" field defines which package manager is expected to be used when working on the current project. It can be set to any of the supported package managers, and will ensure that your teams use the exact same package manager versions without having to install anything else other than Node.js.
This field is currently experimental and needs to be opted-in; check the Corepack page for details about the procedure.

NPM - package.json#engines | How to specify Python?

I need to specify a python version on my package.json.
Can I simple do:
{ engines: { "python": "2.7.11" } } ?
Putting "engines": { "python": "2.7.11" } won't cause any issues (as far as I am aware) in your package.json, but it won't actually do anything either.
Determining an appropriate way to do this depends on particulars of your project. If it's about Python code you've written, you can check the version within the Python script itself. If it's about a build step in package.json, you can perhaps test as part of the build step.
As of NPM 7.x (same is valid for legacy NPM 6.x), the only valid entries for "engines" field in package.json, are the "node" version, and the "npm" version.
Furthermore, this is not a hard constraint unless you use "engine-strict" as well, as stated by the NPM docs:
Unless the user has set the engine-strict config flag, this field is advisory only and will only produce warnings when your package is installed as a dependency.
Your requirement, expecting a specific python version, is more related to an environment requirement than to your Node/NPM environment.
You can achieve this by implementing a "postinstall" NPM script that can result an error if the desired version is not found:
{
"scripts": {
"postinstall": "node ./check-python.js"
}
}
This script will be executed by NPM automatically after npm install. You could also use "preinstall" instead.
Consider using it on your "build" or "prebuild" scripts as well, based on your requirements. See more details on NPM pre- and post- scripts in the docs.
Then, your check-python.js script could be something like:
const { exec } = require('child_process');
const EXPECTED_PYTHON_VERSION = "2.7.11";
exec('python -c "import platform; print(platform.python_version())"',
function(err, stdout, stderr) {
const currentPythonVersion = stdout.toString();
if(currentPythonVersion !== EXPECTED_PYTHON_VERSION) {
throw new Error(`Expected Python version '${EXPECTED_PYTHON_VERSION}' but found '${currentPythonVersion}'. Please fix your Python installation.`);
}
});

How to specify/enforce a specific node.js version to use in package.json?

I am searching for a way to break the build, if a user is using a different node.js version as defined in the project.
Ideally to put some checks in grunt or bower or npm to stop, if a certain npm/node version is not used to run the current build.
Even though engineStrict is deprecated, you can still accomplish this behavior without needing to use an additional script to enforce a Node version in your project.
Add the engines property to your package.json file. For example:
{
"name": "example",
"version": "1.0.0",
"engines": {
"node": ">=14.0.0"
}
}
Create a .npmrc file in your project at the same level as your package.json.
In the newly created .npmrc file, add engine-strict=true.
engine-strict=true
This will enforce the engines you've defined when the user runs npm install. I've created a simple example on GitHub for your reference.
You can use the "engineStrict" property in your package.json
Check the docs for more information: https://docs.npmjs.com/files/package.json
Update on 23rd June 2019
"engineStrict" property is removed in npm 3.0.0.
Reference : https://docs.npmjs.com/files/package.json#enginestrict
"engineStrict" has been removed and "engines" only works for dependencies. If you want to check Node's runtime version this can work for you:
You call this function in your server side code. It uses a regex to check Node's runtime version using eremzeit's response It will throw an error if it's not using the appropriate version:
const checkNodeVersion = version => {
const versionRegex = new RegExp(`^${version}\\..*`);
const versionCorrect = process.versions.node.match(versionRegex);
if (!versionCorrect) {
throw Error(
`Running on wrong Nodejs version. Please upgrade the node runtime to version ${version}`
);
}
};
usage:
checkNodeVersion(8)
You can use the engines property in the package.json file
For example, if you want to make sure that you have a minimum of node.js version 6.9 and a maximum of 6.10, then you can specify the following
package.json
{
"name": "Foo",
....
"engines": {
"node": ">=6.9 <=6.10"
}
}
If you want to enforce a specific version of npm, you might use:
https://github.com/hansl/npm-enforce-version
If you want to enforce a version of node when executing you can read the version of node that is currently running by checking against:
process.versions
For more info: https://nodejs.org/api/process.html#process_process_versions

Install dependencies globally and locally using package.json

Using npm we can install the modules globally using -g option. How can we do this in the package.json file?
Suppose, these are my dependencies in package.json file
"dependencies": {
"mongoose": "1.4.0",
"node.io" : "0.3.3",
"jquery" : "1.5.1",
"jsdom" : "0.2.0",
"cron" : "0.1.2"
}
When i run npm install, I want only node.io to be installed globally, the rest others should be installed locally. Is there an option for this?
New Note: You probably don't want or need to do this. What you probably want to do is just put those types of command dependencies for build/test etc. in the devDependencies section of your package.json. Anytime you use something from scripts in package.json your devDependencies commands (in node_modules/.bin) act as if they are in your path.
For example:
npm i --save-dev mocha # Install test runner locally
npm i --save-dev babel # Install current babel locally
Then in package.json:
// devDependencies has mocha and babel now
"scripts": {
"test": "mocha",
"build": "babel -d lib src",
"prepublish": "babel -d lib src"
}
Then at your command prompt you can run:
npm run build # finds babel
npm test # finds mocha
npm publish # will run babel first
New NEW Note: For awhile now we have had npx, which allows you to run the devDependencies commands without needing to add them to your scripts section (if you want).
For example:
npx webpack
But if you really want to install globally, you can add a preinstall in the scripts section of the package.json:
"scripts": {
"preinstall": "npm i -g themodule"
}
So actually my npm install executes npm install again ... Which is weird but seems to work.
Note: you might have issues if you are using the most common setup for npm where global Node package installs required sudo. One option is to change your npm configuration so this isn't necessary:
npm config set prefix ~/npm, add $HOME/npm/bin to $PATH by appending export PATH=$HOME/npm/bin:$PATH to your ~/.bashrc.
Another, probably better option is to just use nvm to manage Node and you won't have that problem.
Due to the disadvantages described below, I would recommend following the accepted answer:
Use npm install --save-dev [package_name] then execute scripts with:
$ npm run lint
$ npm run build
$ npm test
My original but not recommended answer follows.
Instead of using a global install, you could add the package to your devDependencies (--save-dev) and then run the binary from anywhere inside your project:
"$(npm bin)/<executable_name>" <arguments>...
In your case:
"$(npm bin)"/node.io --help
This engineer provided an npm-exec alias as a shortcut. This engineer uses a shellscript called env.sh. But I prefer to use $(npm bin) directly, to avoid any extra file or setup.
Although it makes each call a little larger, it should just work, preventing:
potential dependency conflicts with global packages (#nalply)
the need for sudo
the need to set up an npm prefix (although I recommend using one anyway)
Disadvantages:
$(npm bin) won't work on Windows.
Tools deeper in your dev tree will not appear in the npm bin folder. (Install npm-run or npm-which to find them.)
It seems a better solution is to place common tasks (such as building and minifying) in the "scripts" section of your package.json, as Jason demonstrates above.
This is a bit old but I ran into the requirement so here is the solution I came up with.
The Problem:
Our development team maintains many .NET web application products we are migrating to AngularJS/Bootstrap. VS2010 does not lend itself easily to custom build processes and my developers are routinely working on multiple releases of our products. Our VCS is Subversion (I know, I know. I'm trying to move to Git but my pesky marketing staff is so demanding) and a single VS solution will include several separate projects. I needed my staff to have a common method for initializing their development environment without having to install the same Node packages (gulp, bower, etc.) several times on the same machine.
TL;DR:
Need "npm install" to install the global Node/Bower development environment as well as all locally required packages for a .NET product.
Global packages should be installed only if not already installed.
Local links to global packages must be created automatically.
The Solution:
We already have a common development framework shared by all developers and all products so I created a NodeJS script to install the global packages when needed and create the local links. The script resides in "....\SharedFiles" relative to the product base folder:
/*******************************************************************************
* $Id: npm-setup.js 12785 2016-01-29 16:34:49Z sthames $
* ==============================================================================
* Parameters: 'links' - Create links in local environment, optional.
*
* <p>NodeJS script to install common development environment packages in global
* environment. <c>packages</c> object contains list of packages to install.</p>
*
* <p>Including 'links' creates links in local environment to global packages.</p>
*
* <p><b>npm ls -g --json</b> command is run to provide the current list of
* global packages for comparison to required packages. Packages are installed
* only if not installed. If the package is installed but is not the required
* package version, the existing package is removed and the required package is
* installed.</p>.
*
* <p>When provided as a "preinstall" script in a "package.json" file, the "npm
* install" command calls this to verify global dependencies are installed.</p>
*******************************************************************************/
var exec = require('child_process').exec;
var fs = require('fs');
var path = require('path');
/*---------------------------------------------------------------*/
/* List of packages to install and 'from' value to pass to 'npm */
/* install'. Value must match the 'from' field in 'npm ls -json' */
/* so this script will recognize a package is already installed. */
/*---------------------------------------------------------------*/
var packages =
{
"bower" : "bower#1.7.2",
"event-stream" : "event-stream#3.3.2",
"gulp" : "gulp#3.9.0",
"gulp-angular-templatecache" : "gulp-angular-templatecache#1.8.0",
"gulp-clean" : "gulp-clean#0.3.1",
"gulp-concat" : "gulp-concat#2.6.0",
"gulp-debug" : "gulp-debug#2.1.2",
"gulp-filter" : "gulp-filter#3.0.1",
"gulp-grep-contents" : "gulp-grep-contents#0.0.1",
"gulp-if" : "gulp-if#2.0.0",
"gulp-inject" : "gulp-inject#3.0.0",
"gulp-minify-css" : "gulp-minify-css#1.2.3",
"gulp-minify-html" : "gulp-minify-html#1.0.5",
"gulp-minify-inline" : "gulp-minify-inline#0.1.1",
"gulp-ng-annotate" : "gulp-ng-annotate#1.1.0",
"gulp-processhtml" : "gulp-processhtml#1.1.0",
"gulp-rev" : "gulp-rev#6.0.1",
"gulp-rev-replace" : "gulp-rev-replace#0.4.3",
"gulp-uglify" : "gulp-uglify#1.5.1",
"gulp-useref" : "gulp-useref#3.0.4",
"gulp-util" : "gulp-util#3.0.7",
"lazypipe" : "lazypipe#1.0.1",
"q" : "q#1.4.1",
"through2" : "through2#2.0.0",
/*---------------------------------------------------------------*/
/* fork of 0.2.14 allows passing parameters to main-bower-files. */
/*---------------------------------------------------------------*/
"bower-main" : "git+https://github.com/Pyo25/bower-main.git"
}
/*******************************************************************************
* run */
/**
* Executes <c>cmd</c> in the shell and calls <c>cb</c> on success. Error aborts.
*
* Note: Error code -4082 is EBUSY error which is sometimes thrown by npm for
* reasons unknown. Possibly this is due to antivirus program scanning the file
* but it sometimes happens in cases where an antivirus program does not explain
* it. The error generally will not happen a second time so this method will call
* itself to try the command again if the EBUSY error occurs.
*
* #param cmd Command to execute.
* #param cb Method to call on success. Text returned from stdout is input.
*******************************************************************************/
var run = function(cmd, cb)
{
/*---------------------------------------------*/
/* Increase the maxBuffer to 10MB for commands */
/* with a lot of output. This is not necessary */
/* with spawn but it has other issues. */
/*---------------------------------------------*/
exec(cmd, { maxBuffer: 1000*1024 }, function(err, stdout)
{
if (!err) cb(stdout);
else if (err.code | 0 == -4082) run(cmd, cb);
else throw err;
});
};
/*******************************************************************************
* runCommand */
/**
* Logs the command and calls <c>run</c>.
*******************************************************************************/
var runCommand = function(cmd, cb)
{
console.log(cmd);
run(cmd, cb);
}
/*******************************************************************************
* Main line
*******************************************************************************/
var doLinks = (process.argv[2] || "").toLowerCase() == 'links';
var names = Object.keys(packages);
var name;
var installed;
var links;
/*------------------------------------------*/
/* Get the list of installed packages for */
/* version comparison and install packages. */
/*------------------------------------------*/
console.log('Configuring global Node environment...')
run('npm ls -g --json', function(stdout)
{
installed = JSON.parse(stdout).dependencies || {};
doWhile();
});
/*--------------------------------------------*/
/* Start of asynchronous package installation */
/* loop. Do until all packages installed. */
/*--------------------------------------------*/
var doWhile = function()
{
if (name = names.shift())
doWhile0();
}
var doWhile0 = function()
{
/*----------------------------------------------*/
/* Installed package specification comes from */
/* 'from' field of installed packages. Required */
/* specification comes from the packages list. */
/*----------------------------------------------*/
var current = (installed[name] || {}).from;
var required = packages[name];
/*---------------------------------------*/
/* Install the package if not installed. */
/*---------------------------------------*/
if (!current)
runCommand('npm install -g '+required, doWhile1);
/*------------------------------------*/
/* If the installed version does not */
/* match, uninstall and then install. */
/*------------------------------------*/
else if (current != required)
{
delete installed[name];
runCommand('npm remove -g '+name, function()
{
runCommand('npm remove '+name, doWhile0);
});
}
/*------------------------------------*/
/* Skip package if already installed. */
/*------------------------------------*/
else
doWhile1();
};
var doWhile1 = function()
{
/*-------------------------------------------------------*/
/* Create link to global package from local environment. */
/*-------------------------------------------------------*/
if (doLinks && !fs.existsSync(path.join('node_modules', name)))
runCommand('npm link '+name, doWhile);
else
doWhile();
};
Now if I want to update a global tool for our developers, I update the "packages" object and check in the new script. My developers check it out and either run it with "node npm-setup.js" or by "npm install" from any of the products under development to update the global environment. The whole thing takes 5 minutes.
In addition, to configure the environment for the a new developer, they must first only install NodeJS and GIT for Windows, reboot their computer, check out the "Shared Files" folder and any products under development, and start working.
The "package.json" for the .NET product calls this script prior to install:
{
"name" : "Books",
"description" : "Node (npm) configuration for Books Database Web Application Tools",
"version" : "2.1.1",
"private" : true,
"scripts":
{
"preinstall" : "node ../../SharedFiles/npm-setup.js links",
"postinstall" : "bower install"
},
"dependencies": {}
}
Notes
Note the script reference requires forward slashes even in a Windows
environment.
"npm ls" will give "npm ERR! extraneous:" messages for all packages
locally linked because they are not listed in the "package.json"
"dependencies".
Edit 1/29/16
The updated npm-setup.js script above has been modified as follows:
Package "version" in var packages is now the "package" value passed to npm install on the command line. This was changed to allow for installing packages from somewhere other than the registered repository.
If the package is already installed but is not the one requested, the existing package is removed and the correct one installed.
For reasons unknown, npm will periodically throw an EBUSY error (-4082) when performing an install or link. This error is trapped and the command re-executed. The error rarely happens a second time and seems to always clear up.
You could use a separate file, like npm_globals.txt, instead of package.json. This file would contain each module on a new line like this,
mongoose#1.4.0
node.io#0.3.3
jquery#1.5.1
jsdom#0.2.0
cron#0.1.2
Then in the command line run,
< npm_globals.txt xargs npm install -g
Check that they installed properly with,
npm list -g --depth=0
As for whether you should do this or not, I think it all depends on use case. For most projects, this isn't necessary; and having your project's package.json encapsulate these tools and dependencies together is much preferred.
But nowadays I find that I'm always installing create-react-app and other CLI's globally when I jump on a new machine. It's nice to have an easy way to install a global tool and its dependencies when versioning doesn't matter much.
And nowadays, I'm using npx, an npm package runner, instead of installing packages globally.
Build your own script to install global dependencies. It doesn't take much. package.json is quite expandable.
const { execSync } = require('child_process');
const fs = require('fs');
const package = JSON.parse(fs.readFileSync('package.json'));
let keys = Object.keys(package.dependencies);
let values = Object.values(package.dependencies);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
let value = values[index].replace("~", "").replace("^", "");
console.log(`Installing: ${key}#${value} globally`,);
execSync('npm i -g ' + `${key}#${value}`);
}
Using the above, you can even make it inline, below!
Look at preinstall below:
{
"name": "Project Name",
"version": "0.1.0",
"description": "Project Description",
"main": "app.js",
"scripts": {
"preinstall": "node -e \"const {execSync} = require('child_process'); JSON.parse(fs.readFileSync('package.json')).globalDependencies.forEach(globaldep => execSync('npm i -g ' + globaldep));\"",
"build": "your transpile/compile script",
"start": "node app.js",
"test": "./node_modules/.bin/mocha --reporter spec",
"patch-release": "npm version patch && npm publish && git add . && git commit -m \"auto-commit\" && git push --follow-tags"
},
"dependencies": [
},
"globalDependencies": [
"cordova#8.1.2",
"ionic",
"potato"
],
"author": "author",
"license": "MIT",
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^5.2.0"
},
"bin": {
"app": "app.js"
}
}
The authors of node may not admit package.json is a project file. But it is.
All modules from package.json are installed to ./node_modules/
I couldn't find this explicitly stated but this is the package.json reference for NPM.
This probably may be door for problems in production.
if project dependencies installed outside project folder, the code may break if anybody else delete or replace your packages or change folder permissions.
Having every thing in one folder is more durable and make system predictable and maintenance tasks easier.

Resources