1'use strict' 2 3module.exports = exports = search 4 5const npm = require('./npm.js') 6const allPackageSearch = require('./search/all-package-search') 7const figgyPudding = require('figgy-pudding') 8const formatPackageStream = require('./search/format-package-stream.js') 9const libSearch = require('libnpm/search') 10const log = require('npmlog') 11const ms = require('mississippi') 12const npmConfig = require('./config/figgy-config.js') 13const output = require('./utils/output.js') 14const usage = require('./utils/usage') 15 16search.usage = usage( 17 'search', 18 'npm search [--long] [search terms ...]' 19) 20 21search.completion = function (opts, cb) { 22 cb(null, []) 23} 24 25const SearchOpts = figgyPudding({ 26 description: {}, 27 exclude: {}, 28 include: {}, 29 limit: {}, 30 log: {}, 31 staleness: {}, 32 unicode: {} 33}) 34 35function search (args, cb) { 36 const opts = SearchOpts(npmConfig()).concat({ 37 description: npm.config.get('description'), 38 exclude: prepareExcludes(npm.config.get('searchexclude')), 39 include: prepareIncludes(args, npm.config.get('searchopts')), 40 limit: npm.config.get('searchlimit') || 20, 41 log: log, 42 staleness: npm.config.get('searchstaleness'), 43 unicode: npm.config.get('unicode') 44 }) 45 if (opts.include.length === 0) { 46 return cb(new Error('search must be called with arguments')) 47 } 48 49 // Used later to figure out whether we had any packages go out 50 let anyOutput = false 51 52 const entriesStream = ms.through.obj() 53 54 let esearchWritten = false 55 libSearch.stream(opts.include, opts).on('data', pkg => { 56 entriesStream.write(pkg) 57 !esearchWritten && (esearchWritten = true) 58 }).on('error', err => { 59 if (esearchWritten) { 60 // If esearch errored after already starting output, we can't fall back. 61 return entriesStream.emit('error', err) 62 } 63 log.warn('search', 'fast search endpoint errored. Using old search.') 64 allPackageSearch(opts) 65 .on('data', pkg => entriesStream.write(pkg)) 66 .on('error', err => entriesStream.emit('error', err)) 67 .on('end', () => entriesStream.end()) 68 }).on('end', () => entriesStream.end()) 69 70 // Grab a configured output stream that will spit out packages in the 71 // desired format. 72 var outputStream = formatPackageStream({ 73 args: args, // --searchinclude options are not highlighted 74 long: npm.config.get('long'), 75 description: npm.config.get('description'), 76 json: npm.config.get('json'), 77 parseable: npm.config.get('parseable'), 78 color: npm.color 79 }) 80 outputStream.on('data', chunk => { 81 if (!anyOutput) { anyOutput = true } 82 output(chunk.toString('utf8')) 83 }) 84 85 log.silly('search', 'searching packages') 86 ms.pipe(entriesStream, outputStream, err => { 87 if (err) return cb(err) 88 if (!anyOutput && !npm.config.get('json') && !npm.config.get('parseable')) { 89 output('No matches found for ' + (args.map(JSON.stringify).join(' '))) 90 } 91 log.silly('search', 'search completed') 92 log.clearProgress() 93 cb(null, {}) 94 }) 95} 96 97function prepareIncludes (args, searchopts) { 98 if (typeof searchopts !== 'string') searchopts = '' 99 return searchopts.split(/\s+/).concat(args).map(function (s) { 100 return s.toLowerCase() 101 }).filter(function (s) { return s }) 102} 103 104function prepareExcludes (searchexclude) { 105 var exclude 106 if (typeof searchexclude === 'string') { 107 exclude = searchexclude.split(/\s+/) 108 } else { 109 exclude = [] 110 } 111 return exclude.map(function (s) { 112 return s.toLowerCase() 113 }) 114} 115