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" : "[email protected]",
"event-stream" : "[email protected]",
"gulp" : "[email protected]",
"gulp-angular-templatecache" : "[email protected]",
"gulp-clean" : "[email protected]",
"gulp-concat" : "[email protected]",
"gulp-debug" : "[email protected]",
"gulp-filter" : "[email protected]",
"gulp-grep-contents" : "[email protected]",
"gulp-if" : "[email protected]",
"gulp-inject" : "[email protected]",
"gulp-minify-css" : "[email protected]",
"gulp-minify-html" : "[email protected]",
"gulp-minify-inline" : "[email protected]",
"gulp-ng-annotate" : "[email protected]",
"gulp-processhtml" : "[email protected]",
"gulp-rev" : "[email protected]",
"gulp-rev-replace" : "[email protected]",
"gulp-uglify" : "[email protected]",
"gulp-useref" : "[email protected]",
"gulp-util" : "[email protected]",
"lazypipe" : "[email protected]",
"q" : "[email protected]",
"through2" : "[email protected]",
/*---------------------------------------------------------------*/
/* 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.
"preferGlobal": true
inside package.json for a module. – Raynos"preferGlobal":true
... i don't really know where to put this in package.json. npmjs.org/doc/json.html The NPM documentation says that preferGlobal is for your own package and that setting it will make it install your own package as a global. it seems like more of a guide, though. – PPPaul