Skip to content

Commit ba8b4fe

Browse files
committed
fix: always bypass cache when ?write=true
The npm CLI makes GET requests with ?write=true in some cases where it's intending to send an immediate PUT or DELETE. Always bypass the cache for such requests, mirroring the behavior of the registry caching mechanisms. Back-ported for v4.
1 parent b758555 commit ba8b4fe

File tree

2 files changed

+52
-10
lines changed

2 files changed

+52
-10
lines changed

index.js

+22-10
Original file line numberDiff line numberDiff line change
@@ -53,26 +53,38 @@ function regFetch (uri, opts) {
5353
})
5454
}
5555
}
56-
if (opts.query) {
57-
let q = opts.query
56+
57+
let q = opts.query
58+
if (q) {
5859
if (typeof q === 'string') {
5960
q = qs.parse(q)
61+
} else if (typeof q !== 'object') {
62+
throw new TypeError('invalid query option, must be string or object')
6063
}
6164
Object.keys(q).forEach(key => {
6265
if (q[key] === undefined) {
6366
delete q[key]
6467
}
6568
})
66-
if (Object.keys(q).length) {
67-
const parsed = url.parse(uri)
68-
parsed.search = '?' + qs.stringify(
69-
parsed.query
70-
? Object.assign(qs.parse(parsed.query), q)
71-
: q
72-
)
73-
uri = url.format(parsed)
69+
}
70+
const parsed = url.parse(uri)
71+
72+
const query = parsed.query ? Object.assign(qs.parse(parsed.query), q || {})
73+
: Object.keys(q || {}).length ? q
74+
: null
75+
76+
if (query) {
77+
if (String(query.write) === 'true' && opts.method === 'GET') {
78+
opts = opts.concat({
79+
offline: false,
80+
'prefer-offline': false,
81+
'prefer-online': true
82+
})
7483
}
84+
parsed.search = '?' + qs.stringify(query)
85+
uri = url.format(parsed)
7586
}
87+
7688
return opts.Promise.resolve(body).then(body => fetch(uri, {
7789
agent: opts.agent,
7890
algorithms: opts.algorithms,

test/index.js

+30
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,36 @@ test('log warning header info', t => {
428428
.then(res => t.equal(res.status, 200, 'got successful response'))
429429
})
430430

431+
test('query string with ?write=true', t => {
432+
const {resolve} = require('path')
433+
const cache = resolve(__dirname, 'index-query-string-with-write-true')
434+
const mkdirp = require('mkdirp').sync
435+
mkdirp(cache)
436+
const rimraf = require('rimraf')
437+
t.teardown(() => rimraf.sync(cache))
438+
439+
const opts = OPTS.concat({ 'prefer-offline': true, cache })
440+
const qsString = opts.concat({ query: { write: 'true' } })
441+
const qsBool = opts.concat({ query: { write: true } })
442+
tnock(t, opts.registry)
443+
.get('/hello?write=true')
444+
.times(6)
445+
.reply(200, { write: 'go for it' })
446+
447+
return fetch.json('/hello?write=true', opts)
448+
.then(res => t.strictSame(res, { write: 'go for it' }))
449+
.then(() => fetch.json('/hello?write=true', opts))
450+
.then(res => t.strictSame(res, { write: 'go for it' }))
451+
.then(() => fetch.json('/hello', qsString))
452+
.then(res => t.strictSame(res, { write: 'go for it' }))
453+
.then(() => fetch.json('/hello', qsString))
454+
.then(res => t.strictSame(res, { write: 'go for it' }))
455+
.then(() => fetch.json('/hello', qsBool))
456+
.then(res => t.strictSame(res, { write: 'go for it' }))
457+
.then(() => fetch.json('/hello', qsBool))
458+
.then(res => t.strictSame(res, { write: 'go for it' }))
459+
})
460+
431461
// TODO
432462
// * npm-session
433463
// * npm-in-ci

0 commit comments

Comments
 (0)