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