cacache 16.0.5 → 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,41 +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
-
14
9
  module.exports = read
15
10
 
16
11
  const MAX_SINGLE_READ_SIZE = 64 * 1024 * 1024
17
- function read (cache, integrity, opts = {}) {
12
+ async function read (cache, integrity, opts = {}) {
18
13
  const { size } = opts
19
- return withContentSri(cache, integrity, (cpath, sri) => {
14
+ const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => {
20
15
  // get size
21
- return lstat(cpath).then(stat => ({ stat, cpath, sri }))
22
- }).then(({ stat, cpath, sri }) => {
23
- if (typeof size === 'number' && stat.size !== size) {
24
- throw sizeError(size, stat.size)
25
- }
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
+ }
26
22
 
27
- if (stat.size > MAX_SINGLE_READ_SIZE) {
28
- return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
29
- }
23
+ if (stat.size > MAX_SINGLE_READ_SIZE) {
24
+ return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
25
+ }
30
26
 
31
- return readFile(cpath, null).then((data) => {
32
- if (!ssri.checkData(data, sri)) {
33
- throw integrityError(sri, cpath)
34
- }
27
+ const data = await fs.readFile(cpath, { encoding: null })
28
+ if (!ssri.checkData(data, sri)) {
29
+ throw integrityError(sri, cpath)
30
+ }
35
31
 
36
- return data
37
- })
38
- })
32
+ return data
39
33
  }
40
34
 
41
35
  const readPipeline = (cpath, size, sri, stream) => {
@@ -57,7 +51,7 @@ module.exports.sync = readSync
57
51
  function readSync (cache, integrity, opts = {}) {
58
52
  const { size } = opts
59
53
  return withContentSriSync(cache, integrity, (cpath, sri) => {
60
- const data = fs.readFileSync(cpath)
54
+ const data = fs.readFileSync(cpath, { encoding: null })
61
55
  if (typeof size === 'number' && size !== data.length) {
62
56
  throw sizeError(size, data.length)
63
57
  }
@@ -76,30 +70,29 @@ module.exports.readStream = readStream
76
70
  function readStream (cache, integrity, opts = {}) {
77
71
  const { size } = opts
78
72
  const stream = new Pipeline()
79
- withContentSri(cache, integrity, (cpath, sri) => {
80
- // just lstat to ensure it exists
81
- return lstat(cpath).then((stat) => ({ stat, cpath, sri }))
82
- }).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
+ })
83
80
  if (typeof size === 'number' && size !== stat.size) {
84
81
  return stream.emit('error', sizeError(size, stat.size))
85
82
  }
86
83
 
87
84
  readPipeline(cpath, stat.size, sri, stream)
88
- }, er => stream.emit('error', er))
85
+ }).catch(err => stream.emit('error', err))
89
86
 
90
87
  return stream
91
88
  }
92
89
 
93
- let copyFile
94
- if (fs.copyFile) {
95
- module.exports.copy = copy
96
- module.exports.copy.sync = copySync
97
- copyFile = util.promisify(fs.copyFile)
98
- }
90
+ module.exports.copy = copy
91
+ module.exports.copy.sync = copySync
99
92
 
100
93
  function copy (cache, integrity, dest) {
101
94
  return withContentSri(cache, integrity, (cpath, sri) => {
102
- return copyFile(cpath, dest)
95
+ return fs.copyFile(cpath, dest)
103
96
  })
104
97
  }
105
98
 
@@ -111,14 +104,17 @@ function copySync (cache, integrity, dest) {
111
104
 
112
105
  module.exports.hasContent = hasContent
113
106
 
114
- function hasContent (cache, integrity) {
107
+ async function hasContent (cache, integrity) {
115
108
  if (!integrity) {
116
- return Promise.resolve(false)
109
+ return false
117
110
  }
118
111
 
119
- return withContentSri(cache, integrity, (cpath, sri) => {
120
- return lstat(cpath).then((stat) => ({ size: stat.size, sri, stat }))
121
- }).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) {
122
118
  if (err.code === 'ENOENT') {
123
119
  return false
124
120
  }
@@ -131,7 +127,7 @@ function hasContent (cache, integrity) {
131
127
  return false
132
128
  }
133
129
  }
134
- })
130
+ }
135
131
  }
