@@ -14,7 +14,7 @@ const readShrinkwrap = require('./install/read-shrinkwrap.js')
14
14
const mutateIntoLogicalTree = require ( './install/mutate-into-logical-tree.js' )
15
15
const output = require ( './utils/output.js' )
16
16
const 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' )
18
18
19
19
const FundConfig = figgyPudding ( {
20
20
browser : { } , // used by ./utils/open-url
@@ -52,96 +52,52 @@ function printJSON (fundingInfo) {
52
52
// level possible, in that process they also carry their dependencies along
53
53
// with them, moving those up in the visual tree
54
54
function 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 ]
58
56
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
- }
57
+ const { name, version } = fundingInfo
58
+ const printableVersion = version ? `@${ version } ` : ''
67
59
68
- function retrieveStackedItem ( funding ) {
69
- const key = seenKey ( funding )
70
- if ( key && seen . has ( key ) ) return seen . get ( key )
71
- }
60
+ const items = Object . keys ( flatCache ) . map ( ( url ) => {
61
+ const deps = flatCache [ url ]
72
62
73
- // ---
74
-
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 || { }
63
+ const packages = deps . map ( ( dep ) => {
64
+ const { name, version } = dep
81
65
82
66
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
67
+ return `${ name } ${ printableVersion } `
68
+ } )
69
+
70
+ return {
71
+ label : url ,
72
+ nodes : [ packages . join ( ', ' ) ]
126
73
}
127
74
} )
128
75
129
- return archy ( result , '' , { unicode : opts . unicode } )
76
+ return archy ( { label : ` ${ name } ${ printableVersion } ` , nodes : items } , '' , { unicode : opts . unicode } )
130
77
}
131
78
132
- function openFundingUrl ( packageName , cb ) {
79
+ function openFundingUrl ( packageName , fundingSourceNumber , cb ) {
133
80
function getUrlAndOpen ( packageMetadata ) {
134
81
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 ) ) {
82
+ const validSources = [ ] . concat ( retrieveFunding ( funding ) ) . filter ( validFundingField )
83
+
84
+ if ( validSources . length === 1 || ( fundingSourceNumber > 0 && fundingSourceNumber <= validSources . length ) ) {
85
+ const { type, url } = validSources [ fundingSourceNumber ? fundingSourceNumber - 1 : 0 ]
86
+ const typePrefix = type ? `${ type } funding` : 'Funding'
87
+ const msg = `${ typePrefix } available at the following URL`
143
88
openUrl ( url , msg , cb )
89
+ } else if ( fundingSourceNumber < 1 ) {
90
+ validSources . forEach ( ( { type, url } , i ) => {
91
+ const typePrefix = type ? `${ type } funding` : 'Funding'
92
+ const msg = `${ typePrefix } available at the following URL`
93
+ console . log ( `${ i + 1 } : ${ msg } : ${ url } ` )
94
+ } )
95
+ console . log ( 'Run `npm fund [<@scope>/]<pkg> 1`, for example, to open the first funding URL listed in that package' )
96
+ cb ( )
144
97
} else {
98
+ const noFundingError = new Error ( `No valid funding method available for: ${ packageName } ` )
99
+ noFundingError . code = 'ENOFUND'
100
+
145
101
throw noFundingError
146
102
}
147
103
}
@@ -161,15 +117,24 @@ function fundCmd (args, cb) {
161
117
const opts = FundConfig ( npmConfig ( ) )
162
118
const dir = path . resolve ( npm . dir , '..' )
163
119
const packageName = args [ 0 ]
120
+ const numberArg = args [ 1 ]
121
+
122
+ const fundingSourceNumber = numberArg && parseInt ( numberArg , 10 )
123
+
124
+ if ( numberArg && ( String ( fundingSourceNumber ) !== numberArg || fundingSourceNumber < 1 ) ) {
125
+ const err = new Error ( '`npm fund [<@scope>/]<pkg> <number>` must be given a positive integer' )
126
+ err . code = 'EFUNDNUMBER'
127
+ throw err
128
+ }
164
129
165
130
if ( opts . global ) {
166
- const err = new Error ( '`npm fund` does not support globals ' )
131
+ const err = new Error ( '`npm fund` does not support global packages ' )
167
132
err . code = 'EFUNDGLOBAL'
168
133
throw err
169
134
}
170
135
171
136
if ( packageName ) {
172
- openFundingUrl ( packageName , cb )
137
+ openFundingUrl ( packageName , fundingSourceNumber , cb )
173
138
return
174
139
}
175
140
0 commit comments