├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── app.js ├── package-lock.json ├── package.json ├── package.nupkg.js ├── packagereference.js ├── packages.config.js ├── packages.js ├── project.json.js ├── project.lock.json.js ├── readme.md └── safeBufferRead.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: richorama 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages.config 3 | project.json 4 | project.lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | packages.config 2 | project.json 3 | project.lock.json 4 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | ## todo: 5 | 6 | * allow the user to customise the starting directory 7 | * add a switch for a package id filter 8 | * include PackageReference format: 9 | https://github.com/NuGet/Home/wiki/PackageReference-Specification 10 | https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files 11 | 12 | */ 13 | 14 | const semver = require('semver'); 15 | var packageReference = require('./packagereference.js'); 16 | var packagesConfig = require('./packages.config.js'); 17 | var projectLockJson = require('./project.lock.json.js'); 18 | var nuspec = require('./package.nupkg.js'); 19 | var packages = require('./packages.js'); 20 | var archy = require('archy'); 21 | var colours = require('colors'); 22 | 23 | var dir = process.cwd(); 24 | 25 | function hasFlag(name) { 26 | return !!process.argv.filter(x => x === '--' + name).length; 27 | } 28 | 29 | function hasValue(name){ 30 | var index = process.argv.indexOf('--' + name); 31 | if (index === -1) return null; 32 | return process.argv[index + 1]; 33 | } 34 | function processXmlPackages(packageList, settings, dir, fileName) { 35 | if (!(packageList && packageList.length)) return; 36 | if (settings.packageFolder) { 37 | packageFolder = settings.packageFolder 38 | } else { 39 | packageFolder = packages.findPackageFolder(dir); 40 | } 41 | 42 | if (!packageFolder) { 43 | console.log("Cannot find 'packages' directory. Have you run 'nuget restore' or 'dotnet restore'?"); 44 | return; 45 | } 46 | 47 | if (!settings.showSystem) { 48 | packageList = packageList.filter(x => x.id.indexOf('System.') !== 0) 49 | } 50 | 51 | const packageDictionary = {}; 52 | packageList.forEach(x => { 53 | packageDictionary[`${x.id}|${x.version}`] = x; 54 | x.label = x.id + " " + (settings.hideVersion ? "" : (x.version || '').green); 55 | }); 56 | 57 | packageList.forEach(x => { 58 | x.nodes = x.nodes || []; 59 | (nuspec.readNuspec(packageFolder, x, settings) || []).forEach(dep => { 60 | if (settings.observingTargets) { 61 | getObservingTargets(packageFolder, x, dep, settings); 62 | } 63 | var resolvedDep = packageDictionary[`${dep.id}|${dep.version}`]; 64 | if (resolvedDep) { 65 | if (x.nodes.filter(y => y.id === dep.id && y.version === dep.version).length) return; // already added 66 | x.nodes.push(resolvedDep); 67 | resolvedDep.used = true; 68 | }/* else { 69 | if (settings.observingTargets) { 70 | getObservingTargets(packageFolder, x, dep, settings); 71 | } 72 | }*/ 73 | }); 74 | }); 75 | displayPackages(packageList, fileName); 76 | } 77 | 78 | function getObservingTargets(packageFolder, x, dep, settings) { 79 | dep.nodes = dep.nodes || []; 80 | (nuspec.readNuspec(packageFolder, dep, settings) || []).forEach(dep2 => { 81 | if (dep.nodes.filter(y => y.id === dep2.id && y.version === dep2.version).length) return; 82 | dep2.label = dep2.id + " " + (settings.hideVersion ? "" : (dep2.version || '').green); 83 | dep2.used = false; 84 | dep.nodes.push(dep2); 85 | getObservingTargets(packageFolder, dep, dep2, settings); 86 | }); 87 | if (x.nodes.filter(y => y.id === dep.id && y.version === dep.version).length) return; 88 | dep.label = dep.id + " " + (settings.hideVersion ? "" : (dep.version || '').green); 89 | dep.used = false; 90 | x.nodes.push(dep); 91 | } 92 | 93 | if (hasFlag('?') || hasFlag('h') || hasFlag('help')){ 94 | var fs = require('fs'); 95 | var path = require('path'); 96 | var version = JSON.parse(fs.readFileSync(path.join(__dirname, './package.json')).toString()).version; 97 | console.log(`nuget-tree version ${version} 98 | Execute this command (nuget-tree) in the directory containing a .NET project. 99 | packages.config, *.csproj or project.lock.json will be parsed to draw a nuget dependency tree. 100 | 101 | Optional command line switches: 102 | --hideVersion : hides the package versions 103 | --showSystem : shows the System.* packages 104 | --onlyTopLevel : lists only the packages at the top level of the tree (i.e. those that are not depended upon by any other package) 105 | --observingTargets : lists the dependencies of dependencies (full tree) 106 | --flat : lists the dependencies without the hierarchy 107 | --why Newtonsoft.Json : shows only dependency trees that reference the given package (Newtonsoft.Json in this case) 108 | `); 109 | return; 110 | } 111 | 112 | var settings = { 113 | hideVersion: hasFlag('hideVersion'), 114 | showSystem: hasFlag('showSystem'), 115 | onlyTopLevel: hasFlag('onlyTopLevel'), 116 | observingTargets: hasFlag('observingTargets'), 117 | flat: hasFlag('flat'), 118 | why:hasValue('why'), 119 | packageFolder:hasValue('packageFolder') 120 | } 121 | 122 | if (settings.why){ 123 | console.log("Showing dependency trees containing " + settings.why); 124 | } 125 | 126 | var packagesFromProjectLockJson = projectLockJson.list(dir, settings); 127 | if (packagesFromProjectLockJson && packagesFromProjectLockJson.length) displayPackages(packagesFromProjectLockJson, 'project.lock.json'); 128 | 129 | processXmlPackages(packagesConfig.list(dir), settings, dir, 'packages.config') 130 | processXmlPackages(packageReference.list(dir), settings, dir, 'csproj') 131 | 132 | 133 | function findWhy(node){ 134 | node.match = false; 135 | 136 | if (node.id.toLowerCase() === settings.why.toLowerCase()) { 137 | node.match = true; 138 | } 139 | 140 | (node.nodes || []).forEach(child => { 141 | node.match = findWhy(child) || node.match 142 | }); 143 | return node.match; 144 | } 145 | 146 | function filterOnlyMatches(node){ 147 | node.nodes = (node.nodes || []).filter(x => x.match); 148 | node.nodes.forEach(filterOnlyMatches) 149 | } 150 | 151 | function getWorkingVersion(version) { 152 | var curr = semver.valid(version); 153 | if(curr == null) { 154 | var alternateArray = version.split('.'); 155 | alternateArray.pop(); 156 | if(alternateArray.length == 0) 157 | return null; 158 | return getWorkingVersion(alternateArray.join('.')); 159 | } 160 | return curr; 161 | } 162 | 163 | function getMaxPackage(packages, pkgs) { 164 | pkgs = pkgs || {}; 165 | packages.forEach(x => { 166 | var curr = getWorkingVersion(x.version); 167 | if(curr == null) { 168 | return; 169 | } 170 | if(x.id in pkgs) { 171 | if(pkgs[x.id] == null) 172 | pkgs[x.id] = curr; 173 | else 174 | if(semver.gt(curr, pkgs[x.id])) 175 | pkgs[x.id] = curr; 176 | } 177 | else { 178 | pkgs[x.id] = curr; 179 | } 180 | getMaxPackage(x.nodes, pkgs); 181 | }); 182 | return pkgs; 183 | } 184 | 185 | function displayPackages(packages, source) { 186 | 187 | if (settings.why){ 188 | var head = { 189 | id :"", 190 | label: source, 191 | match: true, 192 | nodes: packages.filter(x => !x.used) 193 | }; 194 | findWhy(head); 195 | filterOnlyMatches(head); 196 | console.log(archy(head)); 197 | } else if (settings.onlyTopLevel) { 198 | packages.filter(x => !x.used).forEach(x => { 199 | console.log(x.label); 200 | }); 201 | } else if (settings.flat) { 202 | var pkgs = getMaxPackage(packages, {}); 203 | var keys = Object.keys(pkgs); 204 | keys.sort(); 205 | keys.forEach(x => { 206 | console.log(x + " " + (settings.hideVersion ? "" : (pkgs[x] || '').green)); 207 | }); 208 | } else { 209 | var head = { 210 | label: source, 211 | nodes: packages.filter(x => !x.used) 212 | }; 213 | console.log(archy(head)); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuget-tree", 3 | "version": "1.0.20", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nuget-tree", 9 | "version": "1.0.20", 10 | "license": "MIT", 11 | "dependencies": { 12 | "archy": "^1.0.0", 13 | "colors": "^1.1.2", 14 | "semver": "^7.3.2", 15 | "xml2js": "^0.4.17", 16 | "zip": "^1.2.0" 17 | }, 18 | "bin": { 19 | "nuget-tree": "app.js" 20 | } 21 | }, 22 | "node_modules/archy": { 23 | "version": "1.0.0", 24 | "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", 25 | "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" 26 | }, 27 | "node_modules/base64-js": { 28 | "version": "0.0.2", 29 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz", 30 | "integrity": "sha512-Pj9L87dCdGcKlSqPVUjD+q96pbIx1zQQLb2CUiWURfjiBELv84YX+0nGnKmyT/9KkC7PQk7UN1w+Al8bBozaxQ==", 31 | "engines": { 32 | "node": ">= 0.4" 33 | } 34 | }, 35 | "node_modules/bops": { 36 | "version": "0.1.1", 37 | "resolved": "https://registry.npmjs.org/bops/-/bops-0.1.1.tgz", 38 | "integrity": "sha512-Cx1zStcMp+YoFan8OgudNPMih82eJZE+27feki1WeyoFTR9Ye7AR1SUW3saE6QQvdS/g52aJ2IojBjWOiRiLbw==", 39 | "dependencies": { 40 | "base64-js": "0.0.2", 41 | "to-utf8": "0.0.1" 42 | } 43 | }, 44 | "node_modules/colors": { 45 | "version": "1.4.0", 46 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 47 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 48 | "engines": { 49 | "node": ">=0.1.90" 50 | } 51 | }, 52 | "node_modules/lru-cache": { 53 | "version": "6.0.0", 54 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 55 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 56 | "dependencies": { 57 | "yallist": "^4.0.0" 58 | }, 59 | "engines": { 60 | "node": ">=10" 61 | } 62 | }, 63 | "node_modules/sax": { 64 | "version": "1.2.4", 65 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 66 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 67 | }, 68 | "node_modules/semver": { 69 | "version": "7.3.7", 70 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", 71 | "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", 72 | "dependencies": { 73 | "lru-cache": "^6.0.0" 74 | }, 75 | "bin": { 76 | "semver": "bin/semver.js" 77 | }, 78 | "engines": { 79 | "node": ">=10" 80 | } 81 | }, 82 | "node_modules/to-utf8": { 83 | "version": "0.0.1", 84 | "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", 85 | "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==" 86 | }, 87 | "node_modules/xml2js": { 88 | "version": "0.4.23", 89 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", 90 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", 91 | "dependencies": { 92 | "sax": ">=0.6.0", 93 | "xmlbuilder": "~11.0.0" 94 | }, 95 | "engines": { 96 | "node": ">=4.0.0" 97 | } 98 | }, 99 | "node_modules/xmlbuilder": { 100 | "version": "11.0.1", 101 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 102 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", 103 | "engines": { 104 | "node": ">=4.0" 105 | } 106 | }, 107 | "node_modules/yallist": { 108 | "version": "4.0.0", 109 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 110 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 111 | }, 112 | "node_modules/zip": { 113 | "version": "1.2.0", 114 | "resolved": "https://registry.npmjs.org/zip/-/zip-1.2.0.tgz", 115 | "integrity": "sha512-8B4Z9BXJKkI8BkHhKvQan4rwCzUENnj95YHFYrI7F1NbqKCIdW86kujctzEB+kJ6XapHPiAhiZ9xi5GbW5SPdw==", 116 | "dependencies": { 117 | "bops": "~0.1.1" 118 | } 119 | } 120 | }, 121 | "dependencies": { 122 | "archy": { 123 | "version": "1.0.0", 124 | "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", 125 | "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" 126 | }, 127 | "base64-js": { 128 | "version": "0.0.2", 129 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz", 130 | "integrity": "sha512-Pj9L87dCdGcKlSqPVUjD+q96pbIx1zQQLb2CUiWURfjiBELv84YX+0nGnKmyT/9KkC7PQk7UN1w+Al8bBozaxQ==" 131 | }, 132 | "bops": { 133 | "version": "0.1.1", 134 | "resolved": "https://registry.npmjs.org/bops/-/bops-0.1.1.tgz", 135 | "integrity": "sha512-Cx1zStcMp+YoFan8OgudNPMih82eJZE+27feki1WeyoFTR9Ye7AR1SUW3saE6QQvdS/g52aJ2IojBjWOiRiLbw==", 136 | "requires": { 137 | "base64-js": "0.0.2", 138 | "to-utf8": "0.0.1" 139 | } 140 | }, 141 | "colors": { 142 | "version": "1.4.0", 143 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 144 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 145 | }, 146 | "lru-cache": { 147 | "version": "6.0.0", 148 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 149 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 150 | "requires": { 151 | "yallist": "^4.0.0" 152 | } 153 | }, 154 | "sax": { 155 | "version": "1.2.4", 156 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 157 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 158 | }, 159 | "semver": { 160 | "version": "7.3.7", 161 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", 162 | "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", 163 | "requires": { 164 | "lru-cache": "^6.0.0" 165 | } 166 | }, 167 | "to-utf8": { 168 | "version": "0.0.1", 169 | "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", 170 | "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==" 171 | }, 172 | "xml2js": { 173 | "version": "0.4.23", 174 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", 175 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", 176 | "requires": { 177 | "sax": ">=0.6.0", 178 | "xmlbuilder": "~11.0.0" 179 | } 180 | }, 181 | "xmlbuilder": { 182 | "version": "11.0.1", 183 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 184 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" 185 | }, 186 | "yallist": { 187 | "version": "4.0.0", 188 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 189 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 190 | }, 191 | "zip": { 192 | "version": "1.2.0", 193 | "resolved": "https://registry.npmjs.org/zip/-/zip-1.2.0.tgz", 194 | "integrity": "sha512-8B4Z9BXJKkI8BkHhKvQan4rwCzUENnj95YHFYrI7F1NbqKCIdW86kujctzEB+kJ6XapHPiAhiZ9xi5GbW5SPdw==", 195 | "requires": { 196 | "bops": "~0.1.1" 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuget-tree", 3 | "version": "1.0.21", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "archy": "^1.0.0", 13 | "colors": "^1.1.2", 14 | "semver": "^7.3.2", 15 | "xml2js": "^0.4.17", 16 | "zip": "^1.2.0" 17 | }, 18 | "preferGlobal": true, 19 | "bin": { 20 | "nuget-tree": "./app.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/richorama/nuget-tree.git" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package.nupkg.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var zip = require('zip'); 4 | var parseXml = require('xml2js').parseString; 5 | var safeBufferRead = require('./safeBufferRead'); 6 | 7 | module.exports.readNuspec = function (packagesFolder, package, settings) { 8 | if (!packagesFolder) throw new Error("no packagesFolder"); 9 | 10 | var packageFilePath = path.join(packagesFolder, package.id + "." + package.version, package.id + "." + package.version + ".nupkg"); 11 | 12 | if (!fs.existsSync(packageFilePath)) { 13 | packageFilePath = path.join(packagesFolder, package.id.toLowerCase() + "." + package.version, package.id.toLowerCase() + "." + package.version + ".nupkg"); 14 | 15 | if (!fs.existsSync(packageFilePath) && package.version) { 16 | packageFilePath = path.join(packagesFolder, package.id, package.version, package.id + "." + package.version + ".nupkg"); 17 | 18 | if (!fs.existsSync(packageFilePath) && package.version) { 19 | packageFilePath = path.join(packagesFolder, package.id.toLowerCase(), package.version, package.id.toLowerCase() + "." + package.version + ".nupkg"); 20 | 21 | if (!fs.existsSync(packageFilePath)) { 22 | if (!settings.observingTargets) { 23 | console.log("WARN: Cannot find nupkg file for " + package.id); 24 | } 25 | return []; 26 | } 27 | } 28 | } 29 | } 30 | 31 | var nuspecXml = openNuspecFile(packageFilePath); 32 | 33 | if (!nuspecXml) { 34 | console.log("WARN: Cannot find nuspec file for " + package.id); 35 | console.log("Attempted to open this file: " + packageFilePath); 36 | return []; 37 | } 38 | 39 | return readAllDeps(nuspecXml); 40 | } 41 | 42 | function readAllDeps(nuspecXml) { 43 | var dependencies = []; 44 | parseXml(nuspecXml, (err, data) => { 45 | if (err) console.error(err); 46 | if (!data) return; 47 | (data.package.metadata || []).forEach(metadata => { 48 | (metadata.dependencies || []).forEach(dep => { 49 | 50 | // if the nuget package targets multiple version, there are groups 51 | (dep.group || []).forEach(dep => { 52 | (dep.dependency || []).forEach(dep => { 53 | dependencies.push(dep.$); 54 | }) 55 | }); 56 | 57 | // otherwise there are no groups 58 | (dep.dependency || []).forEach(dep => { 59 | dependencies.push(dep.$); 60 | }) 61 | 62 | }); 63 | }); 64 | }); 65 | return dependencies; 66 | } 67 | 68 | 69 | function openNuspecFile(packageFilePath) { 70 | 71 | var pkgData = fs.readFileSync(packageFilePath); 72 | var reader = zip.Reader(pkgData); 73 | var nuspec; 74 | reader.forEach(function (entry, next) { 75 | if (path.extname(entry._header.file_name) === ".nuspec") { 76 | nuspec = safeBufferRead(entry.getData()) 77 | } 78 | }); 79 | return nuspec; 80 | } 81 | -------------------------------------------------------------------------------- /packagereference.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var parseXml = require('xml2js').parseString; 4 | var safeBufferRead = require('./safeBufferRead'); 5 | 6 | module.exports.list = function (dir) { 7 | const files = fs.readdirSync(dir).filter(file => file.toLowerCase().endsWith(".csproj") && fs.existsSync(file)); 8 | if (!files.length) return null; 9 | 10 | const file = files[0]; 11 | 12 | var xml = safeBufferRead(fs.readFileSync(file)); 13 | 14 | var parsedOutput; 15 | parseXml(xml, function (err, result) { 16 | parsedOutput = result; 17 | }); 18 | if (!(parsedOutput && parsedOutput.Project && parsedOutput.Project.ItemGroup)) { 19 | return null; 20 | } 21 | try { 22 | let itemGroup = parsedOutput.Project.ItemGroup; 23 | if (!Array.isArray(itemGroup)) { 24 | itemGroup = [itemGroup]; 25 | } 26 | return itemGroup.filter(a => a && a.PackageReference).map(a => a.PackageReference.map(x => { 27 | return { 28 | id: x.$.Include, 29 | version: x.$.Version || x.Version[0], 30 | targetFramework: null 31 | } 32 | })).reduce((ret, cur) => ret.concat(cur), []); 33 | } catch (err) { 34 | console.log(`Cannot parse ${file}. Is it in a valid format?`); 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages.config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var parseXml = require('xml2js').parseString; 4 | var safeBufferRead = require('./safeBufferRead'); 5 | 6 | module.exports.list = function (dir) { 7 | 8 | if (!fs.existsSync(path.join(dir, 'packages.config'))) { 9 | return null; 10 | } 11 | 12 | var xml = safeBufferRead(fs.readFileSync(path.join(dir, 'packages.config'))); 13 | 14 | var parsedOutput; 15 | parseXml(xml, function (err, result) { 16 | parsedOutput = result; 17 | }); 18 | 19 | try { 20 | return parsedOutput.packages.package.map(x => { 21 | return { 22 | id: x.$.id, 23 | version: x.$.version, 24 | targetFramework: x.$.targetFramework 25 | } 26 | }); 27 | } 28 | catch(err){ 29 | console.log("Cannot parse 'packages.config'. Is it in a valid format?"); 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var os = require('os'); 4 | 5 | module.exports.findPackageFolder = function(dir){ 6 | var parts = dir.split(path.sep); 7 | while (parts.length > 0){ 8 | testPath = path.join(parts.join(path.sep), 'packages'); 9 | if (fs.existsSync(testPath)) return testPath; 10 | parts.pop(); 11 | } 12 | var alternatePath = path.join(os.homedir(), '.nuget', 'packages'); 13 | if (fs.existsSync(alternatePath)) return alternatePath; 14 | return false; 15 | } 16 | -------------------------------------------------------------------------------- /project.json.js: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "dependencies": { 4 | "Microsoft.Extensions.DependencyInjection": "1.0.0", 5 | "Microsoft.Owin.Hosting": "3.0.1", 6 | "Nowin": "0.25.0" 7 | } 8 | ... 9 | 10 | */ 11 | var fs = require('fs'); 12 | var safeBufferRead = require('./safeBufferRead'); 13 | var path = require('path'); 14 | 15 | module.exports.list = function(dir){ 16 | 17 | if (!fs.existsSync(path.join(dir,'project.json'))){ 18 | return []; 19 | } 20 | 21 | var jsonString = safeBufferRead(fs.readFileSync(path.join(dir,'project.json'))).replace("´╗┐",""); 22 | var json = JSON.parse(jsonString); 23 | 24 | var deps = json.dependencies || {}; 25 | return Object.keys(deps).map(id => { 26 | return { 27 | id : id, 28 | version: deps[id] 29 | } 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /project.lock.json.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var safeBufferRead = require('./safeBufferRead'); 4 | var path = require('path'); 5 | 6 | module.exports.list = function(dir, settings){ 7 | 8 | if (!fs.existsSync(path.join(dir,'project.lock.json'))){ 9 | return null; 10 | } 11 | 12 | var jsonString = safeBufferRead(fs.readFileSync(path.join(dir,'project.lock.json'))); 13 | var json = JSON.parse(jsonString); 14 | 15 | var packageDictionary = {}; 16 | 17 | // put all packages in the dictionary 18 | Object.keys(json.targets).forEach(target => { 19 | Object.keys(json.targets[target]).forEach(dep => { 20 | 21 | if (!settings.showSystem && dep.indexOf('System.') === 0) return; 22 | 23 | var depParts = dep.split('/'); 24 | var id = depParts[0]; 25 | var version = depParts[1]; 26 | 27 | if (!packageDictionary[id]) { 28 | packageDictionary[id] = { 29 | id : id, 30 | version: version, 31 | targetFramework : target, 32 | label : id + " " + (settings.hideVersion ? "" : (version || '').green), 33 | nodes:[] 34 | } 35 | } 36 | }); 37 | }); 38 | 39 | // build the hierarchy 40 | Object.keys(json.targets).forEach(target => { 41 | Object.keys(json.targets[target]).forEach(dep => { 42 | 43 | if (!settings.showSystem && dep.indexOf('System.') === 0) return; 44 | 45 | var depParts = dep.split('/'); 46 | var id = depParts[0]; 47 | var dependency = json.targets[target][dep]; 48 | 49 | var pkg = packageDictionary[id]; 50 | Object.keys(dependency.dependencies || []).forEach(dependantId => { 51 | if (packageDictionary[dependantId] && pkg.nodes.filter(x => x.id === dependantId).length === 0){ 52 | var resolvedDep = packageDictionary[dependantId]; 53 | pkg.nodes.push(resolvedDep); 54 | resolvedDep.used = true; 55 | } 56 | }); 57 | 58 | }); 59 | }); 60 | 61 | return Object.keys(packageDictionary).map(key => packageDictionary[key]); 62 | } 63 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # nuget-tree 2 | 3 | ## Installation 4 | 5 | Please install the latest version of [node.js](https://nodejs.org). 6 | 7 | Then install using [npm](https://www.npmjs.com/package/nuget-tree): 8 | 9 | ``` 10 | > npm install -g nuget-tree 11 | ``` 12 | 13 | ## Usage 14 | 15 | Navigate to a directory containing a `packages.config` / `project.lock.json` file. Then run this command: 16 | 17 | ``` 18 | > nuget-tree 19 | ``` 20 | 21 | This will draw a nice dependency tree for you: 22 | 23 | ``` 24 | packages.config 25 | ├─┬ Microsoft.Orleans.OrleansCodeGenerator 1.4.0 26 | │ ├─┬ Microsoft.Orleans.Core 1.4.0 27 | │ │ └── Newtonsoft.Json 9.0.1 28 | │ └─┬ Microsoft.CodeAnalysis.CSharp 1.3.2 29 | │ └─┬ Microsoft.CodeAnalysis.Common 1.3.2 30 | │ └── Microsoft.CodeAnalysis.Analyzers 1.1.0 31 | ├─┬ Microsoft.Orleans.OrleansHost 1.4.0 32 | │ ├─┬ Microsoft.Orleans.Core 1.4.0 33 | │ │ └── Newtonsoft.Json 9.0.1 34 | │ └─┬ Microsoft.Orleans.OrleansRuntime 1.4.0 35 | │ ├─┬ Microsoft.Orleans.Core 1.4.0 36 | │ │ └── Newtonsoft.Json 9.0.1 37 | │ ├─┬ Microsoft.Extensions.DependencyInjection 1.0.0 38 | │ │ └── Microsoft.Extensions.DependencyInjection.Abstractions 1.0.0 39 | │ └── Microsoft.Extensions.DependencyInjection.Abstractions 1.0.0 40 | ├─┬ Microsoft.Owin.Hosting 3.0.1 41 | │ ├── Owin 1.0 42 | │ └─┬ Microsoft.Owin 3.0.1 43 | │ └── Owin 1.0 44 | └── Nowin 0.23.0 45 | ``` 46 | 47 | Optional parameters: 48 | 49 | * `--hideVersion` : hides the package versions 50 | * `--showSystem` : shows the `System.*` packages 51 | * `--onlyTopLevel` : lists only the packages at the top level of the tree (i.e. those that are 52 | not depended upon by any other package) 53 | * `--flat` : lists the dependencies without the hierarchy 54 | * `--why Newtonsoft.Json` : shows only dependency trees that reference the given package (Newtonsoft.Json in this case) 55 | 56 | ## License 57 | 58 | MIT 59 | -------------------------------------------------------------------------------- /safeBufferRead.js: -------------------------------------------------------------------------------- 1 | module.exports = function(buffer){ 2 | if (buffer.length === 0) return ""; 3 | 4 | if (buffer[0] === 255 && buffer[1] === 254){ 5 | // byte order mark indicates utf16 6 | return buffer.toString("utf16le"); 7 | } else { 8 | // otherwise assume utf8 9 | return buffer.toString(); 10 | } 11 | } 12 | --------------------------------------------------------------------------------