Browse Source

clearer code flow

master
Feross Aboukhadijeh 7 years ago
parent
commit
9967ef9b4e
  1. 155
      cmd.js
  2. 4
      package.json

155
cmd.js

@ -13,15 +13,15 @@ const registryUrl = require('registry-url')
const stripAnsi = require('strip-ansi') const stripAnsi = require('strip-ansi')
const termSize = require('term-size') const termSize = require('term-size')
const textTable = require('text-table') const textTable = require('text-table')
const { promisify } = require('util') const setTimeoutAsync = require('timeout-as-promise')
const thanks = require('./') const thanks = require('./')
const readPackageTreeAsync = pify(readPackageTree) const readPackageTreeAsync = pify(readPackageTree)
const setTimeoutAsync = promisify(setTimeout)
const DOWNLOADS_URL = 'https://api.npmjs.org/downloads/point/last-month/' const DOWNLOADS_URL = 'https://api.npmjs.org/downloads/point/last-month/'
const DOWNLOADS_URL_LIMIT = 128 const DOWNLOADS_URL_LIMIT = 128
const RE_REMOVE_URL_PREFIX = /https?:\/\/(www\.)?/
const spinner = ora({ const spinner = ora({
spinner: 'moon', spinner: 'moon',
@ -49,7 +49,6 @@ async function init () {
}) })
const cwd = argv._[0] || process.cwd() const cwd = argv._[0] || process.cwd()
// Get all packages in the nearest `node_modules` folder
spinner.text = chalk`Reading {cyan dependencies} from package tree in {magenta node_modules}...` spinner.text = chalk`Reading {cyan dependencies} from package tree in {magenta node_modules}...`
const rootPath = await pkgDir(cwd) const rootPath = await pkgDir(cwd)
const packageTree = await readPackageTreeAsync(rootPath) const packageTree = await readPackageTreeAsync(rootPath)
@ -58,83 +57,33 @@ async function init () {
// not include the list of maintainers // not include the list of maintainers
spinner.text = chalk`Fetching package {cyan maintainers} from {red npm}...` spinner.text = chalk`Fetching package {cyan maintainers} from {red npm}...`
const pkgNames = packageTree.children.map(node => node.package.name) const pkgNames = packageTree.children.map(node => node.package.name)
const allPkgs = await Promise.all(pkgNames.map(fetchPkg)) const allPkgs = await Promise.all(pkgNames.map(pkgName => fetchPkg(client, pkgName)))
// Fetch download counts for each package
spinner.text = chalk`Fetching package {cyan download counts} from {red npm}...` spinner.text = chalk`Fetching package {cyan download counts} from {red npm}...`
const downloadCounts = await bulkFetchDownloads(pkgNames) const downloadCounts = await bulkFetchDownloads(pkgNames)
// Author name -> list of packages, ordered by download count // Author name -> list of packages (sorted by download count)
const authorInfos = computeAuthorInfos(allPkgs, downloadCounts) const authorsPkgNames = computeAuthorsPkgNames(allPkgs, downloadCounts)
// TODO: compute list of **projects** seeking donations // Array of author names who are seeking donations
// TODO: show direct dependencies first in the list const authorsSeeking = Object.keys(authorsPkgNames)
const donateLinks = []
const rows = Object.keys(authorInfos)
.filter(author => thanks.authors[author] != null) .filter(author => thanks.authors[author] != null)
.sort((author1, author2) => authorInfos[author2].length - authorInfos[author1].length) .sort((author1, author2) => authorsPkgNames[author2].length - authorsPkgNames[author1].length)
.map(author => {
const authorPkgs = authorInfos[author]
const donateLink = thanks.authors[author]
donateLinks.push(donateLink)
const prettyDonateLink = donateLink.replace(/https?:\/\/(www\.)?/, '')
return [
chalk.green(author),
chalk.cyan(prettyDonateLink),
listWithMaxLen(authorPkgs, termSize().columns - 45)
]
})
rows.unshift([ const donateLinks = authorsSeeking
chalk.underline('Author'), .map(author => thanks.authors[author])
chalk.underline('Where to Donate'),
chalk.underline('Dependencies')
])
if (rows.length) { if (authorsSeeking.length) {
spinner.succeed(chalk`You depend on {cyan ${rows.length} authors} who are {magenta seeking donations!} ✨\n`) spinner.succeed(chalk`You depend on {cyan ${authorsSeeking.length} authors} who are {magenta seeking donations!} ✨\n`)
printTable(rows) printTable(authorsSeeking, authorsPkgNames)
if (argv.open) openDonateLinks() if (argv.open) openDonateLinks(donateLinks)
} else { } else {
spinner.succeed('You don\'t depend on any packages from maintainers seeking donations') spinner.info('You don\'t depend on any packages from maintainers seeking donations')
}
async function openDonateLinks () {
const spinner = ora({
spinner: 'hearts',
text: chalk`Opening {cyan donate pages} in your {magenta web browser}...`
}).start()
await setTimeoutAsync(1000)
for (let donateLink of donateLinks) {
await opn(donateLink, { wait: false })
}
spinner.succeed()
}
function printTable (rows) {
const tableOpts = {
stringLength: str => stripAnsi(str).length
}
const table = textTable(rows, tableOpts)
console.log(table + '\n')
} }
async function fetchPkg (pkgName) { // TODO: compute list of **projects** seeking donations
// Note: The registry does not support fetching versions for scoped packages // TODO: show direct dependencies first in the list
const url = isScopedPkg(pkgName) // console.log(readLocalDeps())
? `${registryUrl()}${pkgName.replace('/', '%2F')}`
: `${registryUrl()}${pkgName}/latest`
const opts = {
timeout: 30 * 1000,
staleOk: true
}
return client.getAsync(url, opts)
}
} }
function createRegistryClient () { function createRegistryClient () {
@ -157,6 +106,44 @@ function isScopedPkg (pkgName) {
return pkgName.includes('/') return pkgName.includes('/')
} }
async function fetchPkg (client, pkgName) {
// Note: The registry does not support fetching versions for scoped packages
const url = isScopedPkg(pkgName)
? `${registryUrl()}${pkgName.replace('/', '%2F')}`
: `${registryUrl()}${pkgName}/latest`
const opts = {
timeout: 30 * 1000,
staleOk: true
}
return client.getAsync(url, opts)
}
function printTable (authorsSeeking, authorsPkgNames) {
const rows = authorsSeeking
.map(author => {
const authorPkgs = authorsPkgNames[author]
const donateLink = thanks.authors[author].replace(RE_REMOVE_URL_PREFIX, '')
return [
chalk.green(author),
chalk.cyan(donateLink),
listWithMaxLen(authorPkgs, termSize().columns - 45)
]
})
rows.unshift([
chalk.underline('Author'),
chalk.underline('Where to Donate'),
chalk.underline('Dependencies')
])
const opts = {
stringLength: str => stripAnsi(str).length
}
const table = textTable(rows, opts)
console.log(table + '\n')
}
async function bulkFetchDownloads (pkgNames) { async function bulkFetchDownloads (pkgNames) {
// A few notes: // A few notes:
// - bulk queries do not support scoped packages // - bulk queries do not support scoped packages
@ -184,26 +171,26 @@ async function bulkFetchDownloads (pkgNames) {
return downloads return downloads
} }
function computeAuthorInfos (pkgs, downloadCounts) { function computeAuthorsPkgNames (pkgs, downloadCounts) {
// author name -> array of package names // author name -> array of package names
const authorInfos = {} const authorPkgs = {}
pkgs.forEach(pkg => { pkgs.forEach(pkg => {
pkg.maintainers pkg.maintainers
.map(maintainer => maintainer.name) .map(maintainer => maintainer.name)
.forEach(author => { .forEach(author => {
if (authorInfos[author] == null) authorInfos[author] = [] if (authorPkgs[author] == null) authorPkgs[author] = []
authorInfos[author].push(pkg.name) authorPkgs[author].push(pkg.name)
}) })
}) })
// Sort each author's package list by download count // Sort each author's package list by download count
Object.keys(authorInfos).forEach(author => { Object.keys(authorPkgs).forEach(author => {
const pkgs = authorInfos[author] const pkgs = authorPkgs[author]
pkgs.sort((pkg1, pkg2) => downloadCounts[pkg2] - downloadCounts[pkg1]) pkgs.sort((pkg1, pkg2) => downloadCounts[pkg2] - downloadCounts[pkg1])
}) })
return authorInfos return authorPkgs
} }
function listWithMaxLen (list, maxLen) { function listWithMaxLen (list, maxLen) {
@ -220,3 +207,19 @@ function listWithMaxLen (list, maxLen) {
} }
return str return str
} }
async function openDonateLinks (donateLinks) {
console.log(donateLinks)
const len = donateLinks.length
const spinner = ora({
text: chalk`Opening {cyan ${len} donate pages} in your {magenta web browser}...`
}).start()
for (let donateLink of donateLinks) {
await opn(donateLink, { wait: false })
await setTimeoutAsync(2000)
}
spinner.succeed(chalk`Opened {cyan ${len} donate pages} in your {magenta web browser}`)
}

4
package.json

@ -20,11 +20,13 @@
"ora": "^1.4.0", "ora": "^1.4.0",
"pify": "^3.0.0", "pify": "^3.0.0",
"pkg-dir": "^2.0.0", "pkg-dir": "^2.0.0",
"pkg-up": "^2.0.0",
"read-package-tree": "^5.1.6", "read-package-tree": "^5.1.6",
"registry-url": "^3.1.0", "registry-url": "^3.1.0",
"strip-ansi": "^4.0.0", "strip-ansi": "^4.0.0",
"term-size": "^1.2.0", "term-size": "^1.2.0",
"text-table": "^0.2.0" "text-table": "^0.2.0",
"timeout-as-promise": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"standard": "*" "standard": "*"

Loading…
Cancel
Save