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.
@@ -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')
@@ -13,14 +13,11 @@ const path = require('path')
13
13
  const rimraf = util.promisify(require('rimraf'))
14
14
  const ssri = require('ssri')
15
15
 
16
+ const globify = pattern => pattern.split('\\').join('/')
17
+
16
18
  const hasOwnProperty = (obj, key) =>
17
19
  Object.prototype.hasOwnProperty.call(obj, key)
18
20
 
19
- const stat = util.promisify(fs.stat)
20
- const truncate = util.promisify(fs.truncate)
21
- const writeFile = util.promisify(fs.writeFile)
22
- const readFile = util.promisify(fs.readFile)
23
-
24
21
  const verifyOpts = (opts) => ({
25
22
  concurrency: 20,
26
23
  log: { silly () {} },
@@ -29,7 +26,7 @@ const verifyOpts = (opts) => ({
29
26
 
30
27
  module.exports = verify
31
28
 
32
- function verify (cache, opts) {
29
+ async function verify (cache, opts) {
33
30
  opts = verifyOpts(opts)
34
31
  opts.log.silly('verify', 'verifying cache at', cache)
35
32
 
@@ -43,56 +40,47 @@ function verify (cache, opts) {
43
40
  markEndTime,
44
41
  ]
45
42
 
46
- return steps
47
- .reduce((promise, step, i) => {
48
- const label = step.name
49
- const start = new Date()
50
- return promise.then((stats) => {
51
- return step(cache, opts).then((s) => {
52
- s &&
53
- Object.keys(s).forEach((k) => {
54
- stats[k] = s[k]
55
- })
56
- const end = new Date()
57
- if (!stats.runTime) {
58
- stats.runTime = {}
59
- }
60
-
61
- stats.runTime[label] = end - start
62
- return Promise.resolve(stats)
63
- })
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]
64
51
  })
65
- }, Promise.resolve({}))
66
- .then((stats) => {
67
- stats.runTime.total = stats.endTime - stats.startTime
68
- opts.log.silly(
69
- 'verify',
70
- 'verification finished for',
71
- cache,
72
- 'in',
73
- `${stats.runTime.total}ms`
74
- )
75
- return stats
76
- })
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
77
68
  }
78
69
 
79
- function markStartTime (cache, opts) {
80
- return Promise.resolve({ startTime: new Date() })
70
+ async function markStartTime (cache, opts) {
71
+ return { startTime: new Date() }
81
72
  }
82
73
 
83
- function markEndTime (cache, opts) {
84
- return Promise.resolve({ endTime: new Date() })
74
+ async function markEndTime (cache, opts) {
75
+ return { endTime: new Date() }
85
76
  }
86
77
 
87
- function fixPerms (cache, opts) {
78
+ async function fixPerms (cache, opts) {
88
79
  opts.log.silly('verify', 'fixing cache permissions')
89
- return fixOwner
90
- .mkdirfix(cache, cache)
91
- .then(() => {
92
- // TODO - fix file permissions too
93
- return fixOwner.chownr(cache, cache)
94
- })
95
- .then(() => null)
80
+ await fixOwner.mkdirfix(cache, cache)
81
+ // TODO - fix file permissions too
82
+ await fixOwner.chownr(cache, cache)
83
+ return null
96
84
  }
97
85
 
98
86
  // Implements a naive mark-and-sweep tracing garbage collector.
@@ -104,7 +92,7 @@ function fixPerms (cache, opts) {
104
92
  // 4. If content is live, verify its checksum and delete it if it fails
105
93
  // 5. If content is not marked as live, rimraf it.
106
94
  //
107
- function garbageCollect (cache, opts) {
95
+ async function garbageCollect (cache, opts) {
108
96
  opts.log.silly('verify', 'garbage collecting content')
109
97
  const indexStream = index.lsStream(cache)
110
98
  const liveContent = new Set()
@@ -115,156 +103,135 @@ function garbageCollect (cache, opts) {
115
103
 
116
104
  liveContent.add(entry.integrity.toString())
117
105
  })
118
- return new Promise((resolve, reject) => {
106
+ await new Promise((resolve, reject) => {
119
107
  indexStream.on('end', resolve).on('error', reject)
120
- }).then(() => {
121
- const contentDir = contentPath.contentDir(cache)
122
- return glob(path.join(contentDir, '**'), {
123
- follow: false,
124
- nodir: true,
125
- nosort: true,
126
- }).then((files) => {
127
- return Promise.resolve({
128
- verifiedContent: 0,
129
- reclaimedCount: 0,
130
- reclaimedSize: 0,
131
- badContentCount: 0,
132
- keptSize: 0,
133
- }).then((stats) =>
134
- pMap(
135
- files,
136
- (f) => {
137
- const split = f.split(/[/\\]/)
138
- const digest = split.slice(split.length - 3).join('')
139
- const algo = split[split.length - 4]
140
- const integrity = ssri.fromHex(digest, algo)
141
- if (liveContent.has(integrity.toString())) {
142
- return verifyContent(f, integrity).then((info) => {
143
- if (!info.valid) {
144
- stats.reclaimedCount++
145
- stats.badContentCount++
146
- stats.reclaimedSize += info.size
147
- } else {
148
- stats.verifiedContent++
149
- stats.keptSize += info.size
150
- }
151
- return stats
152
- })
153
- } else {
154
- // No entries refer to this content. We can delete.
155
- stats.reclaimedCount++
156
- return stat(f).then((s) => {
157
- return rimraf(f).then(() => {
158
- stats.reclaimedSize += s.size
159
- return stats
160
- })
161
- })
162
- }
163
- },
164
- { concurrency: opts.concurrency }
165
- ).then(() => stats)
166
- )
167
- })
168
108
  })
169
- }
170
-
171
- function verifyContent (filepath, sri) {
172
- return stat(filepath)
173
- .then((s) => {
174
- const contentInfo = {
175
- size: s.size,
176
- valid: true,
177
- }
178
- return ssri
179
- .checkStream(new fsm.ReadStream(filepath), sri)
180
- .catch((err) => {
181
- if (err.code !== 'EINTEGRITY') {
182
- throw err
183
- }
184
-
185
- return rimraf(filepath).then(() => {
186
- contentInfo.valid = false
187
- })
188
- })
189
- .then(() => contentInfo)
190
- })
191
- .catch((err) => {
192
- if (err.code === 'ENOENT') {
193
- 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
194
145
  }
146
+ return stats
147
+ },
148
+ { concurrency: opts.concurrency }
149
+ )
150
+ return stats
151
+ }
195
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') {
196
165
  throw err
197
- })
166
+ }
167
+
168
+ await rimraf(filepath)
169
+ contentInfo.valid = false
170
+ }
171
+ return contentInfo
198
172
  }
199
173
 
200
- function rebuildIndex (cache, opts) {
174
+ async function rebuildIndex (cache, opts) {
201
175
  opts.log.silly('verify', 'rebuilding index')
202
- return index.ls(cache).then((entries) => {
203
- const stats = {
204
- missingContent: 0,
205
- rejectedEntries: 0,
206
- totalEntries: 0,
207
- }
208
- const buckets = {}
209
- for (const k in entries) {
210
- /* istanbul ignore else */
211
- if (hasOwnProperty(entries, k)) {
212
- const hashed = index.hashKey(k)
213
- const entry = entries[k]
214
- const excluded = opts.filter && !opts.filter(entry)
215
- excluded && stats.rejectedEntries++
216
- if (buckets[hashed] && !excluded) {
217
- buckets[hashed].push(entry)
218
- } else if (buckets[hashed] && excluded) {
219
- // skip
220
- } else if (excluded) {
221
- buckets[hashed] = []
222
- buckets[hashed]._path = index.bucketPath(cache, k)
223
- } else {
224
- buckets[hashed] = [entry]
225
- buckets[hashed]._path = index.bucketPath(cache, k)
226
- }
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)
227
200
  }
228
201
  }
229
- return pMap(
230
- Object.keys(buckets),
231
- (key) => {
232
- return rebuildBucket(cache, buckets[key], stats, opts)
233
- },
234
- { concurrency: opts.concurrency }
235
- ).then(() => stats)
236
- })
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
237
211
  }
238
212
 
239
- function rebuildBucket (cache, bucket, stats, opts) {
240
- return truncate(bucket._path).then(() => {
241
- // This needs to be serialized because cacache explicitly
242
- // lets very racy bucket conflicts clobber each other.
243
- return bucket.reduce((promise, entry) => {
244
- return promise.then(() => {
245
- const content = contentPath(cache, entry.integrity)
246
- return stat(content)
247
- .then(() => {
248
- return index
249
- .insert(cache, entry.key, entry.integrity, {
250
- metadata: entry.metadata,
251
- size: entry.size,
252
- })
253
- .then(() => {
254
- stats.totalEntries++
255
- })
256
- })
257
- .catch((err) => {
258
- if (err.code === 'ENOENT') {
259
- stats.rejectedEntries++
260
- stats.missingContent++
261
- return
262
- }
263
- throw err
264
- })
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,
265
224
  })
266
- }, Promise.resolve())
267
- })
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
+ }
268
235
  }
269
236
 
270
237
  function cleanTmp (cache, opts) {
@@ -276,7 +243,7 @@ function writeVerifile (cache, opts) {
276
243
  const verifile = path.join(cache, '_lastverified')
277
244
  opts.log.silly('verify', 'writing verifile to ' + verifile)
278
245
  try {
279
- return writeFile(verifile, '' + +new Date())
246
+ return fs.writeFile(verifile, `${Date.now()}`)
280
247
  } finally {
281
248
  fixOwner.chownr.sync(cache, verifile)
282
249
  }
@@ -284,8 +251,7 @@ function writeVerifile (cache, opts) {
284
251
 
285
252
  module.exports.lastRun = lastRun
286
253
 
287
- function lastRun (cache) {
288
- return readFile(path.join(cache, '_lastverified'), 'utf8').then(
289
- (data) => new Date(+data)
290
- )
254
+ async function lastRun (cache) {
255
+ const data = await fs.readFile(path.join(cache, '_lastverified'), { encoding: 'utf8' })
256
+ return new Date(+data)
291
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cacache",
3
- "version": "16.0.5",
3
+ "version": "16.1.0",
4
4
  "cache-version": {
5
5
  "content": "2",
6
6
  "index": "5"
@@ -12,7 +12,6 @@
12
12
  "lib/"
13
13
  ],
14
14
  "scripts": {
15
- "benchmarks": "node test/benchmarks",
16
15
  "preversion": "npm test",
17
16
  "postversion": "npm publish",
18
17
  "prepublishOnly": "git push origin --follow-tags",
@@ -70,24 +69,16 @@
70
69
  },
71
70
  "devDependencies": {
72
71
  "@npmcli/eslint-config": "^3.0.1",
73
- "@npmcli/template-oss": "3.4.1",
74
- "benchmark": "^2.1.4",
75
- "chalk": "^4.1.2",
76
- "require-inject": "^1.4.4",
77
- "tacks": "^1.3.0",
72
+ "@npmcli/template-oss": "3.4.3",
78
73
  "tap": "^16.0.0"
79
74
  },
80
- "tap": {
81
- "100": true,
82
- "test-regex": "test/[^/]*.js"
83
- },
84
75
  "engines": {
85
76
  "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
86
77
  },
87
78
  "templateOSS": {
88
79
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
89
80
  "windowsCI": false,
90
- "version": "3.4.1"
81
+ "version": "3.4.3"
91
82
  },
92
83
  "author": "GitHub Inc."
93
84
  }
@@ -1,31 +0,0 @@
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
-
15
- return result
16
- },
17
- // Disposer fn failed, crash process
18
- (err) => {
19
- throw err
20
- // Or process.exit?
21
- })
22
- }
23
-
24
- return creatorFn
25
- .then((resource) => {
26
- // fn(resource) can throw, so wrap in a promise here
27
- return Promise.resolve().then(() => fn(resource))
28
- .then((result) => runDisposer(resource, result))
29
- .catch((err) => runDisposer(resource, err, true))
30
- })
31
- }