lib/search.js
var Promise = require('bluebird')
, async = require('async')
, semver = require('semver')
, _ = require('lodash')
, https = require('follow-redirects').https
, utils = GLOBAL.lib.utils
, _pkg = GLOBAL.lib.install;
var _keywordRequired = [ 'cleverstack module', 'cleverstack-module', 'cleverstack seed', 'cleverstack-seed' ];
Promise.longStackTraces();
/**
* Searchies <queries> within the NPM registry
*
* @param {String[]} queries
* @return {Promise}
* @api public
*/
var searchNPMRegistry = exports.npmRegistry = function(queries) {
return new Promise(function(resolve, reject) {
var repos = [];
async.each(
queries,
function(q, next) {
var search = q.split('@');
if (typeof search[1] === 'undefined') {
search[1] = '*';
}
_pkg.getJSON({
hostname: 'registry.npmjs.org',
path: '/' + encodeURIComponent(search[0])
})
.then(function(body) {
if (typeof body === 'undefined' || (body.error && body.error === 'not_found')) {
return next();
}
var versions = Object.keys(body.versions)
, maxVersion = semver.maxSatisfying(versions, search[1])
, pkg = body.versions[maxVersion];
if (typeof pkg === 'undefined') {
utils.fail('Invalid version ' + search[1] + ' for module ' + search[0], true);
return next();
}
if (_.intersection(pkg.keywords, _keywordRequired).length > 0) {
if (typeof pkg.repository === 'object' && pkg.repository.hasOwnProperty('url')) {
pkg.homepage = pkg.repository.url;
}
repos.push(pkg);
}
next();
});
},
function(err) {
if (!!err) {
return reject(err);
}
resolve(repos);
});
});
};
/**
* Lists all of the CleverStack-friendly
* modules within the NPM registry
*
* @return {Promise}
* @api public
*/
var list = exports.list = function() {
return new Promise(function(resolve, reject) {
_pkg
.getJSON({
hostname: 'registry.npmjs.org',
path: '/-/_view/byKeyword?startkey=["cleverstack"]&endkey=["cleverstack",{}]&group_level=2'
})
.then(function(body) {
if (typeof body !== 'object' || !body.hasOwnProperty('rows')) {
return resolve([]);
}
var rows = body.rows.map(function(r) {
return r.key[ 1 ];
});
searchNPMRegistry(rows)
.then(function(response) {
resolve(response);
})
.catch(function (err) {
reject(err);
});
});
});
};
/**
* Searches through the NPM registry with a
* info message, if queries is empty then simply return resolves
*
* @param {String[]} queries
* @return {Promise}
* @api public
*/
var searchNPM = exports.npm = function(queries) {
return new Promise(function(resolve, reject) {
utils.info(' Searching NPM...');
lib.utils.running('Searching NPM...');
if (queries.length < 1) {
list()
.then(function(res) {
resolve(res);
})
.catch(function(err) {
reject(err);
});
} else {
searchNPMRegistry(queries)
.then(function(res) {
resolve(res);
})
.catch(function(err) {
reject(err);
});
}
});
};
/**
* Lists all CleverStack-friendly modules within the Bower registry
*
* @return {Promise}
* @api private
*/
function listBower() {
return new Promise(function(resolve) {
var repos = [];
utils.info(' Searching Bower...');
lib.utils.running('Searching Bower...');
_pkg.getJSON({
hostname: 'bower-component-list.herokuapp.com',
path: '/keyword/cleverstack'
})
.then(function(body) {
if (!Array.isArray(body)) {
return resolve(repos);
}
async.each(
body,
function(module, next) {
_pkg.getJSON({
hostname: 'bower.herokuapp.com',
path: '/packages/' + encodeURIComponent(module.name)
})
.then(function(bower) {
if (!bower || !bower.url) {
return next();
}
module.url = bower.url;
repos.push(module);
next();
});
}, function() {
resolve(repos);
});
});
});
}
/**
* Returns specific information from a Bower package
* It will try to find if the version exists through
* the packages GitHub page (this is what Bower does anyway)
*
* @param {String} query
* @return {Promise}
* @api public
*/
var searchBower = exports.bower = function(query) {
return new Promise(function(resolve) {
var pkg = query.split('@');
_pkg.getJSON({
hostname: 'bower.herokuapp.com',
path: '/packages/' + encodeURIComponent(pkg[ 0 ])
})
.then(function (bower) {
if (typeof bower === 'undefined') {
return resolve();
} else if (typeof pkg[ 1 ] === 'undefined') {
return resolve(bower);
}
utils.info(' Getting bower package information for ' + pkg[ 0 ] + '...');
var req = https.request({
hostname: 'github.com',
path: '/' + bower.url.split('/').splice(-2).join('/').replace('.git', '') + '/tree/' + pkg[ 1 ],
headers: {
'User-Agent': 'cleverstack'
}
},
function (res) {
if (res.statusCode !== 200) {
utils.fail('Couldn\'t find version ' + pkg[ 1 ] + ' for ' + pkg[ 0 ], true);
return resolve();
}
bower.version = pkg[ 1 ];
bower.bower = true;
resolve(bower);
}
);
req.end();
});
});
};
/**
* Aggregates searches from NPM and Bower
*
* @param {String[]} args
* @return {Promise}
* @api public
*/
exports.aggregate = function(args) {
return new Promise(function(resolve, reject) {
var all = [];
utils.info(' Searching for modules...');
lib.utils.running('Searching for modules...');
all.push(searchNPM(args));
all.push(listBower());
args.forEach(function(arg) {
all.push(searchBower(arg));
});
Promise
.all(all)
.then(function(results) {
var npm = results[ 0 ].map(function (r) { r.type = 'backend'; return r; })
, keys = results[ 1 ]
, repos = [];
results.shift();
results.shift();
var bower = (args.length < 1 ? keys : results);
if (!Array.isArray(bower)) {
bower = [];
}
bower.filter(function(res) {
return typeof res === 'object' && res.hasOwnProperty('url');
}).forEach(function(rep) {
var cleverStackBower = _.find(keys, function(key) {
return key.name === rep.name;
});
if (cleverStackBower) {
rep = _.merge(cleverStackBower, rep);
rep.type = 'frontend';
repos.push(rep);
}
});
resolve([ npm, repos ]);
})
.catch(function(err) {
reject(err);
});
});
};