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 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']
@@ -1,42 +1,35 @@
1
1
  'use strict'
2
2
 
3
- const util = require('util')
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
- return withContentSri(cache, integrity, (cpath, sri) => {
14
+ const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => {
21
15
  // get size
22
- return lstat(cpath).then(stat => ({ stat, cpath, sri }))
23
- }).then(({ stat, cpath, sri }) => {
24
- if (typeof size === 'number' && stat.size !== size) {
25
- throw sizeError(size, stat.size)
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
- if (stat.size > MAX_SINGLE_READ_SIZE) {
29
- return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
30
- }
23
+ if (stat.size > MAX_SINGLE_READ_SIZE) {
24
+ return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
25
+ }
31
26
 
32
- return readFile(cpath, null).then((data) => {
33
- if (!ssri.checkData(data, sri)) {
34
- throw integrityError(sri, cpath)
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
- return data
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
- withContentSri(cache, integrity, (cpath, sri) => {
81
- // just lstat to ensure it exists
82
- return lstat(cpath).then((stat) => ({ stat, cpath, sri }))
83
- }).then(({ stat, cpath, sri }) => {
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
- }, er => stream.emit('error', er))
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 Promise.resolve(false)
109
+ return false
114
110
  }
115
111
 
116
- return withContentSri(cache, integrity, (cpath, sri) => {
117
- return lstat(cpath).then((stat) => ({ size: stat.size, sri, stat }))
118
- }).catch((err) => {
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.lstatSync(cpath)
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 tryFn = () => {
164
- const sri = ssri.parse(integrity)
165
- // If `integrity` has multiple entries, pick the first digest
166
- // with available local data.
167
- const algo = sri.pickAlgorithm()
168
- const digests = sri[algo]
169
-
170
- if (digests.length <= 1) {
171
- const cpath = contentPath(cache, digests[0])
172
- return fn(cpath, digests[0])
173
- } else {
174
- // Can't use race here because a generic error can happen before
175
- // a ENOENT error, and can happen before a valid result
176
- return Promise
177
- .all(digests.map((meta) => {
178
- return withContentSri(cache, meta, fn)
179
- .catch((err) => {
180
- if (err.code === 'ENOENT') {
181
- return Object.assign(
182
- new Error('No matching content found for ' + sri.toString()),
183
- { code: 'ENOENT' }
184
- )
185
- }
186
- return err
187
- })
188
- }))
189
- .then((results) => {
190
- // Return the first non error if it is found
191
- const result = results.find((r) => !(r instanceof Error))
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
- return new Promise((resolve, reject) => {
209
- try {
210
- tryFn()
211
- .then(resolve)
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
- return hasContent(cache, integrity).then((content) => {
13
- // ~pretty~ sure we can't end up with a content lacking sri, but be safe
14
- if (content && content.sri) {
15
- return rimraf(contentPath(cache, content.sri)).then(() => true)
16
- } else {
17
- return false
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
  }
@@ -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 outStream = new fsm.WriteStream(tmpTarget, {
134
- flags: 'wx',
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
- return fixOwner.mkdirfix(cache, path.dirname(tmpTarget)).then(() => ({
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
- return fixOwner
162
- .mkdirfix(cache, destDir)
163
- .then(() => {
164
- return moveFile(tmp.target, destination)
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) {