Isso é um pouco antigo, mas me deparei com o requisito, então aqui está a solução que eu criei.
O problema:
Nossa equipe de desenvolvimento mantém muitos produtos de aplicativos da Web .NET que estamos migrando para o AngularJS / Bootstrap. O VS2010 não se presta facilmente a processos de criação personalizados e meus desenvolvedores estão trabalhando rotineiramente em várias versões de nossos produtos. Nosso VCS é o Subversion (eu sei, eu sei. Estou tentando mudar para o Git, mas minha péssima equipe de marketing é muito exigente) e uma única solução de VS incluirá vários projetos separados. Eu precisava que minha equipe tivesse um método comum para inicializar seu ambiente de desenvolvimento sem precisar instalar os mesmos pacotes Node (gulp, bower etc.) várias vezes na mesma máquina.
TL; DR:
É necessário "npm install" para instalar o ambiente de desenvolvimento global do Node / Bower, bem como todos os pacotes necessários localmente para um produto .NET.
Pacotes globais devem ser instalados apenas se ainda não estiverem instalados.
Links locais para pacotes globais devem ser criados automaticamente.
A solução:
Já temos uma estrutura de desenvolvimento comum compartilhada por todos os desenvolvedores e todos os produtos, por isso criei um script NodeJS para instalar os pacotes globais quando necessário e criar os links locais. O script reside em ".... \ SharedFiles" relativo à pasta base do produto:
/*******************************************************************************
* $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();
};
Agora, se eu quiser atualizar uma ferramenta global para nossos desenvolvedores, atualizo o objeto "packages" e faço check-in no novo script. Meus desenvolvedores fazem check-out e executam-no com "node npm-setup.js" ou por "npm install" de qualquer um dos produtos em desenvolvimento para atualizar o ambiente global. A coisa toda leva 5 minutos.
Além disso, para configurar o ambiente para um novo desenvolvedor, primeiro é necessário instalar o NodeJS e o GIT for Windows, reiniciar o computador, verificar a pasta "Arquivos compartilhados" e todos os produtos em desenvolvimento e começar a trabalhar.
O "package.json" para o produto .NET chama esse script antes da instalação:
{
"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": {}
}
Notas
Observe que a referência de script requer barras, mesmo em um ambiente Windows.
"npm ls" dará "npm ERR! estranhas:" mensagens para todos os pacotes vinculados localmente porque não estão listados nas "dependências" "package.json".
Editar 29/1/16
O npm-setup.js
script atualizado acima foi modificado da seguinte maneira:
O pacote "version" in var packages
agora é o valor do "package" passado para npm install
a linha de comando. Isso foi alterado para permitir a instalação de pacotes de algum lugar que não seja o repositório registrado.
Se o pacote já estiver instalado, mas não o solicitado, o pacote existente será removido e o pacote correto, instalado.
Por motivos desconhecidos, o npm lançará periodicamente um erro EBUSY (-4082) ao executar uma instalação ou link. Este erro está interceptado e o comando é executado novamente. O erro raramente acontece uma segunda vez e parece sempre esclarecer.
"preferGlobal": true
dentro de package.json para um módulo.