Comparing version 0.10.0 to 1.0.0
# Changelog | ||
## 1.0.0 - 2013-07-23 | ||
Total rewrite of bower. | ||
The list bellow highlights the most important stuff: | ||
- Clear architecture and separation of concerns | ||
- Much much faster | ||
- `--json` output for all commands | ||
- `--offline` usage for all commands, except `register` | ||
- Proper `install` and `update` commands, similar to `npm` in behaviour | ||
- Named endpoints when installing, e.g. `bower install backbone-amd=backbone#~1.0.0` | ||
- New interactive conflict resolution strategy | ||
- Prevent human errors when using `register` | ||
- New `home` command, similar to `npm` | ||
- New `cache list` command | ||
- New `prune` command | ||
- Many many general bug fixes | ||
Non-backwards compatible changes: | ||
- `json` from .bowerrc is no longer used | ||
- Programmatic usage changed, specially the commands interface | ||
Users upgrading from `bower-canary` should do a `bower cache clean`. | ||
Users upgrading from `bower@~0.x.x` should remove the `~/.bower` folder manually since it's no longer used. | ||
On Windows the folder is located in `AppData/bower`. | ||
For a complete list of changes that this rewrite and release brings please read: https://github.com/bower/bower/wiki/Rewrite-state | ||
## 0.10.0 - 2013-07-02 | ||
@@ -7,3 +37,3 @@ | ||
- __Change bower default folder from `components` to `bower_components`__ ([#434](https://github.com/bower/bower/issues/434)) | ||
- __Support semver pre-releases and builds ([#188](https://github.com/bower/bower/issues/188)) | ||
- __Support semver pre-releases and builds__ ([#188](https://github.com/bower/bower/issues/188)) | ||
- Use `Content-Type` and `Content-Disposition` to guess file types, such as zip files ([#454](https://github.com/bower/bower/pull/454)) | ||
@@ -10,0 +40,0 @@ - Fix bower failing silently when using an invalid version value in the bower.json file ([#439](https://github.com/bower/bower/issues/439)) |
@@ -1,52 +0,52 @@ | ||
// ========================================== | ||
// BOWER: Help API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var path = require('path'); | ||
var fs = require('graceful-fs'); | ||
var Logger = require('bower-logger'); | ||
var cli = require('../util/cli'); | ||
var createError = require('../util/createError'); | ||
var events = require('events'); | ||
var nopt = require('nopt'); | ||
var _ = require('lodash'); | ||
function help(name) { | ||
var json; | ||
var logger = new Logger(); | ||
var template = require('../util/template'); | ||
var config = require('../core/config'); | ||
if (name) { | ||
json = path.resolve(__dirname, '../../templates/json/help-' + name.replace(/\s+/g, '/') + '.json'); | ||
} else { | ||
json = path.resolve(__dirname, '../../templates/json/help.json'); | ||
} | ||
module.exports = function (name) { | ||
var context = {}; | ||
var emitter = new events.EventEmitter; | ||
var commands = require('../commands'); | ||
fs.exists(json, function (exists) { | ||
if (!exists) { | ||
return logger.emit('error', createError('Unknown command: ' + name, 'EUNKOWNCMD', { | ||
command: name | ||
})); | ||
} | ||
// Aliases | ||
// At the moment we just have the ls, but we might have more | ||
switch (name) { | ||
case 'ls': | ||
name = 'list'; | ||
break; | ||
} | ||
try { | ||
json = require(json); | ||
} catch (error) { | ||
return logger.emit('error', error); | ||
} | ||
var validCommand = !!(name && commands[name]); | ||
var templateName = validCommand ? 'help-' + name : 'help'; | ||
logger.emit('end', json); | ||
}); | ||
if (!validCommand) context = { | ||
commands: Object.keys(commands).sort().join(', ') | ||
} | ||
return logger; | ||
} | ||
_.extend(context, config); | ||
// ------------------- | ||
template(templateName, context) | ||
.on('data', emitter.emit.bind(emitter, 'end')); | ||
help.line = function (argv) { | ||
var options = help.options(argv); | ||
return emitter; | ||
return help(options.argv.remain.slice(1).join(' ')); | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt({}, {}, argv); | ||
var paths = options.argv.remain.slice(1); | ||
return module.exports(paths[0]); | ||
help.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
return cb(null, Object.keys(require('../commands'))); | ||
help.completion = function () { | ||
// TODO | ||
}; | ||
module.exports = help; |
@@ -1,24 +0,16 @@ | ||
// ========================================== | ||
// BOWER: Public Commands List | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
module.exports = { | ||
'help': require('./help'), | ||
'install': require('./install'), | ||
'list': require('./list'), | ||
'ls': require('./list'), | ||
'uninstall': require('./uninstall'), | ||
'update': require('./update'), | ||
'link': require('./link'), | ||
'lookup': require('./lookup'), | ||
'info': require('./info'), | ||
'init': require('./init'), | ||
'register': require('./register'), | ||
'search': require('./search'), | ||
'cache-clean': require('./cache-clean'), | ||
'completion': require('./completion') | ||
cache: require('./cache'), | ||
help: require('./help'), | ||
home: require('./home'), | ||
info: require('./info'), | ||
init: require('./init'), | ||
install: require('./install'), | ||
link: require('./link'), | ||
list: require('./list'), | ||
lookup: require('./lookup'), | ||
prune: require('./prune'), | ||
register: require('./register'), | ||
search: require('./search'), | ||
update: require('./update'), | ||
uninstall: require('./uninstall') | ||
}; |
@@ -1,50 +0,80 @@ | ||
// ========================================== | ||
// BOWER: Lookup API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Logger = require('bower-logger'); | ||
var PackageRepository = require('../core/PackageRepository'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var nopt = require('nopt'); | ||
function info(pkg, property, config) { | ||
var repository; | ||
var logger = new Logger(); | ||
var template = require('../util/template'); | ||
var source = require('../core/source'); | ||
var install = require('./install'); | ||
var help = require('./help'); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
repository = new PackageRepository(config, logger); | ||
var optionTypes = { help: Boolean }; | ||
var shorthand = { 'h': ['--help'] }; | ||
pkg = pkg.split('#'); | ||
pkg = { | ||
name: pkg[0], | ||
version: pkg[1] | ||
}; | ||
module.exports = function (name) { | ||
var emitter = new Emitter; | ||
// If no version was specified, retrieve whole package info | ||
if (!pkg.version) { | ||
repository.versions(pkg.name) | ||
.then(function (versions) { | ||
logger.emit('end', { | ||
name: pkg.name, | ||
versions: versions | ||
}); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
// Otherwise fetch version and retrieve package meta | ||
} else { | ||
repository.fetch({ | ||
source: pkg.name, | ||
target: pkg.version | ||
}) | ||
.spread(function (canonicalDir, pkgMeta) { | ||
pkgMeta = mout.object.filter(pkgMeta, function (value, key) { | ||
return key.charAt(0) !== '_'; | ||
}); | ||
if (name) { | ||
source.info(name, function (err, result) { | ||
if (err) return emitter.emit('error', err); | ||
emitter.emit('end', result); | ||
}); | ||
} | ||
// Retrieve specific property | ||
if (property) { | ||
pkgMeta = mout.object.get(pkgMeta, property); | ||
} | ||
return emitter; | ||
}; | ||
logger.emit('end', pkgMeta); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
} | ||
module.exports.line = function (argv) { | ||
var emitter = new Emitter; | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var names = options.argv.remain.slice(1); | ||
return logger; | ||
} | ||
if (options.help || !names.length) return help('info'); | ||
// ------------------- | ||
module.exports(names[0]) | ||
.on('error', emitter.emit.bind(emitter, 'error')) | ||
.on('end', function (data) { | ||
template('info', data).on('data', emitter.emit.bind(emitter, 'end')); | ||
}); | ||
info.line = function (argv) { | ||
var options = info.options(argv); | ||
var pkg = options.argv.remain[1]; | ||
var property = options.argv.remain[2]; | ||
return emitter; | ||
if (!pkg) { | ||
return null; | ||
} | ||
return info(pkg, property); | ||
}; | ||
module.exports.completion = install.completion; | ||
module.exports.completion.options = shorthand; | ||
info.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
info.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports = info; |
@@ -1,150 +0,190 @@ | ||
// ========================================== | ||
// BOWER: Init API | ||
// ========================================== | ||
// Copyright 2013 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var fs = require('graceful-fs'); | ||
var path = require('path'); | ||
var Q = require('q'); | ||
var inquirer = require('inquirer'); | ||
var Logger = require('bower-logger'); | ||
var endpointParser = require('bower-endpoint-parser'); | ||
var cli = require('../util/cli'); | ||
var Project = require('../core/Project'); | ||
var defaultConfig = require('../config'); | ||
function init(config) { | ||
var project; | ||
var logger = new Logger(); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var util = require('util'); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
project = new Project(config, logger); | ||
var nopt = require('nopt'); | ||
var promptly = require('promptly'); | ||
var _ = require('lodash'); | ||
// Start with existing JSON details | ||
readJson(project, logger) | ||
// Fill in defaults | ||
.then(setDefaults.bind(null, config)) | ||
// Now prompt user to make changes | ||
.then(promptUser) | ||
// Set ignore based on the response | ||
.spread(setIgnore) | ||
// Set dependencies based on the response | ||
.spread(setDependencies.bind(null, project)) | ||
// All done! | ||
.spread(saveJson.bind(null, project)) | ||
.then(function (json) { | ||
logger.emit('end', json); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
var help = require('./help'); | ||
var Manager = require('../core/manager'); | ||
var config = require('../core/config'); | ||
return logger; | ||
} | ||
var optionTypes = { help: Boolean }; | ||
var shorthand = { 'h': ['--help'] }; | ||
function readJson(project, logger) { | ||
return project.hasJson() | ||
.then(function (json) { | ||
if (json) { | ||
logger.warn('existing', 'The existing ' + path.basename(json) + ' file will be used and filled in'); | ||
} | ||
var commonIgnore = ['**/.*', 'node_modules', 'components', 'bower_components', 'test', 'tests']; | ||
return project.getJson(); | ||
}); | ||
} | ||
var Init = function () { | ||
Manager.call(this); | ||
}; | ||
function saveJson(project, json) { | ||
// Cleanup empty props, including objects and arrays | ||
mout.object.forOwn(json, function (value, key) { | ||
if (mout.lang.isEmpty(value)) { | ||
delete json[key]; | ||
} | ||
}); | ||
util.inherits(Init, Manager); | ||
// Save json (true forces file creation) | ||
return project.saveJson(true); | ||
} | ||
Init.prototype.getDependenciesJSON = function () { | ||
var dependencies = Object.keys(this.dependencies); | ||
var remaining = dependencies.length; | ||
var json = {}; | ||
function setDefaults(config, json) { | ||
var name; | ||
dependencies.forEach(function (name) { | ||
var pkg = this.dependencies[name][0]; | ||
// Name | ||
if (!json.name) { | ||
json.name = path.basename(config.cwd); | ||
} | ||
pkg.on('loadJSON', function () { | ||
// TODO: use fetch endpoint here | ||
json[pkg.name] = pkg.commit ? '*' : '~' + pkg.version; | ||
remaining -= 1; | ||
if (remaining === 0) { | ||
this.manager.emit('loadDependencies', json); | ||
} | ||
}).loadJSON(); | ||
}, this); | ||
// Version | ||
if (!json.version) { | ||
json.version = '0.0.0'; | ||
} | ||
if (remaining === 0) { | ||
this.emit('loadDependencies', json); | ||
} | ||
// Main | ||
if (!json.main) { | ||
// Remove '.js' from the end of the package name if it is there | ||
name = path.basename(json.name, '.js'); | ||
return this; | ||
}; | ||
if (fs.existsSync(path.join(config.cwd, 'index.js'))) { | ||
json.main = 'index.js'; | ||
} else if (fs.existsSync(path.join(config.cwd, name + '.js'))) { | ||
json.main = name + '.js'; | ||
} | ||
} | ||
Init.prototype.getMain = function (name) { | ||
name = path.basename(name, '.js'); | ||
if (fs.existsSync(path.join(process.cwd(), 'index.js'))) { | ||
return 'index.js'; | ||
} else if (fs.existsSync(path.join(process.cwd(), name + '.js'))) { | ||
return name + '.js'; | ||
} else { | ||
return null; | ||
} | ||
}; | ||
return json; | ||
} | ||
Init.prototype.showPrompt = function (question, cb) { | ||
var prompt = question.prompt + ': [' + (question.yesno ? 'y' : question.value) + ']'; | ||
if (question.yesno) { | ||
promptly.confirm(prompt, {'default': 'y'}, cb); | ||
} else { | ||
promptly.prompt(prompt, {'default': question.value}, cb); | ||
} | ||
this.emit('prompt', prompt); | ||
}; | ||
function promptUser(json) { | ||
var deferred = Q.defer(); | ||
Init.prototype.prompts = function (dependencies) { | ||
var main = this.json.main || this.getMain(this.json.name) || ''; | ||
var questions = [ | ||
{ | ||
'name': 'name', | ||
'message': 'name', | ||
'default': json.name, | ||
'type': 'input' | ||
}, | ||
{ | ||
'name': 'version', | ||
'message': 'version', | ||
'default': json.version, | ||
'type': 'input' | ||
}, | ||
{ | ||
'name': 'main', | ||
'message': 'main file', | ||
'default': json.main, | ||
'type': 'input' | ||
}, | ||
{ | ||
'name': 'dependencies', | ||
'message': 'set currently installed components as dependencies?', | ||
'default': !mout.object.size(json.dependencies) && !mout.object.size(json.devDependencies), | ||
'type': 'confirm' | ||
}, | ||
{ | ||
'name': 'ignore', | ||
'message': 'add commonly ignored files to ignore list?', | ||
'default': true, | ||
'type': 'confirm' | ||
} | ||
]; | ||
var questions = _.compact([ | ||
{key: 'name', prompt: 'name', value: this.json.name, yesno: false}, | ||
{key: 'version', prompt: 'version', value: this.json.version, yesno: false}, | ||
{key: 'main', prompt: 'main file', value: main, yesno: false}, | ||
_.size(dependencies) ? {key: 'dependencies', prompt: 'add current components as dependencies? (y/n)', value: dependencies, yesno: true} : null, | ||
{key: 'ignore', prompt: 'add commonly ignored files to ignore list? (y/n)', value: commonIgnore, yesno: true} | ||
]); | ||
var index = 0; | ||
var question = questions[index]; | ||
inquirer.prompt(questions, function (answers) { | ||
json.name = answers.name; | ||
json.version = answers.version; | ||
json.main = answers.main; | ||
var cb = function (err, value) { | ||
if (question.yesno) { | ||
if (value) { | ||
this.json[question.key] = question.value; | ||
} | ||
} else if (value) { | ||
this.json[question.key] = value; | ||
return deferred.resolve([json, answers]); | ||
}); | ||
return deferred.promise; | ||
} | ||
function setIgnore(json, answers) { | ||
if (answers.ignore) { | ||
json.ignore = mout.array.combine(json.ignore || [], [ | ||
'**/.*', | ||
'node_modules', | ||
'bower_components', | ||
'test', | ||
'tests' | ||
]); | ||
} | ||
index += 1; | ||
if (index < questions.length) { | ||
question = questions[index]; | ||
this.showPrompt(question, cb); | ||
} else { | ||
this.emit('prompts'); | ||
} | ||
}.bind(this); | ||
this.showPrompt(question, cb); | ||
}; | ||
return [json, answers]; | ||
} | ||
Init.prototype.save = function (data) { | ||
fs.writeFileSync(path.join(this.cwd, config.json), JSON.stringify(data, null, 2)); | ||
}; | ||
function setDependencies(project, json, answers) { | ||
if (answers.dependencies) { | ||
return project.getTree() | ||
.spread(function (tree, flattened, extraneous) { | ||
if (extraneous.length) { | ||
json.dependencies = {}; | ||
module.exports = function () { | ||
var init = new Init(); | ||
// Add extraneous as dependencies | ||
// TODO: The final expanded source is used instead of the original source | ||
// While this the most correct it might be confusing to users | ||
extraneous.forEach(function (extra) { | ||
var jsonEndpoint = endpointParser.decomposed2json(extra.endpoint); | ||
mout.object.mixIn(json.dependencies, jsonEndpoint); | ||
}); | ||
} | ||
init | ||
.on('resolveLocal', init.loadJSON.bind(init)) | ||
.on('loadJSON', init.getDependenciesJSON.bind(init)) | ||
.on('loadDependencies', init.prompts.bind(init)) | ||
.on('prompts', function () { | ||
init.save(init.json); | ||
init.emit('end'); | ||
}) | ||
.resolveLocal(); | ||
return [json, answers]; | ||
}); | ||
} | ||
return init; | ||
}; | ||
return [json, answers]; | ||
} | ||
module.exports.Init = Init; // Purely for testing | ||
// ------------------- | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
if (options.help) return help('init'); | ||
return module.exports(); | ||
init.line = function () { | ||
return init(); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
var word = opts.word; | ||
init.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
// completing options? | ||
if (word.charAt(0) === '-') { | ||
return cb(null, Object.keys(optionTypes).map(function (option) { | ||
return '--' + option; | ||
})); | ||
} | ||
init.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports = init; |
@@ -1,82 +0,51 @@ | ||
// ========================================== | ||
// BOWER: Install API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Logger = require('bower-logger'); | ||
var Project = require('../core/Project'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var nopt = require('nopt'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
function install(endpoints, options, config) { | ||
var project; | ||
var logger = new Logger(); | ||
var Manager = require('../core/manager'); | ||
var config = require('../core/config'); | ||
var source = require('../core/source'); | ||
var save = require('../util/save'); | ||
var help = require('./help'); | ||
options = options || {}; | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
project = new Project(config, logger); | ||
var optionTypes = { help: Boolean, save: Boolean, 'save-dev': Boolean, force: Boolean, 'force-latest': Boolean, production: Boolean }; | ||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'D': ['--save-dev'], 'f': ['--force'], 'F': ['--force-latest'], 'p': ['--production'] }; | ||
// If endpoints is an empty array, null them | ||
if (endpoints && !endpoints.length) { | ||
endpoints = null; | ||
} | ||
module.exports = function (paths, options) { | ||
options = options || {}; | ||
project.install(endpoints, options) | ||
.then(function (installed) { | ||
logger.emit('end', installed); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
var emitter = new Emitter; | ||
var manager = new Manager(paths, { | ||
force: options.force, | ||
forceLatest: options['force-latest'], | ||
production: options.production | ||
}); | ||
return logger; | ||
} | ||
manager | ||
.on('data', emitter.emit.bind(emitter, 'data')) | ||
.on('warn', emitter.emit.bind(emitter, 'warn')) | ||
.on('error', emitter.emit.bind(emitter, 'error')) | ||
.on('resolve', function (resolved) { | ||
// Handle save/save-dev | ||
if (resolved && (options.save || options['save-dev'])) { | ||
save(manager, paths, !options.save, emitter.emit.bind(emitter, 'end')); | ||
} else { | ||
emitter.emit('end'); | ||
} | ||
}) | ||
.resolve(); | ||
// ------------------- | ||
return emitter; | ||
install.line = function (argv) { | ||
var options = install.options(argv); | ||
return install(options.argv.remain.slice(1), options); | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var paths = options.argv.remain.slice(1); | ||
if (options.help) return help('install'); | ||
return module.exports(paths, options); | ||
install.options = function (argv) { | ||
return cli.readOptions({ | ||
'force-latest': { type: Boolean, shorthand: 'F'}, | ||
'production': { type: Boolean, shorthand: 'p' }, | ||
'save': { type: Boolean, shorthand: 'S' }, | ||
'save-dev': { type: Boolean, shorthand: 'D' } | ||
}, argv); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
var cache = path.join(config.completion, 'install.json'); | ||
var done = function done(err, results) { | ||
if (err) return cb(err); | ||
var names = results.map(function (pkg) { | ||
return pkg.name; | ||
}); | ||
return cb(null, names); | ||
}; | ||
fs.readFile(cache, function (err, body) { | ||
if (!err) return done(null, JSON.parse(body)); | ||
// expected error, do the first request and cache the results | ||
source.all(function (err, results) { | ||
if (err) return cb(err); | ||
fs.writeFile(cache, JSON.stringify(results, null, 2), function (err) { | ||
done(err, results); | ||
}); | ||
}); | ||
}); | ||
install.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports.completion.options = shorthand; | ||
module.exports = install; |
@@ -1,121 +0,126 @@ | ||
// ========================================== | ||
// BOWER: Link API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var fs = require('graceful-fs'); | ||
var path = require('path'); | ||
var mkdirp = require('mkdirp'); | ||
var rimraf = require('rimraf'); | ||
var mout = require('mout'); | ||
var Q = require('q'); | ||
var Logger = require('bower-logger'); | ||
var Project = require('../core/Project'); | ||
var createError = require('../util/createError'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var nopt = require('nopt'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var mkdirp = require('mkdirp'); | ||
var rimraf = require('rimraf'); | ||
function linkSelf(config) { | ||
var project; | ||
var logger = new Logger(); | ||
var Manager = require('../core/manager'); | ||
var help = require('./help'); | ||
var template = require('../util/template'); | ||
var config = require('../core/config'); | ||
var isRepo = require('../util/is-repo'); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
project = new Project(config, logger); | ||
var optionTypes = { help: Boolean }; | ||
var shorthand = { 'h': ['--help'] }; | ||
project.getJson() | ||
.then(function (json) { | ||
var src = config.cwd; | ||
var dst = path.join(config.storage.links, json.name); | ||
function linkSelf(emitter) { | ||
var manager = new Manager; | ||
// Delete previous link if any | ||
return Q.nfcall(rimraf, dst) | ||
// Link globally | ||
.then(function () { | ||
return createLink(src, dst); | ||
}) | ||
.then(function () { | ||
logger.emit('end', { | ||
src: src, | ||
dst: dst | ||
}); | ||
}); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
manager | ||
.on('error', emitter.emit.bind('error')) | ||
.once('loadJSON', function () { | ||
var destPath = path.join(config.links, manager.name); | ||
var srcPath = process.cwd(); | ||
deleteLink(destPath, function (err) { | ||
if (err) return emitter.emit('error', err); | ||
createLink(srcPath, destPath, function (err) { | ||
if (err) return emitter.emit('error', err); | ||
template('link', { src: srcPath, dest: destPath }) | ||
.on('data', emitter.emit.bind(emitter, 'end')); | ||
}); | ||
}); | ||
}).loadJSON(); | ||
return logger; | ||
} | ||
function linkTo(name, emitter) { | ||
var destPath = path.join(process.cwd(), config.directory, name); | ||
var srcPath = path.join(config.links, name); | ||
function linkTo(name, localName, config) { | ||
var src; | ||
var dst; | ||
var logger = new Logger(); | ||
deleteLink(destPath, function (err) { | ||
if (err) return emitter.emit('error', err); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
createLink(srcPath, destPath, function (err) { | ||
if (err) return emitter.emit('error', err); | ||
localName = localName || name; | ||
src = path.join(config.storage.links, name); | ||
dst = path.join(process.cwd(), config.directory, localName); | ||
template('link', { src: srcPath, dest: destPath }) | ||
.on('data', emitter.emit.bind(emitter, 'end')); | ||
// Delete destination folder if any | ||
Q.nfcall(rimraf, dst) | ||
// Link locally | ||
.then(function () { | ||
return createLink(src, dst); | ||
}) | ||
.then(function () { | ||
logger.emit('end', { | ||
src: src, | ||
dst: dst | ||
}); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
}); | ||
} | ||
function deleteLink(dest, callback) { | ||
// Delete symlink if already present | ||
// Beware that if the target is a git repo, we can't proceed | ||
isRepo(dest, function (is) { | ||
if (is) return callback(new Error(dest + ' is a local repository, please remove it manually')); | ||
fs.lstat(dest, function (err) { | ||
if (!err || err.code !== 'ENOENT') rimraf(dest, callback); | ||
else callback(); | ||
}); | ||
}); | ||
return logger; | ||
} | ||
function createLink(src, dest, callback) { | ||
var destDir = path.dirname(dest); | ||
function createLink(src, dst) { | ||
var dstDir = path.dirname(dst); | ||
// Create directory | ||
mkdirp(destDir, function (err) { | ||
if (err) return callback(err); | ||
// Create directory | ||
return Q.nfcall(mkdirp, dstDir) | ||
// Check if source exists | ||
.then(function () { | ||
return Q.nfcall(fs.lstat, src) | ||
.fail(function (error) { | ||
if (error.code === 'ENOENT') { | ||
throw createError('Failed to create link to ' + path.basename(src), 'ENOENT', { | ||
details: src + ' doest not exists or points to a non-existent package' | ||
}); | ||
} | ||
fs.lstat(src, function (err) { | ||
if (err && err.code === 'ENOENT') { | ||
return callback(new Error('Attempting to link an unknown package: ' + path.basename(src))); | ||
} | ||
// Create symlink | ||
fs.symlink(src, dest, 'dir', function (err) { | ||
callback(err); | ||
}); | ||
throw error; | ||
}); | ||
}) | ||
// Create symlink | ||
.then(function () { | ||
return Q.nfcall(fs.symlink, src, dst, 'dir'); | ||
}); | ||
}); | ||
} | ||
module.exports = function (name) { | ||
var emitter = new Emitter; | ||
// ------------------- | ||
if (!name) linkSelf(emitter); | ||
else linkTo(name, emitter); | ||
return emitter; | ||
var link = { | ||
linkTo: linkTo, | ||
linkSelf: linkSelf | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var name = options.argv.remain[1]; | ||
link.line = function (argv) { | ||
var options = link.options(argv); | ||
var name = options.argv.remain[1]; | ||
var localName = options.argv.remain[2]; | ||
if (options.help) return help('link'); | ||
return module.exports(name); | ||
if (name) { | ||
return linkTo(name, localName); | ||
} | ||
return linkSelf(); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
fs.readdir(config.links, function (err, dirs) { | ||
// ignore ENOENT, ~/.bower/links not created yet | ||
if (err && err.code === 'ENOENT') return cb(null, []); | ||
cb(err, dirs); | ||
}); | ||
link.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
module.exports.completion.options = shorthand; | ||
link.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports = link; |
@@ -1,245 +0,120 @@ | ||
// ========================================== | ||
// BOWER: List API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var path = require('path'); | ||
var mout = require('mout'); | ||
var semver = require('semver'); | ||
var Q = require('q'); | ||
var Logger = require('bower-logger'); | ||
var Project = require('../core/Project'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var archy = require('archy'); | ||
var nopt = require('nopt'); | ||
var path = require('path'); | ||
var _ = require('lodash'); | ||
function list(options, config) { | ||
var project; | ||
var logger = new Logger(); | ||
var template = require('../util/template'); | ||
var Manager = require('../core/manager'); | ||
var Package = require('../core/package'); | ||
var config = require('../core/config'); | ||
var help = require('./help'); | ||
var fallback = require('../util/fallback'); | ||
options = options || {}; | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
project = new Project(config, logger); | ||
var shorthand = { 'h': ['--help'], 'o': ['--offline'] }; | ||
var optionTypes = { help: Boolean, paths: Boolean, map: Boolean, offline: Boolean, sources: Boolean }; | ||
project.getTree() | ||
.spread(function (tree, flattened, extraneous) { | ||
if (options.paths) { | ||
return logger.emit('end', paths(flattened)); | ||
} | ||
var getTree = function (packages, subPackages, result) { | ||
result = result || {}; | ||
if (config.offline) { | ||
return logger.emit('end', normal(tree, extraneous)); | ||
} | ||
_.each(subPackages || packages, function (pkg) { | ||
result[pkg.name] = {}; | ||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) { | ||
result[pkg.name][name] = {}; | ||
return checkVersions(project, tree, logger) | ||
.then(function () { | ||
logger.emit('end', normal(tree, extraneous)); | ||
}); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
var subPackages = {}; | ||
logger.json = !!options.paths; | ||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) { | ||
subPackages[name] = packages[name] || new Package(name, null); | ||
}); | ||
return logger; | ||
} | ||
getTree(packages, subPackages, result[pkg.name]); | ||
}); | ||
function checkVersions(project, tree, logger) { | ||
var promises; | ||
var nodes = []; | ||
var repository = project.getPackageRepository(); | ||
return result; | ||
}; | ||
// Gather all nodes | ||
project.walkTree(tree, function (node) { | ||
nodes.push(node); | ||
}, true); | ||
var generatePath = function (name, main) { | ||
if (typeof main === 'string') { | ||
return path.join(config.directory, name, main); | ||
} else if (_.isArray(main)) { | ||
main = main.map(function (main) { return generatePath(name, main); }); | ||
return main.length === 1 ? main[0] : main; | ||
} | ||
}; | ||
if (nodes.length) { | ||
logger.info('check-new', 'Checking for new versions of the project dependencies..'); | ||
} | ||
var mainTypes = ['main', 'scripts', 'styles', 'templates', 'images']; | ||
// Check for new versions for each node | ||
promises = nodes.map(function (node) { | ||
var target = node.endpoint.target; | ||
var buildSource = function (pkg, shallow) { | ||
var result = {}; | ||
return repository.versions(node.endpoint.source) | ||
.then(function (versions) { | ||
node.versions = versions; | ||
if (pkg) { | ||
mainTypes.forEach(function (type) { | ||
if (pkg.json[type]) result[type] = generatePath(pkg.name, pkg.json[type]); | ||
// Do not check if node's target is not a valid semver one | ||
if (versions.length && semver.validRange(target)) { | ||
node.update = { | ||
target: semver.maxSatisfying(versions, target), | ||
latest: semver.maxSatisfying(versions, '*') | ||
}; | ||
} | ||
}); | ||
}); | ||
} | ||
if (shallow) { | ||
result.main = getMain(result) || generatePath(pkg.name, ''); | ||
} | ||
// Set the versions also for the root node | ||
tree.versions = []; | ||
return result; | ||
}; | ||
return Q.all(promises); | ||
} | ||
var getMain = function (source) { | ||
for (var i = 0, len = mainTypes.length; i < len; i += 1) { | ||
var type = mainTypes[i]; | ||
if (source[type]) { | ||
return source[type]; | ||
} | ||
} | ||
}; | ||
function paths(flattened) { | ||
var ret = {}; | ||
var shallowTree = function (packages, tree) { | ||
var result = {}; | ||
mout.object.forOwn(flattened, function (pkg, name) { | ||
if (!pkg.missing) { | ||
ret[name] = path.normalize(pkg.canonicalDir + '/' + (pkg.pkgMeta.main || '')); | ||
} | ||
}); | ||
Object.keys(tree).forEach(function (packageName) { | ||
result[packageName] = buildSource(packages[packageName], true).main; | ||
}); | ||
return ret; | ||
} | ||
return result; | ||
}; | ||
function normal(tree, extraneous) { | ||
// Merge extraneous as root dependencies | ||
// but signal it with a flag | ||
extraneous.forEach(function (decEndpoint) { | ||
decEndpoint.extraneous = true; | ||
tree.dependencies[decEndpoint.endpoint.name] = decEndpoint; | ||
}); | ||
var deepTree = function (packages, tree) { | ||
return tree; | ||
} | ||
var result = {}; | ||
// ------------------- | ||
Object.keys(tree).forEach(function (packageName) { | ||
result[packageName] = {}; | ||
result[packageName].source = buildSource(packages[packageName]); | ||
if (Object.keys(tree[packageName]).length) { | ||
result[packageName].dependencies = deepTree(packages, tree[packageName]); | ||
} | ||
}); | ||
return result; | ||
list.line = function (argv) { | ||
var options = list.options(argv); | ||
return list(options); | ||
}; | ||
var getNodes = function (packages, tree) { | ||
return Object.keys(tree).map(function (key) { | ||
var version = packages[key] ? packages[key].version || '' : null; | ||
var upgrade; | ||
if (version && packages[key].tags.indexOf(version)) { | ||
upgrade = packages[key].tags[0]; | ||
} | ||
if (Object.keys(tree[key]).length) { | ||
return { | ||
label: template('tree-branch', { 'package': key, version: version, upgrade: upgrade }, true), | ||
nodes: getNodes(packages, tree[key]) | ||
}; | ||
} else { | ||
return template('tree-branch', { 'package': key, version: version, upgrade: upgrade }, true); | ||
} | ||
}); | ||
list.options = function (argv) { | ||
return cli.readOptions({ | ||
'paths': { type: Boolean, shorthand: 'p' } | ||
}, argv); | ||
}; | ||
var getDependencySrcs = function (list) { | ||
var srcs = []; | ||
var dependency, main; | ||
for (var name in list) { | ||
dependency = list[name]; | ||
main = dependency.source && getMain(dependency.source); | ||
if (dependency.dependencies) { | ||
var depSrcs = getDependencySrcs(dependency.dependencies); | ||
srcs.push.apply(srcs, depSrcs); | ||
} | ||
// add main sources to srcs | ||
if (main) { | ||
if (Array.isArray(main)) { | ||
srcs.push.apply(srcs, main); | ||
} else { | ||
srcs.push(main); | ||
} | ||
} | ||
} | ||
return srcs; | ||
list.completion = function () { | ||
// TODO: | ||
}; | ||
var organizeSources = function (tree) { | ||
// flat source filepaths | ||
var srcs = getDependencySrcs(tree); | ||
// remove duplicates, organize by file extension | ||
var sources = {}; | ||
srcs.forEach(function (src) { | ||
var ext = path.extname(src); | ||
sources[ext] = sources[ext] || []; | ||
if (sources[ext].indexOf(src) === -1) { | ||
sources[ext].push(src); | ||
} | ||
}); | ||
return sources; | ||
}; | ||
module.exports = function (options) { | ||
var manager = new Manager; | ||
var emitter = new Emitter; | ||
options = options || {}; | ||
if (options.sources) { | ||
options.map = true; | ||
} | ||
var emitOut = function (obj) { | ||
// make JSON pretty if started from command line | ||
var output = options.argv ? JSON.stringify(obj, null, 2) : obj; | ||
emitter.emit('data', output); | ||
}; | ||
manager | ||
.on('data', emitter.emit.bind(emitter, 'data')) | ||
.on('error', emitter.emit.bind(emitter, 'error')) | ||
.on('list', function (packages) { | ||
var tree = getTree(packages); | ||
var rootLabel; | ||
if (!options.paths && !options.map) { | ||
return fallback(process.cwd(), [config.json, 'bower.json', 'component.json'], function (name) { | ||
try { | ||
if (name == null) throw new Error('No json'); | ||
rootLabel = require(path.join(process.cwd(), name)).name; | ||
} catch (e) { | ||
rootLabel = path.basename(process.cwd()); | ||
} | ||
emitter.emit('data', archy({ | ||
label: rootLabel, | ||
nodes: getNodes(packages, tree) | ||
})); | ||
}); | ||
} | ||
tree = options.paths ? shallowTree(packages, tree) : deepTree(packages, tree); | ||
if (options.sources) { | ||
// with map, organize it and emit | ||
var sources = organizeSources(tree); | ||
emitOut(sources); | ||
} else { | ||
emitOut(tree); | ||
} | ||
}) | ||
.list(options); | ||
return emitter; | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
if (options.help) return help('list'); | ||
return module.exports(options); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
if (!/^-/.test(opts.word)) return cb(null, []); | ||
var results = Object.keys(optionTypes).map(function (option) { | ||
return '--' + option; | ||
}); | ||
cb(null, results); | ||
}; | ||
module.exports.completion.options = shorthand; | ||
module.exports = list; |
@@ -1,64 +0,54 @@ | ||
// ========================================== | ||
// BOWER: Lookup API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Q = require('q'); | ||
var Logger = require('bower-logger'); | ||
var RegistryClient = require('bower-registry-client'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var nopt = require('nopt'); | ||
function lookup(name, config) { | ||
var registryClient; | ||
var logger = new Logger(); | ||
var template = require('../util/template'); | ||
var source = require('../core/source'); | ||
var install = require('./install'); | ||
var help = require('./help'); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
config.cache = config.storage.registry; | ||
var optionTypes = { help: Boolean }; | ||
var shorthand = { 'h': ['--help'] }; | ||
registryClient = new RegistryClient(config, logger); | ||
module.exports = function (name) { | ||
var emitter = new Emitter; | ||
Q.nfcall(registryClient.lookup.bind(registryClient), name) | ||
.then(function (entry) { | ||
// TODO: Handle entry.type.. for now it's only 'alias' | ||
// When we got published packages, this needs to be adjusted | ||
logger.emit('end', !entry ? null : { | ||
name: name, | ||
url: entry && entry.url | ||
}); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
source.lookup(name, function (err, url) { | ||
if (err) { | ||
source.search(name, function (err, packages) { | ||
if (packages.length) { | ||
template('suggestions', { packages: packages, name: name }) | ||
.on('data', function (data) { | ||
emitter.emit('data', data); | ||
emitter.emit('end'); | ||
}); | ||
} else { | ||
template('warning-missing', {name: name}) | ||
.on('data', function (data) { | ||
emitter.emit('data', data); | ||
emitter.emit('end'); | ||
}); | ||
} | ||
}); | ||
} else { | ||
var result = { name: name, url: url }; | ||
emitter.emit('package', result); | ||
return logger; | ||
} | ||
template('lookup', result) | ||
.on('data', function (data) { | ||
emitter.emit('data', data); | ||
emitter.emit('end'); | ||
}); | ||
// ------------------- | ||
lookup.line = function (argv) { | ||
var options = lookup.options(argv); | ||
var name = options.argv.remain[1]; | ||
if (!name) { | ||
return null; | ||
} | ||
}); | ||
return emitter; | ||
return lookup(name); | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var names = options.argv.remain.slice(1); | ||
lookup.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
if (options.help || !names.length) return help('lookup'); | ||
return module.exports(names[0]); | ||
lookup.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports.completion = install.completion; | ||
module.exports.completion.options = shorthand; | ||
module.exports = lookup; |
@@ -1,66 +0,127 @@ | ||
// ========================================== | ||
// BOWER: Lookup API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Q = require('q'); | ||
var promptly = require('promptly'); | ||
var PackageRepository = require('../core/PackageRepository'); | ||
var Logger = require('bower-logger'); | ||
var cli = require('../util/cli'); | ||
var createError = require('../util/createError'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var nopt = require('nopt'); | ||
var readline = require('readline'); | ||
function register(name, url, config) { | ||
var repository; | ||
var registryClient; | ||
var logger = new Logger(); | ||
var force; | ||
var template = require('../util/template'); | ||
var source = require('../core/source'); | ||
var help = require('./help'); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
force = config.force; | ||
var optionTypes = { help: Boolean }; | ||
var shorthand = { 'h': ['--help'], '-s': ['--silent'] }; | ||
// Bypass any cache | ||
config.offline = false; | ||
config.force = true; | ||
var register = function (name, url, emitter) { | ||
source.register(name, url, function (err) { | ||
if (err) return emitter.emit('error', err); | ||
// Trim name | ||
name = name.trim(); | ||
template('register', {name: name, url: url}) | ||
.on('data', emitter.emit.bind(emitter, 'data')); | ||
}); | ||
}; | ||
process.nextTick(function () { | ||
// Verify name | ||
// TODO: Verify with the new spec regexp? | ||
if (!name) { | ||
return logger.emit('error', createError('Please type a name', 'EINVNAME')); | ||
} | ||
module.exports = function (name, url, options) { | ||
var emitter = new Emitter; | ||
// Convert URLs | ||
url = convertUrl(url, logger); | ||
if (options.silent) register(name, url, emitter); | ||
else { | ||
var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); | ||
// Ensure the URL starts with git:// | ||
// TODO: After the registry server is rewritten this might change | ||
if (!mout.string.startsWith(url, 'git://')) { | ||
return logger.emit('error', createError('The registry only accepts URLs starting with git://', 'EINVFORMAT')); | ||
} | ||
console.log('Registering a package will make it visible and installable via the registry.'); | ||
rl.question('Proceed (y/n)? ', function (res) { | ||
rl.close(); | ||
// Attempt to resolve the package referenced by the URL to ensure | ||
// everything is ok before registering | ||
repository = new PackageRepository(config, logger); | ||
repository.fetch({ name: name, source: url, target: '*' }) | ||
.then(function () { | ||
// If non interactive or user forced, bypass confirmation | ||
if (!config.interactive || force) { | ||
return true; | ||
} | ||
res = res.toLowerCase(); | ||
// Confirm if the user really wants to register | ||
return Q.nfcall(promptly.confirm, 'Registering a package will make it visible and installable via the registry, continue? (y/n)'); | ||
}) | ||
.then(function (result) { | ||
// If user response was negative, abort | ||
if (!result) { | ||
return; | ||
} | ||
if (res === 'y' || res === 'yes') register(name, url, emitter); | ||
// Register | ||
registryClient = repository.getRegistryClient(); | ||
logger.action('register', url, { | ||
name: name, | ||
url: url | ||
}); | ||
return Q.nfcall(registryClient.register.bind(registryClient), name, url); | ||
}) | ||
.then(function (result) { | ||
logger.emit('end', result); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
}); | ||
} | ||
return emitter; | ||
}; | ||
return logger; | ||
} | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var args = options.argv.remain.slice(1); | ||
function convertUrl(url, logger) { | ||
var matches; | ||
var newUrl; | ||
if (options.help || args.length !== 2) return help('register'); | ||
return module.exports(args[0], args[1], options); | ||
// Convert GitHub ssh urls | ||
matches = url.match(/^git@github\.com:([^\/]+\/[^\/]+)\.git$/); | ||
if (matches) { | ||
newUrl = 'git://github.com/' + matches[1] + '.git'; | ||
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl); | ||
return newUrl; | ||
} | ||
// Convert GitHub https urls | ||
matches = url.match(/^https?:\/\/github\.com\/([^\/]+\/[^\/]+)\.git$/); | ||
if (matches) { | ||
newUrl = 'git://github.com/' + matches[1] + '.git'; | ||
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl); | ||
return newUrl; | ||
} | ||
return url; | ||
} | ||
// ------------------- | ||
register.line = function (argv) { | ||
var options = register.options(argv); | ||
var name = options.argv.remain[1]; | ||
var url = options.argv.remain[2]; | ||
if (!name || !url) { | ||
return null; | ||
} | ||
return register(name, url); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
var word = opts.word; | ||
register.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
// completing options? | ||
if (word.charAt(0) === '-') { | ||
return cb(null, Object.keys(optionTypes).map(function (option) { | ||
return '--' + option; | ||
})); | ||
} | ||
register.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports = register; |
@@ -1,61 +0,52 @@ | ||
// ========================================== | ||
// BOWER: Lookup API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Q = require('q'); | ||
var Logger = require('bower-logger'); | ||
var RegistryClient = require('bower-registry-client'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var nopt = require('nopt'); | ||
function search(name, config) { | ||
var registryClient; | ||
var promise; | ||
var logger = new Logger(); | ||
var template = require('../util/template'); | ||
var source = require('../core/source'); | ||
var install = require('./install'); | ||
var help = require('./help'); | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
config.cache = config.storage.registry; | ||
var optionTypes = { help: Boolean }; | ||
var shorthand = { 'h': ['--help'] }; | ||
registryClient = new RegistryClient(config, logger); | ||
module.exports = function (name) { | ||
var emitter = new Emitter; | ||
// If no name was specified, list all packages | ||
if (!name) { | ||
promise = Q.nfcall(registryClient.list.bind(registryClient)); | ||
// Otherwise search it | ||
} else { | ||
promise = Q.nfcall(registryClient.search.bind(registryClient), name); | ||
} | ||
var callback = function (err, results) { | ||
if (err) return emitter.emit('error', err); | ||
promise | ||
.then(function (results) { | ||
logger.emit('end', results); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
emitter.emit('packages', results); | ||
return logger; | ||
} | ||
if (results.length) { | ||
template('search', {results: results}) | ||
.on('data', function (data) { | ||
emitter.emit('data', data); | ||
emitter.emit('end'); | ||
}); | ||
} else { | ||
template('search-empty', {results: results}) | ||
.on('data', function (data) { | ||
emitter.emit('data', data); | ||
emitter.emit('end'); | ||
}); | ||
} | ||
}; | ||
// ------------------- | ||
if (name) { | ||
source.search(name, callback); | ||
} else { | ||
source.all(callback); | ||
} | ||
search.line = function (argv) { | ||
var options = search.options(argv); | ||
return search(options.argv.remain.slice(1).join(' '), options); | ||
}; | ||
return emitter; | ||
search.options = function (argv) { | ||
return cli.readOptions(argv); | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var names = options.argv.remain.slice(1); | ||
if (options.help) return help('search'); | ||
return module.exports(names[0]); | ||
search.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports.completion = install.completion; | ||
module.exports.completion.options = shorthand; | ||
module.exports = search; |
@@ -1,206 +0,55 @@ | ||
// ========================================== | ||
// BOWER: Uninstall API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Logger = require('bower-logger'); | ||
var Project = require('../core/Project'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var async = require('async'); | ||
var nopt = require('nopt'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var _ = require('lodash'); | ||
function uninstall(names, options, config) { | ||
var project; | ||
var logger = new Logger(); | ||
var template = require('../util/template'); | ||
var Manager = require('../core/manager'); | ||
var config = require('../core/config'); | ||
var help = require('./help'); | ||
options = options || {}; | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
project = new Project(config, logger); | ||
var optionTypes = { help: Boolean, force: Boolean, save: Boolean }; | ||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'D': ['--save-dev'], 'f': ['--force'] }; | ||
module.exports = function (names, options) { | ||
var packages, uninstallables, packagesCount = {}; | ||
var emitter = new Emitter; | ||
var manager = new Manager; | ||
var jsonDeps; | ||
var newLine; | ||
options = options || {}; | ||
manager.on('data', emitter.emit.bind(emitter, 'data')); | ||
manager.on('error', emitter.emit.bind(emitter, 'error')); | ||
var resolveLocal = function () { | ||
jsonDeps = manager.json.dependencies || {}; | ||
packages = _.flatten(_.values(manager.dependencies)); | ||
uninstallables = packages.filter(function (pkg) { | ||
return _.include(names, pkg.name); | ||
}); | ||
async.forEach(packages, function (pkg, next) { | ||
pkg.once('loadJSON', next).loadJSON(); | ||
}, function () { | ||
if (showWarnings(options.force) && !options.force) return; | ||
expandUninstallabes(options.force); | ||
uninstall(); | ||
}); | ||
}; | ||
var showWarnings = function (force) { | ||
var foundConflicts = false; | ||
packages.forEach(function (pkg) { | ||
if (!pkg.json.dependencies) return; | ||
if (containsPkg(uninstallables, pkg)) return; | ||
var conflicts = _.intersection( | ||
Object.keys(pkg.json.dependencies), | ||
_.pluck(uninstallables, 'name') | ||
); | ||
if (conflicts.length) { | ||
foundConflicts = true; | ||
if (!force) { | ||
conflicts.forEach(function (conflictName) { | ||
emitter.emit('data', template('warning-uninstall', { packageName: pkg.name, conflictName: conflictName }, true)); | ||
}); | ||
} | ||
} | ||
}); | ||
if (foundConflicts && !force) { | ||
emitter.emit('data', template('warn', { message: 'To proceed, run uninstall with the --force flag'}, true)); | ||
// If names is an empty array, null them | ||
if (names && !names.length) { | ||
names = null; | ||
} | ||
return foundConflicts; | ||
}; | ||
var expandUninstallabes = function (force) { | ||
var x, | ||
pkg, | ||
forcedUninstallables = {}; | ||
// Direct JSON deps have a count of 1 | ||
for (var key in jsonDeps) { | ||
packagesCount[key] = 1; | ||
} | ||
// Count all packages | ||
count(packages, packagesCount); | ||
if (force) { | ||
uninstallables.forEach(function (pkg) { | ||
forcedUninstallables[pkg.name] = true; | ||
}); | ||
} | ||
// Expand the uninstallables deps and nested deps | ||
// Also update the count accordingly | ||
for (x = uninstallables.length - 1; x >= 0; x -= 1) { | ||
parseUninstallableDeps(uninstallables[x]); | ||
} | ||
// Foreach uninstallable, check if it is really to be removed by reading the final count | ||
// If the final count is greater than 0, then it is a shared dep | ||
// In that case, we remove it from the uninstallables unless it's forced to be uninstalled | ||
for (x = uninstallables.length - 1; x >= 0; x -= 1) { | ||
pkg = uninstallables[x]; | ||
if (packagesCount[pkg.name] > 0 && !forcedUninstallables[pkg.name]) uninstallables.splice(x, 1); | ||
} | ||
}; | ||
var count = function (packages, counts, nested) { | ||
packages.forEach(function (pkg) { | ||
counts[pkg.name] = (counts[pkg.name] || 0); | ||
if (nested) counts[pkg.name] += 1; | ||
if (pkg.json.dependencies) { | ||
for (var key in pkg.json.dependencies) { | ||
count(manager.dependencies[key], counts, true); | ||
} | ||
} | ||
project.uninstall(names, options) | ||
.then(function (removed) { | ||
logger.emit('end', removed); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
}; | ||
var parseUninstallableDeps = function (pkg) { | ||
if (!containsPkg(uninstallables, pkg)) uninstallables.push(pkg); | ||
packagesCount[pkg.name] -= 1; | ||
return logger; | ||
} | ||
if (pkg.json.dependencies) { | ||
for (var key in pkg.json.dependencies) { | ||
parseUninstallableDeps(manager.dependencies[key][0]); | ||
} | ||
} | ||
}; | ||
// ------------------- | ||
var containsPkg = function (packages, pkg) { | ||
for (var x = packages.length - 1; x >= 0; x -= 1) { | ||
if (packages[x].name === pkg.name) return true; | ||
} | ||
uninstall.line = function (argv) { | ||
var options = uninstall.options(argv); | ||
var names = options.argv.remain.slice(1); | ||
return false; | ||
}; | ||
var uninstall = function () { | ||
async.forEach(uninstallables, function (pkg, next) { | ||
pkg.on('uninstall', function () { | ||
emitter.emit('package', pkg); | ||
next(); | ||
}).uninstall(); | ||
}, function () { | ||
// Finally save | ||
if (options.save || options['save-dev']) save(!options.save); | ||
emitter.emit('end'); | ||
}); | ||
}; | ||
var save = function (dev) { | ||
var key = dev ? 'devDependencies' : 'dependencies'; | ||
var contents; | ||
if (manager.json[key]) { | ||
names.forEach(function (name) { | ||
delete manager.json[key][name]; | ||
}); | ||
contents = JSON.stringify(manager.json, null, 2) + (newLine ? '\n' : ''); | ||
fs.writeFileSync(path.join(manager.cwd, config.json), contents); | ||
if (!names.length) { | ||
return null; | ||
} | ||
}; | ||
manager.on('loadJSON', function (hasNewLine) { | ||
newLine = hasNewLine; | ||
manager.on('resolveLocal', resolveLocal).resolveLocal(); | ||
}).loadJSON(); | ||
return emitter; | ||
return uninstall(names, options); | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
var names = options.argv.remain.slice(1); | ||
if (options.help || !names.length) return help('uninstall'); | ||
return module.exports(names, options); | ||
uninstall.options = function (argv) { | ||
return cli.readOptions({ | ||
'save': { type: Boolean, shorthand: 'S' }, | ||
'save-dev': { type: Boolean, shorthand: 'D' } | ||
}, argv); | ||
}; | ||
module.exports.completion = function (opts, cb) { | ||
var word = opts.word; | ||
// completing options? | ||
if (opts.words[0] === 'uninstall' && word.charAt(0) === '-') { | ||
return cb(null, Object.keys(optionTypes).map(function (option) { | ||
return '--' + option; | ||
})); | ||
} | ||
fs.readdir(config.directory, function (err, dirs) { | ||
// ignore ENOENT, ./bower_components not created yet | ||
if (err && err.code === 'ENOENT') return cb(null, []); | ||
cb(err, dirs); | ||
}); | ||
uninstall.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports.completion.options = shorthand; | ||
module.exports = uninstall; |
@@ -1,110 +0,49 @@ | ||
// ========================================== | ||
// BOWER: Update API | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var mout = require('mout'); | ||
var Logger = require('bower-logger'); | ||
var Project = require('../core/Project'); | ||
var cli = require('../util/cli'); | ||
var defaultConfig = require('../config'); | ||
var Emitter = require('events').EventEmitter; | ||
var async = require('async'); | ||
var nopt = require('nopt'); | ||
var _ = require('lodash'); | ||
function update(names, options, config) { | ||
var project; | ||
var logger = new Logger(); | ||
var Manager = require('../core/manager'); | ||
var help = require('./help'); | ||
var uninstall = require('./uninstall'); | ||
var save = require('../util/save'); | ||
options = options || {}; | ||
config = mout.object.deepFillIn(config || {}, defaultConfig); | ||
project = new Project(config, logger); | ||
var optionTypes = { help: Boolean, save: Boolean, force: Boolean, 'force-latest': Boolean }; | ||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'f': ['--force'], 'F': ['--force-latest'] }; | ||
module.exports = function (names, options) { | ||
options = options || {}; | ||
var emitter = new Emitter; | ||
var manager = new Manager([], { | ||
force: options.force, | ||
forceLatest: options['force-latest'] | ||
}); | ||
manager.on('data', emitter.emit.bind(emitter, 'data')); | ||
manager.on('error', emitter.emit.bind(emitter, 'error')); | ||
var install = function (err, array) { | ||
var mappings = {}, | ||
endpoints = [], | ||
name, | ||
info, | ||
length, | ||
x; | ||
length = names.length; | ||
if (length) { | ||
for (x = 0; x < length; x += 1) { | ||
name = names[x]; | ||
info = _.find(array, function (info) { return info.name === name; }); | ||
if (!info) { | ||
return emitter.emit('error', new Error('Package ' + name + ' is not installed')); | ||
} | ||
endpoints.push(info.endpoint); | ||
mappings[info.endpoint] = info.name; | ||
} | ||
} else { | ||
array.forEach(function (info) { | ||
endpoints.push(info.endpoint); | ||
mappings[info.endpoint] = info.name; | ||
}); | ||
// If names is an empty array, null them | ||
if (names && !names.length) { | ||
names = null; | ||
} | ||
options.endpointNames = mappings; | ||
project.update(names, options) | ||
.then(function (installed) { | ||
logger.emit('end', installed); | ||
}) | ||
.fail(function (error) { | ||
logger.emit('error', error); | ||
}); | ||
// By default the manager will guess the name of the package from the url | ||
// But this leads to problems when the package name does not match the one in the url | ||
// So the manager now has an option (endpointNames) to deal with this | ||
manager = new Manager(endpoints, options); | ||
manager | ||
.on('data', emitter.emit.bind(emitter, 'data')) | ||
.on('warn', emitter.emit.bind(emitter, 'warn')) | ||
.on('error', emitter.emit.bind(emitter, 'error')) | ||
.on('resolve', function (resolved) { | ||
// Handle save | ||
if (resolved && options.save) save(manager, null, false, emitter.emit.bind(emitter, 'end')); | ||
else emitter.emit('end'); | ||
}) | ||
.resolve(); | ||
}; | ||
return logger; | ||
} | ||
manager.once('resolveLocal', function () { | ||
async.map(_.values(manager.dependencies), function (pkgs, next) { | ||
var pkg = pkgs[0]; | ||
pkg.once('loadJSON', function () { | ||
var endpointInfo = pkg.readEndpoint(); | ||
if (!endpointInfo) return next(); | ||
// ------------------- | ||
// Add tag only if the endpoint is a repository | ||
var endpoint = endpointInfo.endpoint; | ||
var json = pkg.json; | ||
if (!json.commit && (endpointInfo.type === 'git' || endpointInfo.type === 'local-repo')) { | ||
endpoint += '#' + ((!names || names.indexOf(pkg.name) > -1) ? '~' : '') + pkg.version; | ||
} | ||
update.line = function (argv) { | ||
var options = update.options(argv); | ||
return update(options.argv.remain.slice(1), options); | ||
}; | ||
next(null, { name: pkg.name, endpoint: endpoint }); | ||
}).loadJSON(); | ||
}, install); | ||
}).resolveLocal(); | ||
return emitter; | ||
update.options = function (argv) { | ||
return cli.readOptions({ | ||
'force-latest': { type: Boolean, shorthand: 'F' }, | ||
'production': { type: Boolean, shorthand: 'p' } | ||
}, argv); | ||
}; | ||
module.exports.line = function (argv) { | ||
var options = nopt(optionTypes, shorthand, argv); | ||
if (options.help) return help('update'); | ||
var paths = options.argv.remain.slice(1); | ||
return module.exports(paths, options); | ||
update.completion = function () { | ||
// TODO: | ||
}; | ||
module.exports.completion = uninstall.completion; | ||
module.exports.completion.options = shorthand; | ||
module.exports = update; |
@@ -1,20 +0,39 @@ | ||
// ========================================== | ||
// BOWER: Public API Definition | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
var abbrev = require('abbrev'); | ||
var mout = require('mout'); | ||
var commands = require('./commands'); | ||
var PackageRepository = require('./core/PackageRepository'); | ||
var abbreviations = abbrev(Object.keys(commands)); | ||
var abbreviations = abbrev(expandNames(commands)); | ||
abbreviations.i = 'install'; | ||
abbreviations.rm = 'uninstall'; | ||
function expandNames(obj, prefix, stack) { | ||
prefix = prefix || ''; | ||
stack = stack || []; | ||
mout.object.forOwn(obj, function (value, name) { | ||
name = prefix + name; | ||
stack.push(name); | ||
if (typeof value === 'object' && !value.line) { | ||
expandNames(value, name + ' ', stack); | ||
} | ||
}); | ||
return stack; | ||
} | ||
function clearRuntimeCache() { | ||
// Note that in edge cases, some architecture components instance's | ||
// in-memory cache might be skipped. | ||
// If that's a problem, you should create and fresh instances instead. | ||
PackageRepository.clearRuntimeCache(); | ||
} | ||
module.exports = { | ||
commands: commands, | ||
abbreviations: abbreviations, | ||
config: require('./core/config') | ||
commands: commands, | ||
config: require('./config'), | ||
abbreviations: abbreviations, | ||
reset: clearRuntimeCache | ||
}; |
@@ -1,41 +0,45 @@ | ||
// ========================================== | ||
// BOWER: Hogan Renderer w/ template cache | ||
// ========================================== | ||
// Copyright 2012 Twitter, Inc | ||
// Licensed under The MIT License | ||
// http://opensource.org/licenses/MIT | ||
// ========================================== | ||
require('colors'); | ||
var path = require('path'); | ||
var fs = require('graceful-fs'); | ||
var Handlebars = require('handlebars'); | ||
var mout = require('mout'); | ||
var helpers = require('../../templates/helpers'); | ||
var events = require('events'); | ||
var hogan = require('hogan.js'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var templatesDir = path.resolve(__dirname, '../../templates'); | ||
var cache = {}; | ||
require('../util/hogan-colors'); | ||
// Register helpers | ||
mout.object.forOwn(helpers, function (register) { | ||
register(Handlebars); | ||
}); | ||
var templates = {}; | ||
function render(name, data, escape) { | ||
var contents; | ||
module.exports = function (name, context, sync) { | ||
var emitter = new events.EventEmitter; | ||
// Check if already compiled | ||
if (cache[name]) { | ||
return cache[name](data); | ||
} | ||
var templateName = name + '.mustache'; | ||
var templatePath = path.join(__dirname, '../../templates/', templateName); | ||
if (sync) { | ||
if (!templates[templatePath]) templates[templatePath] = fs.readFileSync(templatePath, 'utf-8'); | ||
return hogan.compile(templates[templatePath]).renderWithColors(context); | ||
} else if (templates[templatePath]) { | ||
process.nextTick(function () { | ||
emitter.emit('data', hogan.compile(templates[templatePath]).renderWithColors(context)); | ||
// Otherwise, read the file, compile and cache | ||
contents = fs.readFileSync(path.join(templatesDir, name)).toString(); | ||
cache[name] = Handlebars.compile(contents, { | ||
noEscape: !escape | ||
}); | ||
} else { | ||
fs.readFile(templatePath, 'utf-8', function (err, file) { | ||
if (err) return emitter.emit('error', err); | ||
templates[templatePath] = file; | ||
emitter.emit('data', hogan.compile(file).renderWithColors(context)); | ||
}); | ||
} | ||
// Call the function again | ||
return render(name, data, escape); | ||
} | ||
return emitter; | ||
}; | ||
function exists(name) { | ||
if (cache[name]) { | ||
return true; | ||
} | ||
return fs.existsSync(path.join(templatesDir, name)); | ||
} | ||
module.exports = { | ||
render: render, | ||
exists: exists | ||
}; |
{ | ||
"name": "bower", | ||
"version": "1.0.0", | ||
"description": "The browser package manager.", | ||
"version": "0.10.0", | ||
"author": "Twitter", | ||
@@ -14,6 +14,6 @@ "licenses": [ | ||
"type": "git", | ||
"url": "https://github.com/bower/bower" | ||
"url": "git://github.com/bower/bower.git" | ||
}, | ||
"main": "lib", | ||
"homepage": "http://bower.io/", | ||
"homepage": "http://bower.io", | ||
"engines": { | ||
@@ -23,32 +23,53 @@ "node": ">=0.8.0" | ||
"dependencies": { | ||
"archy": "0.0.2", | ||
"abbrev": "~1.0.4", | ||
"archy": "~0.0.2", | ||
"async": "~0.2.5", | ||
"bower-config": "~0.1.0", | ||
"bower-endpoint-parser": "~0.1.0", | ||
"bower-json": "~0.1.0", | ||
"bower-logger": "~0.1.0", | ||
"bower-registry-client": "~0.1.0", | ||
"chmodr": "~0.1.0", | ||
"colors": "~0.6.0-1", | ||
"fstream": "~0.1.19", | ||
"glob": "~3.1.14", | ||
"hogan.js": "~2.0.0", | ||
"lodash": "~1.0.1", | ||
"mkdirp": "~0.3.4", | ||
"nopt": "~2.0.0", | ||
"promptly": "~0.1.0", | ||
"rc": "~0.3.0", | ||
"read-package-json": "~0.1.8", | ||
"request": "~2.11.4", | ||
"rimraf": "~2.0.3", | ||
"semver": "~2.0.7", | ||
"stable": "~0.1.2", | ||
"tar": "~0.1.13", | ||
"fstream": "~0.1.22", | ||
"fstream-ignore": "~0.0.6", | ||
"glob": "~3.2.1", | ||
"graceful-fs": "~2.0.0", | ||
"handlebars": "~1.0.11", | ||
"inquirer": "~0.2.2", | ||
"junk": "~0.2.0", | ||
"mkdirp": "~0.3.5", | ||
"mout": "~0.6.0", | ||
"nopt": "~2.1.1", | ||
"lru-cache": "~2.3.0", | ||
"open": "0.0.3", | ||
"promptly": "~0.2.0", | ||
"q": "~0.9.2", | ||
"request": "~2.22.0", | ||
"request-progress": "~0.2.0", | ||
"request-replay": "~0.1.2", | ||
"rimraf": "~2.2.0", | ||
"semver": "~2.0.8", | ||
"stringify-object": "~0.1.4", | ||
"tar": "~0.1.17", | ||
"tmp": "~0.0.20", | ||
"unzip": "0.1.7", | ||
"unzip": "~0.1.7", | ||
"update-notifier": "~0.1.3", | ||
"which": "~1.0.5" | ||
}, | ||
"devDependencies": { | ||
"expect.js": "~0.2.0", | ||
"grunt": "~0.4.1", | ||
"grunt-simple-mocha": "~0.4.0", | ||
"grunt-contrib-watch": "~0.5.0", | ||
"grunt-contrib-jshint": "~0.6.0", | ||
"grunt-execute": "~0.1.4", | ||
"grunt-shell": "~0.3.0", | ||
"mocha": "~1.12.0", | ||
"nock": "~0.22.0", | ||
"istanbul": "~0.1.42", | ||
"proxyquire": "~0.4.1" | ||
}, | ||
"scripts": { | ||
"test": "mocha --reporter spec --timeout 40000" | ||
"test": "grunt test" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~1.8.1", | ||
"nock": "~0.17.3" | ||
}, | ||
"bin": { | ||
@@ -55,0 +76,0 @@ "bower": "bin/bower" |
158
README.md
@@ -25,3 +25,6 @@ # BOWER [![Build Status](https://secure.travis-ci.org/bower/bower.png?branch=master)](http://travis-ci.org/bower/bower) | ||
Also make sure that [git](http://git-scm.com/) is installed as some bower | ||
packages require it to be fetched and installed. | ||
## Usage | ||
@@ -41,4 +44,6 @@ | ||
bower install <package> | ||
# Using a specific Git-tagged version from a remote package | ||
# Using a specific version of a package | ||
bower install <package>#<version> | ||
# Using a different name and a specific version of a package | ||
bower install <name>=<package>#<version> | ||
``` | ||
@@ -51,3 +56,3 @@ | ||
public or private. ‡ | ||
* A local Git endpoint, i.e., a folder that's a Git repository. ‡ | ||
* A local endpoint, i.e., a folder that's a Git repository. ‡ | ||
* A shorthand endpoint, e.g., `someone/some-package` (defaults to GitHub). ‡ | ||
@@ -57,7 +62,7 @@ * A URL to a file, including `zip` and `tar` files. It's contents will be | ||
‡ These types of `<package>` make Git tags available. You can specify a | ||
[semver](http://semver.org/) tag to fetch a specific release, and lock the | ||
package to that version. | ||
‡ These types of `<package>` might have versions available. You can specify a | ||
[semver](http://semver.org/) compatible version to fetch a specific release, and lock the | ||
package to that version. You can also use ranges to specify a range of versions. | ||
All package contents are installed in the `components` directory by default. | ||
All package contents are installed in the `bower_components` directory by default. | ||
You should **never** directly modify the contents of this directory. | ||
@@ -131,57 +136,18 @@ | ||
Global configuration is handled by creating a `.bowerrc` in your home directory | ||
(i.e., `~/.bowerrc`). Local configuration is handled by creating a `.bowerrc` | ||
in your project's directory, allowing you to version a project-specific Bower | ||
configuration with the rest of your code base. | ||
The current spec can be read | ||
[here](https://docs.google.com/document/d/1APq7oA9tNao1UYWyOm8dKqlRP2blVkROYLZ2fLIjtWc/edit#heading=h.4pzytc1f9j8k) | ||
in the `Configuration` section. | ||
Bower will combine the local and global configurations (with local settings | ||
taking precedence). | ||
The `.bowerrc` defines several options: | ||
* `directory`: Set the default directory to install packaged components into. | ||
* `endpoint`: Set a custom registry endpoint. | ||
* `json`: Set the default JSON file for Bower to use when resolving dependencies. | ||
* `searchpath`: An array of additional URLs pointing to read-only Bower registries. | ||
* `shorthand_resolver`: Define a custom template for shorthand package names. | ||
```json | ||
{ | ||
"directory": "bower_components", | ||
"endpoint": "https://bower.mycompany.com", | ||
"json": "bower.json", | ||
"searchpath": [ | ||
"https://bower.herokuapp.com" | ||
], | ||
"shorthand_resolver": "git://example.com/{{{ organization }}}/{{{ package }}}.git" | ||
} | ||
``` | ||
The `searchpath` array is useful if your organization wishes to maintain a | ||
private registry of packages while also taking advantage of public Bower | ||
registries. If a package is not found at your private endpoint, Bower will | ||
consult the registries specified in the `searchpath` array. | ||
The `shorthand_resolver` key provides support for defining a custom template | ||
which Bower uses when constructing a URL for a given shorthand. For example, if | ||
a shorthand of `twitter/flight` or `twitter/flight#v1.0.0` is specified in the | ||
package manifest, the following data can be referenced from within the | ||
`.bowerrc` as part of the `shorthand_resolver` template: | ||
* `endpoint`: `twitter/flight` | ||
* `organization`: `twitter` | ||
* `package`: `flight` | ||
**N.B.** To run your own Bower Endpoint for custom packages that are behind a | ||
firewall, you can use a simple implementation of the [Bower Registry](https://github.com/bower/registry). | ||
## Defining a package | ||
You must create a JSON file -- `bower.json` by default -- in your project's | ||
root, and specify all of its dependencies. This is similar to Node's | ||
`package.json`, or Ruby's `Gemfile`, and is useful for locking down a project's | ||
dependencies. | ||
You must create a `bower.json` in your project's root, and specify all of its | ||
dependencies. This is similar to Node's `package.json`, or Ruby's `Gemfile`, | ||
and is useful for locking down a project's dependencies. | ||
*NOTE:* In versions of Bower before 0.9.0 the package metadata file was called `component.json` rather than `bower.json`. This has changed to avoid a name clash with another tool. You can still use `component.json` for now but it is deprecated and the automatic fallback is likely to be removed in an upcoming release. | ||
*NOTE:* In versions of Bower before 0.9.0 the package metadata file was called | ||
`component.json` rather than `bower.json`. This has changed to avoid a name | ||
clash with another tool. You can still use `component.json` for now but it is | ||
deprecated and the automatic fallback is likely to be removed in an upcoming | ||
release. | ||
@@ -230,5 +196,4 @@ You can interactively create a `bower.json` with the following command: | ||
If you pass the `--map` option to Bower's `list` command, it will generate JSON | ||
with dependency objects. Alternatively, you can pass the `--paths` option to | ||
the `list` command to get a simple path-to-name mapping: | ||
If you pass the `--paths` option to Bower's `list` command, you will get a | ||
simple path-to-name mapping: | ||
@@ -243,3 +208,7 @@ ```json | ||
Alternatively, every command supports the `--json` option that makes bower | ||
output JSON. Command result is outputted to `stdout` and error/logs to | ||
`stderr`. | ||
## Programmatic API | ||
@@ -268,18 +237,12 @@ | ||
Commands emit four types of events: `data`, `end`, `result`, and `error`. | ||
Commands emit three types of events: `log`, `end`, and `error`. | ||
`error` will only be emitted if something goes wrong. Not all commands emit all | ||
events; for a detailed look, check out the code in `lib/commands`. | ||
* `log` is emitted to report the state/progress of the command. | ||
* `error` will only be emitted if something goes wrong. | ||
* `end` is emitted when the command successfully ends. | ||
`data` is typically a colorized string, ready to show to an end user. `search` | ||
and `lookup` emit `packages` and `package`, respectively. Those events contain | ||
a JSON representation of the result of the command. | ||
For a better of idea how this works, you may want to check out [our bin | ||
file](https://github.com/bower/bower/blob/master/bin/bower). | ||
file](https://github.com/bower/bower/blob/rewrite/bin/bower). | ||
For the install command, there is an additional `package` event that is emitted | ||
for each installed/uninstalled package. | ||
## Completion (experimental) | ||
@@ -340,28 +303,31 @@ | ||
* [@addyosmani](https://github.com/addyosmani) | ||
* [@angus-c](https://github.com/angus-c) | ||
* [@borismus](https://github.com/borismus) | ||
* [@carsonmcdonald](https://github/@carsonmcdonald) | ||
* [@chriseppstein](https://github.com/chriseppstein) | ||
* [@danwrong](https://github.com/danwrong) | ||
* [@davidmaxwaterman](https://github.com/davidmaxwaterman) | ||
* [@desandro](https://github.com/desandro) | ||
* [@hemanth](https://github.com/hemanth) | ||
* [@isaacs](https://github.com/isaacs) | ||
* [@josh](https://github.com/josh) | ||
* [@jrburke](https://github.com/jrburke) | ||
* [@marcelombc](https://github.com/marcelombc) | ||
* [@mklabs](https://github.com/mklabs) | ||
* [@paulirish](https://github.com/paulirish) | ||
* [@richo](https://github.com/richo) | ||
* [@rvagg](https://github.com/rvagg) | ||
* [@sindresorhus](https://github.com/sindresorhus) | ||
* [@SlexAxton](https://github.com/SlexAxton) | ||
* [@sstephenson](https://github.com/sstephenson) | ||
* [@tomdale](https://github.com/tomdale) | ||
* [@uzquiano](https://github.com/uzquiano) | ||
* [@visionmedia](https://github.com/visionmedia) | ||
* [@wagenet](https://github.com/wagenet) | ||
* [@wibblymat](https://github.com/wibblymat) | ||
* [@wycats](https://github.com/wycats) | ||
[@addyosmani](https://github.com/addyosmani), | ||
[@angus-c](https://github.com/angus-c), | ||
[@borismus](https://github.com/borismus), | ||
[@carsonmcdonald](https://github.com/carsonmcdonald), | ||
[@chriseppstein](https://github.com/chriseppstein), | ||
[@danwrong](https://github.com/danwrong), | ||
[@davidmaxwaterman](https://github.com/davidmaxwaterman), | ||
[@desandro](https://github.com/desandro), | ||
[@hemanth](https://github.com/hemanth), | ||
[@isaacs](https://github.com/isaacs), | ||
[@josh](https://github.com/josh), | ||
[@jrburke](https://github.com/jrburke), | ||
[@marcelombc](https://github.com/marcelombc), | ||
[@marcooliveira](https://github.com/marcooliveira), | ||
[@mklabs](https://github.com/mklabs), | ||
[@necolas](https://github.com/necolas), | ||
[@paulirish](https://github.com/paulirish), | ||
[@richo](https://github.com/richo), | ||
[@rvagg](https://github.com/rvagg), | ||
[@sindresorhus](https://github.com/sindresorhus), | ||
[@SlexAxton](https://github.com/SlexAxton), | ||
[@sstephenson](https://github.com/sstephenson), | ||
[@svnlto](https://github.com/svnlto), | ||
[@tomdale](https://github.com/tomdale), | ||
[@uzquiano](https://github.com/uzquiano), | ||
[@visionmedia](https://github.com/visionmedia), | ||
[@wagenet](https://github.com/wagenet), | ||
[@wibblymat](https://github.com/wibblymat), | ||
[@wycats](https://github.com/wycats) | ||
@@ -368,0 +334,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 7 instances in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 12 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
445898
0
10307
0
4
2
34
11
106
331
+ Addedbower-config@~0.1.0
+ Addedbower-endpoint-parser@~0.1.0
+ Addedbower-json@~0.1.0
+ Addedbower-logger@~0.1.0
+ Addedbower-registry-client@~0.1.0
+ Addedchmodr@~0.1.0
+ Addedfstream-ignore@~0.0.6
+ Addedgraceful-fs@~2.0.0
+ Addedhandlebars@~1.0.11
+ Addedinquirer@~0.2.2
+ Addedjunk@~0.2.0
+ Addedlru-cache@~2.3.0
+ Addedmout@~0.6.0
+ Addedopen@0.0.3
+ Addedq@~0.9.2
+ Addedrequest-progress@~0.2.0
+ Addedrequest-replay@~0.1.2
+ Addedstringify-object@~0.1.4
+ Addedamdefine@1.0.1(transitive)
+ Addedasn1@0.1.11(transitive)
+ Addedassert-plus@0.1.5(transitive)
+ Addedasync@0.9.2(transitive)
+ Addedaws-sign@0.3.0(transitive)
+ Addedboom@0.4.2(transitive)
+ Addedbower-config@0.4.5(transitive)
+ Addedbower-endpoint-parser@0.1.0(transitive)
+ Addedbower-logger@0.1.0(transitive)
+ Addedbower-registry-client@0.1.6(transitive)
+ Addedchmodr@0.1.2(transitive)
+ Addedcli-color@0.2.3(transitive)
+ Addedcombined-stream@0.0.7(transitive)
+ Addedcookie-jar@0.3.0(transitive)
+ Addedcryptiles@0.2.2(transitive)
+ Addedctype@0.5.3(transitive)
+ Addeddelayed-stream@0.0.5(transitive)
+ Addedes5-ext@0.9.2(transitive)
+ Addedevent-emitter@0.2.2(transitive)
+ Addedforever-agent@0.5.2(transitive)
+ Addedform-data@0.0.80.1.4(transitive)
+ Addedfstream-ignore@0.0.10(transitive)
+ Addedglob@3.2.11(transitive)
+ Addedgraceful-fs@2.0.3(transitive)
+ Addedhandlebars@1.0.12(transitive)
+ Addedhawk@0.13.11.0.0(transitive)
+ Addedhoek@0.8.50.9.1(transitive)
+ Addedhttp-signature@0.10.1(transitive)
+ Addedinquirer@0.2.5(transitive)
+ Addedjson-stringify-safe@4.0.0(transitive)
+ Addedjunk@0.2.2(transitive)
+ Addedlodash@1.2.1(transitive)
+ Addedlru-cache@2.3.1(transitive)
+ Addedmatch-stream@0.0.2(transitive)
+ Addedmemoizee@0.2.6(transitive)
+ Addedmime@1.2.11(transitive)
+ Addedminimatch@0.3.0(transitive)
+ Addedmout@0.6.0(transitive)
+ Addedmute-stream@0.0.3(transitive)
+ Addednext-tick@0.1.0(transitive)
+ Addednode-uuid@1.4.8(transitive)
+ Addednopt@2.1.2(transitive)
+ Addedoauth-sign@0.3.0(transitive)
+ Addedopen@0.0.3(transitive)
+ Addedoptimist@0.3.70.6.1(transitive)
+ Addedosenv@0.0.3(transitive)
+ Addedpromptly@0.2.1(transitive)
+ Addedq@0.9.7(transitive)
+ Addedqs@0.6.6(transitive)
+ Addedrequest@2.22.02.27.0(transitive)
+ Addedrequest-progress@0.2.3(transitive)
+ Addedrequest-replay@0.1.100.2.0(transitive)
+ Addedretry@0.6.1(transitive)
+ Addedrimraf@2.2.8(transitive)
+ Addedsntp@0.2.4(transitive)
+ Addedsource-map@0.1.43(transitive)
+ Addedstringify-object@0.1.8(transitive)
+ Addedthrottleit@0.0.2(transitive)
+ Addedtunnel-agent@0.3.0(transitive)
+ Addeduglify-js@2.3.6(transitive)
+ Addedunzip@0.1.11(transitive)
+ Addedwordwrap@0.0.3(transitive)
- Removedasync@~0.2.5
- Removedhogan.js@~2.0.0
- Removedlodash@~1.0.1
- Removedrc@~0.3.0
- Removedread-package-json@~0.1.8
- Removedstable@~0.1.2
- Removedansi@0.3.1(transitive)
- Removeddeep-extend@0.2.11(transitive)
- Removedglob@3.1.21(transitive)
- Removedgraceful-fs@1.1.141.2.3(transitive)
- Removedhogan.js@2.0.0(transitive)
- Removedinherits@1.0.2(transitive)
- Removedini@1.1.0(transitive)
- Removedlodash@1.0.2(transitive)
- Removedlru-cache@2.0.42.7.3(transitive)
- Removedmatch-stream@0.0.1(transitive)
- Removedminimatch@0.2.14(transitive)
- Removednopt@2.0.0(transitive)
- Removednpmlog@0.1.1(transitive)
- Removedpromptly@0.1.0(transitive)
- Removedrc@0.3.5(transitive)
- Removedread-package-json@0.1.13(transitive)
- Removedrequest@2.11.4(transitive)
- Removedrimraf@2.0.3(transitive)
- Removedsemver@1.1.4(transitive)
- Removedslide@1.1.6(transitive)
- Removedstable@0.1.8(transitive)
- Removedunzip@0.1.7(transitive)
Updatedarchy@0.0.2
Updatedfstream@~0.1.22
Updatedglob@~3.2.1
Updatedmkdirp@~0.3.5
Updatednopt@~2.1.1
Updatedpromptly@~0.2.0
Updatedrequest@~2.22.0
Updatedrimraf@~2.2.0
Updatedsemver@~2.0.8
Updatedtar@~0.1.17
Updatedunzip@~0.1.7