Browse Source

show direct dependencies first in the list

master
Feross Aboukhadijeh 7 years ago
parent
commit
8d4de52839
  1. 85
      cmd.js

85
cmd.js

@ -7,16 +7,19 @@ const opn = require('opn')
const ora = require('ora')
const pify = require('pify')
const pkgDir = require('pkg-dir')
const pkgUp = require('pkg-up')
const readPackageTree = require('read-package-tree')
const RegistryClient = require('npm-registry-client') // TODO: use npm-registry-fetch when done
const registryUrl = require('registry-url')
const setTimeoutAsync = require('timeout-as-promise')
const stripAnsi = require('strip-ansi')
const termSize = require('term-size')
const textTable = require('text-table')
const setTimeoutAsync = require('timeout-as-promise')
const { readFile } = require('fs')
const thanks = require('./')
const readFileAsync = pify(readFile)
const readPackageTreeAsync = pify(readPackageTree)
const DOWNLOADS_URL = 'https://api.npmjs.org/downloads/point/last-month/'
@ -58,6 +61,9 @@ async function init () {
})
const cwd = argv._[0] || process.cwd()
spinner.text = chalk`Reading {cyan direct dependencies} from metadata in {magenta package.json}...`
const directPkgNames = await readDirectPkgNames()
spinner.text = chalk`Reading {cyan dependencies} from package tree in {magenta node_modules}...`
const rootPath = await pkgDir(cwd)
const packageTree = await readPackageTreeAsync(rootPath)
@ -71,8 +77,8 @@ async function init () {
spinner.text = chalk`Fetching package {cyan download counts} from {red npm}...`
const downloadCounts = await bulkFetchDownloads(pkgNames)
// Author name -> list of packages (sorted by download count)
const authorsPkgNames = computeAuthorsPkgNames(allPkgs, downloadCounts)
// Author name -> list of packages (sorted by direct dependencies, then download count)
const authorsPkgNames = computeAuthorsPkgNames(allPkgs, downloadCounts, directPkgNames)
// Array of author names who are seeking donations
const authorsSeeking = Object.keys(authorsPkgNames)
@ -84,15 +90,13 @@ async function init () {
if (authorsSeeking.length) {
spinner.succeed(chalk`You depend on {cyan ${authorsSeeking.length} authors} who are {magenta seeking donations!} ✨\n`)
printTable(authorsSeeking, authorsPkgNames)
printTable(authorsSeeking, authorsPkgNames, directPkgNames)
if (argv.open) openDonateLinks(donateLinks)
} else {
spinner.info('You don\'t depend on any packages from maintainers seeking donations')
}
// TODO: compute list of **projects** seeking donations
// TODO: show direct dependencies first in the list
// console.log(readLocalDeps())
}
function createRegistryClient () {
@ -128,15 +132,22 @@ async function fetchPkg (client, pkgName) {
return client.getAsync(url, opts)
}
function printTable (authorsSeeking, authorsPkgNames) {
function printTable (authorsSeeking, authorsPkgNames, directPkgNames) {
const rows = authorsSeeking
.map(author => {
const authorPkgs = authorsPkgNames[author]
// Highlight direct dependencies in a different color
const authorPkgNames = authorsPkgNames[author]
.map(pkgName => {
return directPkgNames.includes(pkgName)
? chalk.green.bold(pkgName)
: pkgName
})
const donateLink = thanks.authors[author].replace(RE_REMOVE_URL_PREFIX, '')
return [
chalk.green(author),
author,
chalk.cyan(donateLink),
listWithMaxLen(authorPkgs, termSize().columns - 45)
listWithMaxLen(authorPkgNames, termSize().columns - 45)
]
})
@ -180,26 +191,35 @@ async function bulkFetchDownloads (pkgNames) {
return downloads
}
function computeAuthorsPkgNames (pkgs, downloadCounts) {
function computeAuthorsPkgNames (pkgs, downloadCounts, directPkgNames) {
// author name -> array of package names
const authorPkgs = {}
const authorPkgNames = {}
pkgs.forEach(pkg => {
pkg.maintainers
.map(maintainer => maintainer.name)
.forEach(author => {
if (authorPkgs[author] == null) authorPkgs[author] = []
authorPkgs[author].push(pkg.name)
if (authorPkgNames[author] == null) authorPkgNames[author] = []
authorPkgNames[author].push(pkg.name)
})
})
// Sort each author's package list by download count
Object.keys(authorPkgs).forEach(author => {
const pkgs = authorPkgs[author]
pkgs.sort((pkg1, pkg2) => downloadCounts[pkg2] - downloadCounts[pkg1])
// Sort each author's package list by direct dependencies, then download count
// dependencies first in the list
Object.keys(authorPkgNames).forEach(author => {
const authorDirectPkgNames = authorPkgNames[author]
.filter(pkgName => directPkgNames.includes(pkgName))
const pkgNames = authorPkgNames[author]
.filter(pkgName => !authorDirectPkgNames.includes(pkgName))
.sort((pkg1, pkg2) => downloadCounts[pkg2] - downloadCounts[pkg1])
pkgNames.unshift(...authorDirectPkgNames)
authorPkgNames[author] = pkgNames
})
return authorPkgs
return authorPkgNames
}
function listWithMaxLen (list, maxLen) {
@ -208,7 +228,7 @@ function listWithMaxLen (list, maxLen) {
let str = ''
for (let i = 0; i < list.length; i++) {
const item = (i === 0 ? '' : ', ') + list[i]
if (stripAnsi(str).length + item.length >= maxLen - ELLIPSIS_LENGTH) {
if (stripAnsi(str).length + stripAnsi(item).length >= maxLen - ELLIPSIS_LENGTH) {
str += ELLIPSIS.replace('XX', list.length - i)
break
}
@ -233,3 +253,28 @@ async function openDonateLinks (donateLinks) {
spinner.succeed(chalk`Opened {cyan ${len} donate pages} in your {magenta web browser} 💻`)
}
async function readDirectPkgNames () {
const pkgPath = await pkgUp()
const pkgStr = await readFileAsync(pkgPath, 'utf8')
let pkg
try {
pkg = JSON.parse(pkgStr)
} catch (err) {
err.message = `Failed to parse package.json: ${err.message}`
throw err
}
return [].concat(
findDeps(pkg, 'dependencies'),
findDeps(pkg, 'devDependencies'),
findDeps(pkg, 'optionalDependencies')
)
function findDeps (pkg, type) {
return pkg[type] && typeof pkg[type] === 'object'
? Object.keys(pkg[type])
: []
}
}

Loading…
Cancel
Save