136
132
 
137
133
  module.exports.hasContent.sync = hasContentSync
@@ -162,61 +158,47 @@ function hasContentSync (cache, integrity) {
162
158
  })
163
159
  }
164
160
 
165
- function withContentSri (cache, integrity, fn) {
166
- const tryFn = () => {
167
- const sri = ssri.parse(integrity)
168
- // If `integrity` has multiple entries, pick the first digest
169
- // with available local data.
170
- const algo = sri.pickAlgorithm()
171
- const digests = sri[algo]
172
-
173
- if (digests.length <= 1) {
174
- const cpath = contentPath(cache, digests[0])
175
- return fn(cpath, digests[0])
176
- } else {
177
- // Can't use race here because a generic error can happen before
178
- // a ENOENT error, and can happen before a valid result
179
- return Promise
180
- .all(digests.map((meta) => {
181
- return withContentSri(cache, meta, fn)
182
- .catch((err) => {
183
- if (err.code === 'ENOENT') {
184
- return Object.assign(
185
- new Error('No matching content found for ' + sri.toString()),
186
- { code: 'ENOENT' }
187
- )
188
- }
189
- return err
190
- })
191
- }))
192
- .then((results) => {
193
- // Return the first non error if it is found
194
- const result = results.find((r) => !(r instanceof Error))
195
- if (result) {
196
- return result
197
- }
198
-
199
- // Throw the No matching content found error
200
- const enoentError = results.find((r) => r.code === 'ENOENT')
201
- if (enoentError) {
202
- throw enoentError
203
- }
204
-
205
- // Throw generic error
206
- throw results.find((r) => r instanceof Error)
207
- })
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
208
191
  }
209
- }
210
192
 
211
- return new Promise((resolve, reject) => {
212
- try {
213
- tryFn()
214
- .then(resolve)
215
- .catch(reject)
216
- } catch (err) {
217
- 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
218
197
  }
219
- })
198
+
199
+ // Throw generic error
200
+ throw results.find((r) => r instanceof Error)
201
+ }
220
202
  }
221
203
 
222
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')
@@ -13,34 +14,35 @@ const path = require('path')
13
14
  const rimraf = util.promisify(require('rimraf'))
14
15
  const ssri = require('ssri')
15
16
  const uniqueFilename = require('unique-filename')
16
- const { disposer } = require('./../util/disposer')
17
17
  const fsm = require('fs-minipass')
18
18
 
19
- const writeFile = util.promisify(fs.writeFile)
20
-
21
19
  module.exports = write
22
20
 
