├── .gitignore ├── .travis.yml ├── README.md ├── bin ├── find-dependencies └── require-analyzer ├── docs ├── docco.css └── require-analyzer.html ├── lib └── require-analyzer.js ├── package.json └── test ├── example-apps-test.js ├── fixtures ├── .gitignore ├── conflicting-app │ ├── index.js │ ├── node_modules │ │ ├── dep1 │ │ │ ├── index.js │ │ │ └── package.json │ │ └── dep2-with-conflict-on-dep1 │ │ │ ├── index.js │ │ │ ├── node_modules │ │ │ ├── dep1 │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ └── dep3 │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ └── package.json │ └── package.json ├── delayed-require │ ├── index.js │ └── other.js ├── dynamic-deps │ ├── index.js │ ├── node_modules │ │ ├── dep1 │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── dep2 │ │ │ ├── index.js │ │ │ └── package.json │ │ └── dep3 │ │ │ ├── index.js │ │ │ └── package.json │ └── package.json ├── example-app1 │ ├── index.js │ ├── node_modules │ │ └── example-dep1 │ │ │ ├── index.js │ │ │ └── package.json │ └── package.json ├── example-app2 │ ├── index.js │ ├── node_modules │ │ ├── example-dep1 │ │ │ ├── index.js │ │ │ └── package.json │ │ └── example-dep2 │ │ │ ├── index.js │ │ │ └── package.json │ └── package.json ├── example-app3 │ ├── index.js │ ├── node_modules │ │ ├── example-dep1 │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── example-dep2 │ │ │ ├── index.js │ │ │ └── package.json │ │ └── example-dep3 │ │ │ ├── index.js │ │ │ └── package.json │ └── package.json ├── explicit-versions │ ├── app.js │ ├── node_modules │ │ ├── makeShiny │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── serveStuff │ │ │ ├── index.js │ │ │ └── package.json │ │ └── writeMyCSS │ │ │ ├── index.js │ │ │ └── package.json │ └── package.json ├── require-only │ └── index.js ├── socket-io-app │ └── index.js ├── subdeps │ ├── app.js │ ├── node_modules │ │ ├── makeShiny │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── serveStuff │ │ │ ├── index.js │ │ │ └── package.json │ │ └── writeMyCSS │ │ │ ├── index.js │ │ │ └── package.json │ ├── otherstuff.js │ └── package.json ├── version-ranges │ ├── app.js │ ├── node_modules │ │ ├── makeShiny │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── serveStuff │ │ │ ├── index.js │ │ │ └── package.json │ │ └── writeMyCSS │ │ │ ├── index.js │ │ │ └── package.json │ ├── otherstuff.js │ └── package.json └── wildcards │ ├── index.js │ └── package.json ├── require-analyzer-cli-test.js └── require-analyzer-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | node_modules/* 3 | !test/fixtures 4 | npm-debug.log 5 | test/fixtures/socket-io-app/node_modules/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.6 5 | - 0.8 6 | 7 | notifications: 8 | email: 9 | - travis@nodejitsu.com 10 | irc: "irc.freenode.org#nodejitsu" 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # require-analyzer 2 | 3 | Determine dependencies for a given node.js file, directory tree, or module in code or on the command line 4 | 5 | # Status 6 | [![Build Status](https://secure.travis-ci.org/nodejitsu/require-analyzer.png)](http://travis-ci.org/nodejitsu/require-analyzer) 7 | 8 | ## Installation 9 | 10 | ### Installing npm (node package manager) 11 |
 12 |   curl http://npmjs.org/install.sh | sh
 13 | 
14 | 15 | ### Installing require-analyzer 16 |
 17 |   [sudo] npm install require-analyzer
 18 | 
19 | NOTE: If you're using `npm >= 1.0` then you need to add the `-g` parameter to install `require-analyzer` globally. 20 | 21 | ## Usage 22 | There are two distinct ways to use the `require-analyzer` library: from the command line or through code. The command line tool is designed to work with `package.json` files so make sure that you have created one for your project first. Checkout [jitsu][0] for a quick and easy way to create a package.json. 23 | 24 | For more information read our blog post at [blog.nodejitsu.com][1]. 25 | 26 | ### Command-line usage 27 | Using require-analyzer from the command line is easy. The binary will attempt to read the `package.json` file in the current directory, then analyze the dependencies and cross reference the result. 28 |
 29 |   $ require-analyzer --help
 30 |   usage: require-analyzer [options] [directory]
 31 | 
 32 |   Analyzes the node.js requirements for the target directory. If no directory
 33 |   is supplied then the current directory is used
 34 | 
 35 |   options:
 36 |     --update     Update versions for existing dependencies
 37 |     -h, --help   You're staring at it
 38 | 
39 | 40 | Here's a sample of `require-analyzer` analyzing its own dependencies: 41 |
 42 |   $ require-analyzer
 43 |   info:  require-analyzer starting in /Users/Charlie/Nodejitsu/require-analyzer
 44 |   warn:  No dependencies found
 45 |   info:  Analyzing dependencies...
 46 |   info:  Done analyzing raw dependencies
 47 |   info:  Retrieved packages from npm
 48 |   info:  Additional dependencies found
 49 |   data:  {
 50 |   data:    findit: '>= 0.0.3',
 51 |   data:    npm: '>= 0.3.18'
 52 |   data:  }
 53 |   info:  Updating /Users/Charlie/Nodejitsu/require-analyzer/package.json
 54 |   info:  require-analyzer updated package.json dependencies
 55 | 
56 | 57 | ### Programmatic usage 58 | The easiest way to use `require-analyzer` programmatically is through the `.analyze()` method. This method will use `fs.stat()` on the path supplied and attempt one of three options: 59 | 60 | 1. If it is a directory that has a package.json, analyze `require` statements from `package.main` 61 | 2. If it is a directory with no package.json analyze every `.js` or `.coffee` file in the directory tree 62 | 3. If it is a file, then analyze `require` statements from that individual file. 63 | 64 | Lets dive into a quick sample usage: 65 | 66 | ```javascript 67 | var analyzer = require('require-analyzer'); 68 | 69 | var options = { 70 | target: 'path/to/your/dependency' // e.g /Users/some-user/your-package 71 | reduce: true 72 | }; 73 | 74 | var deps = analyzer.analyze(options, function (err, pkgs) { 75 | // 76 | // Log all packages that were discovered 77 | // 78 | console.dir(pkgs); 79 | }); 80 | 81 | // 82 | // The call the `.analyze()` returns an `EventEmitter` which outputs 83 | // data at various stages of the analysis operation. 84 | // 85 | deps.on('dependencies', function (raw) { 86 | // 87 | // Log the raw list of dependencies (no versions) 88 | // 89 | console.dir(raw); 90 | }); 91 | 92 | deps.on('search', function (pkgs) { 93 | // 94 | // Log the results from the npm search operation with the current 95 | // active version for each dependency 96 | // 97 | console.dir(pkgs); 98 | }); 99 | 100 | deps.on('reduce', function (reduced) { 101 | // 102 | // Logs the dependencies after they have been cross-referenced with 103 | // sibling dependencies. (i.e. if 'foo' requires 'bar', 'bar' will be removed). 104 | // 105 | console.dir(reduced); 106 | }); 107 | ``` 108 | 109 | ### Further analyzing dependencies 110 | Sometimes when dealing with dependencies it is necessary to further analyze the dependencies that are returned. `require-analyzer` has a convenience method for doing just this: 111 | 112 | ```javascript 113 | var analyzer = require('require-analyzer'); 114 | 115 | var current = { 116 | 'foo': '>= 0.1.0' 117 | }; 118 | 119 | var updated = { 120 | 'foo': '>= 0.2.0', 121 | 'bar': '>= 0.1.0' 122 | }; 123 | 124 | var updates = analyzer.updates(current, updated); 125 | 126 | // 127 | // This will return an object literal with the differential 128 | // updates between the two sets of dependencies: 129 | // 130 | // { 131 | // added: { 'bar': '>= 0.1.0' }, 132 | // updated: { 'foo': '>= 0.2.0' } 133 | // } 134 | // 135 | ``` 136 | 137 | ## Tests 138 |
139 |   npm test
140 | 
141 | 142 | #### Author: [Charlie Robbins][2] 143 | 144 | [0]: http://github.com/nodejitsu/jitsu 145 | [1]: http://blog.nodejitsu.com/analyze-nodejs-dependencies-like-magic 146 | [2]: http://nodejitsu.com 147 | -------------------------------------------------------------------------------- /bin/find-dependencies: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Module = require('module').Module, 4 | __load = Module._load, 5 | packages = {}; 6 | 7 | // 8 | // Monkey punch `Module._load()` to observe the names 9 | // of packages as they are loaded. 10 | // 11 | Module._load = function (name, parent) { 12 | process.send({type: "load", msg: name}); 13 | if(name[0] === "." || name[0] === "/"){ 14 | name = Module._resolveFilename(name, parent)[1]; 15 | } 16 | return __load.apply(Module, arguments); 17 | }; 18 | 19 | try { 20 | process.nextTick(function () { 21 | process.exit(0); 22 | }); 23 | 24 | __load.call(Module, process.argv[2], null, true); 25 | } 26 | catch (ex) { 27 | // 28 | // Log errors and attempt to log as many packages as we can. 29 | // 30 | var eStr = '' + (ex 31 | ? (ex.stack ? ex.stack : ex) 32 | : 'falsey error: ' + ex); 33 | 34 | // 35 | // However, 'cannot find module' errors should be squashed. 36 | // In cases with no node_modules present, this is not an indication of failure. 37 | // This should perhaps be replaced with a node-detective fallback. 38 | // 39 | 40 | if (!("code" in ex 41 | ? ex.code === "MODULE_NOT_FOUND" 42 | : /^Error: Cannot find module '.*'/.test(eStr)) 43 | ) { 44 | process.send({type: "error", msg: eStr}); 45 | } 46 | } -------------------------------------------------------------------------------- /bin/require-analyzer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'), 4 | path = require('path'), 5 | colors = require('colors'), 6 | winston = require('winston'), 7 | argv = require('optimist').argv, 8 | eyes = require('eyes'), 9 | analyzer = require('../lib/require-analyzer'); 10 | 11 | var help = [ 12 | 'usage: require-analyzer [options] [directory]', 13 | '', 14 | 'Analyzes the node.js requirements for the target directory. If no directory', 15 | 'is supplied then the current directory is used', 16 | '', 17 | 'options:', 18 | ' -d, --dir Directory to check for package.json', 19 | ' --update Update versions for existing dependencies', 20 | ' -f, --file File to check for instead of package.json.', 21 | ' --safe display existing dependencies but do change package.json', 22 | ' -h, --help You\'re staring at it' 23 | ].join('\n'); 24 | 25 | if (argv.h || argv.help) { 26 | return console.log(help); 27 | } 28 | 29 | // 30 | // Create an `eyes` inspector for pretty printing dependencies. 31 | // 32 | var inspect = eyes.inspector({ stream: null, 33 | styles: { // Styles applied to stdout 34 | all: null, // Overall style applied to everything 35 | label: 'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]` 36 | other: 'inverted', // Objects which don't have a literal representation, such as functions 37 | key: 'grey', // The keys in object literals, like 'a' in `{a: 1}` 38 | special: 'grey', // null, undefined... 39 | number: 'blue', // 1, 2, 3, etc 40 | bool: 'magenta', // true false 41 | regexp: 'green', // /\d+/ 42 | string: 'yellow' 43 | } 44 | }); 45 | 46 | // 47 | // Configure winston to pretty print in a similar style 48 | // to `jitsu`. 49 | // 50 | winston.cli(); 51 | 52 | // 53 | // ### function listDependencies (pkg, msg) 54 | // #### @pkg {Object} Valid package.json 55 | // #### @msg {string} Message to print before listing 56 | // Lists the dependencies in `pkg` using `eyes` for 57 | // pretty printing the output consistently. 58 | // 59 | function listDependencies (pkg, msgs) { 60 | pkg = pkg || {}; 61 | pkg.dependencies = pkg.dependencies || {}; 62 | 63 | var keys = Object.keys(pkg.dependencies), 64 | list = inspect(pkg.dependencies); 65 | 66 | if (keys.length === 0) { 67 | return winston.warn(msgs.error); 68 | } 69 | else if (keys.length <= 2) { 70 | list = list.replace(/\{\s/, '{ \n') 71 | .replace(/\}/, '\n}') 72 | .replace('\033[90m', ' \033[90m') 73 | .replace(/, /ig, ',\n '); 74 | } 75 | else { 76 | list = list.replace(/\n\s{4}/ig, '\n '); 77 | } 78 | 79 | winston.info(msgs.success); 80 | list.split('\n').forEach(function (line) { 81 | winston.data(line); 82 | }); 83 | } 84 | 85 | var dir = process.cwd(), 86 | source = argv._[0], 87 | pkgFile; 88 | 89 | if (source) { 90 | dir = source[0] === '/' ? source : path.join(argv.d || argv.dir || dir, source); 91 | } 92 | 93 | pkgFile = path.join(dir, argv.f || argv.file || 'package.json'); 94 | 95 | winston.info('require-analyzer starting in ' + dir.magenta); 96 | fs.readFile(pkgFile, function (err, data) { 97 | if (err) { 98 | if (err.errno === 34 && err.code === 'ENOENT') { 99 | data = "{}"; 100 | } 101 | else { 102 | winston.error('Error reading package.json'); 103 | winston.error(err.message.red); 104 | return; 105 | } 106 | } 107 | 108 | var pkg, updated, options = { 109 | target: dir, 110 | reduce: true 111 | }; 112 | if (argv.f || argv.file) { 113 | options.f = argv.f || argv.file; 114 | } 115 | 116 | // 117 | // Attempt to parse the package.json from the current directory. 118 | // 119 | try { 120 | pkg = JSON.parse(data.toString()); 121 | listDependencies(pkg, { 122 | error: 'No dependencies found', 123 | success: 'Found existing dependencies' 124 | }); 125 | } 126 | catch (ex) { 127 | winston.error('Error parsing package.json'); 128 | winston.error(ex.message.red); 129 | return; 130 | } 131 | 132 | // 133 | // ### function mergeResults (results) 134 | // #### @results {Object} Set of results returned from `npm` 135 | // Formats the results according to the `version` of each package. 136 | // 137 | function mergeResults (results) { 138 | var all = analyzer.extractVersions(results); 139 | return analyzer.updates(pkg.dependencies, all); 140 | } 141 | 142 | winston.info('Analyzing dependencies...'); 143 | var emitter = analyzer.analyze(options, function (err, packages) { 144 | if (err) { 145 | winston.error('Error analyzing dependencies'.red); 146 | err.message.split('\n').forEach(function (line) { 147 | winston.error(line); 148 | }); 149 | return; 150 | } 151 | }); 152 | 153 | emitter.on('dependencies', function (deps) { 154 | winston.info('Done analyzing raw dependencies'); 155 | }); 156 | 157 | emitter.on('search', function (results) { 158 | winston.info('Retrieved packages from npm'); 159 | updated = mergeResults(results).updated; 160 | }); 161 | 162 | emitter.on('reduce', function (reduced) { 163 | var added = mergeResults(reduced).added, 164 | newpkg = { 165 | dependencies: analyzer.merge({}, added) 166 | }; 167 | 168 | if (argv.update) { 169 | newpkg.dependencies = analyzer.merge({}, newpkg.dependencies, updated); 170 | } 171 | 172 | listDependencies(newpkg, { 173 | error: 'No additional dependencies found', 174 | success: 'Additional dependencies found' 175 | }); 176 | 177 | // 178 | // If require-analyzer found new dependencies then update the `package.json` 179 | // file in the target `dir`. 180 | // 181 | if (argv.safe) { 182 | winston.info('did not update package.json'); 183 | return; 184 | } 185 | if (Object.keys(newpkg.dependencies).length > 0) { 186 | winston.info('Updating ' + pkgFile.magenta); 187 | pkg.dependencies = analyzer.merge({}, pkg.dependencies, newpkg.dependencies); 188 | fs.writeFile(pkgFile, JSON.stringify(pkg, null, 2), function (err) { 189 | if (err) { 190 | winston.error('Error writing package.json'); 191 | winston.error(err.message.red); 192 | return; 193 | } 194 | 195 | winston.info('require-analyzer updated package.json dependencies'); 196 | }); 197 | } 198 | }); 199 | }); 200 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | body { 3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 4 | font-size: 15px; 5 | line-height: 22px; 6 | color: #252519; 7 | margin: 0; padding: 0; 8 | } 9 | a { 10 | color: #261a3b; 11 | } 12 | a:visited { 13 | color: #261a3b; 14 | } 15 | p { 16 | margin: 0 0 15px 0; 17 | } 18 | h4, h5, h6 { 19 | color: #333; 20 | margin: 6px 0 6px 0; 21 | font-size: 13px; 22 | } 23 | h2, h3 { 24 | margin-bottom: 0; 25 | color: #000; 26 | } 27 | h1 { 28 | margin-top: 40px; 29 | margin-bottom: 15px; 30 | color: #000; 31 | } 32 | #container { 33 | position: relative; 34 | } 35 | #background { 36 | position: fixed; 37 | top: 0; left: 525px; right: 0; bottom: 0; 38 | background: #f5f5ff; 39 | border-left: 1px solid #e5e5ee; 40 | z-index: -1; 41 | } 42 | #jump_to, #jump_page { 43 | background: white; 44 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 45 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 46 | font: 10px Arial; 47 | text-transform: uppercase; 48 | cursor: pointer; 49 | text-align: right; 50 | } 51 | #jump_to, #jump_wrapper { 52 | position: fixed; 53 | right: 0; top: 0; 54 | padding: 5px 10px; 55 | } 56 | #jump_wrapper { 57 | padding: 0; 58 | display: none; 59 | } 60 | #jump_to:hover #jump_wrapper { 61 | display: block; 62 | } 63 | #jump_page { 64 | padding: 5px 0 3px; 65 | margin: 0 0 25px 25px; 66 | } 67 | #jump_page .source { 68 | display: block; 69 | padding: 5px 10px; 70 | text-decoration: none; 71 | border-top: 1px solid #eee; 72 | } 73 | #jump_page .source:hover { 74 | background: #f5f5ff; 75 | } 76 | #jump_page .source:first-child { 77 | } 78 | table td { 79 | border: 0; 80 | outline: 0; 81 | } 82 | td.docs, th.docs { 83 | max-width: 450px; 84 | min-width: 450px; 85 | min-height: 5px; 86 | padding: 10px 25px 1px 50px; 87 | overflow-x: hidden; 88 | vertical-align: top; 89 | text-align: left; 90 | } 91 | .docs pre { 92 | margin: 15px 0 15px; 93 | padding-left: 15px; 94 | } 95 | .docs p tt, .docs p code { 96 | background: #f8f8ff; 97 | border: 1px solid #dedede; 98 | font-size: 12px; 99 | padding: 0 0.2em; 100 | } 101 | .pilwrap { 102 | position: relative; 103 | } 104 | .pilcrow { 105 | font: 12px Arial; 106 | text-decoration: none; 107 | color: #454545; 108 | position: absolute; 109 | top: 3px; left: -20px; 110 | padding: 1px 2px; 111 | opacity: 0; 112 | -webkit-transition: opacity 0.2s linear; 113 | } 114 | td.docs:hover .pilcrow { 115 | opacity: 1; 116 | } 117 | td.code, th.code { 118 | padding: 14px 15px 16px 25px; 119 | width: 100%; 120 | vertical-align: top; 121 | background: #f5f5ff; 122 | border-left: 1px solid #e5e5ee; 123 | } 124 | pre, tt, code { 125 | font-size: 12px; line-height: 18px; 126 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 127 | margin: 0; padding: 0; 128 | } 129 | 130 | 131 | /*---------------------- Syntax Highlighting -----------------------------*/ 132 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 133 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 134 | body .hll { background-color: #ffffcc } 135 | body .c { color: #408080; font-style: italic } /* Comment */ 136 | body .err { border: 1px solid #FF0000 } /* Error */ 137 | body .k { color: #954121 } /* Keyword */ 138 | body .o { color: #666666 } /* Operator */ 139 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 140 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 141 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 142 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 143 | body .gd { color: #A00000 } /* Generic.Deleted */ 144 | body .ge { font-style: italic } /* Generic.Emph */ 145 | body .gr { color: #FF0000 } /* Generic.Error */ 146 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 147 | body .gi { color: #00A000 } /* Generic.Inserted */ 148 | body .go { color: #808080 } /* Generic.Output */ 149 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 150 | body .gs { font-weight: bold } /* Generic.Strong */ 151 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 152 | body .gt { color: #0040D0 } /* Generic.Traceback */ 153 | body .kc { color: #954121 } /* Keyword.Constant */ 154 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 155 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 156 | body .kp { color: #954121 } /* Keyword.Pseudo */ 157 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 158 | body .kt { color: #B00040 } /* Keyword.Type */ 159 | body .m { color: #666666 } /* Literal.Number */ 160 | body .s { color: #219161 } /* Literal.String */ 161 | body .na { color: #7D9029 } /* Name.Attribute */ 162 | body .nb { color: #954121 } /* Name.Builtin */ 163 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 164 | body .no { color: #880000 } /* Name.Constant */ 165 | body .nd { color: #AA22FF } /* Name.Decorator */ 166 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 167 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 168 | body .nf { color: #0000FF } /* Name.Function */ 169 | body .nl { color: #A0A000 } /* Name.Label */ 170 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 171 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 172 | body .nv { color: #19469D } /* Name.Variable */ 173 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 174 | body .w { color: #bbbbbb } /* Text.Whitespace */ 175 | body .mf { color: #666666 } /* Literal.Number.Float */ 176 | body .mh { color: #666666 } /* Literal.Number.Hex */ 177 | body .mi { color: #666666 } /* Literal.Number.Integer */ 178 | body .mo { color: #666666 } /* Literal.Number.Oct */ 179 | body .sb { color: #219161 } /* Literal.String.Backtick */ 180 | body .sc { color: #219161 } /* Literal.String.Char */ 181 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 182 | body .s2 { color: #219161 } /* Literal.String.Double */ 183 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 184 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 185 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 186 | body .sx { color: #954121 } /* Literal.String.Other */ 187 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 188 | body .s1 { color: #219161 } /* Literal.String.Single */ 189 | body .ss { color: #19469D } /* Literal.String.Symbol */ 190 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 191 | body .vc { color: #19469D } /* Name.Variable.Class */ 192 | body .vg { color: #19469D } /* Name.Variable.Global */ 193 | body .vi { color: #19469D } /* Name.Variable.Instance */ 194 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/require-analyzer.html: -------------------------------------------------------------------------------- 1 | require-analyzer.js

require-analyzer.js

/*
  2 |  * require-analyzer.js: Determine dependencies for a given node.js file, directory tree, or module.
  3 |  *
  4 |  * (C) 2010, Nodejitsu Inc.
  5 |  *
  6 |  */
  7 | 
  8 | var util = require('util'),
  9 |     path = require('path'),
 10 |     fs = require('fs'),
 11 |     path = require('path'),
 12 |     events = require('events'),
 13 |     spawn = require('child_process').spawn,
 14 |     npm = require('npm'),
 15 |     npmout = require('npm/lib/utils/output'),
 16 |     npmls = require('npm/lib/utils/read-installed'),
 17 |     semver = require('semver'),
 18 |     findit = require('findit');
 19 | 
 20 | var analyzer = exports,
 21 |     _write = npmout.write;

Create the list of core node.js modules for referencing later. 22 | Map the array of all core modules into an object for easy access.

var core = {};
 23 | Object.keys(process.binding('natives')).forEach(function (mod) {
 24 |   core[mod] = true;
 25 | });

function analyze (options, callback)

26 | 27 |

@options {Object} Options to analyze against

28 | 29 |

@callback {function} Continuation to respond to when complete.

30 | 31 |

Calls the appropriate require-analyzer method based on the result 32 | from fs.stats() on options.target. When dependencies are returned, 33 | npmAnalyze() is called with options and the resulting object is 34 | returned to callback. Also returns an event emitter which outputs 35 | data at various stages of completion with events:

36 | 37 |
dependencies: Results from .dir(), .package(), or .file()
 38 | search: Results from initial npm search in .npmAnalyze()
 39 | reduce: Results from .npmAnalyze() if options.reduce is true.
 40 | 
analyzer.analyze = function (options, callback) {
 41 |   var emitter = new events.EventEmitter();
 42 |   
 43 |   if (!options || !options.target) {

If there are no options and no options.target property 44 | respond with the appropriate error.

    callback(new Error('options and options.target are required'));
 45 |     return emitter;
 46 |   }
 47 |   

Stat the directory and call the appropriate method 48 | on analyzer.

  fs.stat(options.target, function (err, stats) {
 49 |     if (err) {
 50 |       return callback(err);
 51 |     }
 52 |     
 53 |     var analyzeFn, rootDir;
 54 |     if (stats.isDirectory()) {
 55 |       analyzeFn = analyzer.dir;
 56 |     }
 57 |     else if (stats.isFile()) {
 58 |       analyzeFn = analyzer.file;
 59 |     }
 60 |     else {
 61 |       return callback(new Error(target + ' is not a file or a directory.'));
 62 |     }
 63 |     
 64 |     analyzeFn.call(null, options, function (err, deps) {
 65 |       if (err) {
 66 |         emitter.emit('childError', err);
 67 |       }
 68 |       

Emit the dependencies event for streaming results.

      emitter.emit('dependencies', deps);
 69 |       
 70 |       if (options.npm === false) {
 71 |         return callback(null, deps);
 72 |       }
 73 |       
 74 |       var npmEmitter = analyzer.npmAnalyze(deps, options, function (nerr, reduced, suspect) {
 75 |         return callback(err || nerr, reduced, suspect);
 76 |       });
 77 |       

Re-emit the search and reduce events from the npmEmitter 78 | for streaming results.

      ['search', 'reduce'].forEach(function (ev) {
 79 |         npmEmitter.on(ev, function () {
 80 |           var args = Array.prototype.slice.call(arguments);
 81 |           args.unshift(ev);
 82 |           emitter.emit.apply(emitter, args);
 83 |         });
 84 |       });
 85 |     });
 86 |   });
 87 |   
 88 |   return emitter;
 89 | };

function npmAnalyze (deps, options, callback)

90 | 91 |

@deps {Array} List of dependencies to analyze.

92 | 93 |

@options {Object} Set of options to analyze with.

94 | 95 |

@callback {function} Continuation to respond to when complete.

96 | 97 |

Analyzes the list of dependencies using npm, consumes the options:

98 | 99 |
options.reduce: Will remove deps consumed by sibling deps
100 | 
analyzer.npmAnalyze = function (deps, options, callback) {
101 |   var emitter = new events.EventEmitter(),
102 |       pkgs = {};
103 |   
104 |   analyzer.findModulesDir(options.target, function (err, root) {

Setup npm options

    options.npm = { 
105 |       prefix: root,
106 |       exit: false 
107 |     };

Monkey patch npmout.write() so that we don't need log or out files

    npmout.write = function () {
108 |       var args = Array.prototype.slice.call(arguments),
109 |           callback;
110 | 
111 |       args.forEach(function (arg) {
112 |         if (typeof arg === 'function') {
113 |           callback = arg;
114 |         }
115 |       });
116 | 
117 |       callback();
118 |     };
119 | 
120 |     npm.load(options.npm, function (err, npm) {
121 |       if (err) {
122 |         return callback(err);
123 |       }

Analyze dependencies by searching for all installed locally via npm. 124 | Then see if it depends on any other dependencies that are in the 125 | list so those dependencies may be removed (only if options.reduce is set).

      npmls(root, function (err, result) {
126 |         if (err) {
127 |           return callback(err);
128 |         }
129 |         else if (!result || !result.dependencies) {
130 |           return callback(null);
131 |         }
132 | 
133 |         Object.keys(result.dependencies).forEach(function (pkg) {
134 |           if (!deps || deps.indexOf(pkg) !== -1) {
135 |             pkgs[pkg] = result.dependencies[pkg];
136 |           }
137 |         });
138 | 
139 |         emitter.emit('search', pkgs);
140 |         if (!options.reduce) {
141 |           npmout.write = _write;
142 |           return callback(null, pkgs);
143 |         }
144 | 
145 |         var reduced = analyzer.merge({}, pkgs),
146 |             suspect = {};
147 | 
148 |         deps.forEach(function (dep) {
149 |           if (pkgs[dep] && pkgs[dep].dependencies) {
150 |             Object.keys(pkgs[dep].dependencies).forEach(function (cdep) {
151 |               if (reduced[cdep]) {
152 |                 suspect[cdep] = pkgs[cdep];
153 |                 delete reduced[cdep];
154 |               }
155 |             });
156 |           }
157 |         });
158 | 
159 |         emitter.emit('reduce', reduced, suspect);
160 |         npmout.write = _write;
161 |         callback(null, reduced, suspect);   
162 |       });
163 |     });
164 |   });
165 |   
166 |   return emitter;
167 | };

function package (dir, callback)

168 | 169 |

@dir {string} Parent directory to analyze

170 | 171 |

@callback {function} Continuation to respond to when complete.

172 | 173 |

Checks for the existance of a package.json in the specified dir 174 | running analyzer.package() if it exists. Otherwise attempts to run 175 | analyzer.file() on all files in the source tree.

analyzer.dir = function (options, callback) {  

Read the target directory

  fs.readdir(options.target, function (err, files) {
176 |     if (err) {
177 |       return callback(err);
178 |     }
179 |     

If there is a package.json in the directory 180 | then analyze the require(s) based on package.main

    if (files.indexOf('package.json') !== -1) {
181 |       return analyzer.package(options, callback);
182 |     }
183 |     

Otherwise find all files in the directory tree 184 | and attempt to run analyzer.file() on each of them 185 | in parallel.

    var files = [],
186 |         done = [],
187 |         packages = {},
188 |         traversed = false,
189 |         finder = findit.find(options.target);
190 |     
191 |     function onRequired () {

Respond to the callback if all files have been traversed 192 | and all files have been executed via analyzer.file()

      if (traversed && files.length === done.length) {
193 |         callback(null, Object.keys(packages));
194 |       }
195 |     }
196 |      
197 |     finder.on('file', function (file) {

If the file is not .js or .coffee do no analyze it

      var ext = path.extname(file),
198 |           clone = analyzer.merge({}, options);
199 |           
200 |       if (ext !== '.js' && ext !== '.coffee') {
201 |         return;
202 |       }
203 |       
204 |       files.push(file);
205 |       
206 |       clone.target = file;
207 |       analyzer.file(clone, function (err, deps) {
208 |         deps.forEach(function (dep) {
209 |           packages[dep] = true;
210 |         });
211 |         
212 |         done.push(file);
213 |         onRequired();
214 |       });
215 |     });
216 |     
217 |     finder.on('end', function () {
218 |       traversed = true;
219 |       onRequired();
220 |     });
221 |   });
222 | };

function package (dir, callback)

223 | 224 |

@dir {string} Parent path of the package.json to analyze

225 | 226 |

@callback {function} Continuation to respond to when complete.

227 | 228 |

Attempts to read the package.json in the specified dir and then analyzes 229 | the require statements in the script located at package.main

analyzer.package = function (options, callback) {

Attempt to read the package.json in the current directory

  fs.readFile(path.join(options.target, 'package.json'), function (err, pkg) {
230 |     if (err) {
231 |       return callback(err);
232 |     }
233 | 
234 |     try {

Attempt to read the package.json data.

      pkg = JSON.parse(pkg.toString());
235 |       

TODO (indexzero): Support more than main

      if (!pkg.main) {
236 |         return callback(new Error('package.json must have a `main` property.'));
237 |       }
238 |       

Analyze the require(s) based on the main property of the package.json

      options = analyzer.clone(options);
239 |       options.target = path.join(options.target, path.normalize(pkg.main));
240 |       analyzer.file(options, function (err, deps) {
241 |         deps = deps.filter(function (d) { return d !== pkg.name });
242 |         callback(err, deps);
243 |       });
244 |     }
245 |     catch (ex) {
246 |       return callback(ex);
247 |     }
248 |   });
249 | };

function file (file, callback)

250 | 251 |

@file {string} Path of the node script to analyze

252 | 253 |

@callback {callback} Continuation to respond to when complete.

254 | 255 |

Attempts to find the packages required by the node script located at 256 | file by spawning an instance of the find-dependencies helper 257 | script and parsing the output.

analyzer.file = function (options, callback) {

Spawn the find-dependencies bin helper to ensure that we are able to 258 | bypass any modules which have already been required in the current process.

  var packages = {},
259 |       merged = {},
260 |       errs = ['Errors received when analyzing ' + options.target],
261 |       deps = spawn('node', [path.join(__dirname, '..', 'bin', 'find-dependencies'), options.target]);
262 |   
263 |   function parseLines(data, prefix, fn) {
264 |     data = data.toString();
265 |     if (data !== '') {
266 |       data.toString().split('\n').filter(function (line) {
267 |         return line !== '';
268 |       }).forEach(function (line) {
269 |         if (line.indexOf(prefix) !== -1) {
270 |           line = line.replace(prefix, '');
271 |           fn(line);
272 |         } 
273 |       });
274 |     }
275 |   }
276 |   
277 |   deps.stdout.on('data', function (data) {

For each line of data output from the child process remove empty 278 | lines and then add the specified packages to list of known packages.

    parseLines(data, '__!load::', function (dep) {
279 |       packages[dep] = true;
280 |     });
281 |   });
282 |   
283 |   deps.stderr.on('data', function (data) {
284 |     parseLines(data, '__!err::', function (line) {
285 |       errs.push(line);
286 |     });
287 |   });

Set the default timeout to 5 seconds

  options.timeout = options.timeout || 5000;

If a timeout has been set then exit the 288 | process after the specified timespan

  var timeoutId = setTimeout(function () {
289 |     deps.kill();
290 |   }, options.timeout);
291 |   
292 |   deps.on('exit', function () {

Remove the timeout now that we have exited.

    clearTimeout(timeoutId);
293 |     

When the process is complete remove any core node.js packages 294 | (i.e. packages in process.bindings('natives')) and any packages 295 | which are required with a relative directory (i.e. require('./package')).

296 | 297 |

Include any packages which may be of the form require('package/relative/dir') 298 | because those relative directories are still supported by npm: 299 | e.g.: require('socket.io/lib/socket.io/utils').

    packages = Object.keys(packages);
300 |     (options.raw ? packages : packages.filter(function (pkg) {
301 |       return pkg[0] !== '.' && pkg[0] !== '/' && !core[pkg];
302 |     }).map(function (pkg) {
303 |       return pkg.split('/')[0];
304 |     })).forEach(function (pkg) {
305 |       merged[pkg] = true;
306 |     });
307 | 
308 |     return errs.length > 1 
309 |       ? callback(new Error(errs.join('\n')), Object.keys(merged))
310 |       : callback(null, Object.keys(merged));
311 |   });
312 | };

function findModulesDir (target)

313 | 314 |

@target {string} The directory (or file) to search up from

315 | 316 |

Searches up from the specified target until it finds a directory which contains 317 | a folder called node_modules

analyzer.findModulesDir = function (target, callback) {
318 |   fs.stat(target, function (err, stats) {
319 |     if (err) {
320 |       return callback(err);
321 |     }
322 |     
323 |     if (stats.isDirectory()) {
324 |       return fs.readdir(target, function (err, files) {
325 |         if (err) {
326 |           return callback(err);
327 |         }
328 |         
329 |         if (files.indexOf('node_modules') !== -1) {
330 |           return callback(null, target);
331 |         }
332 |       });
333 |     }
334 |     else if (stats.isFile()) {
335 |       return analyzer.findModulesDir(path.dirname(target), callback);
336 |     }
337 |   });
338 | };

function (target [arg1, arg2, ...])

339 | 340 |

@target {Object} Object to merge into

341 | 342 |

Merges all properties in arg1 ... argn 343 | into the target object.

analyzer.merge = function (target) {
344 |   var objs = Array.prototype.slice.call(arguments, 1);
345 |   objs.forEach(function(o) {
346 |     Object.keys(o).forEach(function (attr) {
347 |       if (! o.__lookupGetter__(attr)) {
348 |         target[attr] = o[attr];
349 |       }
350 |     });
351 |   });
352 |   
353 |   return target;
354 | };

function clone (object)

355 | 356 |

@object {Object} Object to clone.

357 | 358 |

Shallow clones the target object.

analyzer.clone = function (object) {
359 |   return Object.keys(object).reduce(function (obj, k) {
360 |     obj[k] = object[k];
361 |     return obj;
362 |   }, {});
363 | };

function extractVersions (dependencies)

364 | 365 |

@dependencies {Object} Set of dependencies to transform

366 | 367 |

Transforms the dependencies object into the format that 368 | package.json files accept.

analyzer.extractVersions = function (dependencies) {
369 |   var all = {};
370 | 
371 |   Object.keys(dependencies).forEach(function (pkg) {
372 |     var version = dependencies[pkg].version.trim().split('.'),
373 |         build   = version[2].match(/^\d+(\-?[\w|\-]+)/);
374 |     
375 |     version[2] = build ? version[2] : 'x';
376 |     all[pkg]   = build ? '>= ' + dependencies[pkg].version : version.join('.');
377 |   });
378 |   
379 |   return all;
380 | }

function updates (current, updated)

381 | 382 |

@current {Object} Current dependencies

383 | 384 |

@updated {Object} Updated dependencies

385 | 386 |

Compares the current dependencies against the 387 | updated dependencies and returns an object 388 | with the differences

389 | 390 |
{
391 |   added: { /* Intersection of updated / current */ }
392 |   updated: { /* Union of updated / current with new versions */ }
393 | }
394 | 
analyzer.updates = function (current, updated) {
395 |   var updates = {
396 |     added: {},
397 |     updated: {}
398 |   };
399 |   
400 |   if (!current) {
401 |     updates.updated = updated || {};
402 |     return updates;
403 |   }
404 |   else if (!updated) {
405 |     return updates;
406 |   }
407 |   

Get the list of all added dependencies

  Object.keys(updated).filter(function (key) {
408 |     return !current[key];
409 |   }).forEach(function (key) {
410 |     updates.added[key] = updated[key];
411 |   });
412 |   

Get the list of all dependencies that have been updated

  Object.keys(updated).filter(function (key) {
413 |     if (!current[key]) {
414 |       return false;
415 |     }
416 |     
417 |     var left = updated[key].replace(/\<|\>|\=|\s/ig, ''),
418 |         right = current[key].replace(/\<|\>|\=|\s/ig, '');
419 |         
420 |     return semver.gt(left, right);
421 |   }).forEach(function (key) {
422 |     updates.updated[key] = updated[key];
423 |   })
424 |   
425 |   return updates;
426 | };
427 | 
428 | 
-------------------------------------------------------------------------------- /lib/require-analyzer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * require-analyzer.js: Determine dependencies for a given node.js file, directory tree, or module. 3 | * 4 | * (C) 2010, Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var util = require('util'), 9 | path = require('path'), 10 | fs = require('fs'), 11 | exists = fs.exists || path.exists, 12 | events = require('events'), 13 | fork = require('child_process').fork, 14 | readInstalled = require('read-installed'), 15 | detective = require('detective'), 16 | resolve = require('resolve'), 17 | semver = require('semver'); 18 | 19 | var analyzer = exports; 20 | 21 | // 22 | // ### function analyze (options, callback) 23 | // #### @options {Object} Options to analyze against 24 | // #### @callback {function} Continuation to respond to when complete. 25 | // Calls `path`. When dependencies are returned, 26 | // `npmAnalyze()` is called with `options` and the resulting object is 27 | // returned to `callback`. Also returns an event emitter which outputs 28 | // data at various stages of completion with events: 29 | // 30 | // dependencies: Results from .dir(), .package(), or .file() 31 | // search: Results from initial npm search in .npmAnalyze() 32 | // reduce: Results from .npmAnalyze() if options.reduce is true. 33 | // 34 | analyzer.analyze = function (options, callback) { 35 | var emitter = new events.EventEmitter(); 36 | 37 | // 38 | // let path determine what to do 39 | // 40 | analyzer.path(options, function (err, deps) { 41 | if (err) { 42 | emitter.emit('childError', err); 43 | return callback(err); 44 | } 45 | 46 | // Emit the `dependencies` event for streaming results. 47 | emitter.emit('dependencies', deps); 48 | 49 | if (options.npm === false || !deps || deps.length === 0) { 50 | return callback(null, deps); 51 | } 52 | 53 | var npmEmitter = analyzer.npmAnalyze(deps, options, callback); 54 | 55 | // 56 | // Re-emit the `search` and `reduce` events from the `npmEmitter` 57 | // for streaming results. 58 | // 59 | ['search', 'reduce'].forEach(function (ev) { 60 | npmEmitter.on(ev, emitter.emit.bind(emitter, ev)); 61 | }); 62 | }); 63 | 64 | return emitter; 65 | }; 66 | 67 | // 68 | // ### function path (options, callback) 69 | // #### @options {Object} Options to analyze against 70 | // #### @callback {function} Continuation to respond to when complete. 71 | // Calls the appropriate `require-analyzer` method based on the result 72 | // from `fs.stats()` on `options.target`. 73 | // 74 | analyzer.path = function(options, callback){ 75 | if (!options || !options.target) { 76 | // 77 | // If there are no `options` and no `options.target` property 78 | // respond with the appropriate error. 79 | // 80 | callback(new Error('options and options.target are required')); 81 | } 82 | 83 | // 84 | // Stat the directory and call the appropriate method 85 | // on `analyzer`. 86 | // 87 | fs.stat(options.target, function (err, stats) { 88 | if (err) { 89 | return callback(err); 90 | } 91 | else if (stats.isDirectory()) { 92 | analyzer.dir(options, callback); 93 | } 94 | else if (stats.isFile()) { 95 | if('fileFilter' in options && !options.fileFilter(options.target)) return; 96 | analyzer.file(options, callback); 97 | } 98 | else { 99 | err = new Error(options.target + ' is not a file or a directory.'); 100 | err.code = 'UNSUPPORTED_TYPE'; 101 | callback(err); 102 | } 103 | }); 104 | }; 105 | 106 | // 107 | // ### function npmAnalyze (deps, options, callback) 108 | // #### @deps {Array} List of dependencies to analyze. 109 | // #### @options {Object} Set of options to analyze with. 110 | // #### @callback {function} Continuation to respond to when complete. 111 | // Analyzes the list of dependencies using `read-installed`, consumes the options: 112 | // 113 | // options.reduce: Will remove deps consumed by sibling deps 114 | // 115 | analyzer.npmAnalyze = function (deps, options, callback) { 116 | var emitter = new events.EventEmitter(), 117 | pkgs = {}; 118 | 119 | if (!deps || deps.length === 0) { 120 | return callback(); 121 | } 122 | 123 | analyzer.findNextDir(options.target, function (err, root) { 124 | if (err) { 125 | return callback(err); 126 | } 127 | 128 | // 129 | // Analyze dependencies by searching for all installed locally via read-installed. 130 | // Then see if it depends on any other dependencies that are in the 131 | // list so those dependencies may be removed (only if `options.reduce` is set). 132 | // 133 | readInstalled(root, 1, function (err, result) { 134 | if (err) { 135 | return callback(err); 136 | } 137 | else if (!result || !result.dependencies || Object.keys(result.dependencies).length === 0) { 138 | // When no dependencies were found, return what we got 139 | if(Array.isArray(deps)){ 140 | return callback(null, deps.reduce(function(obj, prop){ 141 | obj[prop] = "*"; 142 | return obj; 143 | }, {})); 144 | } 145 | else { 146 | return callback(null, deps); 147 | } 148 | } 149 | 150 | Object.keys(result.dependencies).forEach(function (pkg) { 151 | if (result.devDependencies && pkg in result.devDependencies) return; 152 | if (result.bundleDependencies && pkg in result.bundleDependencies) return; 153 | if (!Array.isArray(deps)) { 154 | if (deps[pkg] === '*' || !(pkg in deps) ) { 155 | pkgs[pkg] = pkg in result.dependencies 156 | ? result.dependencies[pkg]['version'] 157 | : deps[pkg]; 158 | } 159 | else { 160 | pkgs[pkg] = deps[pkg]; 161 | } 162 | } 163 | else if (!deps || deps.indexOf(pkg) !== -1) { 164 | pkgs[pkg] = result.dependencies[pkg]['version']; 165 | } 166 | }); 167 | 168 | emitter.emit('search', pkgs); 169 | 170 | if (!options.reduce) { 171 | return callback(null, pkgs); 172 | } 173 | 174 | var reduced = analyzer.clone(pkgs), 175 | suspect = {}; 176 | 177 | Object.keys(deps).forEach(function (dep) { 178 | if (dep in pkgs && pkgs[dep].dependencies) { 179 | Object.keys(pkgs[dep].dependencies).forEach(function (cdep) { 180 | if (cdep in reduced) { 181 | suspect[cdep] = pkgs[cdep]; 182 | delete reduced[cdep]; 183 | } 184 | }); 185 | } 186 | }); 187 | 188 | emitter.emit('reduce', reduced, suspect); 189 | 190 | callback(null, reduced, suspect); 191 | }); 192 | }); 193 | 194 | return emitter; 195 | }; 196 | 197 | function filterFiles(file){ 198 | // 199 | // If the file is not `.js` or `.coffee` do no analyze it 200 | // 201 | var ext = path.extname(file); 202 | return ext === '.js' || ext === '.coffee'; 203 | } 204 | 205 | // 206 | // ### function package (dir, callback) 207 | // #### @dir {string} Parent directory to analyze 208 | // #### @callback {function} Continuation to respond to when complete. 209 | // Checks for the existance of a package.json in the specified `dir` 210 | // running `analyzer.package()` if it exists. Otherwise attempts to run 211 | // `analyzer.file()` on all files in the source tree. 212 | // 213 | analyzer.dir = function (options, callback) { 214 | var target = path.resolve(__dirname, options.target); 215 | 216 | // 217 | // Read the target directory 218 | // 219 | fs.readdir(target, function (err, files) { 220 | if (err) { 221 | return callback(err); 222 | } 223 | 224 | // 225 | // If there is a package.json in the directory 226 | // then analyze the require(s) based on `package.main` 227 | // 228 | if (files.indexOf('package.json') !== -1) { 229 | return analyzer.package(options, callback); 230 | } 231 | 232 | var remaining = files.length, 233 | packages = {}; 234 | 235 | // 236 | // Otherwise find all files in the directory tree 237 | // and attempt to run `analyzer.file()` on each of them 238 | // in parallel. 239 | // 240 | files.forEach(function(file){ 241 | // 242 | // skip all files from 'node_modules' directories 243 | // 244 | if(file === 'node_modules') return remaining--; 245 | 246 | // 247 | // call analyzer.path and currate all dependencies 248 | // 249 | analyzer.path({ 250 | __proto__: options, 251 | target: path.join(target, file), 252 | fileFilter: filterFiles 253 | }, function(err, deps){ 254 | if(err && err.code !== 'UNSUPPORTED_TYPE'){ 255 | // 256 | // skip symlinks & friends 257 | // but forward real errors 258 | // 259 | remaining = -1; //ensures that callback won't be called again 260 | callback(err); 261 | return; 262 | } 263 | 264 | deps.forEach(function(dep){ 265 | packages[dep] = true; 266 | }); 267 | 268 | // 269 | // when all files are analyzed, call the callback 270 | // 271 | if(!--remaining){ 272 | callback(null, Object.keys(packages)); 273 | } 274 | }); 275 | }); 276 | }); 277 | }; 278 | 279 | // 280 | // ### function package (dir, callback) 281 | // #### @dir {string} Parent path of the package.json to analyze 282 | // #### @callback {function} Continuation to respond to when complete. 283 | // Attempts to read the package.json in the specified `dir` and then analyzes 284 | // the require statements in the script located at `package.main` 285 | // 286 | analyzer.package = function (options, callback) { 287 | // 288 | // Attempt to read the package.json in the current directory 289 | // 290 | fs.readFile(path.join(options.target, options.file || 'package.json'), function (err, pkg) { 291 | if (!err) { 292 | try { 293 | // 294 | // Attempt to read the package.json data. 295 | // 296 | pkg = JSON.parse(pkg.toString()); 297 | } 298 | catch (e) { 299 | return callback(e); 300 | } 301 | } 302 | else { 303 | pkg = {}; 304 | } 305 | 306 | try { 307 | // 308 | // Analyze the require(s) based on: 309 | // - the `main` property of the package.json 310 | // - the default file if no package.json exists 311 | // 312 | var todo = 0, 313 | _deps = []; 314 | 315 | function dequeue(err) { 316 | todo--; 317 | if (todo === 0) { 318 | if(err) callback(err); 319 | else mergeDependencies(_deps, pkg, callback); 320 | } 321 | } 322 | 323 | function processOptions(options) { 324 | todo++; 325 | 326 | analyzer.file(options, function (err, deps) { 327 | _deps = _deps.concat(deps.filter(function (d) { 328 | return d !== pkg.name && _deps.indexOf(d) === -1; 329 | })); 330 | 331 | dequeue(err); 332 | }); 333 | } 334 | 335 | var newoptions = analyzer.clone(options); 336 | 337 | function setMain(files, pkg, newoptions, callback) { 338 | function nextFile() { 339 | if (!files.length) { 340 | return callback(pkg, newoptions); 341 | } 342 | 343 | var file = files.shift(); 344 | 345 | exists(file, function(exists){ 346 | if (exists) { 347 | pkg.main = file; 348 | callback(pkg, newoptions); 349 | } 350 | else nextFile(); 351 | }); 352 | } 353 | 354 | nextFile(); 355 | } 356 | 357 | function setTarget(pkg, newoptions) { 358 | var newPath = path.join(newoptions.target, pkg.main ? path.normalize(pkg.main) : '/'), 359 | newTarget = analyzer.resolve(newPath); 360 | 361 | if (newTarget === false) { 362 | todo = 1; 363 | dequeue(new Error('Couldn\'t resolve path ' + newPath)); 364 | } 365 | return processOptions(newoptions); 366 | } 367 | 368 | // add logic to default to app.js or server.js for main if main is not present. 369 | if ( !('main' in pkg) || pkg.main === '') { 370 | setMain(['app.js', 'server.js', 'index.js'], pkg, newoptions, setTarget); 371 | } 372 | else { 373 | setTarget(pkg, newoptions); 374 | } 375 | } 376 | catch (ex) { 377 | return callback(ex); 378 | } 379 | }); 380 | }; 381 | 382 | function analyzeFile (options, callback) { 383 | var remaining = 1; 384 | 385 | function cb(err, data){ 386 | if(!--remaining) callback(); 387 | } 388 | 389 | fs.readFile(options.target, function(err, data){ 390 | if(err) return callback(err); 391 | 392 | var files; 393 | 394 | try { 395 | files = detective.find(data.toString('utf8')); 396 | } catch(e){ 397 | return callback(err); 398 | } 399 | 400 | files.strings.forEach(function(name){ 401 | if(name in options.packages) return; 402 | 403 | options.packages[name] = true; 404 | 405 | var absolutePath = analyzer.resolve(name, options.target); 406 | if(!absolutePath || path.relative(options.target, absolutePath).indexOf('node_modules') >= 0){ 407 | return; 408 | } 409 | 410 | remaining++; 411 | 412 | analyzeFile({ 413 | __proto__: options, 414 | target: absolutePath 415 | }, cb); 416 | }); 417 | 418 | if(files.expressions.length > 0){ 419 | remaining++; 420 | spawnWorker({ 421 | __proto__: options, 422 | target: options.target 423 | }, cb); 424 | } 425 | 426 | cb(); 427 | }); 428 | } 429 | 430 | var findDepsPath = path.join(__dirname, '..', 'bin', 'find-dependencies'); 431 | 432 | function spawnWorker (options, callback) { 433 | // 434 | // Spawn the `find-dependencies` bin helper to ensure that we are able to 435 | // bypass any modules which have already been required in the current process. 436 | // 437 | var packages = options.packages, 438 | errs = options.errors, 439 | deps = fork(findDepsPath, [options.target], {silent: true}); 440 | 441 | deps.send(options.target); 442 | 443 | deps.on('message', function(data){ 444 | switch(data.type){ 445 | case 'load': 446 | packages[data.msg] = true; 447 | break; 448 | case 'error': 449 | errs.push(data.msg); 450 | } 451 | }); 452 | 453 | // 454 | // Set the default timeout to `5 seconds` 455 | // 456 | options.timeout = options.timeout || 5000; 457 | 458 | // 459 | // If a timeout has been set then exit the 460 | // process after the specified timespan 461 | // 462 | var timeoutId = setTimeout(function () { 463 | deps.kill(); 464 | }, options.timeout); 465 | 466 | deps.on('exit', function () { 467 | // 468 | // 469 | // Remove the timeout now that we have exited. 470 | // 471 | clearTimeout(timeoutId); 472 | callback(); 473 | }); 474 | } 475 | 476 | // 477 | // ### function file (file, callback) 478 | // #### @file {string} Path of the node script to analyze 479 | // #### @callback {callback} Continuation to respond to when complete. 480 | // Attempts to find the packages required by the node script located at 481 | // `file` by spawning an instance of the `find-dependencies` helper 482 | // script and parsing the output. 483 | // 484 | 485 | analyzer.file = function(options, callback){ 486 | if(!options.packages) options.packages = {}; 487 | if(!options.errors) options.errors = []; 488 | 489 | analyzeFile(options, function(err){ 490 | if(options.errors.length > 0){ 491 | callback(options.errors); //TODO call with real error object 492 | } else { 493 | // 494 | // Remove any core node.js packages 495 | // (i.e. the ones for which `require.resolve(module) == module`) and any packages 496 | // which are required with a relative directory (i.e. `require('./package')`). 497 | // 498 | // Include any packages which may be of the form `require('package/relative/dir')` 499 | // because those relative directories are still supported by npm: 500 | // e.g.: `require('socket.io/lib/socket.io/utils')`. 501 | // 502 | 503 | var packages = Object.keys(options.packages); 504 | 505 | if(!options.raw){ 506 | packages = packages.filter(function (pkg) { 507 | return pkg[0] !== '.' && pkg[0] !== '/' && !analyzer.isNative(pkg); 508 | }).map(function (pkg) { 509 | return pkg.split(path.sep, 2)[0]; 510 | }).reduce(function(obj, name){ 511 | obj[name] = true; 512 | return obj; 513 | }, {}); 514 | 515 | packages = Object.keys(packages); 516 | } 517 | callback(null, packages); 518 | } 519 | }); 520 | }; 521 | 522 | // 523 | // ### function findNextDir (target) 524 | // #### @target {string} The path to search up from 525 | // Searches up from the specified `target` until it finds a directory 526 | // 527 | analyzer.findNextDir = function(target, callback) { 528 | fs.stat(target, function (err, stats) { 529 | if (err) { 530 | callback(err); 531 | } 532 | else if (stats.isDirectory()) { 533 | callback(null, target); 534 | } 535 | else if (stats.isFile()) { 536 | analyzer.findNextDir(path.dirname(target), callback); 537 | } 538 | else { 539 | callback(new Error(target + ' is not a file or a directory.')); 540 | } 541 | }); 542 | }; 543 | 544 | // 545 | // ### function findModulesDir (target) 546 | // #### @target {string} The directory (or file) to search up from 547 | // Searches up from the specified `target` until it finds a directory which contains 548 | // a folder called `node_modules` 549 | // 550 | analyzer.findModulesDir = function (target, callback) { 551 | analyzer.findNextDir(target, function(err, dir){ 552 | fs.readdir(target, function (err, files) { 553 | if (err) { 554 | callback(err); 555 | } 556 | else if (files.indexOf('node_modules') !== -1 || files.indexOf('package.json') !== -1) { 557 | //TODO ensure it's actually a directory/file 558 | callback(null, target); 559 | } 560 | else if (target === (target = path.dirname(target))){ 561 | callback(new Error('Couldn\'t find a node_modules directory.')); 562 | } 563 | else { 564 | analyzer.findModulesDir(target, callback); 565 | } 566 | }); 567 | }); 568 | }; 569 | 570 | // 571 | // ### function (target [arg1, arg2, ...]) 572 | // #### @target {Object} Object to merge into 573 | // Merges all properties in `arg1 ... argn` 574 | // into the `target` object. 575 | // 576 | // TODO remove this as it isn't used anymore 577 | // 578 | analyzer.merge = function (target) { 579 | var objs = Array.prototype.slice.call(arguments, 1); 580 | objs.forEach(function (o) { 581 | Object.keys(o).forEach(function (attr) { 582 | if ( !('get' in Object.getOwnPropertyDescriptor(o, attr)) ) { 583 | target[attr] = o[attr]; 584 | } 585 | }); 586 | }); 587 | 588 | return target; 589 | }; 590 | 591 | // 592 | // ### function clone (object) 593 | // #### @object {Object} Object to clone. 594 | // Shallow clones the target `object`. 595 | // 596 | analyzer.clone = function (object) { 597 | return Object.keys(object).reduce(function (obj, k) { 598 | obj[k] = object[k]; 599 | return obj; 600 | }, {}); 601 | }; 602 | 603 | // 604 | // ### function extractVersions (dependencies) 605 | // #### @dependencies {Object} Set of dependencies to transform 606 | // Transforms the `dependencies` object into the format that 607 | // package.json files accept. 608 | // 609 | analyzer.extractVersions = function (dependencies) { 610 | var all = {}; 611 | 612 | if (!dependencies) { 613 | return all; 614 | } 615 | 616 | if (Array.isArray(dependencies)) { 617 | dependencies.forEach(function (dep) { 618 | all[dep] = '*'; 619 | }); 620 | return all; 621 | } 622 | 623 | Object.keys(dependencies).forEach(function (pkg) { 624 | var raw = dependencies[pkg] || '*', 625 | parse = semver.expressions.parse.exec(raw.trim()), 626 | version = parse ? parse.slice(1) : raw, 627 | build = version ? version[3] || version[4] : null; 628 | if (!/^[v\d]/.test(raw)) { 629 | all[pkg] = raw; 630 | } 631 | else if (typeof version === 'string') { 632 | all[pkg] = version; 633 | } 634 | else { 635 | version[2] = build ? version[2] : 'x'; 636 | all[pkg] = build ? '>= ' + dependencies[pkg] : version.filter(Boolean).join('.'); 637 | } 638 | }); 639 | 640 | return all; 641 | }; 642 | 643 | // 644 | // ### function updates (current, updated) 645 | // #### @current {Object} Current dependencies 646 | // #### @updated {Object} Updated dependencies 647 | // Compares the `current` dependencies against the 648 | // `updated` dependencies and returns an object 649 | // with the differences 650 | // 651 | // { 652 | // added: { /* Intersection of updated / current */ } 653 | // updated: { /* Union of updated / current with new versions */ } 654 | // } 655 | // 656 | var cleanVersion = /\<|\>|\=|\s/ig; 657 | 658 | analyzer.updates = function (current, updated) { 659 | var updates = { 660 | added: !current && updated || {}, 661 | updated: {} 662 | }; 663 | 664 | if (!current || !updated) { 665 | return updates; 666 | } 667 | 668 | // 669 | // Get the list of all added dependencies 670 | // 671 | Object.keys(updated).filter(function (key) { 672 | return !(key in current); 673 | }).forEach(function (key) { 674 | updates.added[key] = updated[key]; 675 | }); 676 | 677 | // 678 | // Get the list of all dependencies that have been updated 679 | // 680 | Object.keys(updated).filter(function (key) { 681 | if ( !(key in current) ) { 682 | return false; 683 | } 684 | 685 | var left = updated[key].replace(cleanVersion, ''), 686 | right = current[key].replace(cleanVersion, ''); 687 | 688 | return semver.gt(left, right); 689 | }).forEach(function (key) { 690 | updates.updated[key] = updated[key]; 691 | }); 692 | 693 | return updates; 694 | }; 695 | 696 | // 697 | // ### function isNative (module) 698 | // #### @module {string} Module 699 | // Check if `module` is a native module (like `net` or `tty`). 700 | // 701 | // TODO use the resolve module for this 702 | // (faster & doesn't depend on the node version) 703 | // 704 | analyzer.isNative = function (module) { 705 | try { 706 | return require.resolve(module) === module; 707 | } 708 | catch (err) { 709 | return false; 710 | } 711 | }; 712 | 713 | // 714 | // ### function resolve (file, base) 715 | // #### @file {string} filename 716 | // #### @base {string} the root from which the file should be searched 717 | // Check if `module` is a native module (like `net` or `tty`). 718 | // 719 | analyzer.resolve = function(file, base){ 720 | try { 721 | return resolve.sync(file, { 722 | basedir: base && path.dirname(base), 723 | extensions: ['.js', '.coffee'] 724 | }); 725 | } catch (e) { 726 | return false; 727 | } 728 | }; 729 | 730 | function mergeDependencies(deps, pkg, callback) { 731 | var pkgDeps = pkg.dependencies; 732 | 733 | function removeDevDeps(deps) { 734 | var obj = analyzer.clone(deps), dep; 735 | 736 | if('devDependencies' in pkg){ 737 | for (dep in pkg.devDependencies) { 738 | if (dep in obj) { 739 | delete obj[dep]; 740 | } 741 | } 742 | } 743 | 744 | if('bundleDependencies' in pkg){ 745 | for (dep in pkg.bundleDependencies) { 746 | if (dep in obj) { 747 | delete obj[dep]; 748 | } 749 | } 750 | } 751 | return obj; 752 | } 753 | 754 | var merged = {}; 755 | 756 | if (!Array.isArray(deps)) { 757 | if (typeof deps === 'undefined' || 758 | Object.keys(deps).length === 0) { 759 | return callback(null, removeDevDeps(pkgDeps)); 760 | } 761 | } 762 | 763 | if (typeof pkgDeps === 'undefined' || Object.keys(pkgDeps).length === 0) { 764 | deps.forEach(function (d) { 765 | merged[d] = '*'; 766 | }); 767 | 768 | return callback(null, removeDevDeps(merged)); 769 | } 770 | 771 | deps.forEach(function (d) { 772 | merged[d] = pkgDeps[d] || '*'; 773 | }); 774 | 775 | Object.keys(pkgDeps).forEach(function (d) { 776 | if ( !(d in merged) ) { 777 | merged[d] = pkgDeps[d]; 778 | } 779 | }); 780 | 781 | return callback(null, removeDevDeps(merged)); 782 | } 783 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "require-analyzer", 3 | "description": "Determine dependencies for a given node.js file, directory tree, or module in code or on the command line", 4 | "version": "0.5.0", 5 | "author": "Nodejitsu Inc. ", 6 | "maintainers": [ 7 | "indexzero ", 8 | "AvianFlu " 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "http://github.com/nodejitsu/require-analyzer.git" 13 | }, 14 | "dependencies": { 15 | "colors": "0.x.x", 16 | "optimist": "0.3.x", 17 | "semver": "1.0.x", 18 | "winston": "0.6.x", 19 | "detective": "0.0.x", 20 | "resolve": "0.2.x", 21 | "eyes": "0.1.x", 22 | "read-installed": "0.0.x" 23 | }, 24 | "devDependencies": { 25 | "vows": "0.6.x" 26 | }, 27 | "main": "./lib/require-analyzer", 28 | "bin": { 29 | "require-analyzer": "./bin/require-analyzer" 30 | }, 31 | "engines": { 32 | "node": ">= 0.4.0" 33 | }, 34 | "scripts": { 35 | "test": "vows test/*-test.js --spec" 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /test/example-apps-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * example-apps-test.js: check that require-analyzer detects correct 3 | * dependencies for example modules. 4 | * 5 | * (C) 2010, Nodejitsu Inc. 6 | * 7 | */ 8 | 9 | var fs = require('fs'), 10 | exec = require('child_process').exec, 11 | path = require('path'), 12 | vows = require('vows'), 13 | assert = require('assert'), 14 | analyzer = require('../lib/require-analyzer'); 15 | 16 | function dependencies (file, prerunner) { 17 | return function () { 18 | var that = this; 19 | 20 | function runAnalyze () { 21 | analyzer.analyze({ 22 | target: path.join(__dirname, file) 23 | }, 24 | function (err, pkgs) { 25 | return err ? that.callback(err) : that.callback(null, analyzer.extractVersions(pkgs)); 26 | }); 27 | } 28 | 29 | return prerunner 30 | ? prerunner(runAnalyze) 31 | : runAnalyze(); 32 | } 33 | } 34 | 35 | vows.describe('require-analyzer/examples').addBatch({ 36 | "When passed a simple app with a dependency in node_modules but not package.json":{ 37 | topic: dependencies('./fixtures/example-app1'), 38 | "the dependency is properly detected.": function (err, pkgs) { 39 | assert.deepEqual(pkgs, { 40 | 'example-dep1': '0.1.x' 41 | }); 42 | } 43 | }, 44 | "When passed an app with all dependencies in node_modules but only some in package.json":{ 45 | topic: dependencies('./fixtures/example-app2'), 46 | "dependencies are still properly detected.": function (err, pkgs) { 47 | assert.isNull(err); 48 | assert.deepEqual(pkgs, { 49 | 'example-dep1': '0.1.x', 50 | 'example-dep2': '6.5.x' 51 | }); 52 | } 53 | }, 54 | "When passed an app with no package.json and an npm dependency (socket.io)":{ 55 | topic: dependencies('./fixtures/socket-io-app/', function (callback) { 56 | var rootDir = path.join(__dirname, '..'), 57 | modulesDir = path.join(__dirname, 'fixtures', 'socket-io-app', 'node_modules'); 58 | var commands = [ 59 | 'mkdir ' + modulesDir, 60 | 'cd ' + modulesDir, 61 | 'npm install socket.io@0.9' 62 | ]; 63 | 64 | exec(commands.join(' && '), callback); 65 | }), 66 | "the dependency is properly detected.": function (err, pkgs) { 67 | assert.isNull(err); 68 | assert.deepEqual(pkgs, { 69 | 'socket.io': '0.9.x' 70 | }); 71 | } 72 | }, 73 | "When called with a module that needs to know it's the main and has a nextTick require":{ 74 | topic: dependencies('./fixtures/example-app3'), 75 | "first-tick dependencies are still properly detected": function (err, pkgs) { 76 | assert.isNull(err); 77 | assert.deepEqual(pkgs, { 78 | // 79 | // Since carapace now starts apps telling them they are the main module 80 | // it should work like that here too. 81 | // 82 | 'example-dep1': '0.1.x', // if(!module.parent) 83 | 'example-dep2': '6.5.x', // if(require.main === module) 84 | 'example-dep3': '7.5.x', // present in package.json 85 | // 'example-dep4': '*' // only load modules defined in the first tick. 86 | }); 87 | }, 88 | "modules required after the first tick are not detected": function (err, pkgs) { 89 | assert.isNull(err); 90 | assert.isUndefined(pkgs['example-dep4']); 91 | } 92 | }, 93 | "When passed an app with conflicts":{ 94 | topic: dependencies('./fixtures/conflicting-app'), 95 | "first level dependencies should still be detected": function (err, pkgs) { 96 | assert.isNull(err); 97 | assert.deepEqual(pkgs, { 98 | 'dep1': '0.1.x', 99 | 'dep2-with-conflict-on-dep1': '6.5.x', 100 | }); 101 | } 102 | }, 103 | "When passed an app with dynamic dependencies":{ 104 | topic: dependencies('./fixtures/dynamic-deps'), 105 | "dependencies will still be detected without require specified": function (err, pkgs) { 106 | assert.isNull(err); 107 | assert.deepEqual(pkgs, { 108 | 'dep1': '0.1.x', 109 | 'dep2': '6.5.x', 110 | 'dep3': '7.5.x', 111 | 'example-dep1': '0.1.x' 112 | }); 113 | } 114 | }, 115 | "when passed a directory with no package.json or node_modules folder present": { 116 | topic: dependencies('./fixtures/require-only'), 117 | "dependencies are still properly detected": function (err, pkgs) { 118 | assert.isNull(err); 119 | assert.deepEqual(pkgs, { 120 | 'colors': '*', 121 | 'ncp': '*' 122 | }); 123 | } 124 | }, 125 | "when passed a file with no package.json or node_modules folder present": { 126 | topic: dependencies('./fixtures/require-only/index.js'), 127 | "dependencies are still properly detected": function (err, pkgs) { 128 | assert.isNull(err); 129 | assert.deepEqual(pkgs, { 130 | 'colors': '*', 131 | 'ncp': '*' 132 | }); 133 | } 134 | }, 135 | "when the package.json contains npm-supported wildcards that are not valid semver": { 136 | topic: dependencies('./fixtures/wildcards'), 137 | "dependencies should still be properly detected": function (err, pkgs) { 138 | assert.isNull(err); 139 | assert.deepEqual(pkgs, { 140 | 'example-dep1': '*', 141 | 'example-dep2': '*' 142 | }); 143 | } 144 | }, 145 | "When passed an app with explicit dependency versions specified in its package.json": { 146 | topic: dependencies('./fixtures/explicit-versions'), 147 | "dependencies are still properly detected.": function (err, pkgs) { 148 | assert.isNull(err); 149 | assert.deepEqual(pkgs, { 150 | 'serveStuff': '==2.4.7', 151 | 'makeShiny': '==0.16.2', 152 | 'writeMyCSS': '0.17.x' 153 | }); 154 | } 155 | }, 156 | "When passed an app that does not have all its requires in the main script": { 157 | topic: dependencies('./fixtures/subdeps'), 158 | "dependencies are still properly detected.": function (err, pkgs) { 159 | assert.isNull(err); 160 | assert.deepEqual(pkgs, { 161 | 'serveStuff': '2.4.x', 162 | 'makeShiny': '0.16.x', 163 | 'writeMyCSS': '0.17.x' 164 | }); 165 | } 166 | }, 167 | "When passed an app with version ranges in its package.json": { 168 | topic: dependencies('./fixtures/version-ranges'), 169 | "the author's version ranges are not replaced with wildcards.": function (err, pkgs) { 170 | assert.isNull(err); 171 | assert.deepEqual(pkgs, { 172 | 'serveStuff': '==2.4.7', 173 | 'makeShiny': '>=0.15.0 < 0.17.0', 174 | 'writeMyCSS': '0.17.x' 175 | }); 176 | } 177 | }, 178 | "When passed an app with delayed & distributed dependencies": { 179 | topic: dependencies('./fixtures/delayed-require'), 180 | "all dependencies are found": function (err, pkgs){ 181 | assert.isNull(err); 182 | assert.deepEqual(pkgs, { 183 | "socket.io": "*", 184 | "some_module": "*" 185 | }); 186 | } 187 | } 188 | }).export(module); 189 | -------------------------------------------------------------------------------- /test/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | !node_modules/ 2 | !node_modules/* -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/index.js: -------------------------------------------------------------------------------- 1 | var dep1 = require('dep2-with-conflict-on-dep1') 2 | , dep1 = require('dep1'); 3 | -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep1", 3 | "version": "0.1.7", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep2-with-conflict-on-dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | require('dep1') 4 | require('dep3') 5 | 6 | module.exports = { 7 | who: 'dep1-with-conflict-on-dep1', 8 | random: Math.random() 9 | }; -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep2-with-conflict-on-dep1/node_modules/dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep2-with-conflict-on-dep1/node_modules/dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep1", 3 | "version": "2.0.0", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep2-with-conflict-on-dep1/node_modules/dep3/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'dep3', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep2-with-conflict-on-dep1/node_modules/dep3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep3", 3 | "version": "4.0.0", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/node_modules/dep2-with-conflict-on-dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep2-with-conflict-on-dep1", 3 | "version": "6.5.0", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/conflicting-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conflicting-app", 3 | "version": "5.5.23", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/delayed-require/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(a){ 2 | require("./other.js")(a*2); 3 | }; -------------------------------------------------------------------------------- /test/fixtures/delayed-require/other.js: -------------------------------------------------------------------------------- 1 | require("sock" + (function(){return "et.io";}())); 2 | 3 | module.exports = function(a){ 4 | require("some_module").func(a); 5 | }; -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/index.js: -------------------------------------------------------------------------------- 1 | 2 | [ 'dep1', 3 | 'dep2', 4 | 'dep3' 5 | ].map(function (e) { 6 | return require(e) 7 | }) 8 | -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/node_modules/dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/node_modules/dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep1", 3 | "version": "0.1.7", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/node_modules/dep2/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/node_modules/dep2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "dep2", 4 | "version": "6.5.0", 5 | "main": "index.js" 6 | } -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/node_modules/dep3/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/node_modules/dep3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "dep3", 4 | "version": "7.5.0", 5 | "main": "index.js" 6 | } -------------------------------------------------------------------------------- /test/fixtures/dynamic-deps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-app2", 3 | "version": "5.5.23", 4 | "main": "index.js", 5 | "dependencies": { 6 | "example-dep1": "0.1.x" 7 | } 8 | } -------------------------------------------------------------------------------- /test/fixtures/example-app1/index.js: -------------------------------------------------------------------------------- 1 | 2 | var dep1 = require('example-dep1'); 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/example-app1/node_modules/example-dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/example-app1/node_modules/example-dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-dep1", 4 | "version": "0.1.7", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/dominictarr/require-analyzer.git" 8 | }, 9 | "main": "index.js" 10 | } -------------------------------------------------------------------------------- /test/fixtures/example-app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-app1", 4 | "version": "0.1.7", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/dominictarr/require-analyzer.git" 8 | }, 9 | "main": "index.js" 10 | } -------------------------------------------------------------------------------- /test/fixtures/example-app2/index.js: -------------------------------------------------------------------------------- 1 | 2 | var dep1 = require('example-dep1') 3 | , dep1 = require('example-dep2'); 4 | -------------------------------------------------------------------------------- /test/fixtures/example-app2/node_modules/example-dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/example-app2/node_modules/example-dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-dep1", 4 | "version": "0.1.7", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/dominictarr/require-analyzer.git" 8 | }, 9 | "main": "index.js", 10 | "engines": { 11 | "node": ">= 0.4.7" 12 | }, 13 | "dependencies": {}, 14 | "devDependencies": {} 15 | } -------------------------------------------------------------------------------- /test/fixtures/example-app2/node_modules/example-dep2/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/example-app2/node_modules/example-dep2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-dep2", 4 | "version": "6.5.0", 5 | "main": "index.js" 6 | } -------------------------------------------------------------------------------- /test/fixtures/example-app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-app2", 3 | "version": "5.5.23", 4 | "main": "index.js", 5 | "dependencies": { 6 | "example-dep1": "0.1.x" 7 | } 8 | } -------------------------------------------------------------------------------- /test/fixtures/example-app3/index.js: -------------------------------------------------------------------------------- 1 | 2 | if(!module.parent) 3 | require('example-dep1'); 4 | if(require.main == module) 5 | require('example-dep2'); 6 | 7 | process.nextTick(function () { 8 | //this doesn't get detected. 9 | //load your modules syncronously. 10 | require('example-dep4'); 11 | }); -------------------------------------------------------------------------------- /test/fixtures/example-app3/node_modules/example-dep1/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/example-app3/node_modules/example-dep1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-dep1", 4 | "version": "0.1.7", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/dominictarr/require-analyzer.git" 8 | }, 9 | "main": "index.js", 10 | "engines": { 11 | "node": ">= 0.4.7" 12 | }, 13 | "dependencies": {}, 14 | "devDependencies": {} 15 | } -------------------------------------------------------------------------------- /test/fixtures/example-app3/node_modules/example-dep2/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/example-app3/node_modules/example-dep2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-dep2", 4 | "version": "6.5.0", 5 | "main": "index.js" 6 | } -------------------------------------------------------------------------------- /test/fixtures/example-app3/node_modules/example-dep3/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | who: 'example-dep1', 5 | random: Math.random() 6 | }; -------------------------------------------------------------------------------- /test/fixtures/example-app3/node_modules/example-dep3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "name": "example-dep3", 4 | "version": "7.5.0", 5 | "main": "index.js" 6 | } -------------------------------------------------------------------------------- /test/fixtures/example-app3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-app2", 3 | "version": "5.5.23", 4 | "main": "index.js" 5 | } -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/app.js: -------------------------------------------------------------------------------- 1 | var makeShiny = require('makeShiny'), 2 | css = require('writeMyCSS'); 3 | 4 | var app = require('serveStuff'); 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/node_modules/makeShiny/index.js: -------------------------------------------------------------------------------- 1 | console.error('This module has now made things shiny.'); 2 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/node_modules/makeShiny/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "makeShiny", 3 | "description": "A module to make things shiny.", 4 | "version": "0.16.2", 5 | "main": "./index.js" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/node_modules/serveStuff/index.js: -------------------------------------------------------------------------------- 1 | 2 | console.error('This module will serve things with serious amounts of webscale.'); 3 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/node_modules/serveStuff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serveStuff", 3 | "description": "A truly webscale mock server module.", 4 | "version": "2.4.7", 5 | "main": "index" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/node_modules/writeMyCSS/index.js: -------------------------------------------------------------------------------- 1 | console.error('CSSification.'); 2 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/node_modules/writeMyCSS/package.json: -------------------------------------------------------------------------------- 1 | { "name": "writeMyCSS" 2 | , "description": "CSS, webscale edition." 3 | , "version": "0.17.0" 4 | , "main": "./index.js" 5 | , "engines": { "node": ">= 0.2.4" } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/explicit-versions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "AvianFlu", 3 | "name": "explicit-versions", 4 | "description": "A test fixture for the require-analyzer.", 5 | "version": "0.0.1", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/nodejitsu/require-analyzer.git" 9 | }, 10 | "main": "app.js", 11 | "engines": { 12 | "node": "0.4.12" 13 | }, 14 | "dependencies": { 15 | "serveStuff": "==2.4.7", 16 | "makeShiny": "==0.16.2" 17 | }, 18 | "devDependencies": {} 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/require-only/index.js: -------------------------------------------------------------------------------- 1 | var colors = require('colors'), 2 | ncp = require('ncp'); -------------------------------------------------------------------------------- /test/fixtures/socket-io-app/index.js: -------------------------------------------------------------------------------- 1 | var io = require('socket.io'); -------------------------------------------------------------------------------- /test/fixtures/subdeps/app.js: -------------------------------------------------------------------------------- 1 | var app = require('serveStuff'), 2 | otherstuff = require('./otherstuff'); 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/node_modules/makeShiny/index.js: -------------------------------------------------------------------------------- 1 | console.error('This module has now made things shiny.'); 2 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/node_modules/makeShiny/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "makeShiny", 3 | "description": "A module to make things shiny.", 4 | "version": "0.16.2", 5 | "main": "./index.js" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/node_modules/serveStuff/index.js: -------------------------------------------------------------------------------- 1 | 2 | console.error('This module will serve things with serious amounts of webscale.'); 3 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/node_modules/serveStuff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serveStuff", 3 | "description": "A truly webscale mock server module.", 4 | "version": "2.4.7", 5 | "main": "index" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/node_modules/writeMyCSS/index.js: -------------------------------------------------------------------------------- 1 | console.error('CSSification.'); 2 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/node_modules/writeMyCSS/package.json: -------------------------------------------------------------------------------- 1 | { "name": "writeMyCSS" 2 | , "description": "CSS, webscale edition." 3 | , "version": "0.17.0" 4 | , "main": "./index.js" 5 | , "engines": { "node": ">= 0.2.4" } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/otherstuff.js: -------------------------------------------------------------------------------- 1 | var makeShiny = require('makeShiny'), 2 | writeMyCSS = require('writeMyCSS'); 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/subdeps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "AvianFlu", 3 | "name": "subdeps", 4 | "description": "A test fixture for the require-analyzer.", 5 | "version": "0.0.1", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/nodejitsu/require-analyzer.git" 9 | }, 10 | "main": "app.js", 11 | "engines": { 12 | "node": "0.4.12" 13 | }, 14 | "dependencies": { 15 | "serveStuff": "2.4.7", 16 | "makeShiny": "0.16.x" 17 | }, 18 | "devDependencies": {} 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/app.js: -------------------------------------------------------------------------------- 1 | var app = require('serveStuff'), 2 | otherstuff = require('./otherstuff'); 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/node_modules/makeShiny/index.js: -------------------------------------------------------------------------------- 1 | console.error('This module has now made things shiny.'); 2 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/node_modules/makeShiny/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "makeShiny", 3 | "description": "A module to make things shiny.", 4 | "version": "0.16.2", 5 | "main": "./index.js" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/node_modules/serveStuff/index.js: -------------------------------------------------------------------------------- 1 | 2 | console.error('This module will serve things with serious amounts of webscale.'); 3 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/node_modules/serveStuff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serveStuff", 3 | "description": "A truly webscale mock server module.", 4 | "version": "2.4.7", 5 | "main": "index" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/node_modules/writeMyCSS/index.js: -------------------------------------------------------------------------------- 1 | console.error('CSSification.'); 2 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/node_modules/writeMyCSS/package.json: -------------------------------------------------------------------------------- 1 | { "name": "writeMyCSS" 2 | , "description": "CSS, webscale edition." 3 | , "version": "0.17.0" 4 | , "main": "./index.js" 5 | , "engines": { "node": ">= 0.2.4" } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/otherstuff.js: -------------------------------------------------------------------------------- 1 | var makeShiny = require('makeShiny'), 2 | writeMyCSS = require('writeMyCSS'); 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/version-ranges/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "AvianFlu", 3 | "name": "version-ranges", 4 | "description": "A test fixture for the require-analyzer.", 5 | "version": "0.0.1", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/nodejitsu/require-analyzer.git" 9 | }, 10 | "main": "app.js", 11 | "engines": { 12 | "node": "0.4.12" 13 | }, 14 | "dependencies": { 15 | "serveStuff": "==2.4.7", 16 | "makeShiny": ">=0.15.0 < 0.17.0", 17 | "writeMyCSS": "0.17.x" 18 | 19 | }, 20 | "devDependencies": {} 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/wildcards/index.js: -------------------------------------------------------------------------------- 1 | 2 | var dep1 = require('example-dep1') 3 | , dep1 = require('example-dep2'); 4 | -------------------------------------------------------------------------------- /test/fixtures/wildcards/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-app2", 3 | "version": "5.5.23", 4 | "main": "index.js", 5 | "dependencies": { 6 | "example-dep1": "*", 7 | "example-dep2": "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/require-analyzer-cli-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | exec = require('child_process').exec, 3 | path = require('path'), 4 | fs = require('fs'), 5 | assert = require('assert') 6 | 7 | var fixturesDir = path.join(__dirname, 'fixtures'), 8 | package1 = JSON.parse(fs.readFileSync(path.join(fixturesDir, 'example-app3', 'package.json'))); 9 | 10 | vows.describe('require-analyzer/cli').addBatch({ 11 | '--safe option prevents changing package.json': { 12 | topic: function () { 13 | assert.equal(package1.dependencies, null); 14 | exec(path.join(__dirname,'..','bin','require-analyzer'), [ 15 | path.join(__dirname,'fixtures','example-app3'), 16 | '--safe' 17 | ], this.callback); 18 | }, 19 | 'package.json has not changed': function (error, stdout,stderr) { 20 | // 21 | // Now, the package.json should not have changed. 22 | // 23 | var package2 = JSON.parse(fs.readFileSync(path.join(fixturesDir, 'example-app3', 'package.json'))); 24 | assert.deepEqual(package1,package2); 25 | assert.equal(package2.dependencies, null); 26 | } 27 | } 28 | }).export(module) -------------------------------------------------------------------------------- /test/require-analyzer-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * require-analyzer-test.js: Basic tests for the require-analyzer module. 3 | * 4 | * (C) 2010, Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var fs = require('fs'), 9 | path = require('path'), 10 | vows = require('vows'), 11 | assert = require('assert'), 12 | analyzer = require('../lib/require-analyzer'); 13 | 14 | var rawPackages = { 15 | 'npm': '>= 1.0.100 < 1.1.0', 16 | 'graceful-fs': '*', 17 | 'nopt': '*', 18 | 'abbrev': '*', 19 | 'ini': '*', 20 | 'proto-list': '*', 21 | 'semver': '1.0.x', 22 | 'slide': '*', 23 | 'which': '*', 24 | 'findit': '0.0.x', 25 | 'seq': '*', 26 | 'hashish': '*', 27 | 'traverse': '*', 28 | 'chainsaw': '*', 29 | 'colors': '0.x.x', 30 | 'optimist': '0.2.x', 31 | 'winston': '0.5.x', 32 | 'detective': '0.0.x', 33 | 'eyes': '0.1.x' 34 | 35 | }; 36 | 37 | var libDeps = { 38 | 'colors': '0.x.x', 39 | 'read-installed': '0.0.x', 40 | 'resolve': '0.2.x', 41 | 'optimist': '0.3.x', 42 | 'semver': '1.0.x', 43 | 'winston': '0.6.x', 44 | 'detective': '0.0.x', 45 | 'eyes': '0.1.x' 46 | }; 47 | 48 | var libPackages = [ 49 | 'npm', 50 | 'graceful-fs', 51 | 'nopt', 52 | 'abbrev', 53 | 'ini', 54 | 'proto-list', 55 | 'semver', 56 | 'slide', 57 | 'which', 58 | 'seq', 59 | 'hashish', 60 | 'traverse', 61 | 'chainsaw' 62 | ]; 63 | 64 | var depsFromFile = [ 65 | 'read-installed', 66 | 'detective', 67 | 'resolve', 68 | 'semver' 69 | ]; 70 | 71 | var nativeSubjects = {}; 72 | Object.getOwnPropertyNames(process.binding('natives')) 73 | .forEach(function (package) { 74 | nativeSubjects[package] = { 75 | topic: analyzer.isNative(package), 76 | 'should respond with true': function (result) { 77 | assert.isTrue(result); 78 | } 79 | }; 80 | }); 81 | 82 | var nonNativeSubjects = {}; 83 | Object.keys(rawPackages).concat(libPackages).forEach(function (package) { 84 | nonNativeSubjects[package] = { 85 | topic: analyzer.isNative(package), 86 | 'should respond with false': function (result) { 87 | assert.isFalse(result); 88 | } 89 | }; 90 | }); 91 | 92 | vows.describe('require-analyzer').addBatch({ 93 | "When using require-analyzer": { 94 | "the analyze() method": { 95 | "when passed a directory": { 96 | "with a valid package.json": { 97 | topic: function () { 98 | analyzer.analyze({target: path.join(__dirname, '..') }, this.callback) 99 | }, 100 | "should respond with the correct dependencies": function (err, pkgs) { 101 | assert.isNull(err); 102 | assert.deepEqual(pkgs, libDeps); 103 | } 104 | } 105 | } 106 | }, 107 | "the dir() method": { 108 | topic: function () { 109 | var that = this; 110 | analyzer.dir({ target: path.join(__dirname, '..', 'lib') }, this.callback); 111 | }, 112 | "should respond with the correct dependencies": function (err, pkgs) { 113 | assert.isNull(err); 114 | assert.deepEqual(pkgs, depsFromFile); 115 | } 116 | }, 117 | "the package() method": { 118 | topic: function () { 119 | analyzer.package({ target: path.join(__dirname, '..') }, this.callback) 120 | }, 121 | "should respond with the correct dependencies": function (err, pkgs) { 122 | assert.isNull(err); 123 | assert.deepEqual(pkgs, libDeps); 124 | } 125 | }, 126 | "the file() method": { 127 | "when passed a valid file": { 128 | topic: function () { 129 | analyzer.file({ target: path.join(__dirname, '..', 'lib', 'require-analyzer.js') }, this.callback) 130 | }, 131 | "should respond with the correct dependencies": function (err, pkgs) { 132 | console.dir(pkgs); 133 | assert.isNull(err); 134 | assert.deepEqual(pkgs, depsFromFile); 135 | } 136 | } 137 | }, 138 | "the isNative() method": { 139 | "when passed native package": nativeSubjects, 140 | "when passed non-native package": nonNativeSubjects 141 | }, 142 | "the extractVersions() method": { 143 | "when passed a version with a specified build": function(){ 144 | var result = analyzer.extractVersions({"a": "0.1.2-3", "b": "2.3.4-five"}); 145 | assert.deepEqual(result, { 146 | "a": ">= 0.1.2-3", 147 | "b": ">= 2.3.4-five" 148 | }); 149 | } 150 | } 151 | } 152 | }).export(module); 153 | --------------------------------------------------------------------------------