Looking for these?
npm install --recursive
npm update --recursive
No such animal!
Today I watched the Holowaychuk video on vimeo about modular apps. http://vimeo.com/56166857 He showed how he thought in the project main package.json that bundledDependencies should be able to have their deps recursively installed with npm install. They can be with update but only if they are in the tree of node_modules somewhere, and it still is not a single command to get everything. It's still that way, which it is as of 4/16/2013, on node 0.10.4 and npm 1.2.18, win7-64 You can now get a private npm from "Iris npm" or you can use git up to some limit. People have differing reasons for wanting this behavior, and really they want it to function on items from package.json, but I wanted to do something recursive.
Relevant npm issues:
github.com/isaacs/npm/issues/2442 -- asks for the deps of bundled to be installed
github.com/isaacs/npm/issues/1341 -- expects deps of installed to be installed
github.com/isaacs/npm/issues/1558 -- solution may involve allowing path instead of semver
Before getting into the npm api, if ever, I wanted to do a sanity check that recursive install would not a problem. It's really not different from a shell script, as it is. The script here could run update or install from a package.json hook. It says in the npm hooks page that is an anti-pattern, but good-guy npm does survive calling install from a postinstall hook.
File package.json:
{
"name": "npm_recurse_demo",
"version": "0.0.1",
"description": "npm recurse demo",
"dependencies": {
"jade": "~0.28.2",
"express": "~3.1.2"
},
"optionalDependencies": {"sax": "~0.5.2"},
"scripts" : {
"postinstall" : "node exec_util npm_install_rec /dev/npm_recurse_demo/lib/"
}
}
This is using CoffeScriptRedux. Here is the coffeescript:
###
Execute arbitrary args in a dir, singly or recursively.
A list of ignore files prevents bad recursions.
A list of target files triggers exec of args if found in the cwd.
Usage: See npm_install_rec below.
DEF_IGNORES could include node_modules
Note: file is passed but not currently used in exec_args_in_dir.
Paul McCulloch, 16 April 2013
License: <= MIT
###
fs = require "fs"
DEF_IGNORES = [".git"]
exec_args_in_dir = (args, dir) ->
console.log "exec", args, "in", dir
exec = require('child_process').exec
command = exec args, {cwd:dir, stio:"inherit"}, (err,stout,sterr) ->
console.log " cwd:", dir
console.log " err:", err
console.log " stout:", stout
console.log " sterr:", sterr
func_walk = (args, file, parent=null, ignores=DEF_IGNORES, targets=[]) ->
# console.log "file", file, "parent", parent
if args? and file?
if file in targets and parent?
exec_args_in_dir args, parent
else if file not in ignores
if parent?
file = parent + "/" + file
fs.stat file, (err,stats) ->
console.log err if err?
if stats.isDirectory()
for item in fs.readdirSync(file)
do -> func_walk(args, item, file, ignores, targets)
npm_install_rec = (start) ->
func_walk "npm -s install", start, null, DEF_IGNORES, ["package.json"]
npm_update_rec = (start) ->
func_walk "npm -s update", start, null, DEF_IGNORES, ["package.json"]
module.exports = exports =
func_walk: func_walk
exec_args_in_dir: exec_args_in_dir
npm_install_rec: npm_install_rec
npm_update_rec: npm_update_rec
if process.argv?.length is 4
try
module.exports[process.argv[2]](process.argv[3])
catch err
console.log err
Here is the output of coffee-script-redux command:
coffee -jb <exec_util.coffee >exec_util.js
Not more beautiful, but it should be more correct. Is it?
Multi-line comments do not pass and I had to realign the output command of exec_args_in_dir in the coffeescript or it would not "jompile", so made it multiple calls.
// Generated by CoffeeScript 2.0.0-beta4
var DEF_IGNORES, err, exec_args_in_dir, exports, fs, func_walk, npm_install_rec, npm_update_rec;
fs = require('fs');
DEF_IGNORES = ['.git'];
exec_args_in_dir = function (args, dir) {
var command, exec;
console.log('exec', args, 'in', dir);
exec = require('child_process').exec;
return command = exec(args, {
cwd: dir,
stio: 'inherit'
}, function (err, stout, sterr) {
console.log(' cwd:', dir);
console.log(' err:', err);
console.log(' stout:', stout);
return console.log(' sterr:', sterr);
});
};
func_walk = function (args, file, parent, ignores, targets) {
if (null == parent)
parent = null;
if (null == ignores)
ignores = DEF_IGNORES;
if (null == targets)
targets = [];
if (null != args && null != file)
if (in$(file, targets) && null != parent) {
return exec_args_in_dir(args, parent);
} else if (!in$(file, ignores)) {
if (null != parent)
file = parent + '/' + file;
return fs.stat(file, function (err, stats) {
if (null != err)
console.log(err);
if (stats.isDirectory())
return function (accum$) {
var item;
for (var cache$ = fs.readdirSync(file), i$ = 0, length$ = cache$.length; i$ < length$; ++i$) {
item = cache$[i$];
accum$.push(function () {
return func_walk(args, item, file, ignores, targets);
}());
}
return accum$;
}.call(this, []);
});
}
};
npm_install_rec = function (start) {
return func_walk('npm -s install', start, null, DEF_IGNORES, ['package.json']);
};
npm_update_rec = function (start) {
return func_walk('npm -s update', start, null, DEF_IGNORES, ['package.json']);
};
module.exports = exports = {
func_walk: func_walk,
exec_args_in_dir: exec_args_in_dir,
npm_install_rec: npm_install_rec,
npm_update_rec: npm_update_rec
};
if ((null != process.argv ? process.argv.length : void 0) === 4)
try {
module.exports[process.argv[2]](process.argv[3]);
} catch (e$) {
err = e$;
console.log(err);
}
function in$(member, list) {
for (var i = 0, length = list.length; i < length; ++i)
if (i in list && list[i] === member)
return true;
return false;
}