23
- function write (cache, data, opts = {}) {
21
+ async function write (cache, data, opts = {}) {
24
22
  const { algorithms, size, integrity } = opts
25
23
  if (algorithms && algorithms.length > 1) {
26
24
  throw new Error('opts.algorithms only supports a single algorithm for now')
27
25
  }
28
26
 
29
27
  if (typeof size === 'number' && data.length !== size) {
30
- return Promise.reject(sizeError(size, data.length))
28
+ throw sizeError(size, data.length)
31
29
  }
32
30
 
33
31
  const sri = ssri.fromData(data, algorithms ? { algorithms } : {})
34
32
  if (integrity && !ssri.checkData(data, integrity, opts)) {
35
- return Promise.reject(checksumError(integrity, sri))
33
+ throw checksumError(integrity, sri)
36
34
  }
37
35
 
38
- return disposer(makeTmp(cache, opts), makeTmpDisposer,
39
- (tmp) => {
40
- return writeFile(tmp.target, data, { flag: 'wx' })
41
- .then(() => moveToDestination(tmp, cache, sri, opts))
42
- })
43
- .then(() => ({ integrity: sri, size: data.length }))
36
+ const tmp = await makeTmp(cache, opts)
37
+ try {
38
+ await fs.writeFile(tmp.target, data, { flag: 'wx' })
39
+ await moveToDestination(tmp, cache, sri, opts)
40
+ return { integrity: sri, size: data.length }
41
+ } finally {
42
+ if (!tmp.moved) {
43
+ await rimraf(tmp.target)
44
+ }
45
+ }
44
46
  }
45
47
 
46
48
  module.exports.stream = writeStream
@@ -94,21 +96,39 @@ function writeStream (cache, opts = {}) {
94
96
  return new CacacheWriteStream(cache, opts)
95
97
  }
96
98
 
97
- function handleContent (inputStream, cache, opts) {
98
- return disposer(makeTmp(cache, opts), makeTmpDisposer, (tmp) => {
99
- return pipeToTmp(inputStream, cache, tmp.target, opts)
100
- .then((res) => {
101
- return moveToDestination(
102
- tmp,
103
- cache,
104
- res.integrity,
105
- opts
106
- ).then(() => res)
107
- })
108
- })
99
+ async function handleContent (inputStream, cache, opts) {
100
+ const tmp = await makeTmp(cache, opts)
101
+ try {
102
+ const res = await pipeToTmp(inputStream, cache, tmp.target, opts)
103
+ await moveToDestination(
104
+ tmp,
105
+ cache,
106
+ res.integrity,
107
+ opts
108
+ )
109
+ return res
110
+ } finally {
111
+ if (!tmp.moved) {
112
+ await rimraf(tmp.target)
113
+ }
114
+ }
109
115
  }
110
116
 
111
- 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
+
112
132
  let integrity
113
133
  let size
114
134
  const hashStream = ssri.integrityStream({
@@ -123,55 +143,28 @@ function pipeToTmp (inputStream, cache, tmpTarget, opts) {
123
143
  size = s
124
144
  })
125
145
 
126
- const outStream = new fsm.WriteStream(tmpTarget, {
127
- flags: 'wx',
128
- })
129
-
130
- // NB: this can throw if the hashStream has a problem with
131
- // it, and the data is fully written. but pipeToTmp is only
132
- // called in promisory contexts where that is handled.
133
- const pipeline = new Pipeline(
134
- inputStream,
135
- hashStream,
136
- outStream
137
- )
138
-
139
- return pipeline.promise()
140
- .then(() => ({ integrity, size }))
141
- .catch(er => rimraf(tmpTarget).then(() => {
142
- throw er
143
- }))
146
+ const pipeline = new Pipeline(inputStream, hashStream, outStream)
147
+ await pipeline.promise()
148
+ return { integrity, size }
144
149
  }
145
150
 
146
- function makeTmp (cache, opts) {
151
+ async function makeTmp (cache, opts) {
147
152
  const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
148
- return fixOwner.mkdirfix(cache, path.dirname(tmpTarget)).then(() => ({
153
+ await fixOwner.mkdirfix(cache, path.dirname(tmpTarget))
154
+ return {
149
155
  target: tmpTarget,
150
156
  moved: false,
151
- }))
152
- }
153
-
154
- function makeTmpDisposer (tmp) {
155
- if (tmp.moved) {
156
- return Promise.resolve()
157
157
  }
158
-
159
- return rimraf(tmp.target)
160
158
  }
161
159
 
162
- function moveToDestination (tmp, cache, sri, opts) {
160
+ async function moveToDestination (tmp, cache, sri, opts) {
163
161
  const destination = contentPath(cache, sri)
164
162
  const destDir = path.dirname(destination)
165
163
 
166
- return fixOwner
167
- .mkdirfix(cache, destDir)
168
- .then(() => {
169
- return moveFile(tmp.target, destination)
170
- })
171
- .then(() => {
172
- tmp.moved = true
173
- return fixOwner.chownr(cache, destination)
174
- })
164
+ await fixOwner.mkdirfix(cache, destDir)
165
+ await moveFile(tmp.target, destination)
166
+ tmp.moved = true
167
+ await fixOwner.chownr(cache, destination)
175
168
  }
176
169
 
177
170
  function sizeError (expected, found) {