Compare commits
	
		
			5 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a39687b70d | |||
| 950c2373f2 | |||
| 4cb8c71b41 | |||
| 7feafcfd49 | |||
| a410907864 | 
							
								
								
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |   "bracketSpacing": true, | ||||||
|  |   "printWidth": 120, | ||||||
|  |   "singleQuote": true, | ||||||
|  |   "tabWidth": 2, | ||||||
|  |   "trailingComma": "none", | ||||||
|  |   "useTabs": true | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								README.md
									
									
									
									
									
								
							| @ -1,3 +1,41 @@ | |||||||
| # how-npm-am-i.js | # [how-npm-am-i.js](https://git.coolaj86.com/coolaj86/how-npm-am-i.js) | ||||||
| 
 | 
 | ||||||
| Shows how many downloads you get on npm each month, across all your packages. | Shows how many downloads you get on npm each month, across all of your packages. | ||||||
|  | 
 | ||||||
|  | > npx how-npm-am-i <username> [--verbose] | ||||||
|  | 
 | ||||||
|  | # Usage | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | how-npm-am-i isaacs --verbose | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | You've published 161 packages to npm and you get... | ||||||
|  | 
 | ||||||
|  | Package Name                             Downloads | ||||||
|  | @isaacs/example-automatic-publishing:            3 | ||||||
|  | cluster-callresp:                               11 | ||||||
|  | voxer-blog-demo:                                12 | ||||||
|  | tako-session-token:                             13 | ||||||
|  | truncating-stream:                              18 | ||||||
|  | ... | ||||||
|  | rimraf:                                 59,309,683 | ||||||
|  | minimatch:                              63,109,081 | ||||||
|  | yallist:                                66,527,785 | ||||||
|  | lru-cache:                              77,627,756 | ||||||
|  | glob:                                  109,654,115 | ||||||
|  | 
 | ||||||
|  | 1,423,623,087 downloads per month. Not bad! | ||||||
|  | You're *very* npm. | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Install | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | npm install --global how-npm-am-i | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # How I built this | ||||||
|  | 
 | ||||||
|  | See <https://coolaj86.com/articles/how-cool-you-are-on-npm/> | ||||||
							
								
								
									
										92
									
								
								bin/how-npm-am-i.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										92
									
								
								bin/how-npm-am-i.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,92 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var pkg = require('../package.json'); | ||||||
|  | 
 | ||||||
