cacache 12.0.4 → 15.0.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.
@@ -1,94 +1,87 @@
1
1
  'use strict'
2
2
 
3
- const BB = require('bluebird')
3
+ const util = require('util')
4
4
 
5
- const contentPath = require('./content/path')
6
5
  const crypto = require('crypto')
7
- const figgyPudding = require('figgy-pudding')
8
- const fixOwner = require('./util/fix-owner')
9
- const fs = require('graceful-fs')
10
- const hashToSegments = require('./util/hash-to-segments')
11
- const ms = require('mississippi')
6
+ const fs = require('fs')
7
+ const Minipass = require('minipass')
12
8
  const path = require('path')
13
9
  const ssri = require('ssri')
14
- const Y = require('./util/y.js')
15
-
10
+ const contentPath = require('./content/path')
11
+ const fixOwner = require('./util/fix-owner')
12
+ const hashToSegments = require('./util/hash-to-segments')
16
13
  const indexV = require('../package.json')['cache-version'].index
17
14
 
18
- const appendFileAsync = BB.promisify(fs.appendFile)
19
- const readFileAsync = BB.promisify(fs.readFile)
20
- const readdirAsync = BB.promisify(fs.readdir)
21
- const concat = ms.concat
22
- const from = ms.from
15
+ const appendFile = util.promisify(fs.appendFile)
16
+ const readFile = util.promisify(fs.readFile)
17
+ const readdir = util.promisify(fs.readdir)
23
18
 
24
19
  module.exports.NotFoundError = class NotFoundError extends Error {
25
20
  constructor (cache, key) {
26
- super(Y`No cache entry for \`${key}\` found in \`${cache}\``)
21
+ super(`No cache entry for ${key} found in ${cache}`)
27
22
  this.code = 'ENOENT'
28
23
  this.cache = cache
29
24
  this.key = key
30
25
  }
31
26
  }
32
27
 
33
- const IndexOpts = figgyPudding({
34
- metadata: {},
35
- size: {}
36
- })
37
-
38
28
  module.exports.insert = insert
