mirror of
https://github.com/bower/bower.git
synced 2026-02-11 14:34:58 -05:00
Initial take on the commands + renderers + cli.
This commit is contained in:
17
lib/commands/help.js
Normal file
17
lib/commands/help.js
Normal file
@@ -0,0 +1,17 @@
|
||||
var Emitter = require('events').EventEmitter;
|
||||
|
||||
function help(name) {
|
||||
var emitter = new Emitter();
|
||||
|
||||
// TODO
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
module.exports = help;
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
// TODO
|
||||
return help();
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
|
||||
};
|
||||
'help': require('./help'),
|
||||
'install': require('./install')
|
||||
};
|
||||
|
||||
50
lib/commands/install.js
Normal file
50
lib/commands/install.js
Normal file
@@ -0,0 +1,50 @@
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var Project = require('../core/Project');
|
||||
var cli = require('../util/cli');
|
||||
var help = require('./help');
|
||||
|
||||
function install(endpoints, options) {
|
||||
var project;
|
||||
var emitter = new Emitter();
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Sanitize endpoints
|
||||
if (endpoints && !endpoints.length) {
|
||||
endpoints = null;
|
||||
}
|
||||
|
||||
project = new Project(options);
|
||||
project.install(endpoints)
|
||||
.then(function (installed) {
|
||||
emitter.emit('end', installed);
|
||||
}, function (error) {
|
||||
emitter.emit('error', error);
|
||||
}, function (notification) {
|
||||
emitter.emit('data', notification);
|
||||
});
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
module.exports = install;
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = module.exports.options(argv);
|
||||
|
||||
return options.help ?
|
||||
help('install') :
|
||||
install(options.argv.remain.slice(1), options);
|
||||
};
|
||||
|
||||
module.exports.options = function (argv) {
|
||||
return cli.readOptions(argv, {
|
||||
'save': { type: Boolean, shorthand: 'S' },
|
||||
'save-dev': { type: Boolean, shorthand: 'D' },
|
||||
'force': { type: Boolean, shorthand: 'f' },
|
||||
'offline': { type: Boolean, shorthand: 'o' },
|
||||
'production': { type: Boolean, shorthand: 'p' }
|
||||
});
|
||||
};
|
||||
@@ -100,8 +100,10 @@ Manager.prototype.install = function () {
|
||||
var release = decEndpoint.pkgMeta._release;
|
||||
|
||||
deferred.notify({
|
||||
type: 'action',
|
||||
data: 'Installing' + (release ? ' "' + release + '"' : ''),
|
||||
level: 'action',
|
||||
tag: 'install',
|
||||
data: name + (release ? '#' + release : ''),
|
||||
pkgMeta: decEndpoint.pkgMeta,
|
||||
origin: name,
|
||||
endpoint: decEndpoint
|
||||
});
|
||||
@@ -181,6 +183,8 @@ Manager.prototype.areCompatible = function (first, second) {
|
||||
|
||||
Manager.prototype._fetch = function (decEndpoint) {
|
||||
var name = decEndpoint.name;
|
||||
var deferred = this._deferred;
|
||||
var that = this;
|
||||
|
||||
// Mark as being fetched
|
||||
this._fetching[name] = this._fetching[name] || [];
|
||||
@@ -192,24 +196,36 @@ Manager.prototype._fetch = function (decEndpoint) {
|
||||
// because it might be reused if a similar endpoint needs to be resolved
|
||||
decEndpoint.promise = this._repository.fetch(decEndpoint)
|
||||
// When done, call onFetch
|
||||
.spread(this._onFetch.bind(this, decEndpoint))
|
||||
.spread(this._onFetch.bind(this, deferred, decEndpoint))
|
||||
// If it fails, we make the whole process to error out
|
||||
.fail(function (err) {
|
||||
err.origin = that._getOrigin(decEndpoint);
|
||||
err.endpoint = decEndpoint;
|
||||
deferred.reject(err);
|
||||
})
|
||||
// Listen to progress to proxy them to the resolve deferred
|
||||
// Note that we also mark where the notification is coming from
|
||||
.progress(function (notification) {
|
||||
notification.origin = that._getOrigin(decEndpoint);
|
||||
notification.endpoint = decEndpoint;
|
||||
notification.origin = name || decEndpoint.registryName || decEndpoint.resolverName;
|
||||
this._deferred.notify(notification);
|
||||
}.bind(this));
|
||||
deferred.notify(notification);
|
||||
});
|
||||
|
||||
return decEndpoint.promise;
|
||||
};
|
||||
|
||||
Manager.prototype._onFetch = function (decEndpoint, canonicalPkg, pkgMeta) {
|
||||
Manager.prototype._onFetch = function (deferred, decEndpoint, canonicalPkg, pkgMeta) {
|
||||
var name;
|
||||
var resolved;
|
||||
var index;
|
||||
var initialName = decEndpoint.name;
|
||||
|
||||
// If the deferred associated with the process is already rejected,
|
||||
// do not proceed.
|
||||
if (deferred.promise.isRejected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from being fetched list
|
||||
mout.array.remove(this._fetching[initialName], decEndpoint);
|
||||
this._nrFetching--;
|
||||
@@ -373,6 +389,10 @@ Manager.prototype._dissect = function () {
|
||||
this._deferred.resolve(pkgMetas);
|
||||
};
|
||||
|
||||
Manager.prototype._getOrigin = function (decEndpoint) {
|
||||
return decEndpoint.name || decEndpoint.registryName || decEndpoint.resolverName;
|
||||
};
|
||||
|
||||
Manager.prototype._findHighestVersion = function (comparators) {
|
||||
var highest;
|
||||
var matches;
|
||||
@@ -403,4 +423,4 @@ Manager.prototype._findHighestVersion = function (comparators) {
|
||||
return highest;
|
||||
};
|
||||
|
||||
module.exports = Manager;
|
||||
module.exports = Manager;
|
||||
|
||||
@@ -25,7 +25,7 @@ var PackageRepository = function (options) {
|
||||
// -----------------
|
||||
|
||||
PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
var resolver;
|
||||
var res;
|
||||
var deferred = Q.defer();
|
||||
var that = this;
|
||||
|
||||
@@ -33,15 +33,20 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
resolverFactory(decEndpoint, this._options)
|
||||
// Decide if we retrieve from the cache or not
|
||||
// Also decide we if validate the cached entry or not
|
||||
.then(function (res) {
|
||||
resolver = res;
|
||||
.then(function (resolver) {
|
||||
res = resolver;
|
||||
|
||||
// Set the resolver name in the decEndpoint
|
||||
decEndpoint.resolverName = res.getName();
|
||||
decEndpoint.resolverName = resolver.getName();
|
||||
|
||||
// If force flag is used, bypass cache
|
||||
if (that._options.force) {
|
||||
deferred.notify({ type: 'action', data: 'Resolving' });
|
||||
deferred.notify({
|
||||
level: 'action',
|
||||
tag: 'resolve',
|
||||
resolver: resolver,
|
||||
data: 'Resolving ' + resolver.getSource(),
|
||||
});
|
||||
return that._resolve(resolver);
|
||||
}
|
||||
|
||||
@@ -54,23 +59,50 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
if (!canonicalPkg) {
|
||||
// And the offline flag is passed, error out
|
||||
if (that._options.offline) {
|
||||
throw createError('No cached version for ' + resolver.getTarget(), 'ENOCACHE');
|
||||
throw createError('No cached version for ' + resolver.getSource() + '#' + resolver.getTarget(), 'ENOCACHE', {
|
||||
resolver: resolver
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise, we have to resolve it
|
||||
deferred.notify({ type: 'action', data: 'No cached version, resolving..' });
|
||||
deferred.notify({
|
||||
level: 'info',
|
||||
tag: 'not-cached',
|
||||
data: 'No cached version for ' + resolver.getSource() + '#' + resolver.getTarget(),
|
||||
});
|
||||
deferred.notify({
|
||||
level: 'action',
|
||||
tag: 'resolve',
|
||||
resolver: resolver,
|
||||
data: 'Resolving ' + resolver.getSource() + '#' + resolver.getTarget(),
|
||||
});
|
||||
|
||||
return that._resolve(resolver);
|
||||
}
|
||||
|
||||
deferred.notify({
|
||||
level: 'info',
|
||||
tag: 'cached',
|
||||
data: 'Got cached ' + resolver.getSource() + (pkgMeta._release ? '#' + pkgMeta._release: '') + ' entry',
|
||||
canonicalPkg: canonicalPkg,
|
||||
pkgMeta: pkgMeta
|
||||
});
|
||||
|
||||
// If offline flag is used, use directly the cached one
|
||||
if (that._options.offline) {
|
||||
deferred.notify({ type: 'action', data: 'Got cached version' });
|
||||
return [canonicalPkg, pkgMeta];
|
||||
}
|
||||
|
||||
// Otherwise check for new contents
|
||||
process.nextTick(function () {
|
||||
deferred.notify({ type: 'action', data: 'Got cached version, validating..' });
|
||||
deferred.notify({
|
||||
level: 'action',
|
||||
tag: 'check',
|
||||
data: 'Checking ' + resolver.getSource() + '#' + resolver.getTarget() + ' for newer version',
|
||||
canonicalPkg: canonicalPkg,
|
||||
pkgMeta: pkgMeta,
|
||||
resolver: resolver
|
||||
});
|
||||
});
|
||||
|
||||
return resolver.hasNew(canonicalPkg, pkgMeta)
|
||||
@@ -82,13 +114,28 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
}
|
||||
|
||||
// Otherwise resolve to the newest one
|
||||
deferred.notify({ type: 'action', data: 'There\'s a new version, resolving..' });
|
||||
deferred.notify({
|
||||
type: 'info',
|
||||
data: 'There\'s a new version for ' + resolver.getSource() + '#' + resolver.getTarget(),
|
||||
canonicalPkg: canonicalPkg,
|
||||
pkgMeta: pkgMeta,
|
||||
resolver: resolver
|
||||
});
|
||||
deferred.notify({
|
||||
type: 'resolve',
|
||||
resolver: resolver,
|
||||
data: 'Resolving ' + resolver.getSource() + '#' + resolver.getTarget()
|
||||
});
|
||||
|
||||
return that._resolve(resolver);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(deferred.resolve, deferred.reject, deferred.notify);
|
||||
.then(deferred.resolve, deferred.reject, function (notification) {
|
||||
// Store the resolver for each notification
|
||||
notification.resolver = res;
|
||||
deferred.notify(notification);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
@@ -115,4 +162,4 @@ PackageRepository.prototype._resolve = function (resolver) {
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
module.exports = PackageRepository;
|
||||
module.exports = PackageRepository;
|
||||
|
||||
@@ -184,7 +184,9 @@ Project.prototype._readJson = function () {
|
||||
if (path.basename(filename) === 'component.json') {
|
||||
process.nextTick(function () {
|
||||
deferred.notify({
|
||||
type: 'warn',
|
||||
level: 'warn',
|
||||
tag: 'deprecated',
|
||||
json: filename,
|
||||
data: 'You are using the deprecated component.json file'
|
||||
});
|
||||
});
|
||||
@@ -285,4 +287,4 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
|
||||
}, this);
|
||||
};
|
||||
|
||||
module.exports = Project;
|
||||
module.exports = Project;
|
||||
|
||||
@@ -208,4 +208,4 @@ ResolveCache.prototype._sortVersions = function (versions) {
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = ResolveCache;
|
||||
module.exports = ResolveCache;
|
||||
|
||||
@@ -103,7 +103,7 @@ function createResolver(decEndpoint, options) {
|
||||
});
|
||||
};
|
||||
})
|
||||
// If we got the func, simply call and return
|
||||
// If we got the function, simply call and return
|
||||
.then(function (func) {
|
||||
return func();
|
||||
// Finally throw a meaningful error
|
||||
|
||||
@@ -40,18 +40,30 @@ GitResolver.prototype._hasNew = function (canonicalPkg, pkgMeta) {
|
||||
GitResolver.prototype._resolve = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
deferred.notify({ type: 'action', data: 'Finding resolution' });
|
||||
deferred.notify({
|
||||
level: 'action',
|
||||
tag: 'versions',
|
||||
data: 'Finding resolution'
|
||||
});
|
||||
|
||||
this._findResolution()
|
||||
.then(function (resolution) {
|
||||
deferred.notify({ type: 'action', data: 'Checking out "' + (resolution.tag || resolution.branch || resolution.commit) + '"' });
|
||||
deferred.notify({
|
||||
level: 'action',
|
||||
tag: 'checkout',
|
||||
data: 'Checking out "' + (resolution.tag || resolution.branch || resolution.commit) + '"'
|
||||
});
|
||||
|
||||
return this._checkout()
|
||||
// Always run cleanup after checkout to ensure that .git is removed!
|
||||
// If it's not removed, problems might arise when the "tmp" module attempts
|
||||
// to delete the temporary folder
|
||||
.fin(function () {
|
||||
deferred.notify({ type: 'action', data: 'Cleaning up' });
|
||||
deferred.notify({
|
||||
level: 'action',
|
||||
tag: 'cleanup',
|
||||
data: 'Cleaning up git artifacts'
|
||||
});
|
||||
return this._cleanup();
|
||||
}.bind(this));
|
||||
}.bind(this))
|
||||
@@ -179,7 +191,8 @@ GitResolver.prototype._savePkgMeta = function (meta) {
|
||||
if (typeof meta.version === 'string' && semver.neq(meta.version, version)) {
|
||||
process.nextTick(function (metaVersion) {
|
||||
deferred.notify({
|
||||
type: 'warn',
|
||||
level: 'warn',
|
||||
tag: 'mismatch',
|
||||
data: 'Version declared in the json (' + metaVersion + ') is different than the resolved one (' + version + ')'
|
||||
});
|
||||
}.bind(this, meta.version));
|
||||
@@ -194,7 +207,7 @@ GitResolver.prototype._savePkgMeta = function (meta) {
|
||||
}
|
||||
|
||||
// Save version/commit/branch/tag in the release
|
||||
meta._release = version || this._resolution.tag || this._resolution.commit;
|
||||
meta._release = version || this._resolution.tag || this._resolution.commit.substr(0, 10);
|
||||
|
||||
// Save resolution to be used in hasNew later
|
||||
meta._resolution = this._resolution;
|
||||
|
||||
@@ -155,7 +155,9 @@ Resolver.prototype._readJson = function (dir) {
|
||||
// If it is a component.json, warn about the deprecation
|
||||
if (path.basename(filename) === 'component.json') {
|
||||
deferred.notify({
|
||||
type: 'warn',
|
||||
level: 'warn',
|
||||
tag: 'deprecated',
|
||||
json: filename,
|
||||
data: 'Package "' + this._name + '" is using the deprecated component.json file'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ UrlResolver.prototype._savePkgMeta = function (meta) {
|
||||
|
||||
// Store ETAG under _release
|
||||
if (meta._cacheHeaders.ETag) {
|
||||
meta._release = 'e-tag: ' + mout.string.trim(meta._cacheHeaders.ETag, '"');
|
||||
meta._release = 'e-tag:' + mout.string.trim(meta._cacheHeaders.ETag.substr(0, 10), '"');
|
||||
}
|
||||
|
||||
// Store main if is a single file
|
||||
|
||||
94
lib/renderers/cli.js
Normal file
94
lib/renderers/cli.js
Normal file
@@ -0,0 +1,94 @@
|
||||
var mout = require('mout');
|
||||
|
||||
var paddings = {
|
||||
tag: 10,
|
||||
tagPlusLabel: 30
|
||||
};
|
||||
|
||||
var tagColors = {
|
||||
'warn': 'yellow',
|
||||
'error': 'red',
|
||||
'_default': 'cyan',
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
|
||||
function renderData(data) {
|
||||
// Ensure data
|
||||
data.data = data.data || '';
|
||||
|
||||
return 'bower ' + renderTagPlusLabel(data) + ' ' + data.data + '\n';
|
||||
}
|
||||
|
||||
function renderError(err) {
|
||||
var str;
|
||||
|
||||
err.level = 'error';
|
||||
err.tag = err.code;
|
||||
|
||||
str = 'bower ' + renderTagPlusLabel(err) + ' ' + err.message + '\n';
|
||||
|
||||
// Check if additional details were provided
|
||||
if (err.details) {
|
||||
str += err.details + '\n';
|
||||
}
|
||||
|
||||
// Print trace
|
||||
str += '\n' + err.stack + '\n';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function renderEnd() {
|
||||
return '';
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
|
||||
function empty() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function uncolor(str) {
|
||||
return str.replace(/\x1B\[\d+m/g, '');
|
||||
}
|
||||
|
||||
function renderTagPlusLabel(data) {
|
||||
var label;
|
||||
var length;
|
||||
var nrSpaces;
|
||||
var tag = data.tag;
|
||||
var tagColor = tagColors[data.level] || tagColors._default;
|
||||
|
||||
// If there's not enough space, print only the tag
|
||||
if (process.stdout.columns < 120) {
|
||||
return mout.string.rpad(tag, paddings.tag)[tagColor];
|
||||
}
|
||||
|
||||
label = data.origin + '#' + data.endpoint.target;
|
||||
length = tag.length + label.length + 1;
|
||||
nrSpaces = paddings.tagPlusLabel - length;
|
||||
|
||||
// Make at least one space
|
||||
if (nrSpaces < 1) {
|
||||
nrSpaces = 1;
|
||||
}
|
||||
|
||||
return tag[tagColor] + mout.string.repeat(' ', nrSpaces) + label.green;
|
||||
}
|
||||
|
||||
module.exports.colorful = {};
|
||||
module.exports.colorful.head = empty;
|
||||
module.exports.colorful.tail = empty;
|
||||
|
||||
module.exports.colorful.data = renderData;
|
||||
module.exports.colorful.error = renderError;
|
||||
module.exports.colorful.end = renderEnd;
|
||||
|
||||
// The colorless variant simply removes the colors from the colorful methods
|
||||
module.exports.colorless = mout.object.map(module.exports.colorful, function (fn) {
|
||||
return function () {
|
||||
var str = fn.apply(fn, arguments);
|
||||
return uncolor(str);
|
||||
};
|
||||
});
|
||||
5
lib/renderers/index.js
Normal file
5
lib/renderers/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
cli: require('./cli'),
|
||||
json: require('./json'),
|
||||
mute: require('./mute')
|
||||
};
|
||||
38
lib/renderers/json.js
Normal file
38
lib/renderers/json.js
Normal file
@@ -0,0 +1,38 @@
|
||||
var circularJson = require('circular-json');
|
||||
|
||||
function renderHead() {
|
||||
return '[';
|
||||
}
|
||||
|
||||
function renderTail() {
|
||||
return ']\n';
|
||||
}
|
||||
|
||||
function renderData(data) {
|
||||
return stringify(data) + ', ';
|
||||
}
|
||||
|
||||
function renderError(err) {
|
||||
return stringify(err) + ', ';
|
||||
}
|
||||
|
||||
function renderEnd(data) {
|
||||
return data ? stringify(data) : '';
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
|
||||
function uncolor(str) {
|
||||
return str.replace(/\x1B\[\d+m/g, '');
|
||||
}
|
||||
|
||||
function stringify(data) {
|
||||
return uncolor(circularJson.stringify(data, null, ' '));
|
||||
}
|
||||
|
||||
module.exports.head = renderHead;
|
||||
module.exports.tail = renderTail;
|
||||
|
||||
module.exports.data = renderData;
|
||||
module.exports.error = renderError;
|
||||
module.exports.end = renderEnd;
|
||||
7
lib/renderers/mute.js
Normal file
7
lib/renderers/mute.js
Normal file
@@ -0,0 +1,7 @@
|
||||
function empty() {
|
||||
return '';
|
||||
}
|
||||
|
||||
module.exports.data = empty;
|
||||
module.exports.error = empty;
|
||||
module.exports.end = empty;
|
||||
41
lib/util/cli.js
Normal file
41
lib/util/cli.js
Normal file
@@ -0,0 +1,41 @@
|
||||
var mout = require('mout');
|
||||
var nopt = require('nopt');
|
||||
var renderers = require('../renderers');
|
||||
|
||||
function readOptions(argv, options) {
|
||||
var types;
|
||||
var shorthands = {};
|
||||
|
||||
// Configure options that are common to all commands
|
||||
options.help = { type: Boolean, shorthand: 'h' };
|
||||
options.color = { type: Boolean };
|
||||
options.silent = { type: Boolean, shorthand: 's' };
|
||||
options.json = { type: Boolean };
|
||||
options['log-levels'] = { type: String };
|
||||
|
||||
types = mout.object.map(options, function (option) {
|
||||
return option.type;
|
||||
});
|
||||
mout.object.forOwn(options, function (option, name) {
|
||||
shorthands[option.shorthand] = '--' + name;
|
||||
});
|
||||
|
||||
return nopt(types, shorthands, argv);
|
||||
}
|
||||
|
||||
function getRenderer(options) {
|
||||
if (options.silent) {
|
||||
return renderers.mute;
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
return renderers.json;
|
||||
}
|
||||
|
||||
return options.color === false ?
|
||||
renderers.cli.colorless :
|
||||
renderers.cli.colorful;
|
||||
}
|
||||
|
||||
module.exports.readOptions = readOptions;
|
||||
module.exports.createRenderer = getRenderer;
|
||||
Reference in New Issue
Block a user