|
|
|
var path = require('path')
|
|
|
|
var mkdirp = require('mkdirp')
|
|
|
|
var osenv = require('osenv')
|
|
|
|
var rimraf = require('rimraf')
|
|
|
|
var cacheFile = require('npm-cache-filename')
|
|
|
|
var test = require('tap').test
|
|
|
|
var Tacks = require('tacks')
|
|
|
|
var File = Tacks.File
|
|
|
|
|
|
|
|
var common = require('../common-tap.js')
|
|
|
|
|
|
|
|
var PKG_DIR = path.resolve(__dirname, 'search')
|
|
|
|
var CACHE_DIR = path.resolve(PKG_DIR, 'cache')
|
|
|
|
var cacheBase = cacheFile(CACHE_DIR)(common.registry + '/-/all')
|
|
|
|
var cachePath = path.join(cacheBase, '.cache.json')
|
|
|
|
|
|
|
|
test('setup', function (t) {
|
|
|
|
t.pass('all set up')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
|
|
|
|
test('notifies when there are no results', function (t) {
|
|
|
|
setup()
|
|
|
|
var now = Date.now()
|
|
|
|
var cacheContents = {
|
|
|
|
'_updated': now,
|
|
|
|
bar: { name: 'bar', version: '1.0.0' },
|
|
|
|
cool: { name: 'cool', version: '1.0.0' },
|
|
|
|
foo: { name: 'foo', version: '2.0.0' },
|
|
|
|
other: { name: 'other', version: '1.0.0' }
|
|
|
|
}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', 'nomatcheswhatsoeverfromthis',
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--loglevel', 'error',
|
|
|
|
'--cache', CACHE_DIR
|
|
|
|
], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.equal(stderr, '', 'no error output')
|
|
|
|
t.equal(code, 0, 'search gives 0 error code even if no matches')
|
|
|
|
t.match(stdout, /No matches found/, 'Useful message on search failure')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('spits out a useful error when no cache nor network', function (t) {
|
|
|
|
setup()
|
|
|
|
var cacheContents = {}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', 'foo',
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--loglevel', 'silly',
|
|
|
|
'--fetch-retry-mintimeout', 0,
|
|
|
|
'--fetch-retry-maxtimeout', 0,
|
|
|
|
'--cache', CACHE_DIR
|
|
|
|
], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.equal(code, 1, 'non-zero exit code')
|
|
|
|
t.equal(stdout, '', 'no stdout output')
|
|
|
|
t.match(stderr, /No search sources available/, 'useful error')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('can switch to JSON mode', function (t) {
|
|
|
|
setup()
|
|
|
|
var now = Date.now()
|
|
|
|
var cacheContents = {
|
|
|
|
'_updated': now,
|
|
|
|
bar: { name: 'bar', version: '1.0.0' },
|
|
|
|
cool: { name: 'cool', version: '1.0.0' },
|
|
|
|
foo: { name: 'foo', version: '2.0.0' },
|
|
|
|
other: { name: 'other', version: '1.0.0' }
|
|
|
|
}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', 'oo',
|
|
|
|
'--json',
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--loglevel', 'error',
|
|
|
|
'--cache', CACHE_DIR
|
|
|
|
], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.deepEquals(JSON.parse(stdout), [
|
|
|
|
{ name: 'cool', version: '1.0.0' },
|
|
|
|
{ name: 'foo', version: '2.0.0' }
|
|
|
|
], 'results returned as valid json')
|
|
|
|
t.equal(stderr, '', 'no error output')
|
|
|
|
t.equal(code, 0, 'search gives 0 error code even if no matches')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('JSON mode does not notify on empty', function (t) {
|
|
|
|
setup()
|
|
|
|
var now = Date.now()
|
|
|
|
var cacheContents = {
|
|
|
|
'_updated': now,
|
|
|
|
bar: { name: 'bar', version: '1.0.0' },
|
|
|
|
cool: { name: 'cool', version: '1.0.0' },
|
|
|
|
foo: { name: 'foo', version: '2.0.0' },
|
|
|
|
other: { name: 'other', version: '1.0.0' }
|
|
|
|
}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', 'nomatcheswhatsoeverfromthis',
|
|
|
|
'--json',
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--loglevel', 'error',
|
|
|
|
'--cache', CACHE_DIR
|
|
|
|
], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.deepEquals(JSON.parse(stdout), [], 'no notification about no results')
|
|
|
|
t.equal(stderr, '', 'no error output')
|
|
|
|
t.equal(code, 0, 'search gives 0 error code even if no matches')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('can switch to tab separated mode', function (t) {
|
|
|
|
setup()
|
|
|
|
var now = Date.now()
|
|
|
|
var cacheContents = {
|
|
|
|
'_updated': now,
|
|
|
|
bar: { name: 'bar', version: '1.0.0' },
|
|
|
|
cool: { name: 'cool', version: '1.0.0' },
|
|
|
|
foo: { name: 'foo', description: 'this\thas\ttabs', version: '2.0.0' },
|
|
|
|
other: { name: 'other', version: '1.0.0' }
|
|
|
|
}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', 'oo',
|
|
|
|
'--parseable',
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--loglevel', 'error',
|
|
|
|
'--cache', CACHE_DIR
|
|
|
|
], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.equal(stdout, 'cool\t\t\tprehistoric\t\t\nfoo\tthis has tabs\t\tprehistoric\t\t\n', 'correct output, including replacing tabs in descriptions')
|
|
|
|
t.equal(stderr, '', 'no error output')
|
|
|
|
t.equal(code, 0, 'search gives 0 error code even if no matches')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('tab mode does not notify on empty', function (t) {
|
|
|
|
setup()
|
|
|
|
var now = Date.now()
|
|
|
|
var cacheContents = {
|
|
|
|
'_updated': now,
|
|
|
|
bar: { name: 'bar', version: '1.0.0' },
|
|
|
|
cool: { name: 'cool', version: '1.0.0' },
|
|
|
|
foo: { name: 'foo', version: '2.0.0' },
|
|
|
|
other: { name: 'other', version: '1.0.0' }
|
|
|
|
}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', 'nomatcheswhatsoeverfromthis',
|
|
|
|
'--parseable',
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--loglevel', 'error',
|
|
|
|
'--cache', CACHE_DIR
|
|
|
|
], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.equal(stdout, '', 'no notification about no results')
|
|
|
|
t.equal(stderr, '', 'no error output')
|
|
|
|
t.equal(code, 0, 'search gives 0 error code even if no matches')
|
|
|
|
t.done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('no arguments provided should error', function (t) {
|
|
|
|
cleanup()
|
|
|
|
common.npm(['search'], {}, function (err, code, stdout, stderr) {
|
|
|
|
if (err) throw err
|
|
|
|
t.equal(code, 1, 'search finished unsuccessfully')
|
|
|
|
|
|
|
|
t.match(
|
|
|
|
stderr,
|
|
|
|
/search must be called with arguments/,
|
|
|
|
'should have correct error message'
|
|
|
|
)
|
|
|
|
t.end()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
var searches = [
|
|
|
|
{
|
|
|
|
term: 'cool',
|
|
|
|
description: 'non-regex search',
|
|
|
|
location: 103
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: '/cool/',
|
|
|
|
description: 'regex search',
|
|
|
|
location: 103
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'cool',
|
|
|
|
description: 'searches name field',
|
|
|
|
location: 103
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'ool',
|
|
|
|
description: 'excludes matches for --searchexclude',
|
|
|
|
location: 205,
|
|
|
|
inject: {
|
|
|
|
other: { name: 'other', description: 'this is a simple tool' }
|
|
|
|
},
|
|
|
|
extraOpts: ['--searchexclude', 'cool']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'neat lib',
|
|
|
|
description: 'searches description field',
|
|
|
|
location: 141,
|
|
|
|
inject: {
|
|
|
|
cool: {
|
|
|
|
name: 'cool', version: '5.0.0', description: 'this is a neat lib'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'foo',
|
|
|
|
description: 'skips description field with --no-description',
|
|
|
|
location: 80,
|
|
|
|
inject: {
|
|
|
|
cool: {
|
|
|
|
name: 'cool', version: '5.0.0', description: 'foo bar!'
|
|
|
|
}
|
|
|
|
},
|
|
|
|
extraOpts: ['--no-description']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'zkat',
|
|
|
|
description: 'searches maintainers by name',
|
|
|
|
location: 155,
|
|
|
|
inject: {
|
|
|
|
cool: {
|
|
|
|
name: 'cool',
|
|
|
|
version: '5.0.0',
|
|
|
|
maintainers: [{
|
|
|
|
name: 'zkat'
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: '=zkat',
|
|
|
|
description: 'searches maintainers unambiguously by =name',
|
|
|
|
location: 154,
|
|
|
|
inject: {
|
|
|
|
bar: { name: 'bar', description: 'zkat thing', version: '1.0.0' },
|
|
|
|
cool: {
|
|
|
|
name: 'cool',
|
|
|
|
version: '5.0.0',
|
|
|
|
maintainers: [{
|
|
|
|
name: 'zkat'
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'github.com',
|
|
|
|
description: 'searches projects by url',
|
|
|
|
location: 205,
|
|
|
|
inject: {
|
|
|
|
bar: {
|
|
|
|
name: 'bar',
|
|
|
|
url: 'gitlab.com/bar',
|
|
|
|
// For historical reasons, `url` is only present if `versions` is there
|
|
|
|
versions: ['1.0.0'],
|
|
|
|
version: '1.0.0'
|
|
|
|
},
|
|
|
|
cool: {
|
|
|
|
name: 'cool',
|
|
|
|
version: '5.0.0',
|
|
|
|
versions: ['1.0.0'],
|
|
|
|
url: 'github.com/cool/cool'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
term: 'monad',
|
|
|
|
description: 'searches projects by keywords',
|
|
|
|
location: 197,
|
|
|
|
inject: {
|
|
|
|
cool: {
|
|
|
|
name: 'cool',
|
|
|
|
version: '5.0.0',
|
|
|
|
keywords: ['monads']
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
searches.forEach(function (search) {
|
|
|
|
test(search.description, function (t) {
|
|
|
|
setup()
|
|
|
|
var now = Date.now()
|
|
|
|
var cacheContents = {
|
|
|
|
'_updated': now,
|
|
|
|
bar: { name: 'bar', version: '1.0.0' },
|
|
|
|
cool: { name: 'cool', version: '5.0.0' },
|
|
|
|
foo: { name: 'foo', version: '2.0.0' },
|
|
|
|
other: { name: 'other', version: '1.0.0' }
|
|
|
|
}
|
|
|
|
for (var k in search.inject) {
|
|
|
|
cacheContents[k] = search.inject[k]
|
|
|
|
}
|
|
|
|
var fixture = new Tacks(File(cacheContents))
|
|
|
|
fixture.create(cachePath)
|
|
|
|
common.npm([
|
|
|
|
'search', search.term,
|
|
|
|
'--registry', common.registry,
|
|
|
|
'--cache', CACHE_DIR,
|
|
|
|
'--loglevel', 'error',
|
|
|
|
'--color', 'always'
|
|
|
|
].concat(search.extraOpts || []),
|
|
|
|
{},
|
|
|
|
function (err, code, stdout, stderr) {
|
|
|
|
t.equal(stderr, '', 'no error output')
|
|
|
|
t.notEqual(stdout, '', 'got output')
|
|
|
|
t.equal(code, 0, 'search finished successfully')
|
|
|
|
t.ifErr(err, 'search finished successfully')
|
|
|
|
// \033 == \u001B
|
|
|
|
var markStart = '\u001B\\[[0-9][0-9]m'
|
|
|
|
var markEnd = '\u001B\\[0m'
|
|
|
|
|
|
|
|
var re = new RegExp(markStart + '.*?' + markEnd)
|
|
|
|
|
|
|
|
var cnt = stdout.search(re)
|
|
|
|
t.equal(
|
|
|
|
cnt,
|
|
|
|
search.location,
|
|
|
|
search.description + ' search for ' + search.term
|
|
|
|
)
|
|
|
|
t.end()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('cleanup', function (t) {
|
|
|
|
cleanup()
|
|
|
|
t.end()
|
|
|
|
})
|
|
|
|
|
|
|
|
function setup () {
|
|
|
|
cleanup()
|
|
|
|
mkdirp.sync(cacheBase)
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanup () {
|
|
|
|
process.chdir(osenv.tmpdir())
|
|
|
|
rimraf.sync(PKG_DIR)
|
|
|
|
}
|