// ========================================== // BOWER: Manager Object Definition // ========================================== // Copyright 2012 Twitter, Inc // Licensed under The MIT License // http://opensource.org/licenses/MIT // ========================================== // Events: // - install: fired when package installed // - resolve: fired when deps resolved (with a true/false indicating success or error) // - error: fired on all errors // - data: fired when trying to output data // - end: fired when finished installing // ========================================== var events = require('events'); var async = require('async'); var path = require('path'); var glob = require('glob'); var fs = require('fs'); var Package = require('./package'); var UnitWork = require('./unit_work'); var config = require('./config'); var fileExists = require('../util/file-exists'); var template = require('../util/template'); var prune = require('../util/prune'); // read local dependencies (with versions) // read json dependencies (resolving along the way into temp dir) // merge local dependencies with json dependencies // prune and move dependencies into local directory var Manager = function (endpoints, opts) { this.dependencies = {}; this.cwd = process.cwd(); this.endpoints = endpoints || []; this.unitWork = new UnitWork; this.opts = opts || {}; this.errors = []; }; Manager.prototype = Object.create(events.EventEmitter.prototype); Manager.prototype.constructor = Manager; Manager.prototype.loadJSON = function () { var json = path.join(this.cwd, config.json); fileExists(json, function (exists) { if (!exists) { // If the json does not exist, assume one this.json = { name: path.basename(this.cwd), version: '0.0.0' }, this.name = this.json.name; this.version = this.json.version; return this.emit('loadJSON'); } fs.readFile(json, 'utf8', function (err, json) { if (err) return this.emit('error', err); this.json = JSON.parse(json); this.name = this.json.name; this.version = this.json.version; this.emit('loadJSON'); }.bind(this)); }.bind(this)); return this; }; Manager.prototype.resolve = function () { var resolved = function () { // If there is errors, report them if (this.errors.length) return this.reportErrors(); // If there is an error while pruning (conflict) then abort installation if (!this.prune()) return this.emit('resolve', false); // Otherwise all is fine, so we install this.once('install', this.emit.bind(this, 'resolve', true)).install(); }.bind(this); this.once('resolveLocal', function () { if (this.endpoints.length) { this.once('resolveEndpoints', resolved).resolveEndpoints(); } else { this.once('resolveFromJson', resolved).resolveFromJson(); } }).resolveLocal(); return this; }; Manager.prototype.resolveLocal = function () { glob('./' + config.directory + '/*', function (err, dirs) { if (err) return this.emit('error', err); dirs.forEach(function (dir) { var name = path.basename(dir); this.dependencies[name] = []; this.dependencies[name].push(new Package(name, dir, this)); }.bind(this)); this.emit('resolveLocal'); }.bind(this)); return this; }; Manager.prototype.resolveEndpoints = function () { // Iterate through paths // Add to depedencies array // Prune & install var endpointNames = this.opts.endpointNames || {}; async.forEach(this.endpoints, function (endpoint, next) { var name = endpointNames[endpoint]; var pkg = new Package(name, endpoint, this); this.dependencies[name] = this.dependencies[name] || []; this.dependencies[name].push(pkg); pkg.once('resolve', next).resolve(); pkg.once('error', function (err) { this.errors.push({ pkg: pkg, error: err }); next(); }.bind(this)); }.bind(this), this.emit.bind(this, 'resolveEndpoints')); return this; }; Manager.prototype.resolveFromJson = function () { // loadJSON // Resolve dependencies // Add to dependencies array // Prune & install this.once('loadJSON', function () { if (!this.json.dependencies) return this.emit('error', new Error('Could not find any dependencies')); async.forEach(Object.keys(this.json.dependencies), function (name, next) { var endpoint = this.json.dependencies[name]; var pkg = new Package(name, endpoint, this); this.dependencies[name] = this.dependencies[name] || []; this.dependencies[name].push(pkg); pkg.once('resolve', next).resolve(); pkg.once('error', function (err) { this.errors.push({ pkg: pkg, error: err }); next(); }.bind(this)); }.bind(this), this.emit.bind(this, 'resolveFromJson')); }.bind(this)).loadJSON(); return this; }; // Private Manager.prototype.getDeepDependencies = function () { var result = {}; for (var name in this.dependencies) { this.dependencies[name].forEach(function (pkg) { result[pkg.name] = result[pkg.name] || []; result[pkg.name].push(pkg); pkg.getDeepDependencies().forEach(function (pkg) { result[pkg.name] = result[pkg.name] || []; result[pkg.name].push(pkg); }); }); } return result; }; Manager.prototype.prune = function () { try { this.dependencies = prune(this.getDeepDependencies()); } catch (err) { this.emit('error', err); return false; } return true; }; Manager.prototype.install = function () { async.forEach(Object.keys(this.dependencies), function (name, next) { this.dependencies[name][0].once('install', next).install(); }.bind(this), this.emit.bind(this, 'install')); }; Manager.prototype.muteDependencies = function () { for (var name in this.dependencies) { this.dependencies[name].forEach(function (pkg) { pkg.removeAllListeners(); }); } }; Manager.prototype.reportErrors = function () { template('error-summary', { errors: this.errors }).on('data', function (data) { this.muteDependencies(); this.emit('data', data); this.emit('resolve', false); }.bind(this)); }; module.exports = Manager;