From a410907864da3aec69b6a7debb6e238bf4a1d75a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 25 Jun 2019 23:20:45 -0600 Subject: [PATCH] initial commit --- .prettierrc | 8 +++ README.md | 38 ++++++++++++++- bin/how-npm-am-i.js | 85 ++++++++++++++++++++++++++++++++ index.js | 115 ++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 18 +++++++ package.json | 40 +++++++++++++++ test.js | 16 ++++++ 7 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 .prettierrc create mode 100755 bin/how-npm-am-i.js create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 test.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..20f3bd7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "bracketSpacing": true, + "printWidth": 120, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": true +} diff --git a/README.md b/README.md index f5fc943..8bac223 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,39 @@ # how-npm-am-i.js -Shows how many downloads you get on npm each month, across all your packages. \ No newline at end of file +Shows how many downloads you get on npm each month, across all of your packages. + +# Usage + +```bash +npx 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 +``` + +```bash +how-npm-am-i +``` diff --git a/bin/how-npm-am-i.js b/bin/how-npm-am-i.js new file mode 100755 index 0000000..d090f45 --- /dev/null +++ b/bin/how-npm-am-i.js @@ -0,0 +1,85 @@ +#!/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 '); + 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(''); + 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 = 0; + 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; +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..14dc76d --- /dev/null +++ b/index.js @@ -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; + }); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..35ed623 --- /dev/null +++ b/package-lock.json @@ -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==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..bc8ff14 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "how-npm-am-i", + "version": "1.0.2", + "description": "See how many downloads you get on npm each month, across all your packages", + "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 (https://coolaj86.com/)", + "license": "MPL-2.0", + "dependencies": { + "@root/request": "^1.3.11", + "batchasync": "^1.0.2" + }, + "bundledDependencies": [ + "@root/request", + "batchasync" + ] +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..de62f51 --- /dev/null +++ b/test.js @@ -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') + ); + }); +});