cacache 16.0.7 → 16.1.0

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.lstat(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 lstat to ensure it exists
77
+ const stat = await fs.lstat(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
84
  readPipeline(cpath, stat.size, sri, stream)
89
- }, er => stream.emit('error', er))
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.lstat(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
@@ -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 {
@@ -115,7 +114,21 @@ async function handleContent (inputStream, cache, opts) {
115
114
  }
116
115
  }
117
116
 
118
- function pipeToTmp (inputStream, cache, tmpTarget, opts) {
117
+ async function pipeToTmp (inputStream, cache, tmpTarget, opts) {
118
+ const outStream = new fsm.WriteStream(tmpTarget, {
119
+ flags: 'wx',
120
+ })
121
+
122
+ if (opts.integrityEmitter) {
123
+ // we need to create these all simultaneously since they can fire in any order
124
+ const [integrity, size] = await Promise.all([
125
+ events.once(opts.integrityEmitter, 'integrity').then(res => res[0]),
126
+ events.once(opts.integrityEmitter, 'size').then(res => res[0]),
127
+ new Pipeline(inputStream, outStream).promise(),
128
+ ])
129
+ return { integrity, size }
130
+ }
131
+
119
132
  let integrity
120
133
  let size
121
134
  const hashStream = ssri.integrityStream({
@@ -130,43 +143,28 @@ function pipeToTmp (inputStream, cache, tmpTarget, opts) {
130
143
  size = s
131
144
  })
132
145
 
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 }))
146
+ const pipeline = new Pipeline(inputStream, hashStream, outStream)
147
+ await pipeline.promise()
148
+ return { integrity, size }
147
149
  }
148
150
 
149
- function makeTmp (cache, opts) {
151
+ async function makeTmp (cache, opts) {
150
152
  const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
151
- return fixOwner.mkdirfix(cache, path.dirname(tmpTarget)).then(() => ({
153
+ await fixOwner.mkdirfix(cache, path.dirname(tmpTarget))
154
+ return {
152
155
  target: tmpTarget,
153
156
  moved: false,
154
- }))
157
+ }
155
158
  }
156
159
 
157
- function moveToDestination (tmp, cache, sri, opts) {
160
+ async function moveToDestination (tmp, cache, sri, opts) {
158
161
  const destination = contentPath(cache, sri)
159
162
  const destDir = path.dirname(destination)
160
163
 
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
- })
164
+ await fixOwner.mkdirfix(cache, destDir)
165
+ await moveFile(tmp.target, destination)
166
+ tmp.moved = true
167
+ await fixOwner.chownr(cache, destination)
170
168
  }
171
169
 
