Skip to content

Commit

Permalink
Merge 5377541 into 3838a3b
Browse files Browse the repository at this point in the history
  • Loading branch information
kodypeterson committed Mar 23, 2015
2 parents 3838a3b + 5377541 commit 06e7172
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 32 deletions.
67 changes: 37 additions & 30 deletions lib/core/Project.js
Expand Up @@ -14,6 +14,7 @@ var createError = require('../util/createError');
var readJson = require('../util/readJson');
var validLink = require('../util/validLink');
var scripts = require('./scripts');
var LockFile = require('../util/lockFile');

function Project(config, logger) {
// This is the only architecture component that ensures defaults
Expand All @@ -23,6 +24,7 @@ function Project(config, logger) {
this._config = defaultConfig(config);
this._logger = logger || new Logger();
this._manager = new Manager(this._config, this._logger);
this._lockFile = new LockFile(this);

this._options = {};
}
Expand All @@ -44,39 +46,41 @@ Project.prototype.install = function (decEndpoints, options, config) {
this._config = config || {};
this._working = true;

// Analyse the project
return this._analyse()
.spread(function (json, tree) {
// It shows an error when issuing `bower install`
// and no bower.json is present in current directory
if(!that._jsonFile && decEndpoints.length === 0 ) {
throw createError('No bower.json present', 'ENOENT');
}
return that._lockFile.preinstall().then(function() {
// Analyse the project
return that._analyse()
.spread(function (json, tree) {
// It shows an error when issuing `bower install`
// and no bower.json is present in current directory
if (!that._jsonFile && decEndpoints.length === 0) {
throw createError('No bower.json present', 'ENOENT');
}

// Recover tree
that.walkTree(tree, function (node, name) {
if (node.incompatible) {
incompatibles.push(node);
} else if (node.missing || node.different || that._config.force) {
targets.push(node);
} else {
resolved[name] = node;
}
}, true);
// Recover tree
that.walkTree(tree, function (node, name) {
if (node.incompatible) {
incompatibles.push(node);
} else if (node.missing || node.different || that._config.force) {
targets.push(node);
} else {
resolved[name] = node;
}
}, true);

// Add decomposed endpoints as targets
decEndpoints = decEndpoints || [];
decEndpoints.forEach(function (decEndpoint) {
// Mark as new so that a conflict for this target
// always require a choice
// Also allows for the target to be converted in case
// of being *
decEndpoint.newly = true;
targets.push(decEndpoint);
});
// Add decomposed endpoints as targets
decEndpoints = decEndpoints || [];
decEndpoints.forEach(function (decEndpoint) {
// Mark as new so that a conflict for this target
// always require a choice
// Also allows for the target to be converted in case
// of being *
decEndpoint.newly = true;
targets.push(decEndpoint);
});

// Bootstrap the process
return that._bootstrap(targets, resolved, incompatibles);
// Bootstrap the process
return that._bootstrap(targets, resolved, incompatibles);
});
})
.then(function () {
return that._manager.preinstall(that._json);
Expand Down Expand Up @@ -115,6 +119,9 @@ Project.prototype.install = function (decEndpoints, options, config) {
});
});
})
.then(function() {
return that._lockFile.generate();
})
.fin(function () {
that._installed = null;
that._working = false;
Expand Down
119 changes: 119 additions & 0 deletions lib/util/lockFile.js
@@ -0,0 +1,119 @@
var Q = require('q');
var path = require('path');
var fs = require('fs');
var createError = require('../util/createError');
var mout = require('mout');

function LockFile(Project) {
this._project = Project;
this._config = this._project._config;
this._lockFile = path.join(this._config.cwd || '', 'bower.lock');
this._generateLockFile = false;
}

LockFile.prototype.generate = function() {
var that = this;

if (that._generateLockFile) {
return that._project.getTree().then(function (results) {
that._json = results[0];
mout.object.forOwn(that._json.dependencies, function (dependency, packageName) {
dependency.pkgMeta = that._project._manager._dissected[packageName].pkgMeta;
});
return that.writeFile(that._json);
});
}
return Q.resolve({});
};

LockFile.prototype.writeFile = function(json) {
var that = this;
var jsonStr = JSON.stringify(json, null, ' ') + '\n';

return Q.nfcall(fs.writeFile, that._lockFile, jsonStr);
};

LockFile.prototype.preinstall = function(targets, resolved, incompatibles) {
var that = this;

return that._project.getJson().then(function() {
return Q.nfcall(fs.readFile, that._lockFile)
.then(function (json) {
// The lockfile was found
that._lockContents = JSON.parse(json.toString());
return that.compare().spread(function (identical, newDependencies) {
if (!identical && that._project._options.production) {
return Q.reject(createError('bower.json does not match lockfile'));
}
var _jsonDep = that._lockContents.pkgMeta.dependencies;
newDependencies.forEach(function (value) {
that._generateLockFile = true;
// Add the new dependencies into the
// dependency map that we are going to return
// back as the _json.dependencies
_jsonDep[value] = that._project._json.dependencies[value];
});

// Now, update _jsonDep with correct version
// from lockfile to return back to _json.dependencies
mout.object.forOwn(_jsonDep, function (dependency, packageName) {
if (that._lockContents.dependencies[packageName] !== undefined) {
// This is not a new dependency
var lockedPkgInfo = that._lockContents.dependencies[packageName];
dependency = lockedPkgInfo.pkgMeta._source + '#' + lockedPkgInfo.pkgMeta._release;

that._project._manager.configure({});
}
});

that._project._json.dependencies = _jsonDep;

return Q.resolve({});
});
}, function (err) {
if (that._project._options.production && err.code === 'ENOENT') {
// This is a production install
// a lock file is required
return Q.reject(createError('No lockfile was found.', 'ENOENT'));
}
that._generateLockFile = true;
// This is a non-production install
// Continue as normal
return Q.resolve({});
});
});
};

LockFile.prototype.compare = function() {
var that = this;

return that._project.getTree().then(function (results) {
var ret, identical = true;
var lockDependencies = Object.keys(that._lockContents.dependencies);
var newDependencies = Object.keys(results[0].dependencies);

mout.object.forOwn(results[0].dependencies, function (dependency, packageName) {
if (lockDependencies.indexOf(packageName) !== -1 && ret === undefined) {
// This is an already existing dependency
// and not a new one
newDependencies.splice(newDependencies.indexOf(packageName), 1);
if (
that._lockContents.dependencies[packageName].endpoint.target !==
results[0].dependencies[packageName].endpoint.target
) {
// The target version is different
ret = Q.reject(createError('Need to run bower update <package>'));
}
}
}, this);

if (newDependencies.length > 0) {
identical = false;
}

if (ret === undefined) ret = Q.resolve([identical, newDependencies]);
return ret;
});
};

module.exports = LockFile;
126 changes: 125 additions & 1 deletion test/commands/install.js
Expand Up @@ -39,6 +39,8 @@ describe('bower install', function () {

var gitPackage = new helpers.TempDir();

var lockFile = {};

it('writes to bower.json if --save flag is used', function () {
package.prepare();

Expand Down Expand Up @@ -95,7 +97,7 @@ describe('bower install', function () {
});
});


it('does not write to bower.json if only --save-exact flag is used', function() {
package.prepare({
'bower.json': {
Expand Down Expand Up @@ -273,4 +275,126 @@ describe('bower install', function () {
});
});
});

it('generates a lockFile', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
packageGit: gitPackage.path + '#1.0.0'
}
}
});

return helpers.run(install).then(function() {
var lockFileContents = tempDir.readJson('bower.lock');
expect(lockFileContents).to.not.be(undefined);
expect(lockFileContents).to.not.eql({});
lockFile = lockFileContents;
});
});

it('requires a lockFile when production', function (next) {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
packageGit: gitPackage.path + '#1.0.0'
}
}
});

