@@ -4,7 +4,59 @@ const log = require('npmlog')
4
4
const pacote = require ( 'pacote' )
5
5
const path = require ( 'path' )
6
6
const rimraf = promisify ( require ( 'rimraf' ) )
7
+ const semver = require ( 'semver' )
7
8
const BaseCommand = require ( './base-command.js' )
9
+ const npa = require ( 'npm-package-arg' )
10
+ const jsonParse = require ( 'json-parse-even-better-errors' )
11
+
12
+ const searchCachePackage = async ( path , spec , cacheKeys ) => {
13
+ const parsed = npa ( spec )
14
+ if ( parsed . rawSpec !== '' && parsed . type === 'tag' )
15
+ throw new Error ( `Cannot list cache keys for a tagged package.` )
16
+ const searchMFH = new RegExp ( `^make-fetch-happen:request-cache:.*(?<!/[@a-zA-Z]+)/${ parsed . name } /-/(${ parsed . name } [^/]+.tgz)$` )
17
+ const searchPack = new RegExp ( `^make-fetch-happen:request-cache:.*/${ parsed . escapedName } $` )
18
+ const results = new Set ( )
19
+ cacheKeys = new Set ( cacheKeys )
20
+ for ( const key of cacheKeys ) {
21
+ // match on the public key registry url format
22
+ if ( searchMFH . test ( key ) ) {
23
+ // extract the version from the filename
24
+ const filename = key . match ( searchMFH ) [ 1 ]
25
+ const noExt = filename . slice ( 0 , - 4 )
26
+ const noScope = `${ parsed . name . split ( '/' ) . pop ( ) } -`
27
+ const ver = noExt . slice ( noScope . length )
28
+ if ( semver . satisfies ( ver , parsed . rawSpec ) )
29
+ results . add ( key )
30
+ continue
31
+ }
32
+ // is this key a packument?
33
+ if ( ! searchPack . test ( key ) )
34
+ continue
35
+
36
+ results . add ( key )
37
+ let packument , details
38
+ try {
39
+ details = await cacache . get ( path , key )
40
+ packument = jsonParse ( details . data )
41
+ } catch ( _ ) {
42
+ // if we couldn't parse the packument, abort
43
+ continue
44
+ }
45
+ if ( ! packument . versions || typeof packument . versions !== 'object' )
46
+ continue
47
+ // assuming this is a packument
48
+ for ( const ver of Object . keys ( packument . versions ) ) {
49
+ if ( semver . satisfies ( ver , parsed . rawSpec ) ) {
50
+ if ( packument . versions [ ver ] . dist
51
+ && typeof packument . versions [ ver ] . dist === 'object'
52
+ && packument . versions [ ver ] . dist . tarball !== undefined
53
+ && cacheKeys . has ( `make-fetch-happen:request-cache:${ packument . versions [ ver ] . dist . tarball } ` ) )
54
+ results . add ( `make-fetch-happen:request-cache:${ packument . versions [ ver ] . dist . tarball } ` )
55
+ }
56
+ }
57
+ }
58
+ return results
59
+ }
8
60
9
61
class Cache extends BaseCommand {
10
62
static get description ( ) {
@@ -29,21 +81,24 @@ class Cache extends BaseCommand {
29
81
'add <tarball url>' ,
30
82
'add <git url>' ,
31
83
'add <name>@<version>' ,
32
- 'clean' ,
84
+ 'clean [<key>]' ,
85
+ 'ls [<name>@<version>]' ,
33
86
'verify' ,
34
87
]
35
88
}
36
89
37
90
async completion ( opts ) {
38
91
const argv = opts . conf . argv . remain
39
92
if ( argv . length === 2 )
40
- return [ 'add' , 'clean' , 'verify' ]
93
+ return [ 'add' , 'clean' , 'verify' , 'ls' , 'delete' ]
41
94
42
95
// TODO - eventually...
43
96
switch ( argv [ 2 ] ) {
44
97
case 'verify' :
45
98
case 'clean' :
46
99
case 'add' :
100
+ case 'ls' :
101
+ case 'delete' :
47
102
return [ ]
48
103
}
49
104
}
@@ -61,34 +116,47 @@ class Cache extends BaseCommand {
61
116
return await this . add ( args )
62
117
case 'verify' : case 'check' :
63
118
return await this . verify ( )
119
+ case 'ls' :
120
+ return await this . ls ( args )
64
121
default :
65
122
throw Object . assign ( new Error ( this . usage ) , { code : 'EUSAGE' } )
66
123
}
67
124
}
68
125
69
126
// npm cache clean [pkg]*
70
127
async clean ( args ) {
71
- if ( args . length )
72
- throw new Error ( 'npm cache clear does not accept arguments' )
73
-
74
128
const cachePath = path . join ( this . npm . cache , '_cacache' )
75
- if ( ! this . npm . config . get ( 'force' ) ) {
76
- throw new Error ( `As of npm@5, the npm cache self-heals from corruption issues
77
- by treating integrity mismatches as cache misses. As a result,
78
- data extracted from the cache is guaranteed to be valid. If you
79
- want to make sure everything is consistent, use \`npm cache verify\`
80
- instead. Deleting the cache can only make npm go slower, and is
81
- not likely to correct any problems you may be encountering!
82
-
83
- On the other hand, if you're debugging an issue with the installer,
84
- or race conditions that depend on the timing of writing to an empty
85
- cache, you can use \`npm install --cache /tmp/empty-cache\` to use a
86
- temporary cache instead of nuking the actual one.
87
-
88
- If you're sure you want to delete the entire cache, rerun this command
89
- with --force.` )
129
+ if ( args . length === 0 ) {
130
+ if ( ! this . npm . config . get ( 'force' ) ) {
131
+ throw new Error ( `As of npm@5, the npm cache self-heals from corruption issues
132
+ by treating integrity mismatches as cache misses. As a result,
133
+ data extracted from the cache is guaranteed to be valid. If you
134
+ want to make sure everything is consistent, use \`npm cache verify\`
135
+ instead. Deleting the cache can only make npm go slower, and is
136
+ not likely to correct any problems you may be encountering!
137
+
138
+ On the other hand, if you're debugging an issue with the installer,
139
+ or race conditions that depend on the timing of writing to an empty
140
+ cache, you can use \`npm install --cache /tmp/empty-cache\` to use a
141
+ temporary cache instead of nuking the actual one.
142
+
143
+ If you're sure you want to delete the entire cache, rerun this command
144
+ with --force.` )
145
+ }
146
+ return rimraf ( cachePath )
147
+ }
148
+ for ( const key of args ) {
149
+ let entry
150
+ try {
151
+ entry = await cacache . get ( cachePath , key )
152
+ } catch ( err ) {
153
+ this . npm . log . warn ( `Not Found: ${ key } ` )
154
+ break
155
+ }
156
+ this . npm . output ( `Deleted: ${ key } ` )
157
+ await cacache . rm . entry ( cachePath , key )
158
+ await cacache . rm . content ( cachePath , entry . integrity )
90
159
}
91
- return rimraf ( cachePath )
92
160
}
93
161
94
162
// npm cache add <tarball-url>...
@@ -131,6 +199,24 @@ with --force.`)
131
199
this . npm . output ( `Index entries: ${ stats . totalEntries } ` )
132
200
this . npm . output ( `Finished in ${ stats . runTime . total / 1000 } s` )
133
201
}
202
+
203
+ // npm cache ls [--package <spec> ...]
204
+ async ls ( specs ) {
205
+ const cachePath = path . join ( this . npm . cache , '_cacache' )
206
+ const cacheKeys = Object . keys ( await cacache . ls ( cachePath ) )
207
+ if ( specs . length > 0 ) {
208
+ // get results for each package spec specified
209
+ const results = new Set ( )
210
+ for ( const spec of specs ) {
211
+ const keySet = await searchCachePackage ( cachePath , spec , cacheKeys )
212
+ for ( const key of keySet )
213
+ results . add ( key )
214
+ }
215
+ [ ...results ] . sort ( ( a , b ) => a . localeCompare ( b , 'en' ) ) . forEach ( key => this . npm . output ( key ) )
216
+ return
217
+ }
218
+ cacheKeys . sort ( ( a , b ) => a . localeCompare ( b , 'en' ) ) . forEach ( key => this . npm . output ( key ) )
219
+ }
134
220
}
135
221
136
222
module . exports = Cache
0 commit comments