const access = require('libnpmaccess') const {add, sub, format} = require('date-fns') const fetch = require('make-fetch-happen').defaults({ cacheManager: './node_modules/.cache/make-fetch-happen', }) const sortBy = (getProp, dir = 'asc') => (a, z) => { const aP = getProp(a) const zP = getProp(z) const aBigger = dir === 'asc' ? 1 : -1 const zBigger = dir === 'asc' ? -1 : 1 return aP > zP ? aBigger : aP < zP ? zBigger : 0 } async function getUserDownloadStats(user) { const pkgs = Object.keys(await access.lsPackages(user)) const pkgStats = [] for (const pkg of pkgs) { try { pkgStats.push({pkg, stats: await getAllDownloadStats(pkg)}) } catch (error) { console.error(`Error processing ${pkg}`) throw error } } const totalDownloads = pkgStats.reduce( (sum, d) => d.stats.totalDownloads + sum, 0, ) const firstDownload = pkgStats.reduce( (oldest, data) => { if ( data.stats.firstDownload && new Date(data.stats.firstDownload.day) < new Date(oldest.day) ) { return {pkg: data.pkg, ...data.stats.firstDownload} } return oldest }, {pkg: '[Unknown]', downloads: 0, day: format(new Date(), 'yyyy-MM-dd')}, ) pkgStats.sort(sortBy(a => a.stats.averageDailyDownloads, 'desc')) return {user, totalDownloads, firstDownload, pkgStats} } async function getAllDownloadStats(pkg) { const npmLimitDays = 540 const today = new Date() const npmDataStart = new Date('2015-01-10 00:00:00 UTC') const groups = [] let currentStart let currentEnd = sub(npmDataStart, {days: 1}) const requests = [] do { currentStart = add(currentEnd, {days: 1}) currentEnd = add(currentStart, {days: npmLimitDays}) const start = format(currentStart, 'yyyy-MM-dd') const end = format(currentEnd, 'yyyy-MM-dd') requests.push( fetch( `https://api.npmjs.org/downloads/range/${start}:${end}/${pkg}`, ).then(response => { return response.json() }), ) groups.push({start: currentStart, end: currentEnd}) } while (currentEnd < today) const results = await Promise.all(requests) const allDownloads = results.flatMap(r => (r.error ? [] : r.downloads)) const firstDownloadIndex = allDownloads.findIndex(d => d.downloads > 0) const firstDownload = allDownloads[firstDownloadIndex] const totalDownloads = allDownloads.reduce((sum, d) => sum + d.downloads, 0) const downloadDaysCount = allDownloads.length - (1 + firstDownloadIndex) const averageDailyDownloads = Number( (totalDownloads / (downloadDaysCount || 1)).toFixed(4), ) const combinedResult = { start: results[0].start, end: results[results.length - 1].end, package: pkg, // downloads: allDownloads, (uncomment this if you want a silly amount of data...) firstDownload, downloadDaysCount, averageDailyDownloads, totalDownloads, } return combinedResult } module.exports = getUserDownloadStats