├── .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 | [](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 | |
---|---|
/*
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 | 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 against28 | 29 |@callback {function} Continuation to respond to when complete.30 | 31 |Calls the appropriate | analyzer.analyze = function (options, callback) {
41 | var emitter = new events.EventEmitter();
42 |
43 | if (!options || !options.target) { |
If there are no | callback(new Error('options and options.target are required'));
45 | return emitter;
46 | }
47 | |
Stat the directory and call the appropriate method
48 | on | 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 | 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', '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 | 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 = 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 | 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 analyze170 | 171 |@callback {function} Continuation to respond to when complete.172 | 173 |Checks for the existance of a package.json in the specified | 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 | 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 | var files = [],
186 | done = [],
187 | packages = {},
188 | traversed = false,
189 | finder = findit.find(options.target);
190 |
191 | function onRequired () { |
Respond to the | 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 | 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 analyze225 | 226 |@callback {function} Continuation to respond to when complete.227 | 228 |Attempts to read the package.json in the specified | 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 | if (!pkg.main) {
236 | return callback(new Error('package.json must have a `main` property.'));
237 | }
238 | |
Analyze the require(s) based on the | 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 analyze252 | 253 |@callback {callback} Continuation to respond to when complete.254 | 255 |Attempts to find the packages required by the node script located at
256 | | analyzer.file = function (options, callback) { |
Spawn the | 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 | 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 Include any packages which may be of the form | 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 from315 | 316 |Searches up from the specified | 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 into341 | 342 |Merges all properties in | 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 | 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 transform366 | 367 |Transforms the | 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 dependencies383 | 384 |@updated {Object} Updated dependencies385 | 386 |Compares the | 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 | |