172
170
  function sizeError (expected, found) {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const util = require('util')
4
4
  const crypto = require('crypto')
5
- const fs = require('fs')
5
+ const fs = require('@npmcli/fs')
6
6
  const Minipass = require('minipass')
7
7
  const path = require('path')
8
8
  const ssri = require('ssri')
@@ -17,11 +17,6 @@ const _rimraf = require('rimraf')
17
17
  const rimraf = util.promisify(_rimraf)
18
18
  rimraf.sync = _rimraf.sync
19
19
 
20
- const appendFile = util.promisify(fs.appendFile)
21
- const readFile = util.promisify(fs.readFile)
22
- const readdir = util.promisify(fs.readdir)
23
- const writeFile = util.promisify(fs.writeFile)
24
-
25
20
  module.exports.NotFoundError = class NotFoundError extends Error {
26
21
  constructor (cache, key) {
27
22
  super(`No cache entry for ${key} found in ${cache}`)
@@ -85,7 +80,7 @@ async function compact (cache, key, matchFn, opts = {}) {
85
80
  }
86
81
 
87
82
  const write = async (tmp) => {
88
- await writeFile(tmp.target, newIndex, { flag: 'wx' })
83
+ await fs.writeFile(tmp.target, newIndex, { flag: 'wx' })
89
84
  await fixOwner.mkdirfix(cache, path.dirname(bucket))
90
85
  // we use @npmcli/move-file directly here because we
91
86
  // want to overwrite the existing file
@@ -118,7 +113,7 @@ async function compact (cache, key, matchFn, opts = {}) {
118
113
 
119
114
  module.exports.insert = insert
120
115
 
121
- function insert (cache, key, integrity, opts = {}) {
116
+ async function insert (cache, key, integrity, opts = {}) {
122
117
  const { metadata, size } = opts
123
118
  const bucket = bucketPath(cache, key)
124
119
  const entry = {
@@ -128,36 +123,32 @@ function insert (cache, key, integrity, opts = {}) {
128
123
  size,
129
124
  metadata,
130
125
  }
131
- return fixOwner
132
- .mkdirfix(cache, path.dirname(bucket))
133
- .then(() => {
134
- const stringified = JSON.stringify(entry)
135
- // NOTE - Cleverness ahoy!
136
- //
137
- // This works because it's tremendously unlikely for an entry to corrupt
138
- // another while still preserving the string length of the JSON in
139
- // question. So, we just slap the length in there and verify it on read.
140
- //
141
- // Thanks to @isaacs for the whiteboarding session that ended up with
142
- // this.
143
- return appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
144
- })
145
- .then(() => fixOwner.chownr(cache, bucket))
146
- .catch((err) => {
147
- if (err.code === 'ENOENT') {
148
- return undefined
149
- }
126
+ try {
127
+ await fixOwner.mkdirfix(cache, path.dirname(bucket))
128
+ const stringified = JSON.stringify(entry)
129
+ // NOTE - Cleverness ahoy!
130
+ //
131
+ // This works because it's tremendously unlikely for an entry to corrupt
132
+ // another while still preserving the string length of the JSON in
133
+ // question. So, we just slap the length in there and verify it on read.
134
+ //
135
+ // Thanks to @isaacs for the whiteboarding session that ended up with
136
+ // this.
137
+ await fs.appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
138
+ await fixOwner.chownr(cache, bucket)
139
+ } catch (err) {
140
+ if (err.code === 'ENOENT') {
141
+ return undefined
142
+ }
150
143
 
151
- throw err
152
- // There's a class of race conditions that happen when things get deleted
153
- // during fixOwner, or between the two mkdirfix/chownr calls.
154
- //
155
- // It's perfectly fine to just not bother in those cases and lie
156
- // that the index entry was written. Because it's a cache.
157
- })
158
- .then(() => {
159
- return formatEntry(cache, entry)
160
- })
144
+ throw err
145
+ // There's a class of race conditions that happen when things get deleted
146
+ // during fixOwner, or between the two mkdirfix/chownr calls.
147
+ //
148
+ // It's perfectly fine to just not bother in those cases and lie
149
+ // that the index entry was written. Because it's a cache.
150
+ }
151
+ return formatEntry(cache, entry)
161
152
  }
162
153
 
163
154
  module.exports.insert.sync = insertSync
@@ -187,25 +178,24 @@ function insertSync (cache, key, integrity, opts = {}) {
187
178
 
188
179
  module.exports.find = find
189
180
 
190
- function find (cache, key) {
181
+ async function find (cache, key) {
191
182
  const bucket = bucketPath(cache, key)
192
- return bucketEntries(bucket)
193
- .then((entries) => {
194
- return entries.reduce((latest, next) => {
195
- if (next && next.key === key) {
196
- return formatEntry(cache, next)
197
- } else {
198
- return latest
199
- }
200
- }, null)
201
- })
202
- .catch((err) => {
203
- if (err.code === 'ENOENT') {
204
- return null
183
+ try {
184
+ const entries = await bucketEntries(bucket)
185
+ return entries.reduce((latest, next) => {
186
+ if (next && next.key === key) {
187
+ return formatEntry(cache, next)
205
188
  } else {
206
- throw err
189
+ return latest
207
190
  }
208
- })
191
+ }, null)
192
+ } catch (err) {
193
+ if (err.code === 'ENOENT') {
194
+ return null
195
+ } else {
196
+ throw err
197
+ }
198
+ }
209
199
  }
210
200
 
211
201
  module.exports.find.sync = findSync
@@ -257,67 +247,64 @@ function lsStream (cache) {
257
247
  const indexDir = bucketDir(cache)
258
248
  const stream = new Minipass({ objectMode: true })
259
249
 
260
- readdirOrEmpty(indexDir).then(buckets => Promise.all(
261
- buckets.map(bucket => {
250
+ // Set all this up to run on the stream and then just return the stream
251
+ Promise.resolve().then(async () => {
252
+ const buckets = await readdirOrEmpty(indexDir)
253
+ await Promise.all(buckets.map(async (bucket) => {
262
254
  const bucketPath = path.join(indexDir, bucket)
263
- return readdirOrEmpty(bucketPath).then(subbuckets => Promise.all(
264
- subbuckets.map(subbucket => {
265
- const subbucketPath = path.join(bucketPath, subbucket)
266
-
267
- // "/cachename/<bucket 0xFF>/<bucket 0xFF>./*"
268
- return readdirOrEmpty(subbucketPath).then(entries => Promise.all(
269
- entries.map(entry => {
270
- const entryPath = path.join(subbucketPath, entry)
271
- return bucketEntries(entryPath).then(entries =>
272
- // using a Map here prevents duplicate keys from
273
- // showing up twice, I guess?
274
- entries.reduce((acc, entry) => {
275
- acc.set(entry.key, entry)
276
- return acc
277
- }, new Map())
278
- ).then(reduced => {
279
- // reduced is a map of key => entry
280
- for (const entry of reduced.values()) {
281
- const formatted = formatEntry(cache, entry)
282
- if (formatted) {
283
- stream.write(formatted)
284
- }
285
- }
286
- }).catch(err => {
287
- if (err.code === 'ENOENT') {
288
- return undefined
289
- }
290
- throw err
291
- })
292
- })
293
- ))
294
- })
295
- ))
296
- })
297
- ))
298
- .then(
299
- () => stream.end(),
300
- err => stream.emit('error', err)
301
- )
255
+ const subbuckets = await readdirOrEmpty(bucketPath)
256
+ await Promise.all(subbuckets.map(async (subbucket) => {
257
+ const subbucketPath = path.join(bucketPath, subbucket)
258
+
259
+ // "/cachename/<bucket 0xFF>/<bucket 0xFF>./*"
260
+ const subbucketEntries = await readdirOrEmpty(subbucketPath)
261
+ await Promise.all(subbucketEntries.map(async (entry) => {
262
+ const entryPath = path.join(subbucketPath, entry)
263
+ try {
264
+ const entries = await bucketEntries(entryPath)
265
+ // using a Map here prevents duplicate keys from showing up
266
+ // twice, I guess?
267
+ const reduced = entries.reduce((acc, entry) => {
268
+ acc.set(entry.key, entry)
269
+ return acc
270
+ }, new Map())
271
+ // reduced is a map of key => entry
272
+ for (const entry of reduced.values()) {
273
+ const formatted = formatEntry(cache, entry)
274
+ if (formatted) {
275
+ stream.write(formatted)
276
+ }
277
+ }
278
+ } catch (err) {
279
+ if (err.code === 'ENOENT') {
280
+ return undefined
281
+ }
282
+ throw err
283
+ }
284
+ }))
285
+ }))
286
+ }))
287
+ stream.end()
288
+ }).catch(err => stream.emit('error', err))
302
289
 
303
290
  return stream
304
291
  }
305
292
 
306
293
  module.exports.ls = ls
307
294
 
308
- function ls (cache) {
309
- return lsStream(cache).collect().then(entries =>
310
- entries.reduce((acc, xs) => {
311
- acc[xs.key] = xs
312
- return acc
313
- }, {})
314
- )
295
+ async function ls (cache) {
296
+ const entries = await lsStream(cache).collect()
297
+ return entries.reduce((acc, xs) => {
298
+ acc[xs.key] = xs
299
+ return acc
300
+ }, {})
315
301
  }
316
302
 
317
303
  module.exports.bucketEntries = bucketEntries
318
304
 
319
- function bucketEntries (bucket, filter) {
320
- return readFile(bucket, 'utf8').then((data) => _bucketEntries(data, filter))
305
+ async function bucketEntries (bucket, filter) {
306
+ const data = await fs.readFile(bucket, 'utf8')
307
+ return _bucketEntries(data, filter)
321
308
  }
322
309
 
323
310
  module.exports.bucketEntries.sync = bucketEntriesSync
@@ -406,7 +393,7 @@ function formatEntry (cache, entry, keepAll) {
406
393
  }
407
394
 
408
395
  function readdirOrEmpty (dir) {
409
- return readdir(dir).catch((err) => {
396
+ return fs.readdir(dir).catch((err) => {
410
397
  if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
411
398
  return []
412
399
  }