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