@@ -4,7 +4,59 @@ const log = require('npmlog')
44const pacote = require ( 'pacote' )
55const path = require ( 'path' )
66const rimraf = promisify ( require ( 'rimraf' ) )
7+ const semver = require ( 'semver' )
78const 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+ }
860
961class Cache extends BaseCommand {
1062 static get description ( ) {
@@ -29,21 +81,24 @@ class Cache extends BaseCommand {
2981 'add <tarball url>' ,
3082 'add <git url>' ,
3183 'add <name>@<version>' ,
32- 'clean' ,
84+ 'clean [<key>]' ,
85+ 'ls [<name>@<version>]' ,
3386 'verify' ,
3487 ]
3588 }
3689
3790 async completion ( opts ) {
3891 const argv = opts . conf . argv . remain
3992 if ( argv . length === 2 )
40- return [ 'add' , 'clean' , 'verify' ]
93+ return [ 'add' , 'clean' , 'verify' , 'ls' , 'delete' ]
4194
4295 // TODO - eventually...
4396 switch ( argv [ 2 ] ) {
4497 case 'verify' :
4598 case 'clean' :
4699 case 'add' :
100+ case 'ls' :
101+ case 'delete' :
47102 return [ ]
48103 }
49104 }
@@ -61,34 +116,47 @@ class Cache extends BaseCommand {
61116 return await this . add ( args )
62117 case 'verify' : case 'check' :
63118 return await this . verify ( )
119+ case 'ls' :
120+ return await this . ls ( args )
64121 default :
65122 throw Object . assign ( new Error ( this . usage ) , { code : 'EUSAGE' } )
66123 }
67124 }
68125
69126 // npm cache clean [pkg]*
70127 async clean ( args ) {
71- if ( args . length )
72- throw new Error ( 'npm cache clear does not accept arguments' )
73-
74128 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 )
90159 }
91- return rimraf ( cachePath )
92160 }
93161
94162 // npm cache add <tarball-url>...
@@ -131,6 +199,24 @@ with --force.`)
131199 this . npm . output ( `Index entries: ${ stats . totalEntries } ` )
132200 this . npm . output ( `Finished in ${ stats . runTime . total / 1000 } s` )
133201 }
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+ }
134220}
135221
136222module . exports = Cache
0 commit comments