cacache 16.0.7 → 16.1.2

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,18 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const fs = require('fs')
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)
3
+ const fs = require('@npmcli/fs')
8
4
  const move = require('@npmcli/move-file')
9
5
  const pinflight = require('promise-inflight')
10
6
 
11
7
  module.exports = moveFile
12
8
 
13
- function moveFile (src, dest) {
14
- const isWindows = global.__CACACHE_TEST_FAKE_WINDOWS__ ||
15
- process.platform === 'win32'
9
+ async function moveFile (src, dest) {
10
+ const isWindows = process.platform === 'win32'
16
11
 
17
12
  // This isn't quite an fs.rename -- the assumption is that
18
13
  // if `dest` already exists, and we get certain errors while
@@ -23,47 +18,39 @@ function moveFile (src, dest) {
23
18
  // content their own way.
24
19
  //
25
20
  // Note that, as the name suggests, this strictly only supports file moves.
26
- return new Promise((resolve, reject) => {
27
- fs.link(src, dest, (err) => {
28
- if (err) {
29
- if (isWindows && err.code === 'EPERM') {
30
- // XXX This is a really weird way to handle this situation, as it
31
- // results in the src file being deleted even though the dest
32
- // might not exist. Since we pretty much always write files to
33
- // deterministic locations based on content hash, this is likely
34
- // ok (or at worst, just ends in a future cache miss). But it would
35
- // be worth investigating at some time in the future if this is
36
- // really what we want to do here.
37
- return resolve()
38
- } else if (err.code === 'EEXIST' || err.code === 'EBUSY') {
39
- // file already exists, so whatever
40
- return resolve()
41
- } else {
42
- return reject(err)
21
+ try {
22
+ await fs.link(src, dest)
23
+ } catch (err) {
24
+ if (isWindows && err.code === 'EPERM') {
25
+ // XXX This is a really weird way to handle this situation, as it
26
+ // results in the src file being deleted even though the dest
27
+ // might not exist. Since we pretty much always write files to
28
+ // deterministic locations based on content hash, this is likely
29
+ // ok (or at worst, just ends in a future cache miss). But it would
30
+ // be worth investigating at some time in the future if this is
31
+ // really what we want to do here.
32
+ } else if (err.code === 'EEXIST' || err.code === 'EBUSY') {
33
+ // file already exists, so whatever
34
+ } else {
35
+ throw err
36
+ }
37
+ }
38
+ try {
39
+ await Promise.all([
40
+ fs.unlink(src),
41
+ !isWindows && fs.chmod(dest, '0444'),
42
+ ])
43
+ } catch (e) {
44
+ return pinflight('cacache-move-file:' + dest, async () => {
45
+ await fs.stat(dest).catch((err) => {
46
+ if (err.code !== 'ENOENT') {
47
+ // Something else is wrong here. Bail bail bail
48
+ throw err
43
49
  }
44
- } else {
45
- return resolve()
46
- }
47
- })
48
- })
49
- .then(() => {
50
- // content should never change for any reason, so make it read-only
51
- return Promise.all([
52
- unlink(src),
53
- !isWindows && chmod(dest, '0444'),
54
- ])
55
- })
56
- .catch(() => {
57
- return pinflight('cacache-move-file:' + dest, () => {
58
- return stat(dest).catch((err) => {
59
- if (err.code !== 'ENOENT') {
60
- // Something else is wrong here. Bail bail bail
61
- throw err
62
- }
63
- // file doesn't already exist! let's try a rename -> copy fallback
64
- // only delete if it successfully copies
65
- return move(src, dest)
66
- })
67
50
  })
51
+ // file doesn't already exist! let's try a rename -> copy fallback
52
+ // only delete if it successfully copies
53
+ return move(src, dest)
68
54
  })
55
+ }
69
56
  }
package/lib/util/tmp.js CHANGED
@@ -7,15 +7,13 @@ const path = require('path')
7
7
 
8
8
  module.exports.mkdir = mktmpdir
9
9
 
10
- function mktmpdir (cache, opts = {}) {
10
+ async function mktmpdir (cache, opts = {}) {
11
11
  const { tmpPrefix } = opts
12
12
  const tmpDir = path.join(cache, 'tmp')
13
- return fs.mkdir(tmpDir, { recursive: true, owner: 'inherit' })
14
- .then(() => {
15
- // do not use path.join(), it drops the trailing / if tmpPrefix is unset
16
- const target = `${tmpDir}${path.sep}${tmpPrefix || ''}`
17
- return fs.mkdtemp(target, { owner: 'inherit' })
18
- })
13
+ await fs.mkdir(tmpDir, { recursive: true, owner: 'inherit' })
14
+ // do not use path.join(), it drops the trailing / if tmpPrefix is unset
15
+ const target = `${tmpDir}${path.sep}${tmpPrefix || ''}`
16
+ return fs.mkdtemp(target, { owner: 'inherit' })
19
17
  }
20
18
 
21
19
  module.exports.withTmp = withTmp
package/lib/verify.js CHANGED
@@ -5,7 +5,7 @@ const util = require('util')
5
5
  const pMap = require('p-map')
6
6
  const contentPath = require('./content/path')
7
7
  const fixOwner = require('./util/fix-owner')
8
- const fs = require('fs')
8
+ const fs = require('@npmcli/fs')
9
9
  const fsm = require('fs-minipass')
10
10
  const glob = util.promisify(require('glob'))
11
11
  const index = require('./entry-index')
@@ -18,11 +18,6 @@ const globify = pattern => pattern.split('\\').join('/')
18
18
  const hasOwnProperty = (obj, key) =>
19
19
  Object.prototype.hasOwnProperty.call(obj, key)
20
20
 
21
- const stat = util.promisify(fs.stat)
22
- const truncate = util.promisify(fs.truncate)
23
- const writeFile = util.promisify(fs.writeFile)
24
- const readFile = util.promisify(fs.readFile)
25
-
26
21
  const verifyOpts = (opts) => ({
27
22
  concurrency: 20,
28
23
  log: { silly () {} },
@@ -31,7 +26,7 @@ const verifyOpts = (opts) => ({
31
26
 
32
27
  module.exports = verify
33
28
 
34
- function verify (cache, opts) {
29
+ async function verify (cache, opts) {
35
30
  opts = verifyOpts(opts)
36
31
  opts.log.silly('verify', 'verifying cache at', cache)
37
32
 
@@ -45,56 +40,47 @@ function verify (cache, opts) {
45
40
  markEndTime,
46
41
  ]
47
42
 
48
- return steps
49
- .reduce((promise, step, i) => {
50
- const label = step.name
51
- const start = new Date()
52
- return promise.then((stats) => {
53
- return step(cache, opts).then((s) => {
54
- s &&
55
- Object.keys(s).forEach((k) => {
56
- stats[k] = s[k]
57
- })
58
- const end = new Date()
59
- if (!stats.runTime) {
60
- stats.runTime = {}
61
- }
62
-
63
- stats.runTime[label] = end - start
64
- return Promise.resolve(stats)
65
- })
43
+ const stats = {}
44
+ for (const step of steps) {
45
+ const label = step.name
46
+ const start = new Date()
47
+ const s = await step(cache, opts)
48
+ if (s) {
49
+ Object.keys(s).forEach((k) => {
50
+ stats[k] = s[k]
66
51
  })
67
- }, Promise.resolve({}))
68
- .then((stats) => {
69
- stats.runTime.total = stats.endTime - stats.startTime
70
- opts.log.silly(
71
- 'verify',
72
- 'verification finished for',
73
- cache,
74
- 'in',
75
- `${stats.runTime.total}ms`
76
- )
77
- return stats
78
- })
52
+ }
53
+ const end = new Date()
54
+ if (!stats.runTime) {
55
+ stats.runTime = {}
56
+ }
57
+ stats.runTime[label] = end - start
58
+ }
59
+ stats.runTime.total = stats.endTime - stats.startTime
60
+ opts.log.silly(
61
+ 'verify',
62
+ 'verification finished for',
63
+ cache,
64
+ 'in',
65
+ `${stats.runTime.total}ms`
66
+ )
67
+ return stats
79
68
  }
80
69
 
81
- function markStartTime (cache, opts) {
82
- return Promise.resolve({ startTime: new Date() })
70
+ async function markStartTime (cache, opts) {
71
+ return { startTime: new Date() }
83
72
  }
84
73
 
85
- function markEndTime (cache, opts) {
86
- return Promise.resolve({ endTime: new Date() })
74
+ async function markEndTime (cache, opts) {
75
+ return { endTime: new Date() }
87
76
  }
88
77
 
89
- function fixPerms (cache, opts) {
78
+ async function fixPerms (cache, opts) {
90
79
  opts.log.silly('verify', 'fixing cache permissions')
91
- return fixOwner
92
- .mkdirfix(cache, cache)
93
- .then(() => {
94
- // TODO - fix file permissions too
95
- return fixOwner.chownr(cache, cache)
96
- })
97
- .then(() => null)
80
+ await fixOwner.mkdirfix(cache, cache)
81
+ // TODO - fix file permissions too
82
+ await fixOwner.chownr(cache, cache)
83
+ return null
98
84
  }
99
85
 
100
86
  // Implements a naive mark-and-sweep tracing garbage collector.
@@ -106,7 +92,7 @@ function fixPerms (cache, opts) {
106
92
  // 4. If content is live, verify its checksum and delete it if it fails
107
93
  // 5. If content is not marked as live, rimraf it.
108
94
  //
109
- function garbageCollect (cache, opts) {
95
+ async function garbageCollect (cache, opts) {
110
96
  opts.log.silly('verify', 'garbage collecting content')
111
97
  const indexStream = index.lsStream(cache)
112
98
  const liveContent = new Set()
@@ -117,156 +103,135 @@ function garbageCollect (cache, opts) {
117
103
 
118
104
  liveContent.add(entry.integrity.toString())
119
105
  })
120
- return new Promise((resolve, reject) => {
106
+ await new Promise((resolve, reject) => {
121
107
  indexStream.on('end', resolve).on('error', reject)
122
- }).then(() => {
123
- const contentDir = contentPath.contentDir(cache)
124
- return glob(globify(path.join(contentDir, '**')), {
125
- follow: false,
126
- nodir: true,
127
- nosort: true,
128
- }).then((files) => {
129
- return Promise.resolve({
130
- verifiedContent: 0,
131
- reclaimedCount: 0,
132
- reclaimedSize: 0,
133
- badContentCount: 0,
134
- keptSize: 0,
135
- }).then((stats) =>
136
- pMap(
137
- files,
138
- (f) => {
139
- const split = f.split(/[/\\]/)
140
- const digest = split.slice(split.length - 3).join('')
141
- const algo = split[split.length - 4]
142
- const integrity = ssri.fromHex(digest, algo)
143
- if (liveContent.has(integrity.toString())) {
144
- return verifyContent(f, integrity).then((info) => {
145
- if (!info.valid) {
146
- stats.reclaimedCount++
147
- stats.badContentCount++
148
- stats.reclaimedSize += info.size
149
- } else {
150
- stats.verifiedContent++
151
- stats.keptSize += info.size
152
- }
153
- return stats
154
- })
155
- } else {
156
- // No entries refer to this content. We can delete.
157
- stats.reclaimedCount++
158
- return stat(f).then((s) => {
159
- return rimraf(f).then(() => {
160
- stats.reclaimedSize += s.size
161
- return stats
162
- })
163
- })
164
- }
165
- },
166
- { concurrency: opts.concurrency }
167
- ).then(() => stats)
168
- )
169
- })
170
108
  })
171
- }
172
-
173
- function verifyContent (filepath, sri) {
174
- return stat(filepath)
175
- .then((s) => {
176
- const contentInfo = {
177
- size: s.size,
178
- valid: true,
179
- }
180
- return ssri
181
- .checkStream(new fsm.ReadStream(filepath), sri)
182
- .catch((err) => {
183
- if (err.code !== 'EINTEGRITY') {
184
- throw err
185
- }
186
-
187
- return rimraf(filepath).then(() => {
188
- contentInfo.valid = false
189
- })
190
- })
191
- .then(() => contentInfo)
192
- })
193
- .catch((err) => {
194
- if (err.code === 'ENOENT') {
195
- return { size: 0, valid: false }
109
+ const contentDir = contentPath.contentDir(cache)
110
+ const files = await glob(globify(path.join(contentDir, '**')), {
111
+ follow: false,
112
+ nodir: true,
113
+ nosort: true,
114
+ })
115
+ const stats = {
116
+ verifiedContent: 0,
117
+ reclaimedCount: 0,
118
+ reclaimedSize: 0,
119
+ badContentCount: 0,
120
+ keptSize: 0,
121
+ }
122
+ await pMap(
123
+ files,
124
+ async (f) => {
125
+ const split = f.split(/[/\\]/)
126
+ const digest = split.slice(split.length - 3).join('')
127
+ const algo = split[split.length - 4]
128
+ const integrity = ssri.fromHex(digest, algo)
129
+ if (liveContent.has(integrity.toString())) {
130
+ const info = await verifyContent(f, integrity)
131
+ if (!info.valid) {
132
+ stats.reclaimedCount++
133
+ stats.badContentCount++
134
+ stats.reclaimedSize += info.size
135
+ } else {
136
+ stats.verifiedContent++
137
+ stats.keptSize += info.size
138
+ }
139
+ } else {
140
+ // No entries refer to this content. We can delete.
141
+ stats.reclaimedCount++
142
+ const s = await fs.stat(f)
143
+ await rimraf(f)
144
+ stats.reclaimedSize += s.size
196
145
  }
146
+ return stats
147
+ },
148
+ { concurrency: opts.concurrency }
149
+ )
150
+ return stats
151
+ }
197
152
 
153
+ async function verifyContent (filepath, sri) {
154
+ const contentInfo = {}
155
+ try {
156
+ const { size } = await fs.stat(filepath)
157
+ contentInfo.size = size
158
+ contentInfo.valid = true
159
+ await ssri.checkStream(new fsm.ReadStream(filepath), sri)
160
+ } catch (err) {
161
+ if (err.code === 'ENOENT') {
162
+ return { size: 0, valid: false }
163
+ }
164
+ if (err.code !== 'EINTEGRITY') {
198
165
  throw err
199
- })
166
+ }
167
+
168
+ await rimraf(filepath)
169
+ contentInfo.valid = false
170
+ }
171
+ return contentInfo
200
172
  }
201
173
 
202
- function rebuildIndex (cache, opts) {
174
+ async function rebuildIndex (cache, opts) {
203
175
  opts.log.silly('verify', 'rebuilding index')
204
- return index.ls(cache).then((entries) => {
205
- const stats = {
206
- missingContent: 0,
207
- rejectedEntries: 0,
208
- totalEntries: 0,
209
- }
210
- const buckets = {}
211
- for (const k in entries) {
212
- /* istanbul ignore else */
213
- if (hasOwnProperty(entries, k)) {
214
- const hashed = index.hashKey(k)
215
- const entry = entries[k]
216
- const excluded = opts.filter && !opts.filter(entry)
217
- excluded && stats.rejectedEntries++
218
- if (buckets[hashed] && !excluded) {
219
- buckets[hashed].push(entry)
220
- } else if (buckets[hashed] && excluded) {
221
- // skip
222
- } else if (excluded) {
223
- buckets[hashed] = []
224
- buckets[hashed]._path = index.bucketPath(cache, k)
225
- } else {
226
- buckets[hashed] = [entry]
227
- buckets[hashed]._path = index.bucketPath(cache, k)
228
- }
176
+ const entries = await index.ls(cache)
177
+ const stats = {
178
+ missingContent: 0,
179
+ rejectedEntries: 0,
180
+ totalEntries: 0,
181
+ }
182
+ const buckets = {}
183
+ for (const k in entries) {
184
+ /* istanbul ignore else */
185
+ if (hasOwnProperty(entries, k)) {
186
+ const hashed = index.hashKey(k)
187
+ const entry = entries[k]
188
+ const excluded = opts.filter && !opts.filter(entry)
189
+ excluded && stats.rejectedEntries++
190
+ if (buckets[hashed] && !excluded) {
191
+ buckets[hashed].push(entry)
192
+ } else if (buckets[hashed] && excluded) {
193
+ // skip
194
+ } else if (excluded) {
195
+ buckets[hashed] = []
196
+ buckets[hashed]._path = index.bucketPath(cache, k)
197
+ } else {
198
+ buckets[hashed] = [entry]
199
+ buckets[hashed]._path = index.bucketPath(cache, k)
229
200
  }
230
201
  }
231
- return pMap(
232
- Object.keys(buckets),
233
- (key) => {
234
- return rebuildBucket(cache, buckets[key], stats, opts)
235
- },
236
- { concurrency: opts.concurrency }
237
- ).then(() => stats)
238
- })
202
+ }
203
+ await pMap(
204
+ Object.keys(buckets),
205
+ (key) => {
206
+ return rebuildBucket(cache, buckets[key], stats, opts)
207
+ },
208
+ { concurrency: opts.concurrency }
209
+ )
210
+ return stats
239
211
  }
240
212
 
241
- function rebuildBucket (cache, bucket, stats, opts) {
242
- return truncate(bucket._path).then(() => {
243
- // This needs to be serialized because cacache explicitly
244
- // lets very racy bucket conflicts clobber each other.
245
- return bucket.reduce((promise, entry) => {
246
- return promise.then(() => {
247
- const content = contentPath(cache, entry.integrity)
248
- return stat(content)
249
- .then(() => {
250
- return index
251
- .insert(cache, entry.key, entry.integrity, {
252
- metadata: entry.metadata,
253
- size: entry.size,
254
- })
255
- .then(() => {
256
- stats.totalEntries++
257
- })
258
- })
259
- .catch((err) => {
260
- if (err.code === 'ENOENT') {
261
- stats.rejectedEntries++
262
- stats.missingContent++
263
- return
264
- }
265
- throw err
266
- })
213
+ async function rebuildBucket (cache, bucket, stats, opts) {
214
+ await fs.truncate(bucket._path)
215
+ // This needs to be serialized because cacache explicitly
216
+ // lets very racy bucket conflicts clobber each other.
217
+ for (const entry of bucket) {
218
+ const content = contentPath(cache, entry.integrity)
219
+ try {
220
+ await fs.stat(content)
221
+ await index.insert(cache, entry.key, entry.integrity, {
222
+ metadata: entry.metadata,
223
+ size: entry.size,
267
224
  })
268
- }, Promise.resolve())
269
- })
225
+ stats.totalEntries++
226
+ } catch (err) {
227
+ if (err.code === 'ENOENT') {
228
+ stats.rejectedEntries++
229
+ stats.missingContent++
230
+ } else {
231
+ throw err
232
+ }
233
+ }
234
+ }
270
235
  }
271
236
 
272
237
  function cleanTmp (cache, opts) {
@@ -278,7 +243,7 @@ function writeVerifile (cache, opts) {
278
243
  const verifile = path.join(cache, '_lastverified')
279
244
  opts.log.silly('verify', 'writing verifile to ' + verifile)
280
245
  try {
281
- return writeFile(verifile, '' + +new Date())
246
+ return fs.writeFile(verifile, `${Date.now()}`)
282
247
  } finally {
283
248
  fixOwner.chownr.sync(cache, verifile)
284
249
  }
@@ -286,8 +251,7 @@ function writeVerifile (cache, opts) {
286
251
 
287
252
  module.exports.lastRun = lastRun
288
253
 
289
- function lastRun (cache) {
290
- return readFile(path.join(cache, '_lastverified'), 'utf8').then(
291
- (data) => new Date(+data)
292
- )
254
+ async function lastRun (cache) {
255
+ const data = await fs.readFile(path.join(cache, '_lastverified'), { encoding: 'utf8' })
256
+ return new Date(+data)
293
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cacache",
3
- "version": "16.0.7",
3
+ "version": "16.1.2",
4
4
  "cache-version": {
5
5
  "content": "2",
6
6
  "index": "5"
@@ -69,20 +69,16 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@npmcli/eslint-config": "^3.0.1",
72
- "@npmcli/template-oss": "3.4.1",
72
+ "@npmcli/template-oss": "3.5.0",
73
73
  "tap": "^16.0.0"
74
74
  },
75
- "tap": {
76
- "100": true,
77
- "test-regex": "test/[^/]*.js"
78
- },
79
75
  "engines": {
80
76
  "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
81
77
  },
82
78
  "templateOSS": {
83
79
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
84
80
  "windowsCI": false,
85
- "version": "3.4.1"
81
+ "version": "3.5.0"
86
82
  },
87
83
  "author": "GitHub Inc."
88
84
  }