Permalink
Browse files

Merge pull request #53 from fb55/master

Better incorporated `detective`
  • Loading branch information...
2 parents f55a8ac + 86e9cdf commit d42b4102f31d8a85fa34e2fdbd6625c5cfbdb107 @mmalecki mmalecki committed Aug 29, 2012
View
@@ -2,50 +2,45 @@
var Module = require('module').Module,
__load = Module._load,
- detective = require('detective'),
- fs = require('fs'),
packages = {};
//
// Monkey punch `Module._load()` to observe the names
// of packages as they are loaded.
//
Module._load = function (name, parent) {
- console.log('__!load::' + name);
- name = /^[./]/.test(name) ? Module._resolveFilename(name, parent)[1] : name;
+ process.send({type: "load", msg: name});
+ if(name[0] === "." || name[0] === "/"){
+ name = Module._resolveFilename(name, parent)[1];
+ }
return __load.apply(Module, arguments);
-}
+};
try {
process.nextTick(function () {
process.exit(0);
});
- Module._load(process.argv[2], null, true);
+ __load.call(Module, process.argv[2], null, true);
}
catch (ex) {
//
// Log errors and attempt to log as many packages as we can.
//
var eStr = '' + (ex
? (ex.stack ? ex.stack : ex)
- : 'falsey error: ' + ex),
- notFound = /^Error: Cannot find module '(.*)'/.test(eStr);
+ : 'falsey error: ' + ex);
//
// However, 'cannot find module' errors should be squashed.
// In cases with no node_modules present, this is not an indication of failure.
// This should perhaps be replaced with a node-detective fallback.
//
- if (notFound) {
- detective(fs.readFileSync(process.argv[2])).forEach(function (dep) {
- console.log('__!load::' + dep);
- });
- }
- else {
- eStr.split('\n').forEach(function (line) {
- console.error('__!err::' + line);
- });
+ if (!("code" in ex
+ ? ex.code === "MODULE_NOT_FOUND"
+ : /^Error: Cannot find module '.*'/.test(eStr))
+ ) {
+ process.send({type: "error", msg: eStr});
}
}
@@ -9,8 +9,10 @@ var util = require('util'),
path = require('path'),
fs = require('fs'),
events = require('events'),
- spawn = require('child_process').spawn,
+ fork = require('child_process').fork,
readInstalled = require('read-installed'),
+ detective = require('detective'),
+ resolve = require('resolve'),
semver = require('semver'),
findit = require('findit');
@@ -133,6 +135,7 @@ analyzer.npmAnalyze = function (deps, options, callback) {
Object.keys(result.dependencies).forEach(function (pkg) {
if (result.devDependencies && pkg in result.devDependencies) return;
+ if (result.bundleDependencies && pkg in result.bundleDependencies) return;
if (!Array.isArray(deps)) {
if (deps[pkg] === '*' || typeof deps[pkg] === 'undefined') {
pkgs[pkg] = result.dependencies[pkg]
@@ -277,7 +280,8 @@ analyzer.dir = function (options, callback) {
analyzer.package = function (options, callback) {
var deps = {},
pkgDeps = {},
- devDeps = {};
+ devDeps = {},
+ bdlDeps = {};
//
// Attempt to read the package.json in the current directory
//
@@ -290,6 +294,7 @@ analyzer.package = function (options, callback) {
pkg = JSON.parse(pkg.toString());
pkgDeps = pkg.dependencies;
devDeps = pkg.devDependencies;
+ bdlDeps = pkg.bundleDependencies;
}
catch (e) {
return callback(e);
@@ -312,7 +317,7 @@ analyzer.package = function (options, callback) {
function dequeue(err) {
todo--;
if (todo === 0) {
- mergeDependencies(err, _deps, pkgDeps, devDeps, callback);
+ mergeDependencies(err, _deps, pkgDeps, devDeps, bdlDeps, callback);
}
}
@@ -412,52 +417,73 @@ analyzer.package = function (options, callback) {
});
};
-//
-// ### function file (file, callback)
-// #### @file {string} Path of the node script to analyze
-// #### @callback {callback} Continuation to respond to when complete.
-// Attempts to find the packages required by the node script located at
-// `file` by spawning an instance of the `find-dependencies` helper
-// script and parsing the output.
-//
-analyzer.file = function (options, callback) {
+function analyzeFile (options, callback) {
+ var remaining = 1;
+
+ function cb(err, data){
+ if(!--remaining) callback();
+ }
+
+ fs.readFile(options.target, function(err, data){
+ if(err) return callback(err);
+
+ var files;
+
+ try {
+ files = detective.find(data.toString('utf8'));
+ } catch(e){
+ return callback(err);
+ }
+
+ files.strings.forEach(function(name){
+ if(name in options.packages) return;
+
+ options.packages[name] = true;
+
+ var absolutePath = analyzer.resolve(name, options.target);
+ if(!absolutePath || path.relative(options.target, absolutePath).indexOf('node_modules') >= 0){
+ return;
+ }
+
+ remaining++;
+
+ analyzeFile({
+ __proto__: options,
+ target: absolutePath
+ }, cb);
+ });
+
+ if(files.expressions.length > 0){
+ remaining++;
+ spawnWorker({
+ __proto__: options,
+ target: options.target
+ }, cb);
+ }
+
+ cb();
+ });
+}
+
+function spawnWorker (options, callback) {
//
// Spawn the `find-dependencies` bin helper to ensure that we are able to
// bypass any modules which have already been required in the current process.
//
- var packages = {},
- merged = {},
- errs = ['Errors received when analyzing ' + options.target],
- deps = spawn('node', [path.join(__dirname, '..', 'bin', 'find-dependencies'), options.target]);
-
- function parseLines(data, prefix, fn) {
- data = data.toString();
- if (data !== '') {
- data.toString().split('\n').filter(function (line) {
- return line !== '';
- }).forEach(function (line) {
- if (line.indexOf(prefix) !== -1) {
- line = line.replace(prefix, '');
- fn(line);
- }
- });
+ var packages = options.packages,
+ errs = options.errors,
+ deps = fork(path.join(__dirname, '..', 'bin', 'find-dependencies'), [options.target], {silent: true});
+
+ deps.send(options.target);
+
+ deps.on("message", function(data){
+ switch(data.type){
+ case "load":
+ packages[data.msg] = true;
+ break;
+ case "error":
+ errs.push(data.msg);
}
- }
-
- deps.stdout.on('data', function (data) {
- //
- // For each line of data output from the child process remove empty
- // lines and then add the specified packages to list of known packages.
- //
- parseLines(data, '__!load::', function (dep) {
- packages[dep] = true;
- });
- });
-
- deps.stderr.on('data', function (data) {
- parseLines(data, '__!err::', function (line) {
- errs.push(line);
- });
});
//
@@ -480,27 +506,52 @@ analyzer.file = function (options, callback) {
//
clearTimeout(timeoutId);
- //
- // When the process is complete remove any core node.js packages
- // (i.e. the ones for which `require.resolve(module) == module`) and any packages
- // which are required with a relative directory (i.e. `require('./package')`).
- //
- // Include any packages which may be of the form `require('package/relative/dir')`
- // because those relative directories are still supported by npm:
- // e.g.: `require('socket.io/lib/socket.io/utils')`.
- //
- packages = Object.keys(packages);
- (options.raw ? packages : packages.filter(function (pkg) {
- return pkg[0] !== '.' && pkg[0] !== '/' && !analyzer.isNative(pkg);
- }).map(function (pkg) {
- return pkg.split('/')[0];
- })).forEach(function (pkg) {
- merged[pkg] = true;
- });
+ callback();
+ });
+}
- return errs.length > 1
- ? callback(new Error(errs.join('\n')), Object.keys(merged))
- : callback(null, Object.keys(merged));
+//
+// ### function file (file, callback)
+// #### @file {string} Path of the node script to analyze
+// #### @callback {callback} Continuation to respond to when complete.
+// Attempts to find the packages required by the node script located at
+// `file` by spawning an instance of the `find-dependencies` helper
+// script and parsing the output.
+//
+
+analyzer.file = function(options, callback){
+ options.packages = options.packages || {};
+ options.errors = options.errors || [];
+ analyzeFile(options, function(err){
+ if(options.errors.length > 0){
+ callback(options.errors); //TODO call with real error object
+ } else {
+ //
+ // Remove any core node.js packages
+ // (i.e. the ones for which `require.resolve(module) == module`) and any packages
+ // which are required with a relative directory (i.e. `require('./package')`).
+ //
+ // Include any packages which may be of the form `require('package/relative/dir')`
+ // because those relative directories are still supported by npm:
+ // e.g.: `require('socket.io/lib/socket.io/utils')`.
+ //
+
+ var packages = Object.keys(options.packages);
+
+ if(!options.raw){
+ packages = packages.filter(function (pkg) {
+ return pkg[0] !== '.' && pkg[0] !== '/' && !analyzer.isNative(pkg);
+ }).map(function (pkg) {
+ return pkg.split(path.sep, 2)[0];
+ }).reduce(function(obj, name){
+ obj[name] = true;
+ return obj;
+ }, {});
+
+ packages = Object.keys(packages);
+ }
+ callback(null, packages);
+ }
});
};
@@ -666,6 +717,8 @@ analyzer.updates = function (current, updated) {
// #### @module {string} Module
// Check if `module` is a native module (like `net` or `tty`).
//
+// TODO use the resolve module for this
+//
analyzer.isNative = function (module) {
try {
return require.resolve(module) == module;
@@ -675,10 +728,26 @@ analyzer.isNative = function (module) {
}
};
-function mergeDependencies(err, deps, pkgDeps, devDeps, callback) {
+analyzer.resolve = function(file, base){
+ try {
+ return resolve.sync(file, {
+ basedir: base && path.dirname(base),
+ extensions: ['.js', '.coffee']
+ });
+ } catch (e) {
+ return false;
+ }
+};
+
+function mergeDependencies(err, deps, pkgDeps, devDeps, bndlDeps, callback) {
function removeDevDeps(deps) {
- var obj = analyzer.clone(deps);
- for (var dep in devDeps) {
+ var obj = analyzer.clone(deps), dep;
+ for (dep in devDeps) {
+ if (typeof obj[dep] !== 'undefined') {
+ delete obj[dep];
+ }
+ }
+ for (dep in bndlDeps) {
if (typeof obj[dep] !== 'undefined') {
delete obj[dep];
}
View
@@ -18,6 +18,7 @@
"semver": "1.0.x",
"winston": "0.6.x",
"detective": "0.0.x",
+ "resolve": "0.2.x",
"eyes": "0.1.x",
"read-installed": "0.0.x"
},
@@ -174,5 +174,15 @@ vows.describe('require-analyzer/examples').addBatch({
'writeMyCSS': '0.17.x'
});
}
+ },
+ "When passed an app with delayed & distributed dependencies": {
+ topic: dependencies('./fixtures/delayed-require'),
+ "all dependencies are found": function (err, pkgs){
+ assert.isNull(err);
+ assert.deepEqual(pkgs, {
+ "socket.io": "*",
+ "some_module": "*"
+ });
+ }
}
}).export(module);
@@ -0,0 +1,3 @@
+module.exports = function(a){
+ require("./other.js")(a*2);
+};
@@ -0,0 +1,5 @@
+require("sock" + (function(){return "et.io";}()));
+
+module.exports = function(a){
+ require("some_module").func(a);
+};
@@ -38,6 +38,7 @@ var libDeps = {
'colors': '0.x.x',
'findit': '0.0.x',
'read-installed': '0.0.x',
+ 'resolve': '0.2.x',
'optimist': '0.3.x',
'semver': '1.0.x',
'winston': '0.6.x',
@@ -64,6 +65,8 @@ var libPackages = [
var depsFromFile = [
'read-installed',
+ 'detective',
+ 'resolve',
'semver',
'findit'
];

0 comments on commit d42b410

Please sign in to comment.