cacache 12.0.3 → 14.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,86 +1,109 @@
1
1
  'use strict'
2
2
 
3
- const BB = require('bluebird')
3
+ const util = require('util')
4
4
 
5
- const contentPath = require('./path')
6
5
  const figgyPudding = require('figgy-pudding')
7
6
  const fs = require('graceful-fs')
8
- const PassThrough = require('stream').PassThrough
9
- const pipe = BB.promisify(require('mississippi').pipe)
7
+ const fsm = require('fs-minipass')
10
8
  const ssri = require('ssri')
11
- const Y = require('../util/y.js')
9
+ const contentPath = require('./path')
10
+ const Pipeline = require('minipass-pipeline')
12
11
 
13
- const lstatAsync = BB.promisify(fs.lstat)
14
- const readFileAsync = BB.promisify(fs.readFile)
12
+ const lstat = util.promisify(fs.lstat)
13
+ const readFile = util.promisify(fs.readFile)
15
14
 
16
15
  const ReadOpts = figgyPudding({
17
16
  size: {}
18
17
  })
19
18
 
20
19
  module.exports = read
20
+
21
+ const MAX_SINGLE_READ_SIZE = 64 * 1024 * 1024
21
22
  function read (cache, integrity, opts) {
22
23
  opts = ReadOpts(opts)
23
24
  return withContentSri(cache, integrity, (cpath, sri) => {
24
- return readFileAsync(cpath, null).then(data => {
25
- if (typeof opts.size === 'number' && opts.size !== data.length) {
26
- throw sizeError(opts.size, data.length)
27
- } else if (ssri.checkData(data, sri)) {
28
- return data
29
- } else {
25
+ // get size
26
+ return lstat(cpath).then(stat => ({ stat, cpath, sri }))
27
+ }).then(({ stat, cpath, sri }) => {
28
+ if (typeof opts.size === 'number' && stat.size !== opts.size) {
29
+ throw sizeError(opts.size, stat.size)
30
+ }
31
+ if (stat.size > MAX_SINGLE_READ_SIZE) {
32
+ return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
33
+ }
34
+
35
+ return readFile(cpath, null).then((data) => {
36
+ if (!ssri.checkData(data, sri)) {
30
37
  throw integrityError(sri, cpath)
31
38
  }
39
+ return data
32
40
  })
33
41
  })
34
42
  }
35
43
 
44
+ const readPipeline = (cpath, size, sri, stream) => {
45
+ stream.push(
46
+ new fsm.ReadStream(cpath, {
47
+ size,
48
+ readSize: MAX_SINGLE_READ_SIZE
49
+ }),
50
+ ssri.integrityStream({
51
+ integrity: sri,
52
+ size
53
+ })
54
+ )
55
+ return stream
56
+ }
57
+
36
58
  module.exports.sync = readSync
59
+
37
60
  function readSync (cache, integrity, opts) {
38
61
  opts = ReadOpts(opts)
39
62
  return withContentSriSync(cache, integrity, (cpath, sri) => {
40
63
  const data = fs.readFileSync(cpath)
41
64
  if (typeof opts.size === 'number' && opts.size !== data.length) {
42
65
  throw sizeError(opts.size, data.length)
43
- } else if (ssri.checkData(data, sri)) {
66
+ }
67
+
68
+ if (ssri.checkData(data, sri)) {
44
69
  return data
45
- } else {
46
- throw integrityError(sri, cpath)
47
70
  }
71
+
72
+ throw integrityError(sri, cpath)
48
73
  })
49
74
  }
50
75
 
51
76
  module.exports.stream = readStream
52
77
  module.exports.readStream = readStream
78
+
53
79
  function readStream (cache, integrity, opts) {
54
80
  opts = ReadOpts(opts)
55
- const stream = new PassThrough()
81
+
82
+ const stream = new Pipeline()
56
83
  withContentSri(cache, integrity, (cpath, sri) => {
57
- return lstatAsync(cpath).then(stat => ({ cpath, sri, stat }))
58
- }).then(({ cpath, sri, stat }) => {
59
- return pipe(
60
- fs.createReadStream(cpath),
61
- ssri.integrityStream({
62
- integrity: sri,
63
- size: opts.size
64
- }),
65
- stream
66
- )
67
- }).catch(err => {
68
- stream.emit('error', err)
69
- })
84
+ // just lstat to ensure it exists
85
+ return lstat(cpath).then((stat) => ({ stat, cpath, sri }))
86
+ }).then(({ stat, cpath, sri }) => {
87
+ if (typeof opts.size === 'number' && opts.size !== stat.size) {
88
+ return stream.emit('error', sizeError(opts.size, stat.size))
89
+ }
90
+ readPipeline(cpath, stat.size, sri, stream)
91
+ }, er => stream.emit('error', er))
92
+
70
93
  return stream
71
94
  }
72
95
 
73
- let copyFileAsync
96
+ let copyFile
74
97
  if (fs.copyFile) {
75
98
  module.exports.copy = copy
76
99
  module.exports.copy.sync = copySync
77
- copyFileAsync = BB.promisify(fs.copyFile)
100
+ copyFile = util.promisify(fs.copyFile)
78
101
  }
79
102
 
80
103
  function copy (cache, integrity, dest, opts) {
81
104
  opts = ReadOpts(opts)
82
105
  return withContentSri(cache, integrity, (cpath, sri) => {
83
- return copyFileAsync(cpath, dest)
106
+ return copyFile(cpath, dest)
84
107
  })
85
108
  }
86
109
 
@@ -92,13 +115,19 @@ function copySync (cache, integrity, dest, opts) {
92
115
  }
93
116
 
94
117
  module.exports.hasContent = hasContent
118
+
95
119
  function hasContent (cache, integrity) {
96
- if (!integrity) { return BB.resolve(false) }
120
+ if (!integrity) {
121
+ return Promise.resolve(false)
122
+ }
97
123
  return withContentSri(cache, integrity, (cpath, sri) => {
98
- return lstatAsync(cpath).then(stat => ({ size: stat.size, sri, stat }))
99
- }).catch(err => {
100
- if (err.code === 'ENOENT') { return false }
124
+ return lstat(cpath).then((stat) => ({ size: stat.size, sri, stat }))
125
+ }).catch((err) => {
126
+ if (err.code === 'ENOENT') {
127
+ return false
128
+ }
101
129
  if (err.code === 'EPERM') {
130
+ /* istanbul ignore else */
102
131
  if (process.platform !== 'win32') {
103
132
  throw err
104
133
  } else {
@@ -109,15 +138,21 @@ function hasContent (cache, integrity) {
109
138
  }
110
139
 
111
140
  module.exports.hasContent.sync = hasContentSync
141
+
112
142
  function hasContentSync (cache, integrity) {
113
- if (!integrity) { return false }
143
+ if (!integrity) {
144
+ return false
145
+ }
114
146
  return withContentSriSync(cache, integrity, (cpath, sri) => {
115
147
  try {
116
148
  const stat = fs.lstatSync(cpath)
117
149
  return { size: stat.size, sri, stat }
118
150
  } catch (err) {
119
- if (err.code === 'ENOENT') { return false }
151
+ if (err.code === 'ENOENT') {
152
+ return false
153
+ }
120
154
  if (err.code === 'EPERM') {
155
+ /* istanbul ignore else */
121
156
  if (process.platform !== 'win32') {
122
157
  throw err
123
158
  } else {
@@ -129,30 +164,58 @@ function hasContentSync (cache, integrity) {
129
164
  }
130
165
 
131
166
  function withContentSri (cache, integrity, fn) {
132
- return BB.try(() => {
167
+ const tryFn = () => {
133
168
  const sri = ssri.parse(integrity)
134
169
  // If `integrity` has multiple entries, pick the first digest
135
170
  // with available local data.
136
171
  const algo = sri.pickAlgorithm()
137
172
  const digests = sri[algo]
173
+
138
174
  if (digests.length <= 1) {
139
175
  const cpath = contentPath(cache, digests[0])
140
176
  return fn(cpath, digests[0])
141
177
  } else {
142
- return BB.any(sri[sri.pickAlgorithm()].map(meta => {
143
- return withContentSri(cache, meta, fn)
144
- }, { concurrency: 1 }))
145
- .catch(err => {
146
- if ([].some.call(err, e => e.code === 'ENOENT')) {
147
- throw Object.assign(
148
- new Error('No matching content found for ' + sri.toString()),
149
- { code: 'ENOENT' }
150
- )
151
- } else {
152
- throw err[0]
178
+ // Can't use race here because a generic error can happen before 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
153
203
  }
204
+
205
+ // Throw generic error
206
+ throw results.find((r) => r instanceof Error)
154
207
  })
155
208
  }
209
+ }
210
+
211
+ return new Promise((resolve, reject) => {
212
+ try {
213
+ tryFn()
214
+ .then(resolve)
215
+ .catch(reject)
216
+ } catch (err) {
217
+ reject(err)
218
+ }
156
219
  })
157
220
  }
158
221
 
@@ -167,19 +230,19 @@ function withContentSriSync (cache, integrity, fn) {
167
230
  return fn(cpath, digests[0])
168
231
  } else {
169
232
  let lastErr = null
170
- for (const meta of sri[sri.pickAlgorithm()]) {
233
+ for (const meta of digests) {
171
234
  try {
172
235
  return withContentSriSync(cache, meta, fn)
173
236
  } catch (err) {
174
237
  lastErr = err
175
238
  }
176
239
  }
177
- if (lastErr) { throw lastErr }
240
+ throw lastErr
178
241
  }
179
242
  }
180
243
 
181
244
  function sizeError (expected, found) {
182
- var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
245
+ const err = new Error(`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
183
246
  err.expected = expected
184
247
  err.found = found
185
248
  err.code = 'EBADSIZE'
@@ -187,7 +250,7 @@ function sizeError (expected, found) {
187
250
  }
188
251
 
189
252
  function integrityError (sri, path) {
190
- var err = new Error(Y`Integrity verification failed for ${sri} (${path})`)
253
+ const err = new Error(`Integrity verification failed for ${sri} (${path})`)
191
254
  err.code = 'EINTEGRITY'
192
255
  err.sri = sri
193
256
  err.path = path
package/lib/content/rm.js CHANGED
@@ -1,19 +1,18 @@
1
1
  'use strict'
2
2
 
3
- const BB = require('bluebird')
3
+ const util = require('util')
4
4
 
5
5
  const contentPath = require('./path')
6
- const hasContent = require('./read').hasContent
7
- const rimraf = BB.promisify(require('rimraf'))
6
+ const { hasContent } = require('./read')
7
+ const rimraf = util.promisify(require('rimraf'))
8
8
 
9
9
  module.exports = rm
10
+
10
11
  function rm (cache, integrity) {
11
- return hasContent(cache, integrity).then(content => {
12
- if (content) {
13
- const sri = content.sri
14
- if (sri) {
15
- return rimraf(contentPath(cache, sri)).then(() => true)
16
- }
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)
17
16
  } else {
18
17
  return false
19
18
  }
@@ -1,152 +1,175 @@
1
1
  'use strict'
2
2
 
3
- const BB = require('bluebird')
3
+ const util = require('util')
4
4
 
5
5
  const contentPath = require('./path')
6
6
  const fixOwner = require('../util/fix-owner')
7
7
  const fs = require('graceful-fs')
8
8
  const moveFile = require('../util/move-file')
9
- const PassThrough = require('stream').PassThrough
9
+ const Minipass = require('minipass')
10
+ const Pipeline = require('minipass-pipeline')
11
+ const Flush = require('minipass-flush')
10
12
  const path = require('path')
11
- const pipe = BB.promisify(require('mississippi').pipe)
12
- const rimraf = BB.promisify(require('rimraf'))
13
+ const rimraf = util.promisify(require('rimraf'))
13
14
  const ssri = require('ssri')
14
- const to = require('mississippi').to
15
15
  const uniqueFilename = require('unique-filename')
16
- const Y = require('../util/y.js')
16
+ const { disposer } = require('./../util/disposer')
17
+ const fsm = require('fs-minipass')
17
18
 
18
- const writeFileAsync = BB.promisify(fs.writeFile)
19
+ const writeFile = util.promisify(fs.writeFile)
19
20
 
20
21
  module.exports = write
22
+
21
23
  function write (cache, data, opts) {
22
24
  opts = opts || {}
23
25
  if (opts.algorithms && opts.algorithms.length > 1) {
24
- throw new Error(
25
- Y`opts.algorithms only supports a single algorithm for now`
26
- )
26
+ throw new Error('opts.algorithms only supports a single algorithm for now')
27
27
  }
28
28
  if (typeof opts.size === 'number' && data.length !== opts.size) {
29
- return BB.reject(sizeError(opts.size, data.length))
29
+ return Promise.reject(sizeError(opts.size, data.length))
30
30
  }
31
31
  const sri = ssri.fromData(data, {
32
32
  algorithms: opts.algorithms
33
33
  })
34
34
  if (opts.integrity && !ssri.checkData(data, opts.integrity, opts)) {
35
- return BB.reject(checksumError(opts.integrity, sri))
35
+ return Promise.reject(checksumError(opts.integrity, sri))
36
36
  }
37
- return BB.using(makeTmp(cache, opts), tmp => (
38
- writeFileAsync(
39
- tmp.target, data, { flag: 'wx' }
40
- ).then(() => (
41
- moveToDestination(tmp, cache, sri, opts)
42
- ))
43
- )).then(() => ({ integrity: sri, size: data.length }))
37
+
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 }))
44
44
  }
45
45
 
46
46
  module.exports.stream = writeStream
47
- function writeStream (cache, opts) {
48
- opts = opts || {}
49
- const inputStream = new PassThrough()
50
- let inputErr = false
51
- function errCheck () {
52
- if (inputErr) { throw inputErr }
47
+
48
+ // writes proxied to the 'inputStream' that is passed to the Promise
49
+ // 'end' is deferred until content is handled.
50
+ class CacacheWriteStream extends Flush {
51
+ constructor (cache, opts) {
52
+ super()
53
+ this.opts = opts
54
+ this.cache = cache
55
+ this.inputStream = new Minipass()
56
+ this.inputStream.on('error', er => this.emit('error', er))
57
+ this.inputStream.on('drain', () => this.emit('drain'))
58
+ this.handleContentP = null
53
59
  }
54
60
 
55
- let allDone
56
- const ret = to((c, n, cb) => {
57
- if (!allDone) {
58
- allDone = handleContent(inputStream, cache, opts, errCheck)
61
+ write (chunk, encoding, cb) {
62
+ if (!this.handleContentP) {
63
+ this.handleContentP = handleContent(
64
+ this.inputStream,
65
+ this.cache,
66
+ this.opts
67
+ )
59
68
  }
60
- inputStream.write(c, n, cb)
61
- }, cb => {
62
- inputStream.end(() => {
63
- if (!allDone) {
64
- const e = new Error(Y`Cache input stream was empty`)
69
+ return this.inputStream.write(chunk, encoding, cb)
70
+ }
71
+
72
+ flush (cb) {
73
+ this.inputStream.end(() => {
74
+ if (!this.handleContentP) {
75
+ const e = new Error('Cache input stream was empty')
65
76
  e.code = 'ENODATA'
66
- return ret.emit('error', e)
77
+ // empty streams are probably emitting end right away.
78
+ // defer this one tick by rejecting a promise on it.
79
+ return Promise.reject(e).catch(cb)
67
80
  }
68
- allDone.then(res => {
69
- res.integrity && ret.emit('integrity', res.integrity)
70
- res.size !== null && ret.emit('size', res.size)
71
- cb()
72
- }, e => {
73
- ret.emit('error', e)
74
- })
81
+ this.handleContentP.then(
82
+ (res) => {
83
+ res.integrity && this.emit('integrity', res.integrity)
84
+ res.size !== null && this.emit('size', res.size)
85
+ cb()
86
+ },
87
+ (er) => cb(er)
88
+ )
75
89
  })
76
- })
77
- ret.once('error', e => {
78
- inputErr = e
79
- })
80
- return ret
90
+ }
81
91
  }
82
92
 
83
- function handleContent (inputStream, cache, opts, errCheck) {
84
- return BB.using(makeTmp(cache, opts), tmp => {
85
- errCheck()
86
- return pipeToTmp(
87
- inputStream, cache, tmp.target, opts, errCheck
88
- ).then(res => {
89
- return moveToDestination(
90
- tmp, cache, res.integrity, opts, errCheck
91
- ).then(() => res)
92
- })
93
+ function writeStream (cache, opts) {
94
+ opts = opts || {}
95
+ return new CacacheWriteStream(cache, opts)
96
+ }
97
+
98
+ function handleContent (inputStream, cache, opts) {
99
+ return disposer(makeTmp(cache, opts), makeTmpDisposer, (tmp) => {
100
+ return pipeToTmp(inputStream, cache, tmp.target, opts)
101
+ .then((res) => {
102
+ return moveToDestination(
103
+ tmp,
104
+ cache,
105
+ res.integrity,
106
+ opts
107
+ ).then(() => res)
108
+ })
93
109
  })
94
110
  }
95
111
 
96
- function pipeToTmp (inputStream, cache, tmpTarget, opts, errCheck) {
97
- return BB.resolve().then(() => {
98
- let integrity
99
- let size
100
- const hashStream = ssri.integrityStream({
101
- integrity: opts.integrity,
102
- algorithms: opts.algorithms,
103
- size: opts.size
104
- }).on('integrity', s => {
105
- integrity = s
106
- }).on('size', s => {
107
- size = s
108
- })
109
- const outStream = fs.createWriteStream(tmpTarget, {
110
- flags: 'wx'
111
- })
112
- errCheck()
113
- return pipe(inputStream, hashStream, outStream).then(() => {
114
- return { integrity, size }
115
- }).catch(err => {
116
- return rimraf(tmpTarget).then(() => { throw err })
117
- })
112
+ function pipeToTmp (inputStream, cache, tmpTarget, opts) {
113
+ let integrity
114
+ let size
115
+ const hashStream = ssri.integrityStream({
116
+ integrity: opts.integrity,
117
+ algorithms: opts.algorithms,
118
+ size: opts.size
119
+ })
120
+ hashStream.on('integrity', i => { integrity = i })
121
+ hashStream.on('size', s => { size = s })
122
+
123
+ const outStream = new fsm.WriteStream(tmpTarget, {
124
+ flags: 'wx'
118
125
  })
126
+
127
+ // NB: this can throw if the hashStream has a problem with
128
+ // it, and the data is fully written. but pipeToTmp is only
129
+ // called in promisory contexts where that is handled.
130
+ const pipeline = new Pipeline(
131
+ inputStream,
132
+ hashStream,
133
+ outStream
134
+ )
135
+
136
+ return pipeline.promise()
137
+ .then(() => ({ integrity, size }))
138
+ .catch(er => rimraf(tmpTarget).then(() => { throw er }))
119
139
  }
120
140
 
121
141
  function makeTmp (cache, opts) {
122
142
  const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
123
- return fixOwner.mkdirfix(
124
- cache, path.dirname(tmpTarget)
125
- ).then(() => ({
143
+ return fixOwner.mkdirfix(cache, path.dirname(tmpTarget)).then(() => ({
126
144
  target: tmpTarget,
127
145
  moved: false
128
- })).disposer(tmp => (!tmp.moved && rimraf(tmp.target)))
146
+ }))
147
+ }
148
+
149
+ function makeTmpDisposer (tmp) {
150
+ if (tmp.moved) {
151
+ return Promise.resolve()
152
+ }
153
+ return rimraf(tmp.target)
129
154
  }
130
155
 
131
- function moveToDestination (tmp, cache, sri, opts, errCheck) {
132
- errCheck && errCheck()
156
+ function moveToDestination (tmp, cache, sri, opts) {
133
157
  const destination = contentPath(cache, sri)
134
158
  const destDir = path.dirname(destination)
135
159
 
136
- return fixOwner.mkdirfix(
137
- cache, destDir
138
- ).then(() => {
139
- errCheck && errCheck()
140
- return moveFile(tmp.target, destination)
141
- }).then(() => {
142
- errCheck && errCheck()
143
- tmp.moved = true
144
- return fixOwner.chownr(cache, destination)
145
- })
160
+ return fixOwner
161
+ .mkdirfix(cache, destDir)
162
+ .then(() => {
163
+ return moveFile(tmp.target, destination)
164
+ })
165
+ .then(() => {
166
+ tmp.moved = true
167
+ return fixOwner.chownr(cache, destination)
168
+ })
146
169
  }
147
170
 
148
171
  function sizeError (expected, found) {
149
- var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
172
+ const err = new Error(`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
150
173
  err.expected = expected
151
174
  err.found = found
152
175
  err.code = 'EBADSIZE'
@@ -154,7 +177,7 @@ function sizeError (expected, found) {
154
177
  }
155
178
 
156
179
  function checksumError (expected, found) {
157
- var err = new Error(Y`Integrity check failed:
180
+ const err = new Error(`Integrity check failed:
158
181
  Wanted: ${expected}
159
182
  Found: ${found}`)
160
183
  err.code = 'EINTEGRITY'