• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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