cacache 16.0.7 → 16.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/lib/content/read.js +76 -91
- package/lib/content/rm.js +9 -9
- package/lib/content/write.js +32 -32
- package/lib/entry-index.js +92 -104
- package/lib/get.js +68 -73
- package/lib/put.js +14 -21
- package/lib/util/fix-owner.js +36 -39
- package/lib/util/move-file.js +35 -48
- package/lib/util/tmp.js +5 -7
- package/lib/verify.js +156 -192
- package/package.json +3 -7
package/README.md
CHANGED
|
@@ -413,6 +413,19 @@ with an `EINTEGRITY` error.
|
|
|
413
413
|
|
|
414
414
|
`algorithms` has no effect if this option is present.
|
|
415
415
|
|
|
416
|
+
##### `opts.integrityEmitter`
|
|
417
|
+
|
|
418
|
+
*Streaming only* If present, uses the provided event emitter as a source of
|
|
419
|
+
truth for both integrity and size. This allows use cases where integrity is
|
|
420
|
+
already being calculated outside of cacache to reuse that data instead of
|
|
421
|
+
calculating it a second time.
|
|
422
|
+
|
|
423
|
+
The emitter must emit both the `'integrity'` and `'size'` events.
|
|
424
|
+
|
|
425
|
+
NOTE: If this option is provided, you must verify that you receive the correct
|
|
426
|
+
integrity value yourself and emit an `'error'` event if there is a mismatch.
|
|
427
|
+
[ssri Integrity Streams](https://github.com/npm/ssri#integrity-stream) do this for you when given an expected integrity.
|
|
428
|
+
|
|
416
429
|
##### `opts.algorithms`
|
|
417
430
|
|
|
418
431
|
Default: ['sha512']
|
package/lib/content/read.js
CHANGED
|
@@ -1,42 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const fs = require('fs')
|
|
3
|
+
const fs = require('@npmcli/fs')
|
|
6
4
|
const fsm = require('fs-minipass')
|
|
7
5
|
const ssri = require('ssri')
|
|
8
6
|
const contentPath = require('./path')
|
|
9
7
|
const Pipeline = require('minipass-pipeline')
|
|
10
8
|
|
|
11
|
-
const lstat = util.promisify(fs.lstat)
|
|
12
|
-
const readFile = util.promisify(fs.readFile)
|
|
13
|
-
const copyFile = util.promisify(fs.copyFile)
|
|
14
|
-
|
|
15
9
|
module.exports = read
|
|
16
10
|
|
|
17
11
|
const MAX_SINGLE_READ_SIZE = 64 * 1024 * 1024
|
|
18
|
-
function read (cache, integrity, opts = {}) {
|
|
12
|
+
async function read (cache, integrity, opts = {}) {
|
|
19
13
|
const { size } = opts
|
|
20
|
-
|
|
14
|
+
const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => {
|
|
21
15
|
// get size
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
const stat = await fs.stat(cpath)
|
|
17
|
+
return { stat, cpath, sri }
|
|
18
|
+
})
|
|
19
|
+
if (typeof size === 'number' && stat.size !== size) {
|
|
20
|
+
throw sizeError(size, stat.size)
|
|
21
|
+
}
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
if (stat.size > MAX_SINGLE_READ_SIZE) {
|
|
24
|
+
return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
|
|
25
|
+
}
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const data = await fs.readFile(cpath, { encoding: null })
|
|
28
|
+
if (!ssri.checkData(data, sri)) {
|
|
29
|
+
throw integrityError(sri, cpath)
|
|
30
|
+
}
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
})
|
|
39
|
-
})
|
|
32
|
+
return data
|
|
40
33
|
}
|
|
41
34
|
|
|
42
35
|
const readPipeline = (cpath, size, sri, stream) => {
|
|
@@ -58,7 +51,7 @@ module.exports.sync = readSync
|
|
|
58
51
|
function readSync (cache, integrity, opts = {}) {
|
|
59
52
|
const { size } = opts
|
|
60
53
|
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
|
61
|
-
const data = fs.readFileSync(cpath)
|
|
54
|
+
const data = fs.readFileSync(cpath, { encoding: null })
|
|
62
55
|
if (typeof size === 'number' && size !== data.length) {
|
|
63
56
|
throw sizeError(size, data.length)
|
|
64
57
|
}
|
|
@@ -77,16 +70,19 @@ module.exports.readStream = readStream
|
|
|
77
70
|
function readStream (cache, integrity, opts = {}) {
|
|
78
71
|
const { size } = opts
|
|
79
72
|
const stream = new Pipeline()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
// Set all this up to run on the stream and then just return the stream
|
|
74
|
+
Promise.resolve().then(async () => {
|
|
75
|
+
const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => {
|
|
76
|
+
// just stat to ensure it exists
|
|
77
|
+
const stat = await fs.stat(cpath)
|
|
78
|
+
return { stat, cpath, sri }
|
|
79
|
+
})
|
|
84
80
|
if (typeof size === 'number' && size !== stat.size) {
|
|
85
81
|
return stream.emit('error', sizeError(size, stat.size))
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
readPipeline(cpath, stat.size, sri, stream)
|
|
89
|
-
}
|
|
84
|
+
return readPipeline(cpath, stat.size, sri, stream)
|
|
85
|
+
}).catch(err => stream.emit('error', err))
|
|
90
86
|
|
|
91
87
|
return stream
|
|
92
88
|
}
|
|
@@ -96,7 +92,7 @@ module.exports.copy.sync = copySync
|
|
|
96
92
|
|
|
97
93
|
function copy (cache, integrity, dest) {
|
|
98
94
|
return withContentSri(cache, integrity, (cpath, sri) => {
|
|
99
|
-
return copyFile(cpath, dest)
|
|
95
|
+
return fs.copyFile(cpath, dest)
|
|
100
96
|
})
|
|
101
97
|
}
|
|
102
98
|
|
|
@@ -108,14 +104,17 @@ function copySync (cache, integrity, dest) {
|
|
|
108
104
|
|
|
109
105
|
module.exports.hasContent = hasContent
|
|
110
106
|
|
|
111
|
-
function hasContent (cache, integrity) {
|
|
107
|
+
async function hasContent (cache, integrity) {
|
|
112
108
|
if (!integrity) {
|
|
113
|
-
return
|
|
109
|
+
return false
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
|
|
112
|
+
try {
|
|
113
|
+
return await withContentSri(cache, integrity, async (cpath, sri) => {
|
|
114
|
+
const stat = await fs.stat(cpath)
|
|
115
|
+
return { size: stat.size, sri, stat }
|
|
116
|
+
})
|
|
117
|
+
} catch (err) {
|
|
119
118
|
if (err.code === 'ENOENT') {
|
|
120
119
|
return false
|
|
121
120
|
}
|
|
@@ -128,7 +127,7 @@ function hasContent (cache, integrity) {
|
|
|
128
127
|
return false
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
|
-
}
|
|
130
|
+
}
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
module.exports.hasContent.sync = hasContentSync
|
|
@@ -140,7 +139,7 @@ function hasContentSync (cache, integrity) {
|
|
|
140
139
|
|
|
141
140
|
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
|
142
141
|
try {
|
|
143
|
-
const stat = fs.
|
|
142
|
+
const stat = fs.statSync(cpath)
|
|
144
143
|
return { size: stat.size, sri, stat }
|
|
145
144
|
} catch (err) {
|
|
146
145
|
if (err.code === 'ENOENT') {
|
|
@@ -159,61 +158,47 @@ function hasContentSync (cache, integrity) {
|
|
|
159
158
|
})
|
|
160
159
|
}
|
|
161
160
|
|
|
162
|
-
function withContentSri (cache, integrity, fn) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (result) {
|
|
193
|
-
return result
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Throw the No matching content found error
|
|
197
|
-
const enoentError = results.find((r) => r.code === 'ENOENT')
|
|
198
|
-
if (enoentError) {
|
|
199
|
-
throw enoentError
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Throw generic error
|
|
203
|
-
throw results.find((r) => r instanceof Error)
|
|
204
|
-
})
|
|
161
|
+
async function withContentSri (cache, integrity, fn) {
|
|
162
|
+
const sri = ssri.parse(integrity)
|
|
163
|
+
// If `integrity` has multiple entries, pick the first digest
|
|
164
|
+
// with available local data.
|
|
165
|
+
const algo = sri.pickAlgorithm()
|
|
166
|
+
const digests = sri[algo]
|
|
167
|
+
|
|
168
|
+
if (digests.length <= 1) {
|
|
169
|
+
const cpath = contentPath(cache, digests[0])
|
|
170
|
+
return fn(cpath, digests[0])
|
|
171
|
+
} else {
|
|
172
|
+
// Can't use race here because a generic error can happen before
|
|
173
|
+
// a ENOENT error, and can happen before a valid result
|
|
174
|
+
const results = await Promise.all(digests.map(async (meta) => {
|
|
175
|
+
try {
|
|
176
|
+
return await withContentSri(cache, meta, fn)
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err.code === 'ENOENT') {
|
|
179
|
+
return Object.assign(
|
|
180
|
+
new Error('No matching content found for ' + sri.toString()),
|
|
181
|
+
{ code: 'ENOENT' }
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
return err
|
|
185
|
+
}
|
|
186
|
+
}))
|
|
187
|
+
// Return the first non error if it is found
|
|
188
|
+
const result = results.find((r) => !(r instanceof Error))
|
|
189
|
+
if (result) {
|
|
190
|
+
return result
|
|
205
191
|
}
|
|
206
|
-
}
|
|
207
192
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.catch(reject)
|
|
213
|
-
} catch (err) {
|
|
214
|
-
reject(err)
|
|
193
|
+
// Throw the No matching content found error
|
|
194
|
+
const enoentError = results.find((r) => r.code === 'ENOENT')
|
|
195
|
+
if (enoentError) {
|
|
196
|
+
throw enoentError
|
|
215
197
|
}
|
|
216
|
-
|
|
198
|
+
|
|
199
|
+
// Throw generic error
|
|
200
|
+
throw results.find((r) => r instanceof Error)
|
|
201
|
+
}
|
|
217
202
|
}
|
|
218
203
|
|
|
219
204
|
function withContentSriSync (cache, integrity, fn) {
|
package/lib/content/rm.js
CHANGED
|
@@ -8,13 +8,13 @@ const rimraf = util.promisify(require('rimraf'))
|
|
|
8
8
|
|
|
9
9
|
module.exports = rm
|
|
10
10
|
|
|
11
|
-
function rm (cache, integrity) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
11
|
+
async function rm (cache, integrity) {
|
|
12
|
+
const content = await hasContent(cache, integrity)
|
|
13
|
+
// ~pretty~ sure we can't end up with a content lacking sri, but be safe
|
|
14
|
+
if (content && content.sri) {
|
|
15
|
+
await rimraf(contentPath(cache, content.sri))
|
|
16
|
+
return true
|
|
17
|
+
} else {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
20
|
}
|
package/lib/content/write.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const events = require('events')
|
|
3
4
|
const util = require('util')
|
|
4
5
|
|
|
5
6
|
const contentPath = require('./path')
|
|
6
7
|
const fixOwner = require('../util/fix-owner')
|
|
7
|
-
const fs = require('fs')
|
|
8
|
+
const fs = require('@npmcli/fs')
|
|
8
9
|
const moveFile = require('../util/move-file')
|
|
9
10
|
const Minipass = require('minipass')
|
|
10
11
|
const Pipeline = require('minipass-pipeline')
|
|
@@ -15,8 +16,6 @@ const ssri = require('ssri')
|
|
|
15
16
|
const uniqueFilename = require('unique-filename')
|
|
16
17
|
const fsm = require('fs-minipass')
|
|
17
18
|
|
|
18
|
-
const writeFile = util.promisify(fs.writeFile)
|
|
19
|
-
|
|
20
19
|
module.exports = write
|
|
21
20
|
|
|
22
21
|
async function write (cache, data, opts = {}) {
|
|
@@ -36,7 +35,7 @@ async function write (cache, data, opts = {}) {
|
|
|
36
35
|
|
|
37
36
|
const tmp = await makeTmp(cache, opts)
|
|
38
37
|
try {
|
|
39
|
-
await writeFile(tmp.target, data, { flag: 'wx' })
|
|
38
|
+
await fs.writeFile(tmp.target, data, { flag: 'wx' })
|
|
40
39
|
await moveToDestination(tmp, cache, sri, opts)
|
|
41
40
|
return { integrity: sri, size: data.length }
|
|
42
41
|
} finally {
|
|
@@ -81,9 +80,11 @@ class CacacheWriteStream extends Flush {
|
|
|
81
80
|
// defer this one tick by rejecting a promise on it.
|
|
82
81
|
return Promise.reject(e).catch(cb)
|
|
83
82
|
}
|
|
83
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
84
84
|
this.handleContentP.then(
|
|
85
85
|
(res) => {
|
|
86
86
|
res.integrity && this.emit('integrity', res.integrity)
|
|
87
|
+
// eslint-disable-next-line promise/always-return
|
|
87
88
|
res.size !== null && this.emit('size', res.size)
|
|
88
89
|
cb()
|
|
89
90
|
},
|
|
@@ -115,7 +116,21 @@ async function handleContent (inputStream, cache, opts) {
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
function pipeToTmp (inputStream, cache, tmpTarget, opts) {
|
|
119
|
+
async function pipeToTmp (inputStream, cache, tmpTarget, opts) {
|
|
120
|
+
const outStream = new fsm.WriteStream(tmpTarget, {
|
|
121
|
+
flags: 'wx',
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (opts.integrityEmitter) {
|
|
125
|
+
// we need to create these all simultaneously since they can fire in any order
|
|
126
|
+
const [integrity, size] = await Promise.all([
|
|
127
|
+
events.once(opts.integrityEmitter, 'integrity').then(res => res[0]),
|
|
128
|
+
events.once(opts.integrityEmitter, 'size').then(res => res[0]),
|
|
129
|
+
new Pipeline(inputStream, outStream).promise(),
|
|
130
|
+
])
|
|
131
|
+
return { integrity, size }
|
|
132
|
+
}
|
|
133
|
+
|
|
119
134
|
let integrity
|
|
120
135
|
let size
|
|
121
136
|
const hashStream = ssri.integrityStream({
|
|
@@ -130,43 +145,28 @@ function pipeToTmp (inputStream, cache, tmpTarget, opts) {
|
|
|
130
145
|
size = s
|
|
131
146
|
})
|
|
132
147
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// NB: this can throw if the hashStream has a problem with
|
|
138
|
-
// it, and the data is fully written. but pipeToTmp is only
|
|
139
|
-
// called in promisory contexts where that is handled.
|
|
140
|
-
const pipeline = new Pipeline(
|
|
141
|
-
inputStream,
|
|
142
|
-
hashStream,
|
|
143
|
-
outStream
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
return pipeline.promise().then(() => ({ integrity, size }))
|
|
148
|
+
const pipeline = new Pipeline(inputStream, hashStream, outStream)
|
|
149
|
+
await pipeline.promise()
|
|
150
|
+
return { integrity, size }
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
function makeTmp (cache, opts) {
|
|
153
|
+
async function makeTmp (cache, opts) {
|
|
150
154
|
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
|
|
151
|
-
|
|
155
|
+
await fixOwner.mkdirfix(cache, path.dirname(tmpTarget))
|
|
156
|
+
return {
|
|
152
157
|
target: tmpTarget,
|
|
153
158
|
moved: false,
|
|
154
|
-
}
|
|
159
|
+
}
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
function moveToDestination (tmp, cache, sri, opts) {
|
|
162
|
+
async function moveToDestination (tmp, cache, sri, opts) {
|
|
158
163
|
const destination = contentPath(cache, sri)
|
|
159
164
|
const destDir = path.dirname(destination)
|
|
160
165
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
})
|
|
166
|
-
.then(() => {
|
|
167
|
-
tmp.moved = true
|
|
168
|
-
return fixOwner.chownr(cache, destination)
|
|
169
|
-
})
|
|
166
|
+
await fixOwner.mkdirfix(cache, destDir)
|
|
167
|
+
await moveFile(tmp.target, destination)
|
|
168
|
+
tmp.moved = true
|
|
169
|
+
await fixOwner.chownr(cache, destination)
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
function sizeError (expected, found) {
|