1 | 'use strict'
|
2 |
|
3 | module.exports = exports = search
|
4 |
|
5 | const npm = require('./npm.js')
|
6 | const allPackageSearch = require('./search/all-package-search')
|
7 | const figgyPudding = require('figgy-pudding')
|
8 | const formatPackageStream = require('./search/format-package-stream.js')
|
9 | const libSearch = require('libnpm/search')
|
10 | const log = require('npmlog')
|
11 | const ms = require('mississippi')
|
12 | const npmConfig = require('./config/figgy-config.js')
|
13 | const output = require('./utils/output.js')
|
14 | const usage = require('./utils/usage')
|
15 |
|
16 | search.usage = usage(
|
17 | 'search',
|
18 | 'npm search [--long] [search terms ...]'
|
19 | )
|
20 |
|
21 | search.completion = function (opts, cb) {
|
22 | cb(null, [])
|
23 | }
|
24 |
|
25 | const SearchOpts = figgyPudding({
|
26 | description: {},
|
27 | exclude: {},
|
28 | include: {},
|
29 | limit: {},
|
30 | log: {},
|
31 | staleness: {},
|
32 | unicode: {}
|
33 | })
|
34 |
|
35 | function 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 |
|
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 |
|
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 |
|
71 |
|
72 | var outputStream = formatPackageStream({
|
73 | args: args,
|
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 |
|
97 | function 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 |
|
104 | function 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 | }
|