@@ -14,7 +14,7 @@ const readShrinkwrap = require('./install/read-shrinkwrap.js')
1414const mutateIntoLogicalTree = require ( './install/mutate-into-logical-tree.js' )
1515const output = require ( './utils/output.js' )
1616const openUrl = require ( './utils/open-url.js' )
17- const { getFundingInfo, retrieveFunding, validFundingUrl } = require ( './utils/funding.js' )
17+ const { getFundingInfo, retrieveFunding, validFundingField , flatCacheSymbol } = require ( './utils/funding.js' )
1818
1919const FundConfig = figgyPudding ( {
2020 browser : { } , // used by ./utils/open-url
@@ -52,96 +52,56 @@ function printJSON (fundingInfo) {
5252// level possible, in that process they also carry their dependencies along
5353// with them, moving those up in the visual tree
5454function printHuman ( fundingInfo , opts ) {
55- // mapping logic that keeps track of seen items in order to be able
56- // to push all other items from the same type/url in the same place
57- const seen = new Map ( )
55+ const flatCache = fundingInfo [ flatCacheSymbol ] ;
5856
59- function seenKey ( { type, url } = { } ) {
60- return url ? String ( type ) + String ( url ) : null
61- }
62-
63- function setStackedItem ( funding , result ) {
64- const key = seenKey ( funding )
65- if ( key && ! seen . has ( key ) ) seen . set ( key , result )
66- }
67-
68- function retrieveStackedItem ( funding ) {
69- const key = seenKey ( funding )
70- if ( key && seen . has ( key ) ) return seen . get ( key )
71- }
57+ const { name, funding, version } = fundingInfo
58+ const printableVersion = version ? `@${ version } ` : ''
59+ const rootFunding = [ ] //.concat(funding ? retrieveFunding(funding) : []).map(x => x.url)
7260
73- // ---
61+ const items = Object . keys ( flatCache ) . map ( ( url ) => {
62+ const deps = flatCache [ url ]
7463
75- const getFundingItems = ( fundingItems ) =>
76- Object . keys ( fundingItems || { } ) . map ( ( fundingItemName ) => {
77- // first-level loop, prepare the pretty-printed formatted data
78- const fundingItem = fundingItems [ fundingItemName ]
79- const { version, funding } = fundingItem
80- const { type, url } = funding || { }
64+ const packages = deps . map ( ( dep ) => {
65+ const { name, funding, version } = dep
8166
8267 const printableVersion = version ? `@${ version } ` : ''
83- const printableType = type && { label : `type: ${ funding . type } ` }
84- const printableUrl = url && { label : `url: ${ funding . url } ` }
85- const result = {
86- fundingItem,
87- label : fundingItemName + printableVersion ,
88- nodes : [ ]
89- }
90-
91- if ( printableType ) {
92- result . nodes . push ( printableType )
93- }
94-
95- if ( printableUrl ) {
96- result . nodes . push ( printableUrl )
97- }
98-
99- setStackedItem ( funding , result )
100-
101- return result
102- } ) . reduce ( ( res , result ) => {
103- // recurse and exclude nodes that are going to be stacked together
104- const { fundingItem } = result
105- const { dependencies, funding } = fundingItem
106- const items = getFundingItems ( dependencies )
107- const stackedResult = retrieveStackedItem ( funding )
108- items . forEach ( i => result . nodes . push ( i ) )
109-
110- if ( stackedResult && stackedResult !== result ) {
111- stackedResult . label += `, ${ result . label } `
112- items . forEach ( i => stackedResult . nodes . push ( i ) )
113- return res
114- }
115-
116- res . push ( result )
117-
118- return res
119- } , [ ] )
120-
121- const [ result ] = getFundingItems ( {
122- [ fundingInfo . name ] : {
123- dependencies : fundingInfo . dependencies ,
124- funding : fundingInfo . funding ,
125- version : fundingInfo . version
68+ return `${ name } ${ printableVersion } `
69+ } )
70+
71+ return {
72+ label : url ,
73+ nodes : [ packages . join ( ', ' ) ]
12674 }
12775 } )
12876
129- return archy ( result , '' , { unicode : opts . unicode } )
77+
78+ const nodes = [ ] . concat ( rootFunding , items )
79+
80+ return archy ( { label : `${ name } ${ printableVersion } ` , nodes } , '' , { unicode : opts . unicode } )
13081}
13182
132- function openFundingUrl ( packageName , cb ) {
83+ function openFundingUrl ( packageName , fundingSourceNumber , cb ) {
13384 function getUrlAndOpen ( packageMetadata ) {
13485 const { funding } = packageMetadata
135- const { type, url } = retrieveFunding ( funding ) || { }
136- const noFundingError =
137- new Error ( `No funding method available for: ${ packageName } ` )
138- noFundingError . code = 'ENOFUND'
139- const typePrefix = type ? `${ type } funding` : 'Funding'
140- const msg = `${ typePrefix } available at the following URL`
141-
142- if ( validFundingUrl ( funding ) ) {
86+ const validSources = [ ] . concat ( retrieveFunding ( funding ) ) . filter ( validFundingField )
87+
88+ if ( validSources . length === 1 || ( fundingSourceNumber > 0 && fundingSourceNumber <= validSources . length ) ) {
89+ const { type, url } = validSources [ fundingSourceNumber ? fundingSourceNumber - 1 : 0 ]
90+ const typePrefix = type ? `${ type } funding` : 'Funding'
91+ const msg = `${ typePrefix } available at the following URL`
14392 openUrl ( url , msg , cb )
93+ } else if ( packageNumber < 1 ) {
94+ validSources . forEach ( ( { type, url } , i ) => {
95+ const typePrefix = type ? `${ type } funding` : 'Funding'
96+ const msg = `${ typePrefix } available at the following URL`
97+ console . log ( `${ i + 1 } : ${ msg } : ${ url } ` )
98+ } )
99+ console . log ( 'Run `npm fund ${packageName} 1`, for example, to open the first funding URL' )
100+ cb ( )
144101 } else {
102+ const noFundingError = new Error ( `No valid funding method available for: ${ packageName } ` )
103+ noFundingError . code = 'ENOFUND'
104+
145105 throw noFundingError
146106 }
147107 }
@@ -161,15 +121,24 @@ function fundCmd (args, cb) {
161121 const opts = FundConfig ( npmConfig ( ) )
162122 const dir = path . resolve ( npm . dir , '..' )
163123 const packageName = args [ 0 ]
124+ const numberArg = args [ 1 ]
125+
126+ const fundingSourceNumber = numberArg && parseInt ( numberArg , 10 )
127+
128+ if ( numberArg && String ( fundingSourceNumber ) !== numberArg || fundingSourceNumber < 1 ) {
129+ const err = new Error ( '`npm fund [<@scope>/]<pkg> <number>` must be given a positive integer' )
130+ err . code = 'EFUNDNUMBER'
131+ throw err
132+ }
164133
165134 if ( opts . global ) {
166- const err = new Error ( '`npm fund` does not support globals ' )
135+ const err = new Error ( '`npm fund` does not support global packages ' )
167136 err . code = 'EFUNDGLOBAL'
168137 throw err
169138 }
170139
171140 if ( packageName ) {
172- openFundingUrl ( packageName , cb )
141+ openFundingUrl ( packageName , fundingSourceNumber , cb )
173142 return
174143 }
175144
0 commit comments