|  | var args = process.argv.slice(1).join(','); | ||||||
|  | if (/-V|--version/.test(args)) { | ||||||
|  | 	console.info('v' + pkg.version); | ||||||
|  | 	return; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var verbose = false; | ||||||
|  | if (/-v|--verbose/.test(args)) { | ||||||
|  | 	verbose = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var username = process.argv[2]; | ||||||
|  | if (!username) { | ||||||
|  | 	console.error(); | ||||||
|  | 	console.error('Usage: how-npm-am-i <username>'); | ||||||
|  | 	console.error('   Ex: how-npm-am-i isaacs'); | ||||||
|  | 	console.error(); | ||||||
|  | 	process.exit(1); | ||||||
|  | 	return; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var hnai = require('../'); | ||||||
|  | hnai | ||||||
|  | 	.packages(username) | ||||||
|  | 	.then(function(pkgnames) { | ||||||
|  | 		console.info(''); | ||||||
|  | 		if (!pkgnames.length) { | ||||||
|  | 			console.info("You've published " + pkgnames.length + ' packages to npm. Hmm.'); | ||||||
|  | 			console.info('Well, we all have to start somewhere.... except maybe you.... you just... keep doing you. 👍'); | ||||||
|  | 			console.info(''); | ||||||
|  | 			process.exit(1337); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		console.info("You've published " + pkgnames.length + ' packages to npm and you get...'); | ||||||
|  | 		hnai.downloads(pkgnames).then(function(downloads) { | ||||||
|  | 			var total = downloads.reduce(function(sum, el) { | ||||||
|  | 				return sum + el.downloads; | ||||||
|  | 			}, 0); | ||||||
|  | 			if (verbose) { | ||||||
|  | 				tabularize(downloads); | ||||||
|  | 			} | ||||||
|  | 			console.info(total.toLocaleString() + ' downloads per month. Not bad!'); | ||||||
|  | 			if (total >= 100000 || pkgnames.length > 100) { | ||||||
|  | 				console.info("You're a little npm. Keep trying."); | ||||||
|  | 			} else { | ||||||
|  | 				console.info("You're *very* npm. Much wow!"); | ||||||
|  | 			} | ||||||
|  | 			console.info(); | ||||||
|  | 		}); | ||||||
|  | 	}) | ||||||
|  | 	.catch(function(e) { | ||||||
|  | 		console.error(); | ||||||
|  | 		console.error(e); | ||||||
|  | 		console.error(); | ||||||
|  | 		process.exit(1); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | function tabularize(downloads) { | ||||||
|  | 	console.info(''); | ||||||
|  | 	var maxLen = 0; | ||||||
|  | 	var maxCount = "Downloads".length; | ||||||
|  | 	downloads.sort(function(a, b) { | ||||||
|  | 		maxLen = Math.max(a.package.length, b.package.length, maxLen); | ||||||
|  | 		maxCount = Math.max(a.downloads.toLocaleString().length, b.downloads.toLocaleString().length, maxCount); | ||||||
|  | 		return a.downloads - b.downloads; | ||||||
|  | 	}); | ||||||
|  | 	console.info(leftpad('Package Name', maxLen + 1), rightmad('Downloads', maxCount + 1)); | ||||||
|  | 	downloads.forEach(function(stat) { | ||||||
|  | 		console.info(leftpad(stat.package + ':', maxLen + 1), rightmad(stat.downloads.toLocaleString(), maxCount + 1)); | ||||||
|  | 	}); | ||||||
|  | 	console.info(''); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // no, I was right
 | ||||||
|  | function leftpad(str, n) { | ||||||
|  | 	while (str.length < n) { | ||||||
|  | 		str = str + ' '; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function rightmad(str, n) { | ||||||
|  | 	while (str.length < n) { | ||||||
|  | 		str = ' ' + str; | ||||||
|  | 	} | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
							
								
								
									
										115
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | // Inspired in part by
 | ||||||
|  | // * https://www.npmjs.com/package/npmjs-api
 | ||||||
|  | // * https://github.com/kevva/npm-user-packages/blob/master/index.js
 | ||||||
|  | 
 | ||||||
|  | // Official docs at
 | ||||||
|  | // * https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#meta-endpoints
 | ||||||
|  | // * https://github.com/npm/registry/blob/master/docs/download-counts.md
 | ||||||
|  | 
 | ||||||
|  | var batchAsync = require('batchasync').batchAsync; | ||||||
|  | var request = require('@root/request'); | ||||||
|  | request = require('util').promisify(request); | ||||||
|  | 
 | ||||||
|  | var hnai = exports; | ||||||
|  | hnai.packages = function(username) { | ||||||
|  | 	// I didn't bother to implement paging because... I didn't
 | ||||||
|  | 	return request({ | ||||||
|  | 		url: 'https://api.npms.io/v2/search?q=author:' + username + '&size=250&from=0', | ||||||
|  | 		json: true | ||||||
|  | 	}).then(function(resp) { | ||||||
|  | 		// { total: 214, results: [ { package: { name: 'foobar' } } ] }
 | ||||||
|  | 		if (resp.total > 250) { | ||||||
|  | 			console.warn(); | ||||||
|  | 			console.warn("You have more than 250 published packages, but I haven't implemented paging."); | ||||||
|  | 			console.warn("I'd say please open an issue, but you're ovbiously skilled, so please PR."); | ||||||
|  | 			console.warn(); | ||||||
|  | 		} | ||||||
|  | 		return resp.body.results.map(function(result) { | ||||||
|  | 			return result.package.name; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | hnai.downloads = function(pkgnames) { | ||||||
|  | 	// https://github.com/npm/registry/blob/master/docs/download-counts.md#bulk-queries
 | ||||||
|  | 	// FYI: Earliest date is January 10, 2015
 | ||||||
|  | 	// * One-off: https://api.npmjs.org/downloads/range/{period}[/{package}]
 | ||||||
|  | 	// * Bulk: https://api.npmjs.org/downloads/point/last-day/npm,express
 | ||||||
|  | 
 | ||||||
|  | 	// All the batching logic
 | ||||||
|  | 	var batches = []; | ||||||
|  | 	var batch = []; | ||||||
|  | 	pkgnames.forEach(function(pkgname) { | ||||||
|  | 		if ('@' === pkgname[0]) { | ||||||
|  | 			batches.push([pkgname]); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		if (batch.length >= 128) { | ||||||
|  | 			batches.push(batch); | ||||||
|  | 			batch = []; | ||||||
|  | 		} | ||||||
|  | 		batch.push(pkgname); | ||||||
|  | 	}); | ||||||
|  | 	if (batch.length) { | ||||||
|  | 		batches.push(batch); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// do the batching
 | ||||||
|  | 	return batchAsync(4, batches, function(batch) { | ||||||
|  | 		if (1 === batch.length) { | ||||||
|  | 			return one(batch[0]); | ||||||
|  | 		} else { | ||||||
|  | 			return many(batch); | ||||||
|  | 		} | ||||||
|  | 	}).then(function(results) { | ||||||
|  | 		// transform the results
 | ||||||
|  | 		var all = []; | ||||||
|  | 		results.forEach(function(map) { | ||||||
|  | 			//#console.debug('batch size:', Object.keys(map).length);
 | ||||||
|  | 			Object.keys(map).forEach(function(key) { | ||||||
|  | 				if (!map[key]) { | ||||||
|  | 					// can be 'null' for just-published packages
 | ||||||
|  | 					map[key] = { downloads: 0 }; | ||||||
|  | 				} | ||||||
|  | 				all.push({ | ||||||
|  | 					package: key, | ||||||
|  | 					downloads: map[key].downloads | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		// get back one big happy list
 | ||||||
|  | 		return all; | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // these need to be simplified and transformed
 | ||||||
|  | function one(pkgname) { | ||||||
|  | 	return request({ | ||||||
|  | 		url: 'https://api.npmjs.org/downloads/range/last-month/' + pkgname, | ||||||
|  | 		json: true | ||||||
|  | 	}).then(function(resp) { | ||||||
|  | 		// { package: 'foobar', downloads: [{ downloads: 1, day: '2019-06-16' }] }
 | ||||||
|  | 		var result = {}; | ||||||
|  | 		result[resp.body.package] = { | ||||||
|  | 			package: resp.body.package, | ||||||
|  | 			downloads: resp.body.downloads.reduce(function(sum, el) { | ||||||
|  | 				return el.downloads; | ||||||
|  | 			}, 0) | ||||||
|  | 		}; | ||||||
|  | 		return result; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // these are exactly what we want, pretty much
 | ||||||
|  | function many(pkgnames) { | ||||||
|  | 	return request({ | ||||||
|  | 		url: 'https://api.npmjs.org/downloads/point/last-month/' + pkgnames.join(','), | ||||||
|  | 		json: true | ||||||
|  | 	}).then(function(resp) { | ||||||
|  | 		// { foobar: { downloads: 12 } }
 | ||||||
|  | 		return resp.body; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | { | ||||||
|  |   "name": "how-npm-am-i", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "lockfileVersion": 1, | ||||||
|  |   "requires": true, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@root/request": { | ||||||
|  |       "version": "1.3.11", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz", | ||||||
|  |       "integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw==" | ||||||
|  |     }, | ||||||
|  |     "batchasync": { | ||||||
|  |       "version": "1.0.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/batchasync/-/batchasync-1.0.2.tgz", | ||||||
|  |       "integrity": "sha512-YSKKrMr7BLsQnqHOtGKhuqC83CtHu9pGOhSrmPl8WCz7N3qcFJfEQGbM1I3PEOq11FU2rY+a+IokOODzOyVdXw==" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | { | ||||||
|  | 	"name": "how-npm-am-i", | ||||||
|  | 	"version": "1.0.5", | ||||||
|  | 	"description": "See how many downloads you get on npm each month, across all your packages", | ||||||
|  | 	"homepage": "https://git.coolaj86.com/coolaj86/how-npm-am-i.js", | ||||||
|  | 	"main": "index.js", | ||||||
|  | 	"scripts": { | ||||||
|  | 		"test": "node test.js" | ||||||
|  | 	}, | ||||||
|  | 	"repository": { | ||||||
|  | 		"type": "git", | ||||||
|  | 		"url": "https://git.coolaj86.com/coolaj86/how-npm-am-i.js.git" | ||||||
|  | 	}, | ||||||
|  | 	"bin": { | ||||||
|  | 		"how-npm-am-i": "bin/how-npm-am-i.js" | ||||||
|  | 	}, | ||||||
|  | 	"files": [ | ||||||
|  | 		"bin", | ||||||
|  | 		"lib" | ||||||
|  | 	], | ||||||
|  | 	"keywords": [ | ||||||
|  | 		"npm", | ||||||
|  | 		"download", | ||||||
|  | 		"count", | ||||||
|  | 		"monthly", | ||||||
|  | 		"daily", | ||||||
|  | 		"weekly", | ||||||
|  | 		"all", | ||||||
|  | 		"packages" | ||||||
|  | 	], | ||||||
|  | 	"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||||
|  | 	"license": "MPL-2.0", | ||||||
|  | 	"dependencies": { | ||||||
|  | 		"@root/request": "^1.3.11", | ||||||
|  | 		"batchasync": "^1.0.2" | ||||||
|  | 	}, | ||||||
|  | 	"bundledDependencies": [ | ||||||
|  | 		"@root/request", | ||||||
|  | 		"batchasync" | ||||||
|  | 	] | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var hnai = require('./'); | ||||||
|  | hnai.packages('coolaj86').then(function(pkgnames) { | ||||||
|  | 	console.info('Total number of pakcages:', pkgnames.length); | ||||||
|  | 	hnai.downloads(pkgnames).then(function(downloads) { | ||||||
|  | 		console.log( | ||||||
|  | 			'Download Counts:', | ||||||
|  | 			downloads | ||||||
|  | 				.map(function(all) { | ||||||
|  | 					return all.package + ' ' + all.downloads; | ||||||
|  | 				}) | ||||||
|  | 				.join('\n') | ||||||
|  | 		); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user