123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- 'use strict'
- const URL = require('url').URL
- exports.getFundingInfo = getFundingInfo
- exports.retrieveFunding = retrieveFunding
- exports.validFundingField = validFundingField
- const flatCacheSymbol = Symbol('npm flat cache')
- exports.flatCacheSymbol = flatCacheSymbol
- // supports object funding and string shorthand, or an array of these
- // if original was an array, returns an array; else returns the lone item
- function retrieveFunding (funding) {
- const sources = [].concat(funding || []).map(item => (
- typeof item === 'string'
- ? { url: item }
- : item
- ))
- return Array.isArray(funding) ? sources : sources[0]
- }
- // Is the value of a `funding` property of a `package.json`
- // a valid type+url for `npm fund` to display?
- function validFundingField (funding) {
- if (!funding) return false
- if (Array.isArray(funding)) {
- return funding.every(f => !Array.isArray(f) && validFundingField(f))
- }
- try {
- var parsed = new URL(funding.url || funding)
- } catch (error) {
- return false
- }
- if (
- parsed.protocol !== 'https:' &&
- parsed.protocol !== 'http:'
- ) return false
- return Boolean(parsed.host)
- }
- const empty = () => Object.create(null)
- function getFundingInfo (idealTree, opts) {
- let packageWithFundingCount = 0
- const flat = empty()
- const seen = new Set()
- const { countOnly } = opts || {}
- const _trailingDependencies = Symbol('trailingDependencies')
- function tracked (name, version) {
- const key = String(name) + String(version)
- if (seen.has(key)) {
- return true
- }
- seen.add(key)
- }
- function retrieveDependencies (dependencies) {
- const trailing = dependencies[_trailingDependencies]
- if (trailing) {
- return Object.assign(
- empty(),
- dependencies,
- trailing
- )
- }
- return dependencies
- }
- function hasDependencies (dependencies) {
- return dependencies && (
- Object.keys(dependencies).length ||
- dependencies[_trailingDependencies]
- )
- }
- function addToFlatCache (funding, dep) {
- [].concat(funding || []).forEach((f) => {
- const key = f.url
- if (!Array.isArray(flat[key])) {
- flat[key] = []
- }
- flat[key].push(dep)
- })
- }
- function attachFundingInfo (target, funding, dep) {
- if (funding && validFundingField(funding)) {
- target.funding = retrieveFunding(funding)
- if (!countOnly) {
- addToFlatCache(target.funding, dep)
- }
- packageWithFundingCount++
- }
- }
- function getFundingDependencies (tree) {
- const deps = tree && tree.dependencies
- if (!deps) return empty()
- const directDepsWithFunding = Object.keys(deps).map((key) => {
- const dep = deps[key]
- const { name, funding, version } = dep
- // avoids duplicated items within the funding tree
- if (tracked(name, version)) return empty()
- const fundingItem = {}
- if (version) {
- fundingItem.version = version
- }
- attachFundingInfo(fundingItem, funding, dep)
- return {
- dep,
- fundingItem
- }
- })
- return directDepsWithFunding.reduce((res, { dep: directDep, fundingItem }, i) => {
- if (!fundingItem || fundingItem.length === 0) return res
- // recurse
- const transitiveDependencies = directDep.dependencies &&
- Object.keys(directDep.dependencies).length > 0 &&
- getFundingDependencies(directDep)
- // if we're only counting items there's no need
- // to add all the data to the resulting object
- if (countOnly) return null
- if (hasDependencies(transitiveDependencies)) {
- fundingItem.dependencies = retrieveDependencies(transitiveDependencies)
- }
- if (fundingItem.funding && fundingItem.funding.length !== 0) {
- res[directDep.name] = fundingItem
- } else if (fundingItem.dependencies) {
- res[_trailingDependencies] =
- Object.assign(
- empty(),
- res[_trailingDependencies],
- fundingItem.dependencies
- )
- }
- return res
- }, countOnly ? null : empty())
- }
- const idealTreeDependencies = getFundingDependencies(idealTree)
- const result = {
- length: packageWithFundingCount
- }
- if (!countOnly) {
- result.name = idealTree.name || idealTree.path
- if (idealTree && idealTree.version) {
- result.version = idealTree.version
- }
- if (idealTree && idealTree.funding) {
- result.funding = retrieveFunding(idealTree.funding)
- }
- result.dependencies = retrieveDependencies(idealTreeDependencies)
- result[flatCacheSymbol] = flat
- }
- return result
- }
|