39
- function insert (cache, key, integrity, opts) {
40
- opts = IndexOpts(opts)
29
+
30
+ function insert (cache, key, integrity, opts = {}) {
31
+ const { metadata, size } = opts
41
32
  const bucket = bucketPath(cache, key)
42
33
  const entry = {
43
34
  key,
44
35
  integrity: integrity && ssri.stringify(integrity),
45
36
  time: Date.now(),
46
- size: opts.size,
47
- metadata: opts.metadata
37
+ size,
38
+ metadata
48
39
  }
49
- return fixOwner.mkdirfix(
50
- cache, path.dirname(bucket)
51
- ).then(() => {
52
- const stringified = JSON.stringify(entry)
53
- // NOTE - Cleverness ahoy!
54
- //
55
- // This works because it's tremendously unlikely for an entry to corrupt
56
- // another while still preserving the string length of the JSON in
57
- // question. So, we just slap the length in there and verify it on read.
58
- //
59
- // Thanks to @isaacs for the whiteboarding session that ended up with this.
60
- return appendFileAsync(
61
- bucket, `\n${hashEntry(stringified)}\t${stringified}`
62
- )
63
- }).then(
64
- () => fixOwner.chownr(cache, bucket)
65
- ).catch({ code: 'ENOENT' }, () => {
66
- // There's a class of race conditions that happen when things get deleted
67
- // during fixOwner, or between the two mkdirfix/chownr calls.
68
- //
69
- // It's perfectly fine to just not bother in those cases and lie
70
- // that the index entry was written. Because it's a cache.
71
- }).then(() => {
72
- return formatEntry(cache, entry)
73
- })
40
+ return fixOwner
41
+ .mkdirfix(cache, path.dirname(bucket))
42
+ .then(() => {
43
+ const stringified = JSON.stringify(entry)
44
+ // NOTE - Cleverness ahoy!
45
+ //
46
+ // This works because it's tremendously unlikely for an entry to corrupt
47
+ // another while still preserving the string length of the JSON in
48
+ // question. So, we just slap the length in there and verify it on read.
49
+ //
50
+ // Thanks to @isaacs for the whiteboarding session that ended up with this.
51
+ return appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
52
+ })
53
+ .then(() => fixOwner.chownr(cache, bucket))
54
+ .catch((err) => {
55
+ if (err.code === 'ENOENT') {
56
+ return undefined
57
+ }
58
+ throw err
59
+ // There's a class of race conditions that happen when things get deleted
60
+ // during fixOwner, or between the two mkdirfix/chownr calls.
61
+ //
62
+ // It's perfectly fine to just not bother in those cases and lie
63
+ // that the index entry was written. Because it's a cache.
64
+ })
65
+ .then(() => {
66
+ return formatEntry(cache, entry)
67
+ })
74
68
  }
75
69
 
76
70
  module.exports.insert.sync = insertSync
77
- function insertSync (cache, key, integrity, opts) {
78
- opts = IndexOpts(opts)
71
+
72
+ function insertSync (cache, key, integrity, opts = {}) {
73
+ const { metadata, size } = opts
79
74
  const bucket = bucketPath(cache, key)
80
75
  const entry = {
81
76
  key,
82
77
  integrity: integrity && ssri.stringify(integrity),
83
78
  time: Date.now(),
84
- size: opts.size,
85
- metadata: opts.metadata
79
+ size,
80
+ metadata
86
81
  }
87
82
  fixOwner.mkdirfix.sync(cache, path.dirname(bucket))
88
83
  const stringified = JSON.stringify(entry)
89
- fs.appendFileSync(
90
- bucket, `\n${hashEntry(stringified)}\t${stringified}`
91
- )
84
+ fs.appendFileSync(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
92
85
  try {
93
86
  fixOwner.chownr.sync(cache, bucket)
94
87
  } catch (err) {
@@ -100,26 +93,30 @@ function insertSync (cache, key, integrity, opts) {
100
93
  }
101
94
 
102
95
  module.exports.find = find
96
+
103
97
  function find (cache, key) {
104
98
  const bucket = bucketPath(cache, key)
105
- return bucketEntries(bucket).then(entries => {
106
- return entries.reduce((latest, next) => {
107
- if (next && next.key === key) {
108
- return formatEntry(cache, next)
99
+ return bucketEntries(bucket)
100
+ .then((entries) => {
101
+ return entries.reduce((latest, next) => {
102
+ if (next && next.key === key) {
103
+ return formatEntry(cache, next)
104
+ } else {
105
+ return latest
106
+ }
107
+ }, null)
108
+ })
109
+ .catch((err) => {
110
+ if (err.code === 'ENOENT') {
111
+ return null
109
112
  } else {
110
- return latest
113
+ throw err
111
114
  }
112
- }, null)
113
- }).catch(err => {
114
- if (err.code === 'ENOENT') {
115
- return null
116
- } else {
117
- throw err
118
- }
119
- })
115
+ })
120
116
  }
121
117
 
122
118
  module.exports.find.sync = findSync
119
+
123
120
  function findSync (cache, key) {
124
121
  const bucket = bucketPath(cache, key)
125
122
  try {
@@ -140,70 +137,80 @@ function findSync (cache, key) {
140
137
  }
141
138
 
142
139
  module.exports.delete = del
140
+
143
141
  function del (cache, key, opts) {
144
142
  return insert(cache, key, null, opts)
145
143
  }
146
144
 
147
145
  module.exports.delete.sync = delSync
146
+
148
147
  function delSync (cache, key, opts) {
149
148
  return insertSync(cache, key, null, opts)
150
149
  }
151
150
 
152
151
  module.exports.lsStream = lsStream
152
+
153
153
  function lsStream (cache) {
154
154
  const indexDir = bucketDir(cache)
155
- const stream = from.obj()
156
-
157
- // "/cachename/*"
158
- readdirOrEmpty(indexDir).map(bucket => {
159
- const bucketPath = path.join(indexDir, bucket)
160
-
161
- // "/cachename/<bucket 0xFF>/*"
162
- return readdirOrEmpty(bucketPath).map(subbucket => {
163
- const subbucketPath = path.join(bucketPath, subbucket)
164
-
165
- // "/cachename/<bucket 0xFF>/<bucket 0xFF>/*"
166
- return readdirOrEmpty(subbucketPath).map(entry => {
167
- const getKeyToEntry = bucketEntries(
168
- path.join(subbucketPath, entry)
169
- ).reduce((acc, entry) => {
170
- acc.set(entry.key, entry)
171
- return acc
172
- }, new Map())
173
-
174
- return getKeyToEntry.then(reduced => {
175
- for (let entry of reduced.values()) {
176
- const formatted = formatEntry(cache, entry)
177
- formatted && stream.push(formatted)
178
- }
179
- }).catch({ code: 'ENOENT' }, nop)
180
- })
155
+ const stream = new Minipass({ objectMode: true })
156
+
157
+ readdirOrEmpty(indexDir).then(buckets => Promise.all(
158
+ buckets.map(bucket => {
159
+ const bucketPath = path.join(indexDir, bucket)
160
+ return readdirOrEmpty(bucketPath).then(subbuckets => Promise.all(
161
+ subbuckets.map(subbucket => {
162
+ const subbucketPath = path.join(bucketPath, subbucket)
163
+
164
+ // "/cachename/<bucket 0xFF>/<bucket 0xFF>./*"
165
+ return readdirOrEmpty(subbucketPath).then(entries => Promise.all(
166
+ entries.map(entry => {
167
+ const entryPath = path.join(subbucketPath, entry)
168
+ return bucketEntries(entryPath).then(entries =>
169
+ // using a Map here prevents duplicate keys from
170
+ // showing up twice, I guess?
171
+ entries.reduce((acc, entry) => {
172
+ acc.set(entry.key, entry)
173
+ return acc
174
+ }, new Map())
175
+ ).then(reduced => {
176
+ // reduced is a map of key => entry
177
+ for (const entry of reduced.values()) {
178
+ const formatted = formatEntry(cache, entry)
179
+ if (formatted) {
180
+ stream.write(formatted)
181
+ }
182
+ }
183
+ }).catch(err => {
184
+ if (err.code === 'ENOENT') { return undefined }
185
+ throw err
186
+ })
187
+ })
188
+ ))
189
+ })
190
+ ))
181
191
  })
182
- }).then(() => {
183
- stream.push(null)
184
- }, err => {
185
- stream.emit('error', err)
186
- })
192
+ ))
193
+ .then(
194
+ () => stream.end(),
195
+ err => stream.emit('error', err)
196
+ )
187
197
 
188
198
  return stream
189
199
  }
190
200
 
191
201
  module.exports.ls = ls
202
+
192
203
  function ls (cache) {
193
- return BB.fromNode(cb => {
194
- lsStream(cache).on('error', cb).pipe(concat(entries => {
195
- cb(null, entries.reduce((acc, xs) => {
196
- acc[xs.key] = xs
197
- return acc
198
- }, {}))
199
- }))
200
- })
204
+ return lsStream(cache).collect().then(entries =>
205
+ entries.reduce((acc, xs) => {
206
+ acc[xs.key] = xs
207
+ return acc
208
+ }, {})
209
+ )
201
210
  }
202
211
 
203
212
  function bucketEntries (bucket, filter) {
204
- return readFileAsync(
205
- bucket, 'utf8'
206
- ).then(data => _bucketEntries(data, filter))
213
+ return readFile(bucket, 'utf8').then((data) => _bucketEntries(data, filter))
207
214
  }
208
215
 
209
216
  function bucketEntriesSync (bucket, filter) {
@@ -212,9 +219,11 @@ function bucketEntriesSync (bucket, filter) {
212
219
  }
213
220
 
214
221
  function _bucketEntries (data, filter) {
215
- let entries = []
216
- data.split('\n').forEach(entry => {
217
- if (!entry) { return }
222
+ const entries = []
223
+ data.split('\n').forEach((entry) => {
224
+ if (!entry) {
225
+ return
226
+ }
218
227
  const pieces = entry.split('\t')
219
228
  if (!pieces[1] || hashEntry(pieces[1]) !== pieces[0]) {
220
229
  // Hash is no good! Corruption or malice? Doesn't matter!
@@ -235,25 +244,30 @@ function _bucketEntries (data, filter) {
235
244
  return entries
236
245
  }
237
246
 
238
- module.exports._bucketDir = bucketDir
247
+ module.exports.bucketDir = bucketDir
248
+
239
249
  function bucketDir (cache) {
240
250
  return path.join(cache, `index-v${indexV}`)
241
251
  }
242
252
 
243
- module.exports._bucketPath = bucketPath
253
+ module.exports.bucketPath = bucketPath
254
+
244
255
  function bucketPath (cache, key) {
245
256
  const hashed = hashKey(key)
246
- return path.join.apply(path, [bucketDir(cache)].concat(
247
- hashToSegments(hashed)
248
- ))
257
+ return path.join.apply(
258
+ path,
259
+ [bucketDir(cache)].concat(hashToSegments(hashed))
260
+ )
249
261
  }
250
262
 
251
- module.exports._hashKey = hashKey
263
+ module.exports.hashKey = hashKey
264
+
252
265
  function hashKey (key) {
253
266
  return hash(key, 'sha256')
254
267
  }
255
268
 
256
- module.exports._hashEntry = hashEntry
269
+ module.exports.hashEntry = hashEntry
270
+
257
271
  function hashEntry (str) {
258
272
  return hash(str, 'sha1')
259
273
  }
@@ -267,7 +281,9 @@ function hash (str, digest) {
267
281
 
268
282
  function formatEntry (cache, entry) {
269
283
  // Treat null digests as deletions. They'll shadow any previous entries.
270
- if (!entry.integrity) { return null }
284
+ if (!entry.integrity) {
285
+ return null
286
+ }
271
287
  return {
272
288
  key: entry.key,
273
289
  integrity: entry.integrity,
@@ -279,10 +295,11 @@ function formatEntry (cache, entry) {
279
295
  }
280
296
 
281
297
  function readdirOrEmpty (dir) {
282
- return readdirAsync(dir)
283
- .catch({ code: 'ENOENT' }, () => [])
284
- .catch({ code: 'ENOTDIR' }, () => [])
285
- }
298
+ return readdir(dir).catch((err) => {
299
+ if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
300
+ return []
301
+ }
286
302
 
287
- function nop () {
303
+ throw err
304
+ })
288
305
  }
@@ -5,19 +5,14 @@ const LRU = require('lru-cache')
5
5
  const MAX_SIZE = 50 * 1024 * 1024 // 50MB
6
6
  const MAX_AGE = 3 * 60 * 1000
7
7
 
8
- let MEMOIZED = new LRU({
8
+ const MEMOIZED = new LRU({
9
9
  max: MAX_SIZE,
10
10
  maxAge: MAX_AGE,
11
- length: (entry, key) => {
12
- if (key.startsWith('key:')) {
13
- return entry.data.length
14
- } else if (key.startsWith('digest:')) {
15
- return entry.length
16
- }
17
- }
11
+ length: (entry, key) => key.startsWith('key:') ? entry.data.length : entry.length
18
12
  })
19
13
 
20
14
  module.exports.clearMemoized = clearMemoized
15
+
21
16
  function clearMemoized () {
22
17
  const old = {}
23
18
  MEMOIZED.forEach((v, k) => {
@@ -28,22 +23,26 @@ function clearMemoized () {
28
23
  }
29
24
 
30
25
  module.exports.put = put
26
+
31
27
  function put (cache, entry, data, opts) {
32
28
  pickMem(opts).set(`key:${cache}:${entry.key}`, { entry, data })
33
29
  putDigest(cache, entry.integrity, data, opts)
34
30
  }
35
31
 
36
32
  module.exports.put.byDigest = putDigest
33
+
37
34
  function putDigest (cache, integrity, data, opts) {
38
35
  pickMem(opts).set(`digest:${cache}:${integrity}`, data)
39
36
  }
40
37
 
41
38
  module.exports.get = get
39
+
42
40
  function get (cache, key, opts) {
43
41
  return pickMem(opts).get(`key:${cache}:${key}`)
44
42
  }
45
43
 
46
44
  module.exports.get.byDigest = getDigest
45
+
47
46
  function getDigest (cache, integrity, opts) {
48
47
  return pickMem(opts).get(`digest:${cache}:${integrity}`)
49
48
  }
@@ -52,8 +51,14 @@ class ObjProxy {
52
51
  constructor (obj) {
53
52
  this.obj = obj
54
53
  }
55
- get (key) { return this.obj[key] }
56
- set (key, val) { this.obj[key] = val }
54
+
55
+ get (key) {
56
+ return this.obj[key]
57
+ }
58
+
59
+ set (key, val) {
60
+ this.obj[key] = val
61
+ }
57
62
  }
58
63
 
59
64
  function pickMem (opts) {
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ module.exports.disposer = disposer
4
+
5
+ function disposer (creatorFn, disposerFn, fn) {
6
+ const runDisposer = (resource, result, shouldThrow = false) => {
7
+ return disposerFn(resource)
8
+ .then(
9
+ // disposer resolved, do something with original fn's promise
10
+ () => {
11
+ if (shouldThrow) {
12
+ throw result
13
+ }
14
+ return result
15
+ },
16
+ // Disposer fn failed, crash process
17
+ (err) => {
18
+ throw err
19
+ // Or process.exit?
20
+ })
21
+ }
22
+
23
+ return creatorFn
24
+ .then((resource) => {
25
+ // fn(resource) can throw, so wrap in a promise here
26
+ return Promise.resolve().then(() => fn(resource))
27
+ .then((result) => runDisposer(resource, result))
28
+ .catch((err) => runDisposer(resource, err, true))
29
+ })
30
+ }
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const BB = require('bluebird')
3
+ const util = require('util')
4
4
 
5
- const chownr = BB.promisify(require('chownr'))
6
- const mkdirp = BB.promisify(require('mkdirp'))
5
+ const chownr = util.promisify(require('chownr'))
6
+ const mkdirp = require('mkdirp')
7
7
  const inflight = require('promise-inflight')
8
8
  const inferOwner = require('infer-owner')
9
9
 
@@ -32,19 +32,20 @@ const getSelf = () => {
32
32
  }
33
33
 
34
34
  module.exports.chownr = fixOwner
35
+
35
36
  function fixOwner (cache, filepath) {
36
37
  if (!process.getuid) {
37
38
  // This platform doesn't need ownership fixing
38
- return BB.resolve()
39
+ return Promise.resolve()
39
40
  }
40
41
 
41
42
  getSelf()
42
43
  if (self.uid !== 0) {
43
44
  // almost certainly can't chown anyway
44
- return BB.resolve()
45
+ return Promise.resolve()
45
46
  }
46
47
 
47
- return BB.resolve(inferOwner(cache)).then(owner => {
48
+ return Promise.resolve(inferOwner(cache)).then((owner) => {
48
49
  const { uid, gid } = owner
49
50
 
50
51
  // No need to override if it's already what we used.
@@ -52,18 +53,23 @@ function fixOwner (cache, filepath) {
52
53
  return
53
54
  }
54
55
 
55
- return inflight(
56
- 'fixOwner: fixing ownership on ' + filepath,
57
- () => chownr(
56
+ return inflight('fixOwner: fixing ownership on ' + filepath, () =>
57
+ chownr(
58
58
  filepath,
59
59
  typeof uid === 'number' ? uid : self.uid,
60
60
  typeof gid === 'number' ? gid : self.gid
61
- ).catch({ code: 'ENOENT' }, () => null)
61
+ ).catch((err) => {
62
+ if (err.code === 'ENOENT') {
63
+ return null
64
+ }
65
+ throw err
66
+ })
62
67
  )
63
68
  })
64
69
  }
65
70
 
66
71
  module.exports.chownr.sync = fixOwnerSync
72
+
67
73
  function fixOwnerSync (cache, filepath) {
68
74
  if (!process.getuid) {
69
75
  // This platform doesn't need ownership fixing
@@ -71,6 +77,11 @@ function fixOwnerSync (cache, filepath) {
71
77
  }
72
78
  const { uid, gid } = inferOwner.sync(cache)
73
79
  getSelf()
80
+ if (self.uid !== 0) {
81
+ // almost certainly can't chown anyway
82
+ return
83
+ }
84
+
74
85
  if (self.uid === uid && self.gid === gid) {
75
86
  // No need to override if it's already what we used.
76
87
  return
@@ -91,24 +102,30 @@ function fixOwnerSync (cache, filepath) {
91
102
  }
92
103
 
93
104
  module.exports.mkdirfix = mkdirfix
105
+
94
106
  function mkdirfix (cache, p, cb) {
95
107
  // we have to infer the owner _before_ making the directory, even though
96
108
  // we aren't going to use the results, since the cache itself might not
97
109
  // exist yet. If we mkdirp it, then our current uid/gid will be assumed
98
110
  // to be correct if it creates the cache folder in the process.
99
- return BB.resolve(inferOwner(cache)).then(() => {
100
- return mkdirp(p).then(made => {
101
- if (made) {
102
- return fixOwner(cache, made).then(() => made)
103
- }
104
- }).catch({ code: 'EEXIST' }, () => {
105
- // There's a race in mkdirp!
106
- return fixOwner(cache, p).then(() => null)
107
- })
111
+ return Promise.resolve(inferOwner(cache)).then(() => {
112
+ return mkdirp(p)
113
+ .then((made) => {
114
+ if (made) {
115
+ return fixOwner(cache, made).then(() => made)
116
+ }
117
+ })
118
+ .catch((err) => {
119
+ if (err.code === 'EEXIST') {
120
+ return fixOwner(cache, p).then(() => null)
121
+ }
122
+ throw err
123
+ })
108
124
  })
109
125
  }
110
126
 
111
127
  module.exports.mkdirfix.sync = mkdirfixSync
128
+
112
129
  function mkdirfixSync (cache, p) {
113
130
  try {
114
131
  inferOwner.sync(cache)
@@ -3,9 +3,5 @@
3
3
  module.exports = hashToSegments
4
4
 
5
5
  function hashToSegments (hash) {
6
- return [
7
- hash.slice(0, 2),
8
- hash.slice(2, 4),
9
- hash.slice(4)
10
- ]
6
+ return [hash.slice(0, 2), hash.slice(2, 4), hash.slice(4)]
11
7
  }