Skip to content

Commit 669ef94

Browse files
committed
fix(fund): correctly parse and use which config
Previously the `which` param in `npm fund` was validated incorrectly leading to `EFUNDNUMBER` errors when used. This fixes that and does a better job detecting when `which` is pointing to an incorrect array bounds in the returned funding array.
1 parent 72e6d6f commit 669ef94

File tree

3 files changed

+443
-396
lines changed

3 files changed

+443
-396
lines changed

lib/commands/fund.js

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,27 @@ const getPrintableName = ({ name, version }) => {
1616
return `${name}${printableVersion}`
1717
}
1818

19+
const errCode = (msg, code) => Object.assign(new Error(msg), { code })
20+
1921
class Fund extends ArboristWorkspaceCmd {
2022
static description = 'Retrieve funding information'
2123
static name = 'fund'
2224
static params = ['json', 'browser', 'unicode', 'workspace', 'which']
2325
static usage = ['[<package-spec>]']
2426

27+
// XXX: maybe worth making this generic for all commands?
28+
usageMessage (paramsObj = {}) {
29+
let msg = `\`npm ${this.constructor.name}`
30+
const params = Object.entries(paramsObj)
31+
if (params.length) {
32+
msg += ` ${this.constructor.usage}`
33+
}
34+
for (const [key, value] of params) {
35+
msg += ` --${key}=${value}`
36+
}
37+
return `${msg}\``
38+
}
39+
2540
// TODO
2641
/* istanbul ignore next */
2742
async completion (opts) {
@@ -30,25 +45,23 @@ class Fund extends ArboristWorkspaceCmd {
3045

3146
async exec (args) {
3247
const spec = args[0]
33-
const numberArg = this.npm.config.get('which')
3448

35-
const fundingSourceNumber = numberArg && parseInt(numberArg, 10)
36-
37-
const badFundingSourceNumber =
38-
numberArg !== null && (String(fundingSourceNumber) !== numberArg || fundingSourceNumber < 1)
39-
40-
if (badFundingSourceNumber) {
41-
const err = new Error(
42-
'`npm fund [<@scope>/]<pkg> [--which=fundingSourceNumber]` must be given a positive integer'
43-
)
44-
err.code = 'EFUNDNUMBER'
45-
throw err
49+
let fundingSourceNumber = this.npm.config.get('which')
50+
if (fundingSourceNumber != null) {
51+
fundingSourceNumber = parseInt(fundingSourceNumber, 10)
52+
if (isNaN(fundingSourceNumber) || fundingSourceNumber < 1) {
53+
throw errCode(
54+
`${this.usageMessage({ which: 'fundingSourceNumber' })} must be given a positive integer`,
55+
'EFUNDNUMBER'
56+
)
57+
}
4658
}
4759

4860
if (this.npm.global) {
49-
const err = new Error('`npm fund` does not support global packages')
50-
err.code = 'EFUNDGLOBAL'
51-
throw err
61+
throw errCode(
62+
`${this.usageMessage()} does not support global packages`,
63+
'EFUNDGLOBAL'
64+
)
5265
}
5366

5467
const where = this.npm.prefix
@@ -146,6 +159,7 @@ class Fund extends ArboristWorkspaceCmd {
146159

147160
async openFundingUrl ({ path, tree, spec, fundingSourceNumber }) {
148161
const arg = npa(spec, path)
162+
149163
const retrievePackageMetadata = () => {
150164
if (arg.type === 'directory') {
151165
if (tree.path === arg.fetchSpec) {
@@ -178,32 +192,35 @@ class Fund extends ArboristWorkspaceCmd {
178192

179193
const validSources = [].concat(normalizeFunding(funding)).filter(isValidFunding)
180194

181-
const matchesValidSource =
182-
validSources.length === 1 ||
183-
(fundingSourceNumber > 0 && fundingSourceNumber <= validSources.length)
184-
185-
if (matchesValidSource) {
186-
const index = fundingSourceNumber ? fundingSourceNumber - 1 : 0
187-
const { type, url } = validSources[index]
188-
const typePrefix = type ? `${type} funding` : 'Funding'
189-
const msg = `${typePrefix} available at the following URL`
190-
return openUrl(this.npm, url, msg)
191-
} else if (validSources.length && !(fundingSourceNumber >= 1)) {
192-
validSources.forEach(({ type, url }, i) => {
193-
const typePrefix = type ? `${type} funding` : 'Funding'
194-
const msg = `${typePrefix} available at the following URL`
195-
this.npm.output(`${i + 1}: ${msg}: ${url}`)
196-
})
197-
this.npm.output(
198-
/* eslint-disable-next-line max-len */
199-
'Run `npm fund [<@scope>/]<pkg> --which=1`, for example, to open the first funding URL listed in that package'
200-
)
201-
} else {
202-
const noFundingError = new Error(`No valid funding method available for: ${spec}`)
203-
noFundingError.code = 'ENOFUND'
195+
if (!validSources.length) {
196+
throw errCode(`No valid funding method available for: ${spec}`, 'ENOFUND')
197+
}
204198

205-
throw noFundingError
199+
const fundSource = fundingSourceNumber
200+
? validSources[fundingSourceNumber - 1]
201+
: validSources.length === 1 ? validSources[0]
202+
: null
203+
204+
if (fundSource) {
205+
return openUrl(this.npm, ...this.urlMessage(fundSource))
206+
}
207+
208+
const ambiguousUrlMsg = [
209+
...validSources.map((s, i) => `${i + 1}: ${this.urlMessage(s).reverse().join(': ')}`),
210+
`Run ${this.usageMessage({ which: '1' })}` +
211+
', for example, to open the first funding URL listed in that package',
212+
]
213+
if (fundingSourceNumber) {
214+
ambiguousUrlMsg.unshift(`--which=${fundingSourceNumber} is not a valid index`)
206215
}
216+
this.npm.output(ambiguousUrlMsg.join('\n'))
217+
}
218+
219+
urlMessage (source) {
220+
const { type, url } = source
221+
const typePrefix = type ? `${type} funding` : 'Funding'
222+
const message = `${typePrefix} available at the following URL`
223+
return [url, message]
207224
}
208225
}
209226
module.exports = Fund

tap-snapshots/test/lib/commands/fund.js.test.cjs

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
exports[`test/lib/commands/fund.js TAP fund a package with type and multiple sources > should print prompt select message 1`] = `
99
1: Foo funding available at the following URL: http://example.com/foo
1010
2: Lorem funding available at the following URL: http://example.com/foo-lorem
11-
Run \`npm fund [<@scope>/]<pkg> --which=1\`, for example, to open the first funding URL listed in that package
12-
11+
Run \`npm fund [<package-spec>] --which=1\`, for example, to open the first funding URL listed in that package
1312
`
1413

1514
exports[`test/lib/commands/fund.js TAP fund colors > should print output with color info 1`] = `
@@ -23,7 +22,6 @@ exports[`test/lib/commands/fund.js TAP fund colors > should print output with co
2322
 \`-- http://example.com/e
2423
 \`-- [email protected]
2524

26-
2725
`
2826

2927
exports[`test/lib/commands/fund.js TAP fund containing multi-level nested deps with no funding > should omit dependencies with no funding declared 1`] = `
@@ -33,54 +31,37 @@ [email protected]
3331
\`-- http://example.com/donate
3432
3533
36-
3734
`
3835

3936
exports[`test/lib/commands/fund.js TAP fund in which same maintainer owns all its deps > should print stack packages together 1`] = `
4037
http://example.com/donate
4138
4239
43-
4440
`
4541

4642
exports[`test/lib/commands/fund.js TAP fund pkg missing version number > should print name only 1`] = `
4743
http://example.com/foo
4844
\`-- foo
4945
46+
`
5047

48+
exports[`test/lib/commands/fund.js TAP fund using bad which value: index too high > should print message about invalid which 1`] = `
49+
--which=100 is not a valid index
50+
1: Funding available at the following URL: http://example.com
51+
2: Funding available at the following URL: http://sponsors.example.com/me
52+
3: Funding available at the following URL: http://collective.example.com
53+
Run \`npm fund [<package-spec>] --which=1\`, for example, to open the first funding URL listed in that package
5154
`
5255

5356
exports[`test/lib/commands/fund.js TAP fund using nested packages with multiple sources > should prompt with all available URLs 1`] = `
5457
1: Funding available at the following URL: https://one.example.com
5558
2: Funding available at the following URL: https://two.example.com
56-
Run \`npm fund [<@scope>/]<pkg> --which=1\`, for example, to open the first funding URL listed in that package
57-
58-
`
59-
60-
exports[`test/lib/commands/fund.js TAP fund using nested packages with multiple sources, with a source number > should open the numbered URL 1`] = `
61-
Funding available at the following URL:
62-
https://one.example.com
63-
`
64-
65-
exports[`test/lib/commands/fund.js TAP fund using package argument > should open funding url 1`] = `
66-
individual funding available at the following URL:
67-
http://example.com/donate
68-
`
69-
70-
exports[`test/lib/commands/fund.js TAP fund using pkg name while having conflicting versions > should open greatest version 1`] = `
71-
Funding available at the following URL:
72-
http://example.com/2
73-
`
74-
75-
exports[`test/lib/commands/fund.js TAP fund using string shorthand > should open string-only url 1`] = `
76-
Funding available at the following URL:
77-
https://example.com/sponsor
59+
Run \`npm fund [<package-spec>] --which=1\`, for example, to open the first funding URL listed in that package
7860
`
7961

8062
exports[`test/lib/commands/fund.js TAP fund with no package containing funding > should print empty funding info 1`] = `
8163
8264
83-
8465
`
8566

8667
exports[`test/lib/commands/fund.js TAP sub dep with fund info and a parent with no funding info > should nest sub dep as child of root 1`] = `
@@ -90,25 +71,22 @@ [email protected]
9071
\`-- http://example.com/c
9172
9273
93-
9474
`
9575

96-
exports[`test/lib/commands/fund.js TAP workspaces filter funding info by a specific workspace > should display only filtered workspace name and its deps 1`] = `
76+
exports[`test/lib/commands/fund.js TAP workspaces filter funding info by a specific workspace name > should display only filtered workspace name and its deps 1`] = `
9777
9878
\`-- https://example.com/a
9979
10080
\`-- http://example.com/c
10181
10282
103-
10483
`
10584

106-
exports[`test/lib/commands/fund.js TAP workspaces filter funding info by a specific workspace > should display only filtered workspace path and its deps 1`] = `
85+
exports[`test/lib/commands/fund.js TAP workspaces filter funding info by a specific workspace path > should display only filtered workspace name and its deps 1`] = `
10786
10887
\`-- https://example.com/a
10988
11089
\`-- http://example.com/c
11190
11291
113-
11492
`

0 commit comments

Comments
 (0)