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