describe('resolverFactory', function () { var tempSource; var logger = new Logger(); var registryClient = new RegistryClient(mout.object.fillIn({ cache: defaultConfig._registry }, defaultConfig)); afterEach(function (next) { logger.removeAllListeners(); if (tempSource) { rimraf(tempSource, next); tempSource = null; } else { next(); } }); after(function (next) { rimraf('dejavu', next); }); function callFactory (decEndpoint, config) { return resolverFactory(decEndpoint, config || defaultConfig, logger, registryClient); } it('should recognize git remote endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; endpoints = { // git: 'git://hostname.com/user/project': 'git://hostname.com/user/project', 'git://hostname.com/user/project/': 'git://hostname.com/user/project', 'git://hostname.com/user/project.git': 'git://hostname.com/user/project.git', 'git://hostname.com/user/project.git/': 'git://hostname.com/user/project.git', // git@: 'git@hostname.com:user/project': 'git@hostname.com:user/project', 'git@hostname.com:user/project/': 'git@hostname.com:user/project', 'git@hostname.com:user/project.git': 'git@hostname.com:user/project.git', 'git@hostname.com:user/project.git/': 'git@hostname.com:user/project.git', // git+ssh: 'git+ssh://user@hostname.com:project': 'ssh://user@hostname.com:project', 'git+ssh://user@hostname.com:project/': 'ssh://user@hostname.com:project', 'git+ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git', 'git+ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git', 'git+ssh://user@hostname.com/project': 'ssh://user@hostname.com/project', 'git+ssh://user@hostname.com/project/': 'ssh://user@hostname.com/project', 'git+ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git', 'git+ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git', // git+http 'git+http://hostname.com/project/blah': 'http://hostname.com/project/blah', 'git+http://hostname.com/project/blah/': 'http://hostname.com/project/blah', 'git+http://hostname.com/project/blah.git': 'http://hostname.com/project/blah.git', 'git+http://hostname.com/project/blah.git/': 'http://hostname.com/project/blah.git', 'git+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah', 'git+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah', 'git+http://user@hostname.com/project/blah.git': 'http://user@hostname.com/project/blah.git', 'git+http://user@hostname.com/project/blah.git/': 'http://user@hostname.com/project/blah.git', // git+https 'git+https://hostname.com/project/blah': 'https://hostname.com/project/blah', 'git+https://hostname.com/project/blah/': 'https://hostname.com/project/blah', 'git+https://hostname.com/project/blah.git': 'https://hostname.com/project/blah.git', 'git+https://hostname.com/project/blah.git/': 'https://hostname.com/project/blah.git', 'git+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah', 'git+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah', 'git+https://user@hostname.com/project/blah.git': 'https://user@hostname.com/project/blah.git', 'git+https://user@hostname.com/project/blah.git/': 'https://user@hostname.com/project/blah.git', // ssh .git$ 'ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git', 'ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git', 'ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git', 'ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git', // http .git$ 'http://hostname.com/project.git': 'http://hostname.com/project.git', 'http://hostname.com/project.git/': 'http://hostname.com/project.git', 'http://user@hostname.com/project.git': 'http://user@hostname.com/project.git', 'http://user@hostname.com/project.git/': 'http://user@hostname.com/project.git', // https .git$ 'https://hostname.com/project.git': 'https://hostname.com/project.git', 'https://hostname.com/project.git/': 'https://hostname.com/project.git', 'https://user@hostname.com/project.git': 'https://user@hostname.com/project.git', 'https://user@hostname.com/project.git/': 'https://user@hostname.com/project.git', // shorthand 'bower/bower': 'git://github.com/bower/bower.git' }; mout.object.forOwn(endpoints, function (value, key) { // Test without name and target promise = promise.then(function () { return callFactory({source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver).to.not.be(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('*'); }); // Test with target promise = promise.then(function () { return callFactory({source: key, target: 'commit-ish'}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver).to.not.be(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('commit-ish'); }); // Test with name promise = promise.then(function () { return callFactory({name: 'foo', source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver).to.not.be(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); }); }); promise .then(next.bind(next, null)) .done(); }); it('should recognize GitHub endpoints correctly', function (next) { var promise = Q.resolve(); var gitHub; var nonGitHub; gitHub = { // git: 'git://github.com/user/project': 'git://github.com/user/project.git', 'git://github.com/user/project/': 'git://github.com/user/project.git', 'git://github.com/user/project.git': 'git://github.com/user/project.git', 'git://github.com/user/project.git/': 'git://github.com/user/project.git', // git@: 'git@github.com:user/project': 'git@github.com:user/project.git', 'git@github.com:user/project/': 'git@github.com:user/project.git', 'git@github.com:user/project.git': 'git@github.com:user/project.git', 'git@github.com:user/project.git/': 'git@github.com:user/project.git', // git+ssh: 'git+ssh://git@github.com:project/blah': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com:project/blah/': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com:project/blah.git': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com:project/blah.git/': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com/project/blah': 'ssh://git@github.com/project/blah.git', 'git+ssh://git@github.com/project/blah/': 'ssh://git@github.com/project/blah.git', 'git+ssh://git@github.com/project/blah.git': 'ssh://git@github.com/project/blah.git', 'git+ssh://git@github.com/project/blah.git/': 'ssh://git@github.com/project/blah.git', // git+http 'git+http://github.com/project/blah': 'http://github.com/project/blah.git', 'git+http://github.com/project/blah/': 'http://github.com/project/blah.git', 'git+http://github.com/project/blah.git': 'http://github.com/project/blah.git', 'git+http://github.com/project/blah.git/': 'http://github.com/project/blah.git', 'git+http://user@github.com/project/blah': 'http://user@github.com/project/blah.git', 'git+http://user@github.com/project/blah/': 'http://user@github.com/project/blah.git', 'git+http://user@github.com/project/blah.git': 'http://user@github.com/project/blah.git', 'git+http://user@github.com/project/blah.git/': 'http://user@github.com/project/blah.git', // git+https 'git+https://github.com/project/blah': 'https://github.com/project/blah.git', 'git+https://github.com/project/blah/': 'https://github.com/project/blah.git', 'git+https://github.com/project/blah.git': 'https://github.com/project/blah.git', 'git+https://github.com/project/blah.git/': 'https://github.com/project/blah.git', 'git+https://user@github.com/project/blah': 'https://user@github.com/project/blah.git', 'git+https://user@github.com/project/blah/': 'https://user@github.com/project/blah.git', 'git+https://user@github.com/project/blah.git': 'https://user@github.com/project/blah.git', 'git+https://user@github.com/project/blah.git/': 'https://user@github.com/project/blah.git', // ssh .git$ 'ssh://git@github.com:project/blah.git': 'ssh://git@github.com:project/blah.git', 'ssh://git@github.com:project/blah.git/': 'ssh://git@github.com:project/blah.git', 'ssh://git@github.com/project/blah.git': 'ssh://git@github.com/project/blah.git', 'ssh://git@github.com/project/blah.git/': 'ssh://git@github.com/project/blah.git', // http .git$ 'http://github.com/project/blah.git': 'http://github.com/project/blah.git', 'http://github.com/project/blah.git/': 'http://github.com/project/blah.git', 'http://user@github.com/project/blah.git': 'http://user@github.com/project/blah.git', 'http://user@github.com/project/blah.git/': 'http://user@github.com/project/blah.git', // https 'https://github.com/project/blah.git': 'https://github.com/project/blah.git', 'https://github.com/project/blah.git/': 'https://github.com/project/blah.git', 'https://user@github.com/project/blah.git': 'https://user@github.com/project/blah.git', 'https://user@github.com/project/blah.git/': 'https://user@github.com/project/blah.git', // shorthand 'bower/bower': 'git://github.com/bower/bower.git' }; nonGitHub = [ 'git://github.com/user/project/bleh.git', 'git://xxxxgithub.com/user/project.git', 'git@xxxxgithub.com:user:project.git', 'git@xxxxgithub.com:user/project.git', 'git+ssh://git@xxxxgithub.com:user/project', 'git+ssh://git@xxxxgithub.com/user/project', 'git+http://user@xxxxgithub.com/user/project', 'git+https://user@xxxxgithub.com/user/project', 'ssh://git@xxxxgithub.com:user/project.git', 'ssh://git@xxxxgithub.com/user/project.git', 'http://xxxxgithub.com/user/project.git', 'https://xxxxgithub.com/user/project.git', 'http://user@xxxxgithub.com/user/project.git', 'https://user@xxxxgithub.com/user/project.git' ]; // Test GitHub ones mout.object.forOwn(gitHub, function (value, key) { // Test without name and target promise = promise.then(function () { return callFactory({source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('*'); }); // Test with target promise = promise.then(function () { return callFactory({source: key, target: 'commit-ish'}); }) .then(function (resolver) { if (value) { expect(resolver).to.be.a(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('commit-ish'); } else { expect(resolver).to.not.be.a(resolvers.GitHub); } }); // Test with name promise = promise.then(function () { return callFactory({name: 'foo', source: key}); }) .then(function (resolver) { if (value) { expect(resolver).to.be.a(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); } else { expect(resolver).to.not.be.a(resolvers.GitHub); } }); }); // Test similar to GitHub but not real GitHub nonGitHub.forEach(function (value) { promise = promise.then(function () { return callFactory({source: value}); }) .then(function (resolver) { expect(resolver).to.not.be.a(resolvers.GitHub); expect(resolver).to.be.a(resolvers.GitRemote); }); }); promise .then(next.bind(next, null)) .done(); }); it('should recognize local fs git endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; var temp; endpoints = {}; // Absolute path temp = path.resolve(__dirname, '../assets/package-a'); endpoints[temp] = temp; // Absolute path that ends with a / // See: https://github.com/bower/bower/issues/898 temp = path.resolve(__dirname, '../assets/package-a') + '/'; endpoints[temp] = temp; // Relative path endpoints[__dirname + '/../assets/package-a'] = temp; // TODO: test with backslashes on windows and ~/ on unix mout.object.forOwn(endpoints, function (value, key) { // Test without name promise = promise.then(function () { return callFactory({source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitFs); expect(resolver.getTarget()).to.equal('*'); }); // Test with name promise = promise.then(function () { return callFactory({name: 'foo', source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitFs); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); }); }); promise .then(next.bind(next, null)) .done(); }); it('should recognize svn remote endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; endpoints = { // svn: 'svn://hostname.com/user/project': 'http://hostname.com/user/project', 'svn://hostname.com/user/project/': 'http://hostname.com/user/project', // svn@: 'svn://svn@hostname.com:user/project': 'http://svn@hostname.com:user/project', 'svn://svn@hostname.com:user/project/': 'http://svn@hostname.com:user/project', // svn+http 'svn+http://hostname.com/project/blah': 'http://hostname.com/project/blah', 'svn+http://hostname.com/project/blah/': 'http://hostname.com/project/blah', 'svn+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah', 'svn+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah', // svn+https 'svn+https://hostname.com/project/blah': 'https://hostname.com/project/blah', 'svn+https://hostname.com/project/blah/': 'https://hostname.com/project/blah', 'svn+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah', 'svn+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah', // svn+ssh 'svn+ssh://hostname.com/project/blah': 'svn+ssh://hostname.com/project/blah', 'svn+ssh://hostname.com/project/blah/': 'svn+ssh://hostname.com/project/blah', 'svn+ssh://user@hostname.com/project/blah': 'svn+ssh://user@hostname.com/project/blah', 'svn+ssh://user@hostname.com/project/blah/': 'svn+ssh://user@hostname.com/project/blah', // svn+file 'svn+file:///project/blah': 'file:///project/blah', 'svn+file:///project/blah/': 'file:///project/blah' }; mout.object.forOwn(endpoints, function (value, key) { // Test without name and target promise = promise.then(function () { return callFactory({source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Svn); expect(resolver).to.not.be(resolvers.GitHub); expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value); expect(resolver.getTarget()).to.equal('*'); }); // Test with target promise = promise.then(function () { return callFactory({source: key, target: 'commit-ish'}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Svn); expect(resolver).to.not.be(resolvers.GitHub); expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value); expect(resolver.getTarget()).to.equal('commit-ish'); }); // Test with name promise = promise.then(function () { return callFactory({name: 'foo', source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Svn); expect(resolver).to.not.be(resolvers.GitHub); expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); }); }); promise .then(next.bind(next, null)) .done(); }); it('should recognize local fs files/folder endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; var temp; tempSource = path.resolve(__dirname, '../tmp/tmp'); mkdirp.sync(tempSource); fs.writeFileSync(path.join(tempSource, '.git'), 'foo'); fs.writeFileSync(path.join(tempSource, 'file.with.multiple.dots'), 'foo'); endpoints = {}; // Absolute path to folder with .git file endpoints[tempSource] = tempSource; // Relative path to folder with .git file endpoints[__dirname + '/../tmp/tmp'] = tempSource; // Absolute path to folder temp = path.resolve(__dirname, '../assets/test-temp-dir'); endpoints[temp] = temp; // Absolute + relative path to folder endpoints[__dirname + '/../assets/test-temp-dir'] = temp; // Absolute path to file temp = path.resolve(__dirname, '../assets/package-zip.zip'); endpoints[temp] = temp; // Absolute + relative path to file endpoints[__dirname + '/../assets/package-zip.zip'] = temp; // Relative ../ endpoints['../'] = path.normalize(__dirname + '/../../..'); // Relative ./ endpoints['./test/assets'] = path.join(__dirname, '../assets'); // Relative with just one slash, to test fs resolution // priority against shorthands endpoints['./test'] = path.join(__dirname, '..'); // Test files with multiple dots (PR #474) temp = path.join(tempSource, 'file.with.multiple.dots'); endpoints[temp] = temp; mout.object.forOwn(endpoints, function (value, key) { // Test without name promise = promise.then(function () { return callFactory({source: key}); }) .then(function (resolver) { expect(resolver.getSource()).to.equal(value); expect(resolver).to.be.a(resolvers.Fs); expect(resolver.getTarget()).to.equal('*'); }); // Test with name promise = promise.then(function () { return callFactory({name: 'foo', source: key}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Fs); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); expect(resolver.getSource()).to.equal(value); }); }); promise .then(next.bind(next, null)) .done(); }); it('should recognize URL endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; endpoints = [ 'http://bower.io/foo.js', 'https://bower.io/foo.js' ]; endpoints.forEach(function (source) { // Test without name promise = promise.then(function () { return callFactory({source: source}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Url); expect(resolver.getSource()).to.equal(source); }); // Test with name promise = promise.then(function () { return callFactory({name: 'foo', source: source}); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Url); expect(resolver.getName()).to.equal('foo'); expect(resolver.getSource()).to.equal(source); }); }); promise .then(next.bind(next, null)) .done(); }); it('should recognize registry endpoints correctly', function (next) { // Create a 'dejavu' file at the root to prevent regressions of #666 fs.writeFileSync('dejavu', 'foo'); callFactory({source: 'dejavu'}) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver.getSource()).to.equal('git://github.com/IndigoUnited/dejavu.git'); expect(resolver.getTarget()).to.equal('*'); }) .then(function () { // Test with name return callFactory({source: 'dejavu', name: 'foo'}) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver.getSource()).to.equal('git://github.com/IndigoUnited/dejavu.git'); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); }); }) .then(function () { // Test with target return callFactory({source: 'dejavu', target: '~2.0.0'}) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver.getTarget()).to.equal('~2.0.0'); next(); }); }) .done(); }); it('should error out if the package was not found in the registry', function (next) { callFactory({source: 'some-package-that-will-never-exist'}) .then(function () { throw new Error('Should have failed'); }, function (err) { expect(err).to.be.an(Error); expect(err.code).to.equal('ENOTFOUND'); expect(err.message).to.contain('some-package-that-will-never-exist'); next(); }) .done(); }); it('should set registry to true on the decomposed endpoint if fetched from the registry', function (next) { var decEndpoint = {source: 'dejavu'}; callFactory(decEndpoint) .then(function () { expect(decEndpoint.registry).to.be(true); next(); }) .done(); }); it('should use the configured shorthand resolver', function (next) { callFactory({source: 'bower/bower'}) .then(function (resolver) { var config; expect(resolver.getSource()).to.equal('git://github.com/bower/bower.git'); config = mout.object.fillIn({ shorthandResolver: 'git://bower.io/{{owner}}/{{package}}/{{shorthand}}' }, defaultConfig); return callFactory({source: 'IndigoUnited/promptly'}, config); }) .then(function (resolver) { expect(resolver.getSource()).to.equal('git://bower.io/IndigoUnited/promptly/IndigoUnited/promptly'); next(); }) .done(); }); it('should not expand using the shorthand resolver if it looks like a SSH URL', function (next) { callFactory({source: 'bleh@xxx.com:foo/bar'}) .then(function (resolver) { throw new Error('Should have failed'); }, function (err) { expect(err).to.be.an(Error); expect(err.code).to.equal('ENOTFOUND'); expect(err.message).to.contain('bleh@xxx.com:foo/bar'); next(); }) .done(); }); it('should error out if there\'s no suitable resolver for a given source', function (next) { resolverFactory({source: 'some-package-that-will-never-exist'}, defaultConfig, logger) .then(function () { throw new Error('Should have failed'); }, function (err) { expect(err).to.be.an(Error); expect(err.code).to.be('ENORESOLVER'); expect(err.message).to.contain('appropriate resolver'); next(); }) .done(); }); it.skip('should use config.cwd when resolving relative paths'); it('should not swallow constructor errors when instantiating resolvers', function (next) { var promise = Q.resolve(); var endpoints; // TODO: test with others endpoints = [ 'http://bower.io/foo.js', path.resolve(__dirname, '../assets/test-temp-dir') ]; endpoints.forEach(function (source) { promise = promise.then(function () { return callFactory({source: source, target: 'bleh'}); }) .then(function () { throw new Error('Should have failed'); }, function (err) { expect(err).to.be.an(Error); expect(err.message).to.match(/can't resolve targets/i); expect(err.code).to.equal('ENORESTARGET'); }); }); promise .then(next.bind(next, null)) .done(); }); describe('.clearRuntimeCache', function () { it('should call every resolver static method that clears the runtime cache', function () { var originalMethods = {}; var called = []; var error; mout.object.forOwn(resolvers, function (ConcreteResolver, key) { originalMethods[key] = ConcreteResolver.clearRuntimeCache; ConcreteResolver.clearRuntimeCache = function () { called.push(key); return originalMethods[key].apply(this, arguments); }; }); try { resolverFactory.clearRuntimeCache(); } catch (e) { error = e; } finally { mout.object.forOwn(resolvers, function (ConcreteResolver, key) { ConcreteResolver.clearRuntimeCache = originalMethods[key]; }); } if (error) { throw error; } expect(called.sort()).to.eql(Object.keys(resolvers).sort()); }); }); });
Project.prototype._restoreNode = function (node, flattened, jsonKey) { var deps; // Do not restore if the node is missing if (node.missing) { return; } node.dependencies = node.dependencies || {}; node.dependants = node.dependants || {}; // Only process deps that are yet processed deps = mout.object.filter(node.pkgMeta[jsonKey], function (value, key) { return !node.dependencies[key]; }); mout.object.forOwn(deps, function (value, key) { var local = flattened[key]; var json = endpointParser.json2decomposed(key, value); var restored; var compatible; var originalSource; // Check if the dependency is not installed if (!local) { flattened[key] = restored = json; restored.missing = true; // Even if it is installed, check if it's compatible // Note that linked packages are interpreted as compatible // This might change in the future: #673 } else { compatible = local.linked || (!local.missing && json.target === local.pkgMeta._target); if (!compatible) { restored = json; if (!local.missing) { restored.pkgMeta = local.pkgMeta; restored.canonicalDir = local.canonicalDir; restored.incompatible = true; } else { restored.missing = true; } } else { restored = local; mout.object.mixIn(local, json); } // Check if source changed, marking as different if it did // We only do this for direct root dependencies that are compatible if (node.root && compatible) { originalSource = mout.object.get(local, 'pkgMeta._originalSource'); if (originalSource && originalSource !== json.source) { restored.different = true; } } } // Cross reference node.dependencies[key] = restored; restored.dependants = restored.dependants || {}; restored.dependants[node.name] = node; // Call restore for this dependency this._restoreNode(restored, flattened, 'dependencies'); // Do the same for the incompatible local package if (local && restored !== local) { this._restoreNode(local, flattened, 'dependencies'); } }, this); };
that.walkTree(tree, function (node, nodeName) { if (name === nodeName) { dependants.push.apply(dependants, mout.object.values(node.dependants)); } }, true);
it('should recognize local fs files/folder endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; var temp; tempSource = path.resolve(__dirname, '../tmp/tmp'); mkdirp.sync(tempSource); fs.writeFileSync(path.join(tempSource, '.git'), 'foo'); fs.writeFileSync(path.join(tempSource, 'file.with.multiple.dots'), 'foo'); endpoints = {}; // Absolute path to folder with .git file endpoints[tempSource] = tempSource; // Relative path to folder with .git file endpoints[__dirname + '/../tmp/tmp'] = tempSource; // Absolute path to folder temp = path.resolve(__dirname, '../assets/test-temp-dir'); endpoints[temp] = temp; // Absolute + relative path to folder endpoints[__dirname + '/../assets/test-temp-dir'] = temp; // Absolute path to file temp = path.resolve(__dirname, '../assets/package-zip.zip'); endpoints[temp] = temp; // Absolute + relative path to file endpoints[__dirname + '/../assets/package-zip.zip'] = temp; // Relative ../ endpoints['../'] = path.normalize(__dirname + '/../../..'); // Relative ./ endpoints['./test/assets'] = path.join(__dirname, '../assets'); // Relative with just one slash, to test fs resolution // priority against shorthands endpoints['./test'] = path.join(__dirname, '..'); // Test files with multiple dots (PR #474) temp = path.join(tempSource, 'file.with.multiple.dots'); endpoints[temp] = temp; mout.object.forOwn(endpoints, function (value, key) { // Test without name promise = promise.then(function () { return callFactory({ source: key }); }) .then(function (resolver) { expect(resolver.getSource()).to.equal(value); expect(resolver).to.be.a(resolvers.Fs); expect(resolver.getTarget()).to.equal('*'); }); // Test with name promise = promise.then(function () { return callFactory({ name: 'foo', source: key }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Fs); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); expect(resolver.getSource()).to.equal(value); }); }); promise .then(next.bind(next, null)) .done(); });
var path = require('path'); var fs = require('graceful-fs'); var Handlebars = require('handlebars'); var mout = require('mout'); var helpers = require('../../templates/helpers'); var templatesDir = path.resolve(__dirname, '../../templates'); var cache = {}; // Register helpers mout.object.forOwn(helpers, function (register) { register(Handlebars); }); function render(name, data, escape) { var contents; // Check if already compiled if (cache[name]) { return cache[name](data); } // Otherwise, read the file, compile and cache contents = fs.readFileSync(path.join(templatesDir, name)).toString(); cache[name] = Handlebars.compile(contents, { noEscape: !escape }); // Call the function again return render(name, data, escape); }
Manager.prototype._electSuitable = function (name, semvers, nonSemvers) { var suitable; var resolution; var unresolvable; var dataPicks; var save; var choices; var picks = []; // If there are both semver and non-semver, there's no way // to figure out the suitable one if (semvers.length && nonSemvers.length) { picks.push.apply(picks, semvers); picks.push.apply(picks, nonSemvers); // If there are only non-semver ones, the suitable is elected // only if there's one } else if (nonSemvers.length) { if (nonSemvers.length === 1) { return Q.resolve(nonSemvers[0]); } picks.push.apply(picks, nonSemvers); // If there are only semver ones, figure out the which one // is compatible with every requirement } else { suitable = mout.array.find(semvers, function (subject) { return semvers.every(function (decEndpoint) { return subject === decEndpoint || semver.satisfies(subject.pkgMeta.version, decEndpoint.target); }); }); if (suitable) { return Q.resolve(suitable); } picks.push.apply(picks, semvers); } // At this point, there's a conflict this._conflicted[name] = true; // Prepare data to be sent bellow // 1 - Sort picks by version/release picks.sort(function (pick1, pick2) { var version1 = pick1.pkgMeta.version; var version2 = pick2.pkgMeta.version; var comp; // If both have versions, compare their versions using semver if (version1 && version2) { comp = semver.compare(version1, version2); if (comp) { return comp; } } else { // If one of them has a version, it's considered higher if (version1) { return 1; } if (version2) { return -1; } } // Give priority to the one with most dependants if (pick1.dependants.length > pick2.dependants.length) { return -1; } if (pick1.dependants.length < pick2.dependants.length) { return 1; } return 0; }); // 2 - Transform data dataPicks = picks.map(function (pick) { var dataPick = this.toData(pick); dataPick.dependants = pick.dependants.map(this.toData, this); dataPick.dependants.sort(function (dependant1, dependant2) { return dependant1.endpoint.name.localeCompare(dependant2.endpoint.name); }); return dataPick; }, this); // Check if there's a resolution that resolves the conflict // Note that if one of them is marked as unresolvable, // the resolution has no effect resolution = this._resolutions[name]; unresolvable = mout.object.find(picks, function (pick) { return pick.unresolvable; }); if (resolution && !unresolvable) { if (semver.validRange(resolution)) { suitable = mout.array.findIndex(picks, function (pick) { return pick.pkgMeta.version && semver.satisfies(pick.pkgMeta.version, resolution); }); } else { suitable = mout.array.findIndex(picks, function (pick) { return pick.pkgMeta._release === resolution; }); } if (suitable === -1) { this._logger.warn('resolution', 'Unsuitable resolution declared for ' + name + ': ' + resolution, { name: name, picks: dataPicks, resolution: resolution }); } else { this._logger.conflict('solved', 'Unable to find suitable version for ' + name, { name: name, picks: dataPicks, resolution: resolution, suitable: dataPicks[suitable] }); return Q.resolve(picks[suitable]); } } // If force latest is enabled, resolve to the highest semver version // or whatever non-semver if none available if (this._forceLatest) { suitable = picks.length - 1; this._logger.conflict('solved', 'Unable to find suitable version for ' + name, { name: name, picks: dataPicks, suitable: dataPicks[suitable], forced: true }); return Q.resolve(picks[suitable]); } // If interactive is disabled, error out if (!this._config.interactive) { throw createError('Unable to find suitable version for ' + name, 'ECONFLICT', { name: name, picks: dataPicks }); } // At this point the user needs to make a decision this._logger.conflict('incompatible', 'Unable to find suitable version for ' + name, { name: name, picks: dataPicks }); choices = picks.map(function (pick, index) { return index + 1; }); return Q.nfcall(this._logger.prompt.bind(this._logger), { type: 'input', message: 'Answer:', validate: function (choice) { choice = Number(mout.string.trim(choice.trim(), '!')); if (!choice || choice < 1 || choice > picks.length) { return 'Invalid choice'; } return true; } }) .then(function (choice) { var pick; var resolution; // Sanitize choice choice = choice.trim(); save = /^!/.test(choice) || /!$/.test(choice); // Save if prefixed or suffixed with ! choice = Number(mout.string.trim(choice, '!')); pick = picks[choice - 1]; // Store choice into resolutions if (pick.target === '*') { resolution = pick.pkgMeta._release || '*'; } else { resolution = pick.target; } if (save) { this._logger.info('resolution', 'Saved ' + name + '#' + resolution + ' as resolution', { name: name, resolution: resolution, action: this._resolutions[name] ? 'edit' : 'add' }); this._resolutions[name] = resolution; } return pick; }.bind(this)); };
else it('should recognize svn remote endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; endpoints = { // svn: 'svn://hostname.com/user/project': 'http://hostname.com/user/project', 'svn://hostname.com/user/project/': 'http://hostname.com/user/project', // svn@: 'svn://svn@hostname.com:user/project': 'http://svn@hostname.com:user/project', 'svn://svn@hostname.com:user/project/': 'http://svn@hostname.com:user/project', // svn+http 'svn+http://hostname.com/project/blah': 'http://hostname.com/project/blah', 'svn+http://hostname.com/project/blah/': 'http://hostname.com/project/blah', 'svn+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah', 'svn+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah', // svn+https 'svn+https://hostname.com/project/blah': 'https://hostname.com/project/blah', 'svn+https://hostname.com/project/blah/': 'https://hostname.com/project/blah', 'svn+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah', 'svn+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah', // svn+ssh 'svn+ssh://hostname.com/project/blah': 'svn+ssh://hostname.com/project/blah', 'svn+ssh://hostname.com/project/blah/': 'svn+ssh://hostname.com/project/blah', 'svn+ssh://user@hostname.com/project/blah': 'svn+ssh://user@hostname.com/project/blah', 'svn+ssh://user@hostname.com/project/blah/': 'svn+ssh://user@hostname.com/project/blah', // svn+file 'svn+file:///project/blah': 'file:///project/blah', 'svn+file:///project/blah/': 'file:///project/blah' }; mout.object.forOwn(endpoints, function (value, key) { // Test without name and target promise = promise.then(function () { return callFactory({ source: key }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Svn); expect(resolver).to.not.be(resolvers.GitHub); expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value); expect(resolver.getTarget()).to.equal('*'); }); // Test with target promise = promise.then(function () { return callFactory({ source: key, target: 'commit-ish' }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Svn); expect(resolver).to.not.be(resolvers.GitHub); expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value); expect(resolver.getTarget()).to.equal('commit-ish'); }); // Test with name promise = promise.then(function () { return callFactory({ name: 'foo', source: key }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.Svn); expect(resolver).to.not.be(resolvers.GitHub); expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); }); }); promise .then(next.bind(next, null)) .done(); });
delete config.json; // If interactive is auto (null), guess its value if (config.interactive == null) { config.interactive = ( process.bin === 'upt' && tty.isatty(1) && !process.env.CI ); } // If `analytics` hasn't been explicitly set, we disable // it when ran programatically. if (config.analytics == null) { // Don't enable analytics on CI server unless explicitly configured. config.analytics = config.interactive; } // Merge common CLI options into the config mout.object.mixIn(config, cli.readOptions({ force: {type: Boolean, shorthand: 'f'}, offline: {type: Boolean, shorthand: 'o'}, verbose: {type: Boolean, shorthand: 'V'}, quiet: {type: Boolean, shorthand: 'q'}, loglevel: {type: String, shorthand: 'l'}, json: {type: Boolean, shorthand: 'j'}, silent: {type: Boolean, shorthand: 's'} })); module.exports = config;
} // Get the command to execute while (options.argv.remain.length) { command = options.argv.remain.join(' '); // Alias lookup if (bower.abbreviations[command]) { command = bower.abbreviations[command].replace(/\s/g, '.'); break; } command = command.replace(/\s/g, '.'); // Direct lookup if (mout.object.has(bower.commands, command)) { break; } options.argv.remain.pop(); } // Execute the command commandFunc = command && mout.object.get(bower.commands, command); command = command && command.replace(/\./g, ' '); // If no command was specified, show bower help // Do the same if the command is unknown if (!commandFunc) { logger = bower.commands.help(); command = 'help';
analytics.setup(upt.config).then(function () { // Execute the command commandFunc = command && mout.object.get(upt.commands, command); command = command && command.replace(/\./g, ' '); // If no command was specified, show upt help // Do the same if the command is unknown if (!commandFunc) { logger = upt.commands.help(); command = 'help'; // If the user requested help, show the command's help // Do the same if the actual command is a group of other commands (e.g.: cache) } else if (options.help || !commandFunc.line) { logger = upt.commands.help(command); command = 'help'; // Call the line method } else { logger = commandFunc.line(process.argv); // If the method failed to interpret the process arguments // show the command help if (!logger) { logger = upt.commands.help(command); command = 'help'; } } // Get the renderer and configure it with the executed command renderer = cli.getRenderer(command, logger.json, upt.config); logger .on('end', function (data) { if (!upt.config.silent && !upt.config.quiet) { renderer.end(data); } }) .on('error', function (err) { if (levels.error >= loglevel) { renderer.error(err); } process.exit(1); }) .on('log', function (log) { if (levels[log.level] >= loglevel) { renderer.log(log); } }) .on('prompt', function (prompt, callback) { renderer.prompt(prompt) .then(function (answer) { callback(answer); }); }); // Warn if HOME is not SET if (!osenv.home()) { logger.warn('no-home', 'HOME not set, user configuration will not be loaded'); } if (upt.config.interactive) { var updateNotifier = require('update-notifier'); // Check for newer version of Upt var notifier = updateNotifier({ packageName: pkg.name, packageVersion: pkg.version }); if (notifier.update && levels.info >= loglevel) { notifier.notify(); } } });
var createError = require('../../util/createError'); function FsResolver(decEndpoint, config, logger) { Resolver.call(this, decEndpoint, config, logger); // Ensure absolute path this._source = path.resolve(this._config.cwd, this._source); // If target was specified, simply reject the promise if (this._target !== '*') { throw createError('File system sources can\'t resolve targets', 'ENORESTARGET'); } } util.inherits(FsResolver, Resolver); mout.object.mixIn(FsResolver, Resolver); // ----------------- FsResolver.isTargetable = function () { return false; }; // TODO: Should we store latest mtimes in the resolution and compare? // This would be beneficial when copying big files/folders // TODO: There's room for improvement by using streams if the source // is an archive file, by piping read stream to the zip extractor // This will likely increase the complexity of code but might worth it FsResolver.prototype._resolve = function () { return this._copy()
Hw2Core(function () { var Q = require('q'); var mout = require('mout'); var Logger = require('bower-logger'); var osenv = require('osenv'); var pkg = require('../package.json'); var upt = require('../src/lib'); var cli = require('../src/lib/util/cli'); var rootCheck = require('../src/lib/util/rootCheck'); var analytics = require('../src/lib/util/analytics'); var options; var renderer; var loglevel; var command; var commandFunc; var logger; var levels = Logger.LEVELS; options = cli.readOptions({ version: {type: Boolean, shorthand: 'v'}, help: {type: Boolean, shorthand: 'h'}, 'allow-root': {type: Boolean} }); // Handle print of version if (options.version) { process.stdout.write(pkg.version + '\n'); process.exit(); } // Root check rootCheck(options, upt.config); // Set loglevel if (upt.config.silent) { loglevel = levels.error; } else if (upt.config.verbose) { loglevel = -Infinity; Q.longStackSupport = true; } else if (upt.config.quiet) { loglevel = levels.warn; } else { loglevel = levels[upt.config.loglevel] || levels.info; } // Get the command to execute while (options.argv.remain.length) { command = options.argv.remain.join(' '); // Alias lookup if (upt.abbreviations[command]) { command = upt.abbreviations[command].replace(/\s/g, '.'); break; } command = command.replace(/\s/g, '.'); // Direct lookup if (mout.object.has(upt.commands, command)) { break; } options.argv.remain.pop(); } // Ask for Insights on first run. analytics.setup(upt.config).then(function () { // Execute the command commandFunc = command && mout.object.get(upt.commands, command); command = command && command.replace(/\./g, ' '); // If no command was specified, show upt help // Do the same if the command is unknown if (!commandFunc) { logger = upt.commands.help(); command = 'help'; // If the user requested help, show the command's help // Do the same if the actual command is a group of other commands (e.g.: cache) } else if (options.help || !commandFunc.line) { logger = upt.commands.help(command); command = 'help'; // Call the line method } else { logger = commandFunc.line(process.argv); // If the method failed to interpret the process arguments // show the command help if (!logger) { logger = upt.commands.help(command); command = 'help'; } } // Get the renderer and configure it with the executed command renderer = cli.getRenderer(command, logger.json, upt.config); logger .on('end', function (data) { if (!upt.config.silent && !upt.config.quiet) { renderer.end(data); } }) .on('error', function (err) { if (levels.error >= loglevel) { renderer.error(err); } process.exit(1); }) .on('log', function (log) { if (levels[log.level] >= loglevel) { renderer.log(log); } }) .on('prompt', function (prompt, callback) { renderer.prompt(prompt) .then(function (answer) { callback(answer); }); }); // Warn if HOME is not SET if (!osenv.home()) { logger.warn('no-home', 'HOME not set, user configuration will not be loaded'); } if (upt.config.interactive) { var updateNotifier = require('update-notifier'); // Check for newer version of Upt var notifier = updateNotifier({ packageName: pkg.name, packageVersion: pkg.version }); if (notifier.update && levels.info >= loglevel) { notifier.notify(); } } }); });
SvnResolver.clearRuntimeCache = function () { // Reset cache for branches, tags, etc mout.object.forOwn(SvnResolver._cache, function (lru) { lru.reset(); }); };
which.sync('svn'); hasSvn = true; } catch (ex) { hasSvn = false; } function SvnResolver(decEndpoint, config, logger) { Resolver.call(this, decEndpoint, config, logger); if (!hasSvn) { throw createError('svn is not installed or not in the PATH', 'ENOSVN'); } } util.inherits(SvnResolver, Resolver); mout.object.mixIn(SvnResolver, Resolver); // ----------------- SvnResolver.getSource = function (source) { var uri = this._source || source; return uri .replace(/^svn\+(https?|file):\/\//i, '$1://') // Change svn+http or svn+https or svn+file to http(s), file respectively .replace('svn://', 'http://') // Change svn to http .replace(/\/+$/, ''); // Remove trailing slashes }; SvnResolver.prototype._hasNew = function (canonicalDir, pkgMeta) { var oldResolution = pkgMeta._resolution || {};
mout.object.forOwn(setup.resolved, function (decEndpoint, name) { decEndpoint.dependants = mout.object.values(decEndpoint.dependants); this._resolved[name] = [decEndpoint]; this._installed[name] = decEndpoint.pkgMeta; }, this);
mout.object.forOwn(flattened, function (node) { if (names.indexOf(node.endpoint.name) !== -1) { children.push.apply(children, mout.object.keys(node.bowerDependencies)); } });
Manager.prototype._dissect = function () { var err; var componentsDir; var promise = Q.resolve(); var suitables = {}; var that = this; // If something failed, reject the whole resolve promise // with the first error if (this._hasFailed) { clearTimeout(this._failFastTimeout); // Cancel fail fast timeout err = mout.object.values(this._failed)[0][0]; this._deferred.reject(err); return; } // Find a suitable version for each package name mout.object.forOwn(this._resolved, function (decEndpoints, name) { var semvers; var nonSemvers; // Filter semver ones semvers = decEndpoints.filter(function (decEndpoint) { return !!decEndpoint.pkgMeta.version; }); // Sort semver ones DESC semvers.sort(function (first, second) { var result = semver.rcompare(first.pkgMeta.version, second.pkgMeta.version); // If they are equal and one of them is a wildcard target, // give lower priority if (!result) { if (first.target === '*') { return 1; } if (second.target === '*') { return -1; } } return result; }); // Convert wildcard targets to semver range targets if they are newly // Note that this can only be made if they can be targetable // If they are not, the resolver is incapable of handling targets semvers.forEach(function (decEndpoint) { if (decEndpoint.newly && decEndpoint.target === '*' && !decEndpoint.untargetable) { decEndpoint.target = '~' + decEndpoint.pkgMeta.version; decEndpoint.originalTarget = '*'; } }); // Filter non-semver ones nonSemvers = decEndpoints.filter(function (decEndpoint) { return !decEndpoint.pkgMeta.version; }); promise = promise.then(function () { return that._electSuitable(name, semvers, nonSemvers) .then(function (suitable) { suitables[name] = suitable; }); }); }, this); // After a suitable version has been elected for every package promise .then(function () { // Look for extraneous resolutions mout.object.forOwn(this._resolutions, function (resolution, name) { if (this._conflicted[name]) { return; } this._logger.info('resolution', 'Removed unnecessary ' + name + '#' + resolution + ' resolution', { name: name, resolution: resolution, action: 'delete' }); delete this._resolutions[name]; }, this); // Filter only packages that need to be installed componentsDir = path.resolve(that._config.cwd, that._config.directory); this._dissected = mout.object.filter(suitables, function (decEndpoint, name) { var installedMeta = this._installed[name]; var dst; // Analyse a few props if (installedMeta && installedMeta._target === decEndpoint.target && installedMeta._originalSource === decEndpoint.source && installedMeta._release === decEndpoint.pkgMeta._release ) { return false; } // Skip if source is the same as dest dst = path.join(componentsDir, name); if (dst === decEndpoint.canonicalDir) { return false; } return true; }, this); // Resolve with meaningful data return mout.object.map(this._dissected, function (decEndpoint) { return this.toData(decEndpoint); }, this); }.bind(this)) .then(this._deferred.resolve, this._deferred.reject); };
nodes.forEach(function (node) { children.push.apply(children, mout.object.keys(node.bowerDependencies)); });
it('should recognize GitHub endpoints correctly', function (next) { var promise = Q.resolve(); var gitHub; var nonGitHub; gitHub = { // git: 'git://github.com/user/project': 'git://github.com/user/project.git', 'git://github.com/user/project/': 'git://github.com/user/project.git', 'git://github.com/user/project.git': 'git://github.com/user/project.git', 'git://github.com/user/project.git/': 'git://github.com/user/project.git', // git@: 'git@github.com:user/project': 'git@github.com:user/project.git', 'git@github.com:user/project/': 'git@github.com:user/project.git', 'git@github.com:user/project.git': 'git@github.com:user/project.git', 'git@github.com:user/project.git/': 'git@github.com:user/project.git', // git+ssh: 'git+ssh://git@github.com:project/blah': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com:project/blah/': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com:project/blah.git': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com:project/blah.git/': 'ssh://git@github.com:project/blah.git', 'git+ssh://git@github.com/project/blah': 'ssh://git@github.com/project/blah.git', 'git+ssh://git@github.com/project/blah/': 'ssh://git@github.com/project/blah.git', 'git+ssh://git@github.com/project/blah.git': 'ssh://git@github.com/project/blah.git', 'git+ssh://git@github.com/project/blah.git/': 'ssh://git@github.com/project/blah.git', // git+http 'git+http://github.com/project/blah': 'http://github.com/project/blah.git', 'git+http://github.com/project/blah/': 'http://github.com/project/blah.git', 'git+http://github.com/project/blah.git': 'http://github.com/project/blah.git', 'git+http://github.com/project/blah.git/': 'http://github.com/project/blah.git', 'git+http://user@github.com/project/blah': 'http://user@github.com/project/blah.git', 'git+http://user@github.com/project/blah/': 'http://user@github.com/project/blah.git', 'git+http://user@github.com/project/blah.git': 'http://user@github.com/project/blah.git', 'git+http://user@github.com/project/blah.git/': 'http://user@github.com/project/blah.git', // git+https 'git+https://github.com/project/blah': 'https://github.com/project/blah.git', 'git+https://github.com/project/blah/': 'https://github.com/project/blah.git', 'git+https://github.com/project/blah.git': 'https://github.com/project/blah.git', 'git+https://github.com/project/blah.git/': 'https://github.com/project/blah.git', 'git+https://user@github.com/project/blah': 'https://user@github.com/project/blah.git', 'git+https://user@github.com/project/blah/': 'https://user@github.com/project/blah.git', 'git+https://user@github.com/project/blah.git': 'https://user@github.com/project/blah.git', 'git+https://user@github.com/project/blah.git/': 'https://user@github.com/project/blah.git', // ssh .git$ 'ssh://git@github.com:project/blah.git': 'ssh://git@github.com:project/blah.git', 'ssh://git@github.com:project/blah.git/': 'ssh://git@github.com:project/blah.git', 'ssh://git@github.com/project/blah.git': 'ssh://git@github.com/project/blah.git', 'ssh://git@github.com/project/blah.git/': 'ssh://git@github.com/project/blah.git', // http .git$ 'http://github.com/project/blah.git': 'http://github.com/project/blah.git', 'http://github.com/project/blah.git/': 'http://github.com/project/blah.git', 'http://user@github.com/project/blah.git': 'http://user@github.com/project/blah.git', 'http://user@github.com/project/blah.git/': 'http://user@github.com/project/blah.git', // https 'https://github.com/project/blah.git': 'https://github.com/project/blah.git', 'https://github.com/project/blah.git/': 'https://github.com/project/blah.git', 'https://user@github.com/project/blah.git': 'https://user@github.com/project/blah.git', 'https://user@github.com/project/blah.git/': 'https://user@github.com/project/blah.git', // shorthand 'bower/bower': 'git://github.com/bower/bower.git' }; nonGitHub = [ 'git://github.com/user/project/bleh.git', 'git://xxxxgithub.com/user/project.git', 'git@xxxxgithub.com:user:project.git', 'git@xxxxgithub.com:user/project.git', 'git+ssh://git@xxxxgithub.com:user/project', 'git+ssh://git@xxxxgithub.com/user/project', 'git+http://user@xxxxgithub.com/user/project', 'git+https://user@xxxxgithub.com/user/project', 'ssh://git@xxxxgithub.com:user/project.git', 'ssh://git@xxxxgithub.com/user/project.git', 'http://xxxxgithub.com/user/project.git', 'https://xxxxgithub.com/user/project.git', 'http://user@xxxxgithub.com/user/project.git', 'https://user@xxxxgithub.com/user/project.git' ]; // Test GitHub ones mout.object.forOwn(gitHub, function (value, key) { // Test without name and target promise = promise.then(function () { return callFactory({ source: key }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('*'); }); // Test with target promise = promise.then(function () { return callFactory({ source: key, target: 'commit-ish' }); }) .then(function (resolver) { if (value) { expect(resolver).to.be.a(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('commit-ish'); } else { expect(resolver).to.not.be.a(resolvers.GitHub); } }); // Test with name promise = promise.then(function () { return callFactory({ name: 'foo', source: key }); }) .then(function (resolver) { if (value) { expect(resolver).to.be.a(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); } else { expect(resolver).to.not.be.a(resolvers.GitHub); } }); }); // Test similar to GitHub but not real GitHub nonGitHub.forEach(function (value) { promise = promise.then(function () { return callFactory({ source: value }); }) .then(function (resolver) { expect(resolver).to.not.be.a(resolvers.GitHub); expect(resolver).to.be.a(resolvers.GitRemote); }); }); promise .then(next.bind(next, null)) .done(); });
var depsSatisfied = function (packageName) { return mout.array.difference(mout.object.keys(packages[packageName].dependencies), installed, ordered).length === 0; };
it('should recognize git remote endpoints correctly', function (next) { var promise = Q.resolve(); var endpoints; endpoints = { // git: 'git://hostname.com/user/project': 'git://hostname.com/user/project', 'git://hostname.com/user/project/': 'git://hostname.com/user/project', 'git://hostname.com/user/project.git': 'git://hostname.com/user/project.git', 'git://hostname.com/user/project.git/': 'git://hostname.com/user/project.git', // git@: 'git@hostname.com:user/project': 'git@hostname.com:user/project', 'git@hostname.com:user/project/': 'git@hostname.com:user/project', 'git@hostname.com:user/project.git': 'git@hostname.com:user/project.git', 'git@hostname.com:user/project.git/': 'git@hostname.com:user/project.git', // git+ssh: 'git+ssh://user@hostname.com:project': 'ssh://user@hostname.com:project', 'git+ssh://user@hostname.com:project/': 'ssh://user@hostname.com:project', 'git+ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git', 'git+ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git', 'git+ssh://user@hostname.com/project': 'ssh://user@hostname.com/project', 'git+ssh://user@hostname.com/project/': 'ssh://user@hostname.com/project', 'git+ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git', 'git+ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git', // git+http 'git+http://hostname.com/project/blah': 'http://hostname.com/project/blah', 'git+http://hostname.com/project/blah/': 'http://hostname.com/project/blah', 'git+http://hostname.com/project/blah.git': 'http://hostname.com/project/blah.git', 'git+http://hostname.com/project/blah.git/': 'http://hostname.com/project/blah.git', 'git+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah', 'git+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah', 'git+http://user@hostname.com/project/blah.git': 'http://user@hostname.com/project/blah.git', 'git+http://user@hostname.com/project/blah.git/': 'http://user@hostname.com/project/blah.git', // git+https 'git+https://hostname.com/project/blah': 'https://hostname.com/project/blah', 'git+https://hostname.com/project/blah/': 'https://hostname.com/project/blah', 'git+https://hostname.com/project/blah.git': 'https://hostname.com/project/blah.git', 'git+https://hostname.com/project/blah.git/': 'https://hostname.com/project/blah.git', 'git+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah', 'git+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah', 'git+https://user@hostname.com/project/blah.git': 'https://user@hostname.com/project/blah.git', 'git+https://user@hostname.com/project/blah.git/': 'https://user@hostname.com/project/blah.git', // ssh .git$ 'ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git', 'ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git', 'ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git', 'ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git', // http .git$ 'http://hostname.com/project.git': 'http://hostname.com/project.git', 'http://hostname.com/project.git/': 'http://hostname.com/project.git', 'http://user@hostname.com/project.git': 'http://user@hostname.com/project.git', 'http://user@hostname.com/project.git/': 'http://user@hostname.com/project.git', // https .git$ 'https://hostname.com/project.git': 'https://hostname.com/project.git', 'https://hostname.com/project.git/': 'https://hostname.com/project.git', 'https://user@hostname.com/project.git': 'https://user@hostname.com/project.git', 'https://user@hostname.com/project.git/': 'https://user@hostname.com/project.git', // shorthand 'bower/bower': 'git://github.com/bower/bower.git' }; mout.object.forOwn(endpoints, function (value, key) { // Test without name and target promise = promise.then(function () { return callFactory({ source: key }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver).to.not.be(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('*'); }); // Test with target promise = promise.then(function () { return callFactory({ source: key, target: 'commit-ish' }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver).to.not.be(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getTarget()).to.equal('commit-ish'); }); // Test with name promise = promise.then(function () { return callFactory({ name: 'foo', source: key }); }) .then(function (resolver) { expect(resolver).to.be.a(resolvers.GitRemote); expect(resolver).to.not.be(resolvers.GitHub); expect(resolver.getSource()).to.equal(value); expect(resolver.getName()).to.equal('foo'); expect(resolver.getTarget()).to.equal('*'); }); }); promise .then(next.bind(next, null)) .done(); });
Manager.prototype._dissect = function () { var err; var componentsDir; var promise = Q.resolve(); var suitables = {}; var that = this; // If something failed, reject the whole resolve promise // with the first error if (this._hasFailed) { clearTimeout(this._failFastTimeout); // Cancel fail fast timeout err = mout.object.values(this._failed)[0][0]; this._deferred.reject(err); return; } // Find a suitable version for each package name mout.object.forOwn(this._resolved, function (decEndpoints, name) { var semvers; var nonSemvers; // Filter out non-semver ones semvers = decEndpoints.filter(function (decEndpoint) { return !!decEndpoint.pkgMeta.version; }); // Sort semver ones DESC semvers.sort(function (first, second) { var result = semver.rcompare(first.pkgMeta.version, second.pkgMeta.version); // If they are equal and one of them is a wildcard target, // give lower priority if (!result) { if (first.target === '*') { return 1; } if (second.target === '*') { return -1; } } return result; }); // Convert wildcard targets to semver range targets if they are newly // Note that this can only be made if they can be targetable // If they are not, the resolver is incapable of handling targets semvers.forEach(function (decEndpoint) { if (decEndpoint.newly && decEndpoint.target === '*' && !decEndpoint.untargetable) { decEndpoint.target = '^' + decEndpoint.pkgMeta.version; decEndpoint.originalTarget = '*'; } }); // Filter non-semver ones nonSemvers = decEndpoints.filter(function (decEndpoint) { return !decEndpoint.pkgMeta.version; }); promise = promise.then(function () { return that._electSuitable(name, semvers, nonSemvers) .then(function (suitable) { suitables[name] = suitable; }); }); }, this); // After a suitable version has been elected for every package promise .then(function () { // Look for extraneous resolutions mout.object.forOwn(this._resolutions, function (resolution, name) { if (this._conflicted[name]) { return; } // Packages that didn't have any conflicts in resolution yet have // "resolution" entry in library.json are deemed unnecessary // // Probably in future we want to use them for force given resolution, but for now... // See: https://github.com/bower/bower/issues/1362 this._logger.warn('extra-resolution', 'Unnecessary resolution: ' + name + '#' + resolution, { name: name, resolution: resolution, action: 'delete' }); }, this); // Filter only libraries that need to be installed componentsDir = relativeToBaseDir(this._config.cwd)(this._config.directory); this._dissected = mout.object.filter(suitables, function (decEndpoint, name) { var installedMeta = this._installed[name]; var dst; // Skip linked dependencies if (decEndpoint.linked) { return false; } // Skip if source is the same as dest // FIXME: Shoudn't we force installation if force flag is used here? dst = path.join(componentsDir, name); if (dst === decEndpoint.canonicalDir) { return false; } // We skip installing decEndpoint, if: // 1. We have installed package with the same name // 2. Packages have matching meta fields (needs explanation) // 3. We didn't force the installation if (installedMeta && installedMeta._target === decEndpoint.target && installedMeta._originalSource === decEndpoint.source && installedMeta._release === decEndpoint.pkgMeta._release ) { return this._config.force; } return true; }, this); }.bind(this)) .then(this._deferred.resolve, this._deferred.reject); };
function promptUser(logger, json) { var questions = [ { 'name': 'name', 'message': 'name', 'default': json.name, 'type': 'input' }, { 'name': 'version', 'message': 'version', 'default': json.version, 'type': 'input' }, { 'name': 'description', 'message': 'description', 'default': json.description, 'type': 'input' }, { 'name': 'main', 'message': 'main file', 'default': json.main, 'type': 'input' }, { 'name': 'keywords', 'message': 'keywords', 'default': json.keywords ? json.keywords.toString() : null, 'type': 'input' }, { 'name': 'authors', 'message': 'authors', 'default': json.authors ? json.authors.toString() : null, 'type': 'input' }, { 'name': 'license', 'message': 'license', 'default': json.license || 'MIT', 'type': 'input' }, { 'name': 'homepage', 'message': 'homepage', 'default': json.homepage, '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' }, { 'name': 'private', 'message': 'would you like to mark this package as private which prevents it from being accidentally published to the registry?', 'default': !!json.private, 'type': 'confirm' } ]; return Q.nfcall(logger.prompt.bind(logger), questions) .then(function (answers) { json.name = answers.name; json.version = answers.version; json.description = answers.description; json.main = answers.main; json.keywords = toArray(answers.keywords); json.authors = toArray(answers.authors, ','); json.license = answers.license; json.homepage = answers.homepage; json.private = answers.private || null; return [json, answers]; }); }
function download(url, file, options) { var operation; var response; var deferred = Q.defer(); var progressDelay = 8000; options = mout.object.mixIn({ retries: 5, factor: 2, minTimeout: 1000, maxTimeout: 35000, randomize: true }, options || {}); // Retry on network errors operation = retry.operation(options); operation.attempt(function () { var req; var writeStream; var contentLength; var bytesDownloaded = 0; req = progress(request(url, options), { delay: progressDelay }) .on('response', function (res) { var status = res.statusCode; if (status < 200 || status >= 300) { return deferred.reject(createError('Status code of ' + status, 'EHTTP')); } response = res; contentLength = Number(res.headers['content-length']); }) .on('data', function (data) { bytesDownloaded += data.length; }) .on('progress', function (state) { deferred.notify(state); }) .on('end', function () { // Check if the whole file was downloaded // In some unstable connections the ACK/FIN packet might be sent in the // middle of the download // See: https://github.com/joyent/node/issues/6143 if (contentLength && bytesDownloaded < contentLength) { req.emit('error', createError('Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read', 'EINCOMPLETE')); } }) .on('error', function (error) { var timeout = operation._timeouts[0]; // Reject if error is not a network error if (errorCodes.indexOf(error.code) === -1) { return deferred.reject(error); } // Next attempt will start reporting download progress immediately progressDelay = 0; // Check if there are more retries if (operation.retry(error)) { // Ensure that there are no more events from this request req.removeAllListeners(); req.on('error', function () {}); // Ensure that there are no more events from the write stream writeStream.removeAllListeners(); writeStream.on('error', function () {}); return deferred.notify({ retry: true, delay: timeout, error: error }); } // No more retries, reject! deferred.reject(error); }); // Pipe read stream to write stream writeStream = req .pipe(fs.createWriteStream(file)) .on('error', deferred.reject) .on('close', function () { deferred.resolve(response); }); }); return deferred.promise; }
.then(function () { return mout.object.filter(packages, function (dir) { return !!dir; }); });
.then(function () { // Resolve with meaningful data return mout.object.map(that._dissected, function (decEndpoint) { return this.toData(decEndpoint); }, that); })
throw createError('URL sources can\'t resolve targets', 'ENORESTARGET'); } // If the name was guessed, remove the ? part if (this._guessedName) { pos = this._name.indexOf('?'); if (pos !== -1) { this._name = path.basename(this._name.substr(0, pos)); } } this._remote = url.parse(this._source); } util.inherits(UrlResolver, Resolver); mout.object.mixIn(UrlResolver, Resolver); // ----------------- UrlResolver.isTargetable = function () { return false; }; UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) { var oldCacheHeaders = pkgMeta._cacheHeaders || {}; var reqHeaders = {}; // If the previous cache headers contain an ETag, // send the "If-None-Match" header with it if (oldCacheHeaders.ETag) { reqHeaders['If-None-Match'] = oldCacheHeaders.ETag;
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) { decEndpoint.dependencies = decEndpoint.dependencies || {}; // Parse package dependencies mout.object.forOwn(pkgMeta[jsonKey], function (value, key) { var resolved; var fetching; var compatible; var childDecEndpoint = endpointParser.json2decomposed(key, value); // Check if a compatible one is already resolved // If there's one, we don't need to resolve it twice resolved = this._resolved[key]; if (resolved) { // Find if there's one with the exact same target compatible = mout.array.find(resolved, function (resolved) { return childDecEndpoint.target === resolved.target; }, this); // If we found one, merge stuff instead of adding as resolved if (compatible) { decEndpoint.dependencies[key] = compatible; compatible.dependants.push(decEndpoint); compatible.dependants = this._uniquify(compatible.dependants); return; } // Find one that is compatible compatible = mout.array.find(resolved, function (resolved) { return this._areCompatible(childDecEndpoint, resolved); }, this); // If we found one, add as resolved // and copy resolved properties from the compatible one if (compatible) { decEndpoint.dependencies[key] = compatible; childDecEndpoint.canonicalDir = compatible.canonicalDir; childDecEndpoint.pkgMeta = compatible.pkgMeta; childDecEndpoint.dependencies = compatible.dependencies; childDecEndpoint.dependants = [decEndpoint]; this._resolved[key].push(childDecEndpoint); return; } } // Check if a compatible one is being fetched // If there's one, we wait and reuse it to avoid resolving it twice fetching = this._fetching[key]; if (fetching) { compatible = mout.array.find(fetching, function (fetching) { return this._areCompatible(childDecEndpoint, fetching); }, this); if (compatible) { compatible.promise .then(function () { this._parseDependencies(decEndpoint, pkgMeta, jsonKey); }.bind(this)); return; } } // Mark endpoint as unresolvable if the parent is also unresolvable childDecEndpoint.unresolvable = !!decEndpoint.unresolvable; // Otherwise, just fetch it from the repository decEndpoint.dependencies[key] = childDecEndpoint; childDecEndpoint.dependants = [decEndpoint]; this._fetch(childDecEndpoint); }, this); };
.then(function () { // If the resolutions is empty, delete key if (!mout.object.size(this._json.resolutions)) { delete this._json.resolutions; } }.bind(this))
function resolveName( name, values ) { return mout.object.get( values, name ) || ""; }