cacache 16.0.6 → 16.1.1

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.
@@ -2,13 +2,12 @@
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')
9
9
  const uniqueFilename = require('unique-filename')
10
10
 
11
- const { disposer } = require('./util/disposer')
12
11
  const contentPath = require('./content/path')
13
12
  const fixOwner = require('./util/fix-owner')
14
13
  const hashToSegments = require('./util/hash-to-segments')
@@ -18,11 +17,6 @@ const _rimraf = require('rimraf')
18
17
  const rimraf = util.promisify(_rimraf)
19
18
  rimraf.sync = _rimraf.sync
20
19
 
21
- const appendFile = util.promisify(fs.appendFile)
22
- const readFile = util.promisify(fs.readFile)
23
- const readdir = util.promisify(fs.readdir)
24
- const writeFile = util.promisify(fs.writeFile)
25
-
26
20
  module.exports.NotFoundError = class NotFoundError extends Error {
27
21
  constructor (cache, key) {
28
22
  super(`No cache entry for ${key} found in ${cache}`)
@@ -86,7 +80,7 @@ async function compact (cache, key, matchFn, opts = {}) {
86
80
  }
87
81
 
88
82
  const write = async (tmp) => {
89
- await writeFile(tmp.target, newIndex, { flag: 'wx' })
83
+ await fs.writeFile(tmp.target, newIndex, { flag: 'wx' })
90
84
  await fixOwner.mkdirfix(cache, path.dirname(bucket))
91
85
  // we use @npmcli/move-file directly here because we
92
86
  // want to overwrite the existing file
@@ -102,7 +96,12 @@ async function compact (cache, key, matchFn, opts = {}) {
102
96
  }
103
97
 
104
98
  // write the file atomically
105
- await disposer(setup(), teardown, write)
99
+ const tmp = await setup()
100
+ try {
101
+ await write(tmp)
102
+ } finally {
103
+ await teardown(tmp)
104
+ }
106
105
 
107
106
  // we reverse the list we generated such that the newest
108
107
  // entries come first in order to make looping through them easier
@@ -114,7 +113,7 @@ async function compact (cache, key, matchFn, opts = {}) {
114
113
 
115
114
  module.exports.insert = insert
116
115
 
117
- function insert (cache, key, integrity, opts = {}) {
116
+ async function insert (cache, key, integrity, opts = {}) {
118
117
  const { metadata, size } = opts
119
118
  const bucket = bucketPath(cache, key)
120
119
  const entry = {
@@ -124,36 +123,32 @@ function insert (cache, key, integrity, opts = {}) {
124
123
  size,
125
124
  metadata,
126
125
  }
127
- return fixOwner
128
- .mkdirfix(cache, path.dirname(bucket))
129
- .then(() => {
130
- const stringified = JSON.stringify(entry)
131
- // NOTE - Cleverness ahoy!
132
- //
133
- // This works because it's tremendously unlikely for an entry to corrupt
134
- // another while still preserving the string length of the JSON in
135
- // question. So, we just slap the length in there and verify it on read.
136
- //
137
- // Thanks to @isaacs for the whiteboarding session that ended up with
138
- // this.
139
- return appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`)
140
- })
141
- .then(() => fixOwner.chownr(cache, bucket))
142
- .catch((err) => {
143
- if (err.code === 'ENOENT') {
144
- return undefined
145
- }
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
+ }
146
143
 
147
- throw err
148
- // There's a class of race conditions that happen when things get deleted
149
- // during fixOwner, or between the two mkdirfix/chownr calls.
150
- //
151
- // It's perfectly fine to just not bother in those cases and lie
152
- // that the index entry was written. Because it's a cache.
153
- })
154
- .then(() => {
155
- return formatEntry(cache, entry)
156
- })
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)
157
152
  }
158
153
 
159
154
  module.exports.insert.sync = insertSync
@@ -183,25 +178,24 @@ function insertSync (cache, key, integrity, opts = {}) {
183
178
 
184
179
  module.exports.find = find
185
180
 
186
- function find (cache, key) {
181
+ async function find (cache, key) {
187
182
  const bucket = bucketPath(cache, key)
188
- return bucketEntries(bucket)
189
- .then((entries) => {
190
- return entries.reduce((latest, next) => {
191
- if (next && next.key === key) {
192
- return formatEntry(cache, next)
193
- } else {
194
- return latest
195
- }
196
- }, null)
197
- })
198
- .catch((err) => {
199
- if (err.code === 'ENOENT') {
200
- 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)
201
188
  } else {
202
- throw err
189
+ return latest
203
190
  }
204
- })
191
+ }, null)
192
+ } catch (err) {
193
+ if (err.code === 'ENOENT') {
194
+ return null
195
+ } else {
196
+ throw err
197
+ }
198
+ }
205
199
  }
206
200
 
207
201
  module.exports.find.sync = findSync
@@ -253,67 +247,64 @@ function lsStream (cache) {
253
247
  const indexDir = bucketDir(cache)
254
248
  const stream = new Minipass({ objectMode: true })
255
249
 
256
- readdirOrEmpty(indexDir).then(buckets => Promise.all(
257
- 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) => {
258
254
  const bucketPath = path.join(indexDir, bucket)
259
- return readdirOrEmpty(bucketPath).then(subbuckets => Promise.all(
260
- subbuckets.map(subbucket => {
261
- const subbucketPath = path.join(bucketPath, subbucket)
262
-
263
- // "/cachename/<bucket 0xFF>/<bucket 0xFF>./*"
264
- return readdirOrEmpty(subbucketPath).then(entries => Promise.all(
265
- entries.map(entry => {
266
- const entryPath = path.join(subbucketPath, entry)
267
- return bucketEntries(entryPath).then(entries =>
268
- // using a Map here prevents duplicate keys from
269
- // showing up twice, I guess?
270
- entries.reduce((acc, entry) => {
271
- acc.set(entry.key, entry)
272
- return acc
273
- }, new Map())
274
- ).then(reduced => {
275
- // reduced is a map of key => entry
276
- for (const entry of reduced.values()) {
277
- const formatted = formatEntry(cache, entry)
278
- if (formatted) {
279
- stream.write(formatted)
280
- }
281
- }
282
- }).catch(err => {
283
- if (err.code === 'ENOENT') {
284
- return undefined
285
- }
286
- throw err
287
- })
288
- })
289
- ))
290
- })
291
- ))
292
- })
293
- ))
294
- .then(
295
- () => stream.end(),
296
- err => stream.emit('error', err)
297
- )
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))
298
289
 
299
290
  return stream
300
291
  }
301
292
 
302
293
  module.exports.ls = ls
303
294
 
304
- function ls (cache) {
305
- return lsStream(cache).collect().then(entries =>
306
- entries.reduce((acc, xs) => {
307
- acc[xs.key] = xs
308
- return acc
309
- }, {})
310
- )
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
+ }, {})
311
301
  }
312
302
 
313
303
  module.exports.bucketEntries = bucketEntries
314
304
 
315
- function bucketEntries (bucket, filter) {
316
- 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)
317
308
  }
318
309
 
319
310
  module.exports.bucketEntries.sync = bucketEntriesSync
@@ -402,7 +393,7 @@ function formatEntry (cache, entry, keepAll) {
402
393
  }
403
394
 
404
395
  function readdirOrEmpty (dir) {
405
- return readdir(dir).catch((err) => {
396
+ return fs.readdir(dir).catch((err) => {
406
397
  if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
407
398
  return []
408
399
  }
package/lib/get.js CHANGED
@@ -3,61 +3,53 @@
3
3
  const Collect = require('minipass-collect')
4
4
  const Minipass = require('minipass')
5
5
  const Pipeline = require('minipass-pipeline')
6
- const fs = require('fs')
7
- const util = require('util')
8
6
 
9
7
  const index = require('./entry-index')
10
8
  const memo = require('./memoization')
11
9
  const read = require('./content/read')
12
10
 
13
- const writeFile = util.promisify(fs.writeFile)
14
-
15
- function getData (cache, key, opts = {}) {
11
+ async function getData (cache, key, opts = {}) {
16
12
  const { integrity, memoize, size } = opts
17
13
  const memoized = memo.get(cache, key, opts)
18
14
  if (memoized && memoize !== false) {
19
- return Promise.resolve({
15
+ return {
20
16
  metadata: memoized.entry.metadata,
21
17
  data: memoized.data,
22
18
  integrity: memoized.entry.integrity,
23
19
  size: memoized.entry.size,
24
- })
20
+ }
25
21
  }
26
22
 
27
- return index.find(cache, key, opts).then((entry) => {
28
- if (!entry) {
29
- throw new index.NotFoundError(cache, key)
30
- }
23
+ const entry = await index.find(cache, key, opts)
24
+ if (!entry) {
25
+ throw new index.NotFoundError(cache, key)
26
+ }
27
+ const data = await read(cache, entry.integrity, { integrity, size })
28
+ if (memoize) {
29
+ memo.put(cache, entry, data, opts)
30
+ }
31
31
 
32
- return read(cache, entry.integrity, { integrity, size }).then((data) => {
33
- if (memoize) {
34
- memo.put(cache, entry, data, opts)
35
- }
36
-
37
- return {
38
- data,
39
- metadata: entry.metadata,
40
- size: entry.size,
41
- integrity: entry.integrity,
42
- }
43
- })
44
- })
32
+ return {
33
+ data,
34
+ metadata: entry.metadata,
35
+ size: entry.size,
36
+ integrity: entry.integrity,
37
+ }
45
38
  }
46
39
  module.exports = getData
47
40
 
48
- function getDataByDigest (cache, key, opts = {}) {
41
+ async function getDataByDigest (cache, key, opts = {}) {
49
42
  const { integrity, memoize, size } = opts
50
43
  const memoized = memo.get.byDigest(cache, key, opts)
51
44
  if (memoized && memoize !== false) {
52
- return Promise.resolve(memoized)
45
+ return memoized
53
46
  }
54
47
 
55
- return read(cache, key, { integrity, size }).then((res) => {
56
- if (memoize) {
57
- memo.put.byDigest(cache, key, res, opts)
58
- }
59
- return res
60
- })
48
+ const res = await read(cache, key, { integrity, size })
49
+ if (memoize) {
50
+ memo.put.byDigest(cache, key, res, opts)
51
+ }
52
+ return res
61
53
  }
62
54
  module.exports.byDigest = getDataByDigest
63
55
 
@@ -135,36 +127,35 @@ function getStream (cache, key, opts = {}) {
135
127
  }
136
128
 
137
129
  const stream = new Pipeline()
138
- index
139
- .find(cache, key)
140
- .then((entry) => {
141
- if (!entry) {
142
- throw new index.NotFoundError(cache, key)
143
- }
144
-
145
- stream.emit('metadata', entry.metadata)
146
- stream.emit('integrity', entry.integrity)
147
- stream.emit('size', entry.size)
148
- stream.on('newListener', function (ev, cb) {
149
- ev === 'metadata' && cb(entry.metadata)
150
- ev === 'integrity' && cb(entry.integrity)
151
- ev === 'size' && cb(entry.size)
152
- })
153
-
154
- const src = read.readStream(
155
- cache,
156
- entry.integrity,
157
- { ...opts, size: typeof size !== 'number' ? entry.size : size }
158
- )
159
-
160
- if (memoize) {
161
- const memoStream = new Collect.PassThrough()
162
- memoStream.on('collect', data => memo.put(cache, entry, data, opts))
163
- stream.unshift(memoStream)
164
- }
165
- stream.unshift(src)
130
+ // Set all this up to run on the stream and then just return the stream
131
+ Promise.resolve().then(async () => {
132
+ const entry = await index.find(cache, key)
133
+ if (!entry) {
134
+ throw new index.NotFoundError(cache, key)
135
+ }
136
+
137
+ stream.emit('metadata', entry.metadata)
138
+ stream.emit('integrity', entry.integrity)
139
+ stream.emit('size', entry.size)
140
+ stream.on('newListener', function (ev, cb) {
141
+ ev === 'metadata' && cb(entry.metadata)
142
+ ev === 'integrity' && cb(entry.integrity)
143
+ ev === 'size' && cb(entry.size)
166
144
  })
167
- .catch((err) => stream.emit('error', err))
145
+
146
+ const src = read.readStream(
147
+ cache,
148
+ entry.integrity,
149
+ { ...opts, size: typeof size !== 'number' ? entry.size : size }
150
+ )
151
+
152
+ if (memoize) {
153
+ const memoStream = new Collect.PassThrough()
154
+ memoStream.on('collect', data => memo.put(cache, entry, data, opts))
155
+ stream.unshift(memoStream)
156
+ }
157
+ stream.unshift(src)
158
+ }).catch((err) => stream.emit('error', err))
168
159
 
169
160
  return stream
170
161
  }
@@ -208,44 +199,26 @@ function info (cache, key, opts = {}) {
208
199
  }
209
200
  module.exports.info = info
210
201
 
211
- function copy (cache, key, dest, opts = {}) {
212
- if (read.copy) {
213
- return index.find(cache, key, opts).then((entry) => {
214
- if (!entry) {
215
- throw new index.NotFoundError(cache, key)
216
- }
217
- return read.copy(cache, entry.integrity, dest, opts)
218
- .then(() => {
219
- return {
220
- metadata: entry.metadata,
221
- size: entry.size,
222
- integrity: entry.integrity,
223
- }
224
- })
225
- })
202
+ async function copy (cache, key, dest, opts = {}) {
203
+ const entry = await index.find(cache, key, opts)
204
+ if (!entry) {
205
+ throw new index.NotFoundError(cache, key)
206
+ }
207
+ await read.copy(cache, entry.integrity, dest, opts)
208
+ return {
209
+ metadata: entry.metadata,
210
+ size: entry.size,
211
+ integrity: entry.integrity,
226
212
  }
227
-
228
- return getData(cache, key, opts).then((res) => {
229
- return writeFile(dest, res.data).then(() => {
230
- return {
231
- metadata: res.metadata,
232
- size: res.size,
233
- integrity: res.integrity,
234
- }
235
- })
236
- })
237
213
  }
238
- module.exports.copy = copy
239
214
 
240
- function copyByDigest (cache, key, dest, opts = {}) {
241
- if (read.copy) {
242
- return read.copy(cache, key, dest, opts).then(() => key)
243
- }
215
+ module.exports.copy = copy
244
216
 
245
- return getDataByDigest(cache, key, opts).then((res) => {
246
- return writeFile(dest, res).then(() => key)
247
- })
217
+ async function copyByDigest (cache, key, dest, opts = {}) {
218
+ await read.copy(cache, key, dest, opts)
219
+ return key
248
220
  }
221
+
249
222
  module.exports.copy.byDigest = copyByDigest
250
223
 
251
224
  module.exports.hasContent = read.hasContent
package/lib/put.js CHANGED
@@ -14,20 +14,16 @@ const putOpts = (opts) => ({
14
14
 
15
15
  module.exports = putData
16
16
 
17
- function putData (cache, key, data, opts = {}) {
17
+ async function putData (cache, key, data, opts = {}) {
18
18
  const { memoize } = opts
19
19
  opts = putOpts(opts)
20
- return write(cache, data, opts).then((res) => {
21
- return index
22
- .insert(cache, key, res.integrity, { ...opts, size: res.size })
23
- .then((entry) => {
24
- if (memoize) {
25
- memo.put(cache, entry, data, opts)
26
- }
20
+ const res = await write(cache, data, opts)
21
+ const entry = await index.insert(cache, key, res.integrity, { ...opts, size: res.size })
22
+ if (memoize) {
23
+ memo.put(cache, entry, data, opts)
24
+ }
27
25
 
28
- return res.integrity
29
- })
30
- })
26
+ return res.integrity
31
27
  }
32
28
 
33
29
  module.exports.stream = putStream
@@ -37,6 +33,7 @@ function putStream (cache, key, opts = {}) {
37
33
  opts = putOpts(opts)
38
34
  let integrity
39
35
  let size
36
+ let error
40
37
 
41
38
  let memoData
42
39
  const pipeline = new Pipeline()
@@ -58,28 +55,24 @@ function putStream (cache, key, opts = {}) {
58
55
  .on('size', (s) => {
59
56
  size = s
60
57
  })
58
+ .on('error', (err) => {
59
+ error = err
60
+ })
61
61
 
62
62
  pipeline.push(contentStream)
63
63
 
64
64
  // last but not least, we write the index and emit hash and size,
65
65
  // and memoize if we're doing that
66
66
  pipeline.push(new Flush({
67
- flush () {
68
- return index
69
- .insert(cache, key, integrity, { ...opts, size })
70
- .then((entry) => {
71
- if (memoize && memoData) {
72
- memo.put(cache, entry, memoData, opts)
73
- }
74
-
75
- if (integrity) {
76
- pipeline.emit('integrity', integrity)
77
- }
78
-
79
- if (size) {
80
- pipeline.emit('size', size)
81
- }
82
- })
67
+ async flush () {
68
+ if (!error) {
69
+ const entry = await index.insert(cache, key, integrity, { ...opts, size })
70
+ if (memoize && memoData) {
71
+ memo.put(cache, entry, memoData, opts)
72
+ }
73
+ pipeline.emit('integrity', integrity)
74
+ pipeline.emit('size', size)
75
+ }
83
76
  },
84
77
  }))
85
78
 
@@ -33,40 +33,38 @@ const getSelf = () => {
33
33
 
34
34
  module.exports.chownr = fixOwner
35
35
 
36
- function fixOwner (cache, filepath) {
36
+ async function fixOwner (cache, filepath) {
37
37
  if (!process.getuid) {
38
38
  // This platform doesn't need ownership fixing
39
- return Promise.resolve()
39
+ return
40
40
  }
41
41
 
42
42
  getSelf()
43
43
  if (self.uid !== 0) {
44
44
  // almost certainly can't chown anyway
45
- return Promise.resolve()
45
+ return
46
46
  }
47
47
 
48
- return Promise.resolve(inferOwner(cache)).then((owner) => {
49
- const { uid, gid } = owner
48
+ const { uid, gid } = await inferOwner(cache)
50
49
 
51
- // No need to override if it's already what we used.
52
- if (self.uid === uid && self.gid === gid) {
53
- return
54
- }
50
+ // No need to override if it's already what we used.
51
+ if (self.uid === uid && self.gid === gid) {
52
+ return
53
+ }
55
54
 
56
- return inflight('fixOwner: fixing ownership on ' + filepath, () =>
57
- chownr(
58
- filepath,
59
- typeof uid === 'number' ? uid : self.uid,
60
- typeof gid === 'number' ? gid : self.gid
61
- ).catch((err) => {
62
- if (err.code === 'ENOENT') {
63
- return null
64
- }
65
-
66
- throw err
67
- })
68
- )
69
- })
55
+ return inflight('fixOwner: fixing ownership on ' + filepath, () =>
56
+ chownr(
57
+ filepath,
58
+ typeof uid === 'number' ? uid : self.uid,
59
+ typeof gid === 'number' ? gid : self.gid
60
+ ).catch((err) => {
61
+ if (err.code === 'ENOENT') {
62
+ return null
63
+ }
64
+
65
+ throw err
66
+ })
67
+ )
70
68
  }
71
69
 
72
70
  module.exports.chownr.sync = fixOwnerSync
@@ -105,26 +103,25 @@ function fixOwnerSync (cache, filepath) {
105
103
 
106
104
  module.exports.mkdirfix = mkdirfix
107
105
 
108
- function mkdirfix (cache, p, cb) {
106
+ async function mkdirfix (cache, p, cb) {
109
107
  // we have to infer the owner _before_ making the directory, even though
110
108
  // we aren't going to use the results, since the cache itself might not
111
109
  // exist yet. If we mkdirp it, then our current uid/gid will be assumed
112
110
  // to be correct if it creates the cache folder in the process.
113
- return Promise.resolve(inferOwner(cache)).then(() => {
114
- return mkdirp(p)
115
- .then((made) => {
116
- if (made) {
117
- return fixOwner(cache, made).then(() => made)
118
- }
119
- })
120
- .catch((err) => {
121
- if (err.code === 'EEXIST') {
122
- return fixOwner(cache, p).then(() => null)
123
- }
124
-
125
- throw err
126
- })
127
- })
111
+ await inferOwner(cache)
112
+ try {
113
+ const made = await mkdirp(p)
114
+ if (made) {
115
+ await fixOwner(cache, made)
116
+ return made
117
+ }
118
+ } catch (err) {
119
+ if (err.code === 'EEXIST') {
120
+ await fixOwner(cache, p)
121
+ return null
122
+ }
123
+ throw err
124
+ }
128
125
  }
129
126
 
130
127
  module.exports.mkdirfix.sync = mkdirfixSync