1const { Minipass } = require('minipass') 2const Pipeline = require('minipass-pipeline') 3const libSearch = require('libnpmsearch') 4const log = require('../utils/log-shim.js') 5 6const formatSearchStream = require('../utils/format-search-stream.js') 7 8function filter (data, include, exclude) { 9 const words = [data.name] 10 .concat(data.maintainers.map(m => `=${m.username}`)) 11 .concat(data.keywords || []) 12 .map(f => f && f.trim && f.trim()) 13 .filter(f => f) 14 .join(' ') 15 .toLowerCase() 16 17 if (exclude.find(e => match(words, e))) { 18 return false 19 } 20 21 return true 22} 23 24function match (words, pattern) { 25 if (pattern.startsWith('/')) { 26 if (pattern.endsWith('/')) { 27 pattern = pattern.slice(0, -1) 28 } 29 pattern = new RegExp(pattern.slice(1)) 30 return words.match(pattern) 31 } 32 return words.indexOf(pattern) !== -1 33} 34 35const BaseCommand = require('../base-command.js') 36class Search extends BaseCommand { 37 static description = 'Search for packages' 38 static name = 'search' 39 static params = [ 40 'long', 41 'json', 42 'color', 43 'parseable', 44 'description', 45 'searchopts', 46 'searchexclude', 47 'registry', 48 'prefer-online', 49 'prefer-offline', 50 'offline', 51 ] 52 53 static usage = ['[search terms ...]'] 54 55 async exec (args) { 56 const opts = { 57 ...this.npm.flatOptions, 58 ...this.npm.flatOptions.search, 59 include: args.map(s => s.toLowerCase()).filter(s => s), 60 exclude: this.npm.flatOptions.search.exclude.split(/\s+/), 61 } 62 63 if (opts.include.length === 0) { 64 throw new Error('search must be called with arguments') 65 } 66 67 // Used later to figure out whether we had any packages go out 68 let anyOutput = false 69 70 class FilterStream extends Minipass { 71 write (pkg) { 72 if (filter(pkg, opts.include, opts.exclude)) { 73 super.write(pkg) 74 } 75 } 76 } 77 78 const filterStream = new FilterStream() 79 80 // Grab a configured output stream that will spit out packages in the 81 // desired format. 82 const outputStream = formatSearchStream({ 83 args, // --searchinclude options are not highlighted 84 ...opts, 85 }) 86 87 log.silly('search', 'searching packages') 88 const p = new Pipeline( 89 libSearch.stream(opts.include, opts), 90 filterStream, 91 outputStream 92 ) 93 94 p.on('data', chunk => { 95 if (!anyOutput) { 96 anyOutput = true 97 } 98 this.npm.output(chunk.toString('utf8')) 99 }) 100 101 await p.promise() 102 if (!anyOutput && !this.npm.config.get('json') && !this.npm.config.get('parseable')) { 103 this.npm.output('No matches found for ' + (args.map(JSON.stringify).join(' '))) 104 } 105 106 log.silly('search', 'search completed') 107 log.clearProgress() 108 } 109} 110module.exports = Search 111