UNPKG

3.44 kBJavaScriptView Raw
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}