Add update command and also finish install.

This commit is contained in:
André Cruz
2013-05-29 10:38:39 +01:00
parent d62ccfd234
commit 70a4ab863d
7 changed files with 319 additions and 89 deletions

View File

@@ -18,7 +18,6 @@ function Project(config) {
// -----------------
Project.prototype.install = function (endpoints, options) {
var repairResult;
var that = this;
// If already working, error out
@@ -26,6 +25,9 @@ Project.prototype.install = function (endpoints, options) {
return Q.reject(createError('Already working', 'EWORKING'));
}
options = options || {};
this._production = !!options.production;
// If no endpoints were specified, simply repair the project
// Note that we also repair incompatible packages
if (!endpoints) {
@@ -38,58 +40,155 @@ Project.prototype.install = function (endpoints, options) {
// Start by repairing the project, installing only missing packages
return this._repair()
// Analyse the project
.then(function (result) {
repairResult = result;
return that._analyse();
})
.then(that._analyse.bind(this))
.spread(function (json, tree, flattened) {
var targetNames = {};
var targets = [];
var installed = {};
var targets = {};
var resolved = {};
var installed;
// Mark targets
endpoints.forEach(function (target) {
var decEndpoint = endpointParser.decompose(target);
targetNames[decEndpoint.name] = true;
targets.push(decEndpoint);
endpoints.forEach(function (endpoint) {
var decEndpoint = endpointParser.decompose(endpoint);
targets[decEndpoint.name] = decEndpoint;
});
// Mark every package from the tree as installed
// if they are not a target or a non-shared descendant of a target
// TODO: We should traverse the tree (deep first) and
// add each leaf to the resolved
// If a leaf is a target, we abort traversal of it
mout.object.forOwn(flattened, function (decEndpoint, name) {
if (targetNames[name]) {
// Mark every package from the tree as resolved
// if it's not a target or a non-shared descendant of a target
// This is done by walking the tree (deep first) and abort traversal
// as soon as one target was found
that._walkTree(tree, function (node, name) {
if (targets[name]) {
return false; // Abort traversal
}
resolved[name] = node.pkgMeta;
});
installed = mout.object.map(flattened, function (decEndpoint) {
return decEndpoint.pkgMeta;
});
// Bootstrap the process
return that._bootstrap(targets, resolved, installed)
// Handle save and saveDev options
.then(function () {
var key;
if (!options.save && !options.saveDev) {
return;
}
installed[name] = decEndpoint.pkgMeta;
});
key = options.save ? 'dependencies' : 'devDependencies';
that._json[key] = that._json[key] || {};
// Configure the manager and kick in the resolve process
return that._manager
.configure(targets, installed)
.resolve()
// Install resolved ones
.then(function () {
return that._manager.install();
})
// Resolve the promise with the repair and install results,
// by merging them together
.then(function (result) {
return mout.object.fillIn(result, repairResult);
mout.object.forOwn(targets, function (decEndpoint) {
var source = decEndpoint.source === decEndpoint.registryName ? '' : decEndpoint.source;
var target = decEndpoint.pkgMeta.version ? '~' + decEndpoint.pkgMeta.version : decEndpoint.target;
that._json[key][decEndpoint.name] = mout.string.ltrim(source + '#' + target, ['#']);
});
return that._saveJson()
.progress(function (notification) {
return notification;
});
});
})
.fin(function () {
that._working = false;
});
// TODO: handle save saveDev production
};
Project.prototype.update = function (names) {
Project.prototype.update = function (names, options) {
var that = this;
var targets;
var resolved;
var installed;
var repaired;
var promise;
// If already working, error out
if (this._working) {
return Q.reject(createError('Already working', 'EWORKING'));
}
options = options || {};
this._production = !!options.production;
// If no names were specified, we update every package
if (!names) {
// Analyse the project
promise = this._analyse()
.spread(function (json, tree, flattened) {
// Mark each json entry as targets
targets = mout.object.map(json.dependencies, function (value, key) {
return endpointParser.json2decomposed(key, value);
});
// Mark installed
installed = mout.object.map(flattened, function (decEndpoint) {
return decEndpoint.pkgMeta;
});
});
// Otherwise we selectively update the specified ones
} else {
// Start by repairing the project
// Note that we also repair incompatible packages
promise = this._repair(true)
// Analyse the project
.then(function (result) {
repaired = result;
return that._analyse();
})
.spread(function (json, tree, flattened) {
targets = {};
resolved = {};
// Mark targets
names.forEach(function (name) {
var decEndpoint = flattened[name];
var jsonEntry;
if (!decEndpoint) {
throw createError('Package ' + name + ' is not installed', 'ENOTINSTALLED');
}
// If it was repaired, don't include in the targets
if (repaired[name]) {
return;
}
// Use json entry if available,
// fallbacking to the installed one
jsonEntry = json.dependencies && json.dependencies[name];
if (jsonEntry) {
targets[name] = endpointParser.json2decomposed(name, jsonEntry);
} else {
targets[name] = decEndpoint;
}
});
// Mark every package from the tree as resolved
// if it's not a target or a non-shared descendant of a target
that._walkTree(tree, function (node, name) {
if (targets[name]) {
return false; // Abort traversal
}
resolved[name] = node.pkgMeta;
});
// Mark installed
installed = mout.object.map(flattened, function (decEndpoint) {
return decEndpoint.pkgMeta;
});
});
}
// Bootstrap the process
return promise.then(function () {
return that._bootstrap(targets, resolved, installed);
})
.fin(function () {
that._working = false;
});
};
Project.prototype.uninstall = function (names, options) {
@@ -100,10 +199,6 @@ Project.prototype.getTree = function () {
};
Project.prototype.getFlatTree = function () {
};
// -----------------
Project.prototype._analyse = function () {
@@ -117,28 +212,50 @@ Project.prototype._analyse = function () {
root = {
name: json.name,
source: this._config.cwd,
target: json.version,
dir: this._config.cwd,
pkgMeta: json
};
// Restore the original dependencies cross-references,
// that is, the parent-child relationships
this._restoreNode(root, flattened);
// Do the same for the dev dependencies
if (!this._config.production) {
if (!this._production) {
this._restoreNode(root, flattened, 'devDependencies');
}
// Parse extraneous
mout.object.forOwn(flattened, function (decEndpoint) {
if (!decEndpoint.dependants) {
decEndpoint.extraneous = true;
}
});
return [json, root, flattened];
}.bind(this));
};
Project.prototype._bootstrap = function (targets, resolved, installed) {
// Configure the manager and kick in the resolve process
return this._manager
.configure(mout.object.values(targets), resolved, installed)
.resolve()
// Install resolved ones
.then(function () {
return this._manager.install();
}.bind(this));
};
Project.prototype._repair = function (incompatible) {
var that = this;
return this._analyse()
.spread(function (json, tree, flattened) {
var targets = [];
var installed = {};
var resolved = {};
var isBroken = false;
// Figure out which are the missing/incompatible ones
@@ -150,8 +267,8 @@ Project.prototype._repair = function (incompatible) {
} else if (incompatible && decEndpoint.incompatible) {
targets.push(decEndpoint);
isBroken = true;
} else {
installed[name] = decEndpoint.pkgMeta;
} else if (!decEndpoint.extraneous) {
resolved[name] = decEndpoint.pkgMeta;
}
});
@@ -161,23 +278,22 @@ Project.prototype._repair = function (incompatible) {
}
// Configure the manager and kick in the resolve process
return that._manager
.configure(targets, installed)
.resolve()
// Install after resolve
.then(function () {
return that._manager.install();
});
return that._bootstrap(targets, resolved);
});
};
Project.prototype._readJson = function () {
var deferred = Q.defer();
var that = this;
var deferred;
// TODO: refactor!
if (this._json) {
return Q.resolve(this._json);
}
deferred = Q.defer();
// Read local json
Q.nfcall(bowerJson.find, this._config.cwd)
this._json = Q.nfcall(bowerJson.find, this._config.cwd)
.then(function (filename) {
// If it is a component.json, warn about the deprecation
if (path.basename(filename) === 'component.json') {
@@ -193,6 +309,8 @@ Project.prototype._readJson = function () {
});
}
that._jsonFile = filename;
// Read it
return Q.nfcall(bowerJson.read, filename)
.fail(function (err) {
@@ -202,9 +320,36 @@ Project.prototype._readJson = function () {
});
}, function () {
// No json file was found, assume one
return Q.nfcall(bowerJson.parse, { name: path.basename(this._config.cwd) });
}.bind(this))
.then(deferred.resolve, deferred.reject, deferred.notify);
return Q.nfcall(bowerJson.parse, {
name: path.basename(that._config.cwd)
});
})
.then(function (json) {
that._json = json;
deferred.resolve(json);
}, deferred.reject, deferred.notify);
return deferred.promise;
};
Project.prototype._saveJson = function (json) {
var deferred = Q.defer();
if (!this._jsonFile) {
process.nextTick(function () {
deferred.notify({
level: 'warn',
id: 'no-json',
message: 'No bower.json file to save to'
});
deferred.resolve();
});
} else {
json = json || this._json;
Q.nfcall(fs.writeFile, this._jsonFile, JSON.stringify(json, null, ' '))
.then(deferred.resolve, deferred.reject, deferred.notify);
}
return deferred.promise;
};
@@ -212,7 +357,6 @@ Project.prototype._readJson = function () {
Project.prototype._readInstalled = function () {
var componentsDir = path.join(this._config.cwd, this._config.directory);
// TODO: refactor
// Gather all folders that are actual packages by
// looking for the package metadata file
return Q.nfcall(glob, '*/.bower.json', {
@@ -227,14 +371,18 @@ Project.prototype._readInstalled = function () {
filenames.forEach(function (filename) {
var promise;
var name = path.dirname(filename);
var jsonFile = path.join(componentsDir, filename);
// Read package metadata
promise = Q.nfcall(fs.readFile, path.join(componentsDir, filename))
promise = Q.nfcall(fs.readFile, jsonFile)
.then(function (contents) {
var pkgMeta = JSON.parse(contents.toString());
decEndpoints[name] = {
name: name,
source: pkgMeta._source,
target: pkgMeta.version,
dir: path.dirname(jsonFile),
pkgMeta: pkgMeta
};
});
@@ -251,6 +399,22 @@ Project.prototype._readInstalled = function () {
});
};
Project.prototype._walkTree = function (node, fn) {
var queue = [node];
var result;
while (queue.length) {
node = queue.shift();
result = fn(node, node.name);
if (result === false) {
continue;
}
queue.unshift.apply(queue, mout.object.values(node.dependencies));
}
};
Project.prototype._restoreNode = function (node, flattened, jsonKey) {
// Do not restore if already processed or if the node is
// missing or incompatible
@@ -267,15 +431,14 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
// Check if the dependency is not installed
if (!local) {
local = json;
flattened[key] = local = json;
local.missing = true;
flattened[key] = local;
// Even if it is installed, check if it's compatible
} else if (!local.incompatible && !this._manager.areCompatible(local.pkgMeta.version || '*', json.target)) {
json.pkgMeta = local.pkgMeta;
flattened[key] = local = json;
local = json;
local.incompatible = true;
flattened[key] = local;
}
// Cross reference