return helpers.run(install, [[], {production: true}]).then(function() {
next(new Error('Error not thrown as expected'));
}, function() {
next();
});
});

it('installs from lockFile when exists', function (next) {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
packageGit: gitPackage.path + '#1.0.0'
}
},
'bower.lock': lockFile
});

return helpers.run(install).then(function() {
next();
}, function() {
next();
});
});

it('error when tampering with version number', function (next) {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
packageGit: gitPackage.path + '#1.0.1'
}
},
'bower.lock': lockFile
});

return helpers.run(install).then(function() {
next(new Error('Error not thrown as expected'));
}, function() {
next();
});
});

it('new dependencies added in bower.json are installed', function () {
package.prepare();

tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
packageGit: gitPackage.path + '#1.0.0',
package: '0.1.1'
}
},
'bower.lock': lockFile
});

return helpers.run(install).then(function() {
expect(tempDir.read('bower_components/package/bower.json')).to.contain('"version": "0.1.1"');
var lockFileContents = tempDir.read('bower.lock');
expect(lockFileContents).to.contain('"package"');
expect(lockFileContents).to.contain('"_release": "0.1.1"');
lockFile = lockFileContents;
});
});

it('should install from lock file', function () {
package.prepare();

// Modify the lock file to match
// test bower.json to test that
// even though a newer version is available
// the lock file is installing what it has
lockFile = JSON.parse(lockFile);
lockFile.dependencies.package.endpoint.target = '~0.1.0';

tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
packageGit: gitPackage.path + '#1.0.0',
package: '~0.1.0'
}
},
'bower.lock': lockFile
});

return helpers.run(install).then(function() {
expect(tempDir.read('bower_components/package/bower.json')).to.contain('"version": "0.1.1"');
expect(tempDir.read('bower.lock')).to.contain('"package"');
expect(tempDir.read('bower.lock')).to.contain('"_release": "0.1.1"');
});
});
});
1 change: 0 additions & 1 deletion test/commands/list.js
Expand Up @@ -10,7 +10,6 @@ var commands = {
};

describe('bower list', function () {

var tempDir = new helpers.TempDir();

var gitPackage = new helpers.TempDir();
Expand Down
10 changes: 10 additions & 0 deletions test/helpers.js
Expand Up @@ -53,6 +53,16 @@ after(function () {
rimraf.sync(tmpLocation);
});

afterEach(function () {
glob.sync('**/bower.lock', {
cwd: tmpLocation,
dot: true
}).map(function (removePath) {
var fullPath = path.join(tmpLocation, removePath);
rimraf.sync(fullPath);
});
});

exports.TempDir = (function() {
function TempDir (defaults) {
this.path = path.join(tmpLocation, uuid.v4());
Expand Down

0 comments on commit 06e7172

Please sign in to comment.