• 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      constructor () {
72        super({ objectMode: true })
73      }
74
75      write (pkg) {
76        if (filter(pkg, opts.include, opts.exclude)) {
77          super.write(pkg)
78        }
79      }
80    }
81
82    const filterStream = new FilterStream()
83
84    // Grab a configured output stream that will spit out packages in the desired format.
85    const outputStream = await formatSearchStream({
86      args, // --searchinclude options are not highlighted
87      ...opts,
88    })
89
90    log.silly('search', 'searching packages')
91    const p = new Pipeline(
92      libSearch.stream(opts.include, opts),
93      filterStream,
94      outputStream
95    )
96
97    p.on('data', chunk => {
98      if (!anyOutput) {
99        anyOutput = true
100      }
101      this.npm.output(chunk.toString('utf8'))
102    })
103
104    await p.promise()
105    if (!anyOutput && !this.npm.config.get('json') && !this.npm.config.get('parseable')) {
106      this.npm.output('No matches found for ' + (args.map(JSON.stringify).join(' ')))
107    }
108
109    log.silly('search', 'search completed')
110    log.clearProgress()
111  }
112}
113module.exports = Search
114