mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Compare commits
66 Commits
feature/sh
...
v1.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64d990ba10 | ||
|
|
cb019c405b | ||
|
|
26f80d25be | ||
|
|
fe9a1bb5fc | ||
|
|
79679f9b08 | ||
|
|
5ef5403d69 | ||
|
|
23afb3a129 | ||
|
|
fac08cf835 | ||
|
|
dd67cc7de0 | ||
|
|
8531534241 | ||
|
|
0993621bb8 | ||
|
|
45fe5c37cc | ||
|
|
35b60d8d89 | ||
|
|
63b4d37207 | ||
|
|
793268ed54 | ||
|
|
a4a05a5413 | ||
|
|
821979bab1 | ||
|
|
69be742619 | ||
|
|
298982b522 | ||
|
|
9f2b3d1cd4 | ||
|
|
800119fc92 | ||
|
|
725fc26880 | ||
|
|
ed27e87540 | ||
|
|
4fc2b5cf76 | ||
|
|
749d46930d | ||
|
|
7acafc26d6 | ||
|
|
2817936b87 | ||
|
|
022c5a8401 | ||
|
|
50fd944a62 | ||
|
|
9c9cd8164b | ||
|
|
8744449016 | ||
|
|
ada6fc18d9 | ||
|
|
2c9d847a1d | ||
|
|
816cdda6bb | ||
|
|
833f97198e | ||
|
|
293d5cc02b | ||
|
|
dbb302f22a | ||
|
|
87d21e7968 | ||
|
|
9dd79a8061 | ||
|
|
a1287416d4 | ||
|
|
00dc877f0a | ||
|
|
335081053d | ||
|
|
4af22cfbc0 | ||
|
|
ab4f8a0e39 | ||
|
|
7e110603b5 | ||
|
|
94455192ad | ||
|
|
c4ca24a537 | ||
|
|
ea1f5d1ff0 | ||
|
|
77b7355433 | ||
|
|
e727566741 | ||
|
|
0f68da4eb8 | ||
|
|
1a7abfd3b7 | ||
|
|
905c2775d2 | ||
|
|
126da9ee12 | ||
|
|
1080cb71c4 | ||
|
|
39a295901a | ||
|
|
7e55b0b099 | ||
|
|
b4aa90b402 | ||
|
|
a1ecf8a413 | ||
|
|
4d59d266c1 | ||
|
|
7e0a2ea4cb | ||
|
|
912808b672 | ||
|
|
a352d51711 | ||
|
|
4ad5ed64d7 | ||
|
|
3838a3b4fb | ||
|
|
7d748ae15e |
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,5 +1,44 @@
|
||||
# Changelog
|
||||
|
||||
## 1.5.2 - 2015-08-25
|
||||
|
||||
- Revert update semver version from 2.x to 5.x, fixes ([#1896](https://github.com/bower/bower/issues/1896))
|
||||
- Make bower commands work from subdirectories, fixes ([#1893](https://github.com/bower/bower/issues/1893))
|
||||
- Put auto shallow cloning for git behind a flag, fixes ([#1764](https://github.com/bower/bower/issues/1764))
|
||||
|
||||
## 1.5.1 - 2015-08-24
|
||||
|
||||
- If cwd provided explicitly, force using it, fixes #1866
|
||||
|
||||
## 1.5.0 - 2015-08-24
|
||||
|
||||
- Pluggable Resolvers! http://bower.io/docs/pluggable-resolvers/
|
||||
- Update semver version from 2.x to 5.x ([#1852](https://github.com/bower/bower/issues/1852))
|
||||
- Auto-sort dependencies alphabetically ([#1381](https://github.com/bower/bower/issues/1381))
|
||||
- Make bower commands work from subdirectories ([#1866](https://github.com/bower/bower/issues/1866))
|
||||
- No longer prefer installing bower as global module ([#1865](https://github.com/bower/bower/issues/1865))
|
||||
|
||||
## 1.4.1 - 2015-04-01
|
||||
|
||||
- [fix] Reading .bowerrc upwards directory tree ([#1763](https://github.com/bower/bower/issues/1763))
|
||||
- [fix] Update bower-registry-client so it uses the same bower-config as bower
|
||||
|
||||
## 1.4.0 - 2015-03-30
|
||||
|
||||
- Add login and unregister commands ([#1719](https://github.com/bower/bower/issues/1719))
|
||||
- Automatically detecting smart Git hosts ([#1628](https://github.com/bower/bower/issues/1628))
|
||||
- [bower/config#23] Allow npm config variables ([#1711](https://github.com/bower/bower/issues/1711))
|
||||
- [bower/config#24] Merge .bowerrc files upwards directory tree ([#1689](https://github.com/bower/bower/issues/1689))
|
||||
- Better homedir detection (514eb8f)
|
||||
- Add --save-exact flag ([#1654](https://github.com/bower/bower/issues/1654))
|
||||
- Ensure extracted files are readable (tar-fs) ([#1548](https://github.com/bower/bower/issues/1548))
|
||||
- The version command in the programmatic API now returns the new version ([#1755](https://github.com/bower/bower/issues/1755))
|
||||
- Some minor fixes: #1639, #1620, #1576, #1557, 962a565, a464f5a
|
||||
- Improved Windows support (AppVeyor CI, tests actually passing on Windows)
|
||||
- OSX testing enabled on TravisCI
|
||||
|
||||
It also includes improved test coverage (~60% -> ~85%) and many refactors.
|
||||
|
||||
## 1.3.12 - 2014-09-28
|
||||
|
||||
- [stability] Fix versions for unstable dependencies ([#1532](https://github.com/bower/bower/pull/1532))
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = function (grunt) {
|
||||
simplemocha: {
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
timeout: '10000'
|
||||
timeout: '15000'
|
||||
},
|
||||
full: {
|
||||
src: ['test/test.js']
|
||||
|
||||
46
README.md
46
README.md
@@ -77,25 +77,18 @@ password, you should add the following environment variable: `GIT_SSH -
|
||||
C:\Program Files\TortoiseGit\bin\TortoisePlink.exe`. Adjust the `TortoisePlink`
|
||||
path if needed.
|
||||
|
||||
### Ubuntu users
|
||||
|
||||
To use Bower on Ubuntu, you might need to link `nodejs` executable to `node`:
|
||||
|
||||
```
|
||||
sudo ln -s /usr/bin/nodejs /usr/bin/node
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
|
||||
|
||||
## Completion (experimental)
|
||||
|
||||
_NOTE_: Completion is still not implemented for the 1.0.0 release
|
||||
|
||||
Bower now has an experimental `completion` command that is based on, and works
|
||||
similarly to the [npm completion](https://npmjs.org/doc/completion.html). It is
|
||||
not available for Windows users.
|
||||
|
||||
This command will output a Bash / ZSH script to put into your `~/.bashrc`,
|
||||
`~/.bash_profile`, or `~/.zshrc` file.
|
||||
|
||||
```sh
|
||||
$ bower completion >> ~/.bash_profile
|
||||
```
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
@@ -106,7 +99,7 @@ $ bower completion >> ~/.bash_profile
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions of all kinds from anyone. Please take a moment to
|
||||
We welcome [contributions](https://github.com/bower/bower/graphs/contributors) of all kinds from anyone. Please take a moment to
|
||||
review the [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
* [Bug reports](CONTRIBUTING.md#bugs)
|
||||
@@ -120,27 +113,8 @@ Note that on Windows for tests to pass you need to configure Git before cloning:
|
||||
git config --global core.autocrlf input
|
||||
```
|
||||
|
||||
## Bower Team
|
||||
|
||||
Bower is made by lots of people across the globe, contributions large and small. Our thanks to everyone who has played a part.
|
||||
|
||||
### Core team
|
||||
|
||||
* [@satazor](https://github.com/satazor)
|
||||
* [@wibblymat](https://github.com/wibblymat)
|
||||
* [@paulirish](https://github.com/paulirish)
|
||||
* [@benschwarz](https://github.com/benschwarz)
|
||||
* [@svnlto](https://github.com/svnlto)
|
||||
* [@sheerun](https://github.com/sheerun)
|
||||
|
||||
### Bower Alumni
|
||||
|
||||
* [@fat](https://github.com/fat)
|
||||
* [@maccman](https://github.com/maccman)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2015 Twitter and other contributors
|
||||
Copyright (c) 2015 Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
|
||||
|
||||
Licensed under the MIT License
|
||||
|
||||
@@ -22,7 +22,7 @@ install:
|
||||
# Get the latest stable version of Node 0.STABLE.latest
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
# Install subversion
|
||||
- choco install svn
|
||||
- choco install svn -y
|
||||
# Install bower
|
||||
- npm install
|
||||
|
||||
|
||||
@@ -66,11 +66,13 @@ module.exports = {
|
||||
install: commandFactory('./install'),
|
||||
link: commandFactory('./link'),
|
||||
list: commandFactory('./list'),
|
||||
login: commandFactory('./login'),
|
||||
lookup: commandFactory('./lookup'),
|
||||
prune: commandFactory('./prune'),
|
||||
register: commandFactory('./register'),
|
||||
search: commandFactory('./search'),
|
||||
update: commandFactory('./update'),
|
||||
uninstall: commandFactory('./uninstall'),
|
||||
unregister: commandFactory('./unregister'),
|
||||
version: commandFactory('./version')
|
||||
};
|
||||
|
||||
@@ -13,6 +13,12 @@ var createError = require('../util/createError');
|
||||
function init(logger, config) {
|
||||
var project;
|
||||
|
||||
config = config || {};
|
||||
|
||||
if (!config.cwd) {
|
||||
config.cwd = process.cwd();
|
||||
}
|
||||
|
||||
config = defaultConfig(config);
|
||||
|
||||
// This command requires interactive to be enabled
|
||||
|
||||
@@ -81,7 +81,7 @@ function checkVersions(project, tree, logger) {
|
||||
}, true);
|
||||
|
||||
if (nodes.length) {
|
||||
logger.info('check-new', 'Checking for new versions of the project dependencies..');
|
||||
logger.info('check-new', 'Checking for new versions of the project dependencies...');
|
||||
}
|
||||
|
||||
// Check for new versions for each node
|
||||
|
||||
123
lib/commands/login.js
Normal file
123
lib/commands/login.js
Normal file
@@ -0,0 +1,123 @@
|
||||
var Configstore = require('configstore');
|
||||
var GitHub = require('github');
|
||||
var Q = require('q');
|
||||
|
||||
var createError = require('../util/createError');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
function login(logger, options, config) {
|
||||
var configstore = new Configstore('bower-github');
|
||||
|
||||
config = defaultConfig(config);
|
||||
|
||||
var promise;
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.token) {
|
||||
promise = Q.resolve({ token: options.token });
|
||||
} else {
|
||||
// This command requires interactive to be enabled
|
||||
if (!config.interactive) {
|
||||
logger.emit('error', createError('Login requires an interactive shell', 'ENOINT', {
|
||||
details: 'Note that you can manually force an interactive shell with --config.interactive'
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var questions = [
|
||||
{
|
||||
'name': 'username',
|
||||
'message': 'Username',
|
||||
'type': 'input',
|
||||
'default': configstore.get('username')
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'message': 'Password',
|
||||
'type': 'password'
|
||||
}
|
||||
];
|
||||
|
||||
var github = new GitHub({
|
||||
version: '3.0.0'
|
||||
});
|
||||
|
||||
promise = Q.nfcall(logger.prompt.bind(logger), questions)
|
||||
.then(function (answers) {
|
||||
configstore.set('username', answers.username);
|
||||
|
||||
github.authenticate({
|
||||
type: 'basic',
|
||||
username: answers.username,
|
||||
password: answers.password
|
||||
});
|
||||
|
||||
return Q.ninvoke(github.authorization, 'create', {
|
||||
scopes: ['user', 'repo'],
|
||||
note: 'Bower command line client (' + (new Date()).toISOString() + ')'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(function (result) {
|
||||
configstore.set('accessToken', result.token);
|
||||
|
||||
return result;
|
||||
}, function (error) {
|
||||
var message;
|
||||
|
||||
try {
|
||||
message = JSON.parse(error.message).message;
|
||||
} catch (e) {
|
||||
message = 'Authorization failed';
|
||||
}
|
||||
|
||||
var questions = [
|
||||
{
|
||||
'name': 'otpcode',
|
||||
'message': 'Two-Factor Auth Code',
|
||||
'type': 'input'
|
||||
}
|
||||
];
|
||||
|
||||
if (message === 'Must specify two-factor authentication OTP code.') {
|
||||
return Q.nfcall(logger.prompt.bind(logger), questions)
|
||||
.then(function (answers) {
|
||||
return Q.ninvoke(github.authorization, 'create', {
|
||||
scopes: ['user', 'repo'],
|
||||
note: 'Bower command line client (' + (new Date()).toISOString() + ')',
|
||||
headers: {
|
||||
'X-GitHub-OTP': answers.otpcode
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
configstore.set('accessToken', result.token);
|
||||
|
||||
return result;
|
||||
}, function () {
|
||||
logger.emit('error', createError(message, 'EAUTH'));
|
||||
});
|
||||
} else {
|
||||
logger.emit('error', createError(message, 'EAUTH'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
login.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var options = cli.readOptions({
|
||||
token: { type: String, shorthand: 't' },
|
||||
}, argv);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [options];
|
||||
};
|
||||
|
||||
module.exports = login;
|
||||
85
lib/commands/unregister.js
Normal file
85
lib/commands/unregister.js
Normal file
@@ -0,0 +1,85 @@
|
||||
var chalk = require('chalk');
|
||||
var Q = require('q');
|
||||
|
||||
var defaultConfig = require('../config');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var Tracker = require('../util/analytics').Tracker;
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function unregister(logger, name, config) {
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var repository;
|
||||
var registryClient;
|
||||
var tracker;
|
||||
var force;
|
||||
|
||||
config = defaultConfig(config);
|
||||
force = config.force;
|
||||
tracker = new Tracker(config);
|
||||
|
||||
// Bypass any cache
|
||||
config.offline = false;
|
||||
config.force = true;
|
||||
|
||||
// Trim name
|
||||
name = name.trim();
|
||||
|
||||
repository = new PackageRepository(config, logger);
|
||||
|
||||
tracker.track('unregister');
|
||||
|
||||
if (!config.accessToken) {
|
||||
return logger.emit('error',
|
||||
createError('Use "bower login" with collaborator credentials', 'EFORBIDDEN')
|
||||
);
|
||||
}
|
||||
|
||||
return Q.resolve()
|
||||
.then(function () {
|
||||
// If non interactive or user forced, bypass confirmation
|
||||
if (!config.interactive || force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Q.nfcall(logger.prompt.bind(logger), {
|
||||
type: 'confirm',
|
||||
message: 'You are about to remove component "' + chalk.cyan.underline(name) + '" from the bower registry (' + chalk.cyan.underline(config.registry.register) + '). It is generally considered bad behavior to remove versions of a library that others are depending on. Are you really sure?',
|
||||
default: false
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
// If user response was negative, abort
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
registryClient = repository.getRegistryClient();
|
||||
|
||||
logger.action('unregister', name, { name: name });
|
||||
|
||||
return Q.nfcall(registryClient.unregister.bind(registryClient), name);
|
||||
})
|
||||
.then(function (result) {
|
||||
tracker.track('unregistered');
|
||||
logger.info('Package unregistered', name);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
unregister.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain[1];
|
||||
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports = unregister;
|
||||
@@ -40,6 +40,7 @@ function bump(project, versionArg, message) {
|
||||
})
|
||||
.then(function () {
|
||||
console.log('v' + newVersion);
|
||||
return newVersion;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var tty = require('tty');
|
||||
var object = require('mout').object;
|
||||
var bowerConfig = require('bower-config');
|
||||
var Configstore = require('configstore');
|
||||
|
||||
var cachedConfigs = {};
|
||||
|
||||
@@ -18,6 +19,9 @@ function readCachedConfig(cwd) {
|
||||
}
|
||||
|
||||
var config = cachedConfigs[cwd] = bowerConfig.read(cwd);
|
||||
var configstore = new Configstore('bower-github').all;
|
||||
|
||||
object.mixIn(config, configstore);
|
||||
|
||||
// Delete the json attribute because it is no longer supported
|
||||
// and conflicts with --json
|
||||
|
||||
@@ -80,6 +80,17 @@ Manager.prototype.configure = function (setup) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* It starts recursive fetching of targets. The flow is as follows:
|
||||
*
|
||||
* 1. this._fetch() that calls:
|
||||
* 2. this._onFetchError() or this._onFetchSuccess() that calls:
|
||||
* 3. this._parseDependencies() that calls for all dependencies:
|
||||
* 4. this._fetch() again.
|
||||
*
|
||||
* Fetching and parsing of dependencies continues until all dependencies are fetched.
|
||||
*
|
||||
*/
|
||||
Manager.prototype.resolve = function () {
|
||||
// If already resolving, error out
|
||||
if (this._working) {
|
||||
@@ -337,7 +348,7 @@ Manager.prototype._onFetchSuccess = function (decEndpoint, canonicalDir, pkgMeta
|
||||
resolved.push(decEndpoint);
|
||||
|
||||
// Parse dependencies
|
||||
this._parseDependencies(decEndpoint, pkgMeta, 'dependencies');
|
||||
this._parseDependencies(decEndpoint, pkgMeta);
|
||||
|
||||
// Check if there are incompatibilities for this package name
|
||||
// If there are, we need to fetch them
|
||||
@@ -414,13 +425,21 @@ Manager.prototype._failFast = function () {
|
||||
}.bind(this), 20000);
|
||||
};
|
||||
|
||||
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) {
|
||||
/**
|
||||
* Parses decEndpoint dependencies and fetches missing ones.
|
||||
*
|
||||
* It fetches all non-yet-fetching dependencies with _fetch.
|
||||
*
|
||||
*
|
||||
* @param {string} pkgMeta Metadata of package to resolve dependencies.
|
||||
*/
|
||||
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta) {
|
||||
var pending = [];
|
||||
|
||||
decEndpoint.dependencies = decEndpoint.dependencies || {};
|
||||
|
||||
// Parse package dependencies
|
||||
mout.object.forOwn(pkgMeta[jsonKey], function (value, key) {
|
||||
mout.object.forOwn(pkgMeta.dependencies, function (value, key) {
|
||||
var resolved;
|
||||
var fetching;
|
||||
var compatible;
|
||||
@@ -489,11 +508,24 @@ Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey)
|
||||
if (pending.length > 0) {
|
||||
Q.all(pending)
|
||||
.then(function () {
|
||||
this._parseDependencies(decEndpoint, pkgMeta, jsonKey);
|
||||
this._parseDependencies(decEndpoint, pkgMeta);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* After batch of packages has been fetched, the results are stored in _resolved hash.
|
||||
*
|
||||
* {
|
||||
* packageName: [array, of, decEndpoints]
|
||||
* }
|
||||
*
|
||||
* This method takes all possible decEndpoints and selects only one of them per packageName.
|
||||
*
|
||||
* It stores selected packages in the this._dissected array.
|
||||
*
|
||||
* The _dissected array is used later on by install method to move packages to config.directory.
|
||||
*/
|
||||
Manager.prototype._dissect = function () {
|
||||
var err;
|
||||
var componentsDir;
|
||||
@@ -571,6 +603,11 @@ Manager.prototype._dissect = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
// Packages that didn't have any conflicts in resolution yet have
|
||||
// "resolution" entry in bower.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,
|
||||
@@ -590,12 +627,16 @@ Manager.prototype._dissect = function () {
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Analyse a few props
|
||||
// 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 &&
|
||||
|
||||
@@ -38,8 +38,7 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
that._extendLog(log, info);
|
||||
});
|
||||
|
||||
// Get the appropriate resolver
|
||||
return resolverFactory(decEndpoint, this._config, logger, this._registryClient)
|
||||
return this._getResolver(decEndpoint, logger)
|
||||
// Decide if we retrieve from the cache or not
|
||||
// Also decide if we validate the cached entry or not
|
||||
.then(function (resolver) {
|
||||
@@ -91,7 +90,7 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
logger.action('validate', (pkgMeta._release ? pkgMeta._release + ' against ': '') +
|
||||
resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : ''));
|
||||
|
||||
return resolver.hasNew(canonicalDir, pkgMeta)
|
||||
return resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
// If there are no new contents, resolve to
|
||||
// the cached one
|
||||
@@ -117,15 +116,15 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
PackageRepository.prototype.versions = function (source) {
|
||||
// Resolve the source using the factory because the
|
||||
// source can actually be a registry name
|
||||
return resolverFactory.getConstructor(source, this._config, this._registryClient)
|
||||
.spread(function (ConcreteResolver, source) {
|
||||
return this._getResolver({ source: source })
|
||||
.then(function (resolver) {
|
||||
// If offline, resolve using the cached versions
|
||||
if (this._config.offline) {
|
||||
return this._resolveCache.versions(source);
|
||||
return this._resolveCache.versions(resolver.getSource());
|
||||
}
|
||||
|
||||
// Otherwise, fetch remotely
|
||||
return ConcreteResolver.versions(source);
|
||||
return resolver.constructor.versions(resolver.getSource());
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
@@ -168,6 +167,13 @@ PackageRepository.clearRuntimeCache = function () {
|
||||
|
||||
// ---------------------
|
||||
|
||||
PackageRepository.prototype._getResolver = function (decEndpoint, logger) {
|
||||
logger = logger || this._logger;
|
||||
|
||||
// Get the appropriate resolver
|
||||
return resolverFactory(decEndpoint, { config: this._config, logger: logger }, this._registryClient);
|
||||
};
|
||||
|
||||
PackageRepository.prototype._resolve = function (resolver, logger) {
|
||||
var that = this;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ var createError = require('../util/createError');
|
||||
var readJson = require('../util/readJson');
|
||||
var validLink = require('../util/validLink');
|
||||
var scripts = require('./scripts');
|
||||
var sortobject = require('deep-sort-object');
|
||||
|
||||
function Project(config, logger) {
|
||||
// This is the only architecture component that ensures defaults
|
||||
@@ -98,11 +99,11 @@ Project.prototype.install = function (decEndpoints, options, config) {
|
||||
}
|
||||
|
||||
if (that._options.save) {
|
||||
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
|
||||
that._json.dependencies = sortobject(mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint));
|
||||
}
|
||||
|
||||
if (that._options.saveDev) {
|
||||
that._json.devDependencies = mout.object.mixIn(that._json.devDependencies || {}, jsonEndpoint);
|
||||
that._json.devDependencies = sortobject(mout.object.mixIn(that._json.devDependencies || {}, jsonEndpoint));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,162 +5,205 @@ var mout = require('mout');
|
||||
var resolvers = require('./resolvers');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function createInstance(decEndpoint, config, logger, registryClient) {
|
||||
return getConstructor(decEndpoint.source, config, registryClient)
|
||||
.spread(function (ConcreteResolver, source, fromRegistry) {
|
||||
var decEndpointCopy = mout.object.pick(decEndpoint, ['name', 'target']);
|
||||
var pluginResolverFactory = require('./resolvers/pluginResolverFactory');
|
||||
|
||||
decEndpointCopy.source = source;
|
||||
function createInstance(decEndpoint, options, registryClient) {
|
||||
decEndpoint = mout.object.pick(decEndpoint, ['name', 'target', 'source']);
|
||||
|
||||
// Signal if it was fetched from the registry
|
||||
if (fromRegistry) {
|
||||
decEndpoint.registry = true;
|
||||
// If no name was specified, assume the name from the registry
|
||||
if (!decEndpointCopy.name) {
|
||||
decEndpointCopy.name = decEndpoint.name = decEndpoint.source;
|
||||
}
|
||||
}
|
||||
options.version = require('../../package.json').version;
|
||||
|
||||
return new ConcreteResolver(decEndpointCopy, config, logger);
|
||||
return getConstructor(decEndpoint, options, registryClient)
|
||||
.spread(function (ConcreteResolver, decEndpoint) {
|
||||
return new ConcreteResolver(decEndpoint, options.config, options.logger);
|
||||
});
|
||||
}
|
||||
|
||||
function getConstructor(source, config, registryClient) {
|
||||
var absolutePath,
|
||||
promise;
|
||||
function getConstructor(decEndpoint, options, registryClient) {
|
||||
var source = decEndpoint.source;
|
||||
var config = options.config;
|
||||
|
||||
// Below we try a series of async tests to guess the type of resolver to use
|
||||
// If a step was unable to guess the resolver, it returns undefined
|
||||
// If a step can guess the resolver, it returns with construcotor of resolver
|
||||
|
||||
var promise = Q.resolve();
|
||||
|
||||
var addResolver = function (resolverFactory) {
|
||||
promise = promise.then(function (result) {
|
||||
if (result === undefined) {
|
||||
return resolverFactory(decEndpoint, options);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Plugin resolvers.
|
||||
//
|
||||
// It requires each resolver defined in config.resolvers and calls
|
||||
// its "match" to check if given resolves supports given decEndpoint
|
||||
addResolver(function () {
|
||||
var selectedResolver;
|
||||
|
||||
var resolverNames = config.resolvers || [];
|
||||
|
||||
var resolverPromises = resolverNames.map(function (resolverName) {
|
||||
var resolver = resolvers[resolverName]
|
||||
|| pluginResolverFactory(require(resolverName), options);
|
||||
|
||||
return function () {
|
||||
if (selectedResolver === undefined) {
|
||||
var match = resolver.match.bind(resolver);
|
||||
|
||||
return Q.fcall(match, source).then(function (result) {
|
||||
if (result) {
|
||||
return selectedResolver = resolver;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return selectedResolver;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return resolverPromises.reduce(Q.when, new Q(undefined)).then(function (resolver) {
|
||||
if (resolver) {
|
||||
return Q.fcall(resolver.locate.bind(resolver), decEndpoint.source).then(function (result) {
|
||||
if (result && result !== decEndpoint.source) {
|
||||
decEndpoint.source = result;
|
||||
decEndpoint.registry = true;
|
||||
return getConstructor(decEndpoint, options, registryClient);
|
||||
} else {
|
||||
return [resolver, decEndpoint];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Git case: git git+ssh, git+http, git+https
|
||||
// .git at the end (probably ssh shorthand)
|
||||
// git@ at the start
|
||||
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
|
||||
source = source.replace(/^git\+/, '');
|
||||
return Q.fcall(function () {
|
||||
addResolver(function() {
|
||||
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
|
||||
decEndpoint.source = source.replace(/^git\+/, '');
|
||||
|
||||
// If it's a GitHub repository, return the specialized resolver
|
||||
if (resolvers.GitHub.getOrgRepoPair(source)) {
|
||||
return [resolvers.GitHub, source];
|
||||
return [resolvers.GitHub, decEndpoint];
|
||||
}
|
||||
|
||||
return [resolvers.GitRemote, source];
|
||||
});
|
||||
}
|
||||
return [resolvers.GitRemote, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
// SVN case: svn, svn+ssh, svn+http, svn+https, svn+file
|
||||
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
|
||||
return Q.fcall(function () {
|
||||
return [resolvers.Svn, source];
|
||||
});
|
||||
}
|
||||
addResolver(function () {
|
||||
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
|
||||
return [resolvers.Svn, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
// URL case
|
||||
if (/^https?:\/\//i.exec(source)) {
|
||||
return Q.fcall(function () {
|
||||
return [resolvers.Url, source];
|
||||
});
|
||||
}
|
||||
addResolver(function () {
|
||||
if (/^https?:\/\//i.exec(source)) {
|
||||
return [resolvers.Url, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
// Below we try a series of async tests to guess the type of resolver to use
|
||||
// If a step was unable to guess the resolver, it throws an error
|
||||
// If a step was able to guess the resolver, it resolves with a function
|
||||
// That function returns a promise that will resolve with the concrete type
|
||||
|
||||
// If source is ./ or ../ or an absolute path
|
||||
absolutePath = path.resolve(config.cwd, source);
|
||||
|
||||
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) || path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath) {
|
||||
promise = Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
|
||||
.then(function (stats) {
|
||||
if (stats.isDirectory()) {
|
||||
return function () {
|
||||
return Q.resolve([resolvers.GitFs, absolutePath]);
|
||||
};
|
||||
}
|
||||
addResolver(function () {
|
||||
var absolutePath = path.resolve(config.cwd, source);
|
||||
|
||||
throw new Error('Not a Git repository');
|
||||
})
|
||||
// If not, check if source is a valid Subversion repository
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, path.join(absolutePath, '.svn'))
|
||||
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) ||
|
||||
path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath
|
||||
) {
|
||||
return Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
|
||||
.then(function (stats) {
|
||||
decEndpoint.source = absolutePath;
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return function () {
|
||||
return Q.resolve([resolvers.Svn, absolutePath]);
|
||||
};
|
||||
return Q.resolve([resolvers.GitFs, decEndpoint]);
|
||||
}
|
||||
|
||||
throw new Error('Not a Subversion repository');
|
||||
});
|
||||
})
|
||||
// If not, check if source is a valid file/folder
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, absolutePath)
|
||||
.then(function () {
|
||||
return function () {
|
||||
return Q.resolve([resolvers.Fs, absolutePath]);
|
||||
};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
promise = Q.reject(new Error('Not an absolute or relative file'));
|
||||
}
|
||||
throw new Error('Not a Git repository');
|
||||
})
|
||||
// If not, check if source is a valid Subversion repository
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, path.join(absolutePath, '.svn'))
|
||||
.then(function (stats) {
|
||||
decEndpoint.source = absolutePath;
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return Q.resolve([resolvers.Svn, decEndpoint]);
|
||||
}
|
||||
|
||||
throw new Error('Not a Subversion repository');
|
||||
});
|
||||
})
|
||||
// If not, check if source is a valid file/folder
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, absolutePath)
|
||||
.then(function () {
|
||||
decEndpoint.source = absolutePath;
|
||||
|
||||
return Q.resolve([resolvers.Fs, decEndpoint]);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return promise
|
||||
// Check if is a shorthand and expand it
|
||||
.fail(function (err) {
|
||||
var parts;
|
||||
|
||||
addResolver(function () {
|
||||
// Skip ssh and/or URL with auth
|
||||
if (/[:@]/.test(source)) {
|
||||
throw err;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure exactly only one "/"
|
||||
parts = source.split('/');
|
||||
var parts = source.split('/');
|
||||
if (parts.length === 2) {
|
||||
source = mout.string.interpolate(config.shorthandResolver, {
|
||||
decEndpoint.source = mout.string.interpolate(config.shorthandResolver, {
|
||||
shorthand: source,
|
||||
owner: parts[0],
|
||||
package: parts[1]
|
||||
});
|
||||
|
||||
return function () {
|
||||
return getConstructor(source, config, registryClient);
|
||||
};
|
||||
return getConstructor(decEndpoint, options, registryClient);
|
||||
}
|
||||
});
|
||||
|
||||
throw err;
|
||||
})
|
||||
// As last resort, we try the registry
|
||||
.fail(function (err) {
|
||||
addResolver(function () {
|
||||
if (!registryClient) {
|
||||
throw err;
|
||||
return;
|
||||
}
|
||||
|
||||
return function () {
|
||||
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
|
||||
.then(function (entry) {
|
||||
if (!entry) {
|
||||
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
|
||||
}
|
||||
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
|
||||
.then(function (entry) {
|
||||
if (!entry) {
|
||||
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
|
||||
}
|
||||
|
||||
// TODO: Handle entry.type.. for now it's only 'alias'
|
||||
// When we got published packages, this needs to be adjusted
|
||||
source = entry.url;
|
||||
decEndpoint.registry = true;
|
||||
|
||||
return getConstructor(source, config, registryClient)
|
||||
.spread(function (ConcreteResolver, source) {
|
||||
return [ConcreteResolver, source, true];
|
||||
});
|
||||
});
|
||||
};
|
||||
})
|
||||
// If we got the function, simply call and return
|
||||
.then(function (func) {
|
||||
return func();
|
||||
// Finally throw a meaningful error
|
||||
}, function () {
|
||||
if (!decEndpoint.name) {
|
||||
decEndpoint.name = decEndpoint.source;
|
||||
}
|
||||
|
||||
decEndpoint.source = entry.url;
|
||||
|
||||
return getConstructor(decEndpoint, options);
|
||||
});
|
||||
});
|
||||
|
||||
addResolver(function () {
|
||||
throw createError('Could not find appropriate resolver for ' + source, 'ENORESOLVER');
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function clearRuntimeCache() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var GitRemoteResolver = require('./GitRemoteResolver');
|
||||
var download = require('../../util/download');
|
||||
var extract = require('../../util/extract');
|
||||
@@ -37,7 +38,9 @@ function GitHubResolver(decEndpoint, config, logger) {
|
||||
}
|
||||
|
||||
// Enable shallow clones for GitHub repos
|
||||
this._shallowClone = true;
|
||||
this._shallowClone = function() {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(GitHubResolver, GitRemoteResolver);
|
||||
|
||||
@@ -26,8 +26,10 @@ function GitRemoteResolver(decEndpoint, config, logger) {
|
||||
this._host = url.parse(this._source).host;
|
||||
}
|
||||
|
||||
// Disable shallow clones
|
||||
this._shallowClone = false;
|
||||
this._remote = url.parse(this._source);
|
||||
|
||||
// Verify whether the server supports shallow cloning
|
||||
this._shallowClone = this._supportsShallowCloning;
|
||||
}
|
||||
|
||||
util.inherits(GitRemoteResolver, GitResolver);
|
||||
@@ -115,34 +117,36 @@ GitRemoteResolver.prototype._fastClone = function (resolution) {
|
||||
branch = resolution.tag || resolution.branch;
|
||||
args = ['clone', this._source, '-b', branch, '--progress', '.'];
|
||||
|
||||
// If the host does not support shallow clones, we don't use --depth=1
|
||||
if (this._shallowClone && !GitRemoteResolver._noShallow.get(this._host)) {
|
||||
args.push('--depth', 1);
|
||||
}
|
||||
|
||||
return cmd('git', args, { cwd: this._tempDir })
|
||||
.spread(function (stdout, stderr) {
|
||||
// Only after 1.7.10 --branch accepts tags
|
||||
// Detect those cases and inform the user to update git otherwise it's
|
||||
// a lot slower than newer versions
|
||||
if (!/branch .+? not found/i.test(stderr)) {
|
||||
return;
|
||||
return this._shallowClone().then(function (shallowCloningSupported) {
|
||||
// If the host does not support shallow clones, we don't use --depth=1
|
||||
if (shallowCloningSupported && !GitRemoteResolver._noShallow.get(this._host)) {
|
||||
args.push('--depth', 1);
|
||||
}
|
||||
|
||||
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
|
||||
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
|
||||
}, function (err) {
|
||||
// Some git servers do not support shallow clones
|
||||
// When that happens, we mark this host and try again
|
||||
if (!GitRemoteResolver._noShallow.has(that._source) &&
|
||||
err.details &&
|
||||
/(rpc failed|shallow|--depth)/i.test(err.details)
|
||||
) {
|
||||
GitRemoteResolver._noShallow.set(that._host, true);
|
||||
return that._fastClone(resolution);
|
||||
}
|
||||
return cmd('git', args, { cwd: that._tempDir })
|
||||
.spread(function (stdout, stderr) {
|
||||
// Only after 1.7.10 --branch accepts tags
|
||||
// Detect those cases and inform the user to update git otherwise it's
|
||||
// a lot slower than newer versions
|
||||
if (!/branch .+? not found/i.test(stderr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
|
||||
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
|
||||
}, function (err) {
|
||||
// Some git servers do not support shallow clones
|
||||
// When that happens, we mark this host and try again
|
||||
if (!GitRemoteResolver._noShallow.has(that._source) &&
|
||||
err.details &&
|
||||
/(rpc failed|shallow|--depth)/i.test(err.details)
|
||||
) {
|
||||
GitRemoteResolver._noShallow.set(that._host, true);
|
||||
return that._fastClone(resolution);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -160,6 +164,79 @@ GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
|
||||
}
|
||||
};
|
||||
|
||||
// Verifies whether the server supports shallow cloning.
|
||||
// This is done according to the rules found in the following links:
|
||||
// * https://github.com/dimitri/el-get/pull/1921/files
|
||||
// * http://stackoverflow.com/questions/9270488/is-it-possible-to-detect-whether-a-http-git-remote-is-smart-or-dumb
|
||||
//
|
||||
// Summary of the rules:
|
||||
// * Protocols like ssh or git always support shallow cloning
|
||||
// * HTTP-based protocols can be verified by sending a HEAD or GET request to the URI (appended to the URL of the Git repo):
|
||||
// /info/refs?service=git-upload-pack
|
||||
// * If the server responds with a 'Content-Type' header of 'application/x-git-upload-pack-advertisement',
|
||||
// the server supports shallow cloning ("smart server")
|
||||
// * If the server responds with a different content type, the server does not support shallow cloning ("dumb server")
|
||||
// * Instead of doing the HEAD or GET request using an HTTP client, we're letting Git and Curl do the heavy lifting.
|
||||
// Calling Git with the GIT_CURL_VERBOSE=2 env variable will provide the Git and Curl output, which includes
|
||||
// the content type. This has the advantage that Git will take care of using stored credentials and any additional
|
||||
// negotiation that needs to take place.
|
||||
//
|
||||
// The above should cover most cases, including BitBucket.
|
||||
GitRemoteResolver.prototype._supportsShallowCloning = function () {
|
||||
var value = true;
|
||||
|
||||
// Verify that the remote could be parsed and that a protocol is set
|
||||
// This case is unlikely, but let's still cover it.
|
||||
if (this._remote == null || this._remote.protocol == null) {
|
||||
return Q.resolve(false);
|
||||
}
|
||||
|
||||
|
||||
if (!this._host || !this._config.shallowCloneHosts || this._config.shallowCloneHosts.indexOf(this._host) === -1) {
|
||||
return Q.resolve(false);
|
||||
}
|
||||
|
||||
// Check for protocol - the remote check for hosts supporting shallow cloning is only required for
|
||||
// HTTP or HTTPS, not for Git or SSH.
|
||||
// Also check for hosts that have been checked in a previous request and have been found to support
|
||||
// shallow cloning.
|
||||
if (mout.string.startsWith(this._remote.protocol, 'http')
|
||||
&& !GitRemoteResolver._canShallow.get(this._host)) {
|
||||
// Provide GIT_CURL_VERBOSE=2 environment variable to capture curl output.
|
||||
// Calling ls-remote includes a call to the git-upload-pack service, which returns the content type in the response.
|
||||
var processEnv = mout.object.merge(process.env, { 'GIT_CURL_VERBOSE': 2 });
|
||||
|
||||
value = cmd('git', ['ls-remote', '--heads', this._source], {
|
||||
env: processEnv
|
||||
})
|
||||
.spread(function (stdout, stderr) {
|
||||
// Check stderr for content-type, ignore stdout
|
||||
var isSmartServer;
|
||||
|
||||
// If the content type is 'x-git', then the server supports shallow cloning
|
||||
isSmartServer = mout.string.contains(stderr,
|
||||
'Content-Type: application/x-git-upload-pack-advertisement');
|
||||
|
||||
this._logger.debug('detect-smart-git', 'Smart Git host detected: ' + isSmartServer);
|
||||
|
||||
if (isSmartServer) {
|
||||
// Cache this host
|
||||
GitRemoteResolver._canShallow.set(this._host, true);
|
||||
}
|
||||
|
||||
return isSmartServer;
|
||||
}.bind(this));
|
||||
}
|
||||
else {
|
||||
// One of the following cases:
|
||||
// * A non-HTTP/HTTPS protocol
|
||||
// * A host that has been checked before and that supports shallow cloning
|
||||
return Q.resolve(true);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
// ------------------------------
|
||||
|
||||
// Grab refs remotely
|
||||
@@ -198,4 +275,7 @@ GitRemoteResolver.refs = function (source) {
|
||||
// Store hosts that do not support shallow clones here
|
||||
GitRemoteResolver._noShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
|
||||
|
||||
// Store hosts that support shallow clones here
|
||||
GitRemoteResolver._canShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
|
||||
|
||||
module.exports = GitRemoteResolver;
|
||||
|
||||
@@ -40,7 +40,7 @@ mout.object.mixIn(GitResolver, Resolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
GitResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
GitResolver.prototype._hasNew = function (pkgMeta) {
|
||||
var oldResolution = pkgMeta._resolution || {};
|
||||
|
||||
return this._findResolution()
|
||||
|
||||
@@ -43,9 +43,8 @@ Resolver.prototype.getPkgMeta = function () {
|
||||
return this._pkgMeta;
|
||||
};
|
||||
|
||||
Resolver.prototype.hasNew = function (canonicalDir, pkgMeta) {
|
||||
Resolver.prototype.hasNew = function (pkgMeta) {
|
||||
var promise;
|
||||
var metaFile;
|
||||
var that = this;
|
||||
|
||||
// If already working, error out
|
||||
@@ -56,24 +55,7 @@ Resolver.prototype.hasNew = function (canonicalDir, pkgMeta) {
|
||||
this._working = true;
|
||||
|
||||
// Avoid reading the package meta if already given
|
||||
if (pkgMeta) {
|
||||
promise = this._hasNew(canonicalDir, pkgMeta);
|
||||
// Otherwise call _hasNew with both the package meta and the canonical dir
|
||||
} else {
|
||||
metaFile = path.join(canonicalDir, '.bower.json');
|
||||
|
||||
promise = readJson(metaFile)
|
||||
.spread(function (pkgMeta) {
|
||||
return that._hasNew(canonicalDir, pkgMeta);
|
||||
}, function (err) {
|
||||
that._logger.debug('read-json', 'Failed to read ' + metaFile, {
|
||||
filename: metaFile,
|
||||
error: err
|
||||
});
|
||||
|
||||
return true; // Simply resolve to true if there was an error reading the file
|
||||
});
|
||||
}
|
||||
promise = this._hasNew(pkgMeta);
|
||||
|
||||
return promise.fin(function () {
|
||||
that._working = false;
|
||||
@@ -143,7 +125,7 @@ Resolver.prototype._resolve = function () {
|
||||
|
||||
// Abstract functions that can be re-implemented by concrete resolvers
|
||||
// as necessary
|
||||
Resolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
Resolver.prototype._hasNew = function (pkgMeta) {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ var Q = require('q');
|
||||
var which = require('which');
|
||||
var LRU = require('lru-cache');
|
||||
var mout = require('mout');
|
||||
var path = require('path');
|
||||
var Resolver = require('./Resolver');
|
||||
var semver = require('../../util/semver');
|
||||
var createError = require('../../util/createError');
|
||||
@@ -41,7 +40,7 @@ SvnResolver.getSource = function (source) {
|
||||
.replace(/\/+$/, ''); // Remove trailing slashes
|
||||
};
|
||||
|
||||
SvnResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
SvnResolver.prototype._hasNew = function (pkgMeta) {
|
||||
var oldResolution = pkgMeta._resolution || {};
|
||||
|
||||
return this._findResolution()
|
||||
@@ -87,13 +86,13 @@ SvnResolver.prototype._export = function () {
|
||||
});
|
||||
|
||||
if (resolution.type === 'commit') {
|
||||
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/trunk'), '-r' + resolution.commit, this._tempDir]);
|
||||
promise = cmd('svn', ['export', '--force', this._source + '/trunk', '-r' + resolution.commit, this._tempDir]);
|
||||
} else if (resolution.type === 'branch' && resolution.branch === 'trunk') {
|
||||
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/trunk'), this._tempDir]);
|
||||
promise = cmd('svn', ['export', '--force', this._source + '/trunk', this._tempDir]);
|
||||
} else if (resolution.type === 'branch') {
|
||||
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/branches/' + resolution.branch), this._tempDir]);
|
||||
promise = cmd('svn', ['export', '--force', this._source + '/branches/' + resolution.branch, this._tempDir]);
|
||||
} else {
|
||||
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/tags/' + resolution.tag), this._tempDir]);
|
||||
promise = cmd('svn', ['export', '--force', this._source + '/tags/' + resolution.tag, this._tempDir]);
|
||||
}
|
||||
|
||||
// Throttle the progress reporter to 1 time each sec
|
||||
@@ -325,7 +324,7 @@ SvnResolver.tags = function (source) {
|
||||
return Q.resolve(value);
|
||||
}
|
||||
|
||||
value = cmd('svn', ['list', source + path.normalize('/tags'), '--verbose'])
|
||||
value = cmd('svn', ['list', source + '/tags', '--verbose'])
|
||||
.spread(function (stout) {
|
||||
var tags = SvnResolver.parseSubversionListOutput(stout.toString());
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ UrlResolver.isTargetable = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
UrlResolver.prototype._hasNew = function (pkgMeta) {
|
||||
var oldCacheHeaders = pkgMeta._cacheHeaders || {};
|
||||
var reqHeaders = {};
|
||||
|
||||
|
||||
322
lib/core/resolvers/pluginResolverFactory.js
Normal file
322
lib/core/resolvers/pluginResolverFactory.js
Normal file
@@ -0,0 +1,322 @@
|
||||
var Q = require('q');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var object = require('mout/object');
|
||||
|
||||
var semver = require('../../util/semver');
|
||||
var createError = require('../../util/createError');
|
||||
var readJson = require('../../util/readJson');
|
||||
var removeIgnores = require('../../util/removeIgnores');
|
||||
|
||||
function pluginResolverFactory(resolverFactory, bower) {
|
||||
bower = bower || {};
|
||||
|
||||
if (typeof resolverFactory !== 'function') {
|
||||
throw createError('Resolver has "' + typeof resolverFactory + '" type instead of "function" type.', 'ERESOLERAPI');
|
||||
}
|
||||
|
||||
var resolver = resolverFactory(bower);
|
||||
|
||||
function maxSatisfyingVersion(versions, target) {
|
||||
var versionsArr, index;
|
||||
|
||||
versionsArr = versions.map(function (obj) { return obj.version; });
|
||||
|
||||
// Find a satisfying version, enabling strict match so that pre-releases
|
||||
// have lower priority over normal ones when target is *
|
||||
index = semver.maxSatisfyingIndex(versionsArr, target, true);
|
||||
|
||||
if (index !== -1) {
|
||||
return versions[index];
|
||||
}
|
||||
}
|
||||
|
||||
function PluginResolver(decEndpoint) {
|
||||
this._decEndpoint = decEndpoint;
|
||||
}
|
||||
|
||||
// @private
|
||||
PluginResolver.prototype.getEndpoint = function () {
|
||||
return object.merge(this._decEndpoint, {
|
||||
name: this.getName(),
|
||||
source: this.getSource(),
|
||||
target: this.getTarget()
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getSource = function () {
|
||||
return this._decEndpoint.source;
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getTarget = function () {
|
||||
return this._decEndpoint.target || '*';
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getName = function() {
|
||||
if (!this._decEndpoint.name && typeof resolver.getName === 'function') {
|
||||
return resolver.getName.call(resolver, this.getSource());
|
||||
} else if (!this._decEndpoint.name) {
|
||||
return path.basename(this.getSource());
|
||||
} else {
|
||||
return this._decEndpoint.name;
|
||||
}
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getPkgMeta = function () {
|
||||
return this._pkgMeta;
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
// Plugin Resolver is always considered potentially cacheable
|
||||
// The "resolve" method decides whether to use cached or fetch new version.
|
||||
PluginResolver.prototype.isCacheable = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Not only it's always potentially cacheable, but also always potenially new.
|
||||
// The "resolve" handles logic of re-downloading target if needed.
|
||||
PluginResolver.prototype.hasNew = function (pkgMeta) {
|
||||
if (this.hasNewPromise) {
|
||||
return this.hasNewPromise;
|
||||
}
|
||||
|
||||
this._pkgMeta = pkgMeta;
|
||||
|
||||
return this.hasNewPromise = this.resolve().then(function (result) {
|
||||
return result !== undefined;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype.resolve = function () {
|
||||
if (this.resolvePromise) {
|
||||
return this.resolvePromise;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
|
||||
return this.resolvePromise = Q.fcall(function() {
|
||||
var target = that.getTarget();
|
||||
|
||||
// It means that we can accept ranges as targets
|
||||
if(that.constructor.isTargetable()) {
|
||||
that._release = target;
|
||||
|
||||
if (semver.validRange(target)) {
|
||||
return Q.fcall(resolver.releases.bind(resolver), that.getSource())
|
||||
.then(function (result) {
|
||||
if (!result) {
|
||||
throw createError('Resolver did not provide releases of package.');
|
||||
}
|
||||
|
||||
var releases = this._releases = result;
|
||||
|
||||
var versions = releases.filter(function (target) {
|
||||
return semver.clean(target.version);
|
||||
});
|
||||
|
||||
var maxRelease = maxSatisfyingVersion(versions, target);
|
||||
|
||||
if (maxRelease) {
|
||||
that._version = maxRelease.version;
|
||||
that._release = that._decEndpoint.target = maxRelease.target;
|
||||
} else {
|
||||
throw createError('No version found that was able to satisfy ' + target, 'ENORESTARGET', {
|
||||
details: !versions.length ?
|
||||
'No versions found in ' + that.getSource() :
|
||||
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (semver.validRange(target) && target !== '*') {
|
||||
return Q.reject(createError('Resolver does not accept version ranges (' + target + ')'));
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
|
||||
// We pass old _resolution (if hasNew has been called before contents).
|
||||
// So resolver can decide wheter use cached version of contents new one.
|
||||
if (typeof resolver.fetch !== 'function') {
|
||||
throw createError('Resolver does not implement the "fetch" method.');
|
||||
}
|
||||
|
||||
var cached = {};
|
||||
|
||||
if (that._releases) {
|
||||
cached.releases = that._releases;
|
||||
}
|
||||
|
||||
if (that._pkgMeta) {
|
||||
cached.endpoint = {
|
||||
name: that._pkgMeta.name,
|
||||
source: that._pkgMeta._source,
|
||||
target: that._pkgMeta._target
|
||||
};
|
||||
|
||||
cached.release = that._pkgMeta._release;
|
||||
|
||||
cached.version = that._pkgMeta.version;
|
||||
|
||||
cached.resolution = that._pkgMeta._resolution || {};
|
||||
}
|
||||
|
||||
return Q.fcall(resolver.fetch.bind(resolver), that.getEndpoint(), cached);
|
||||
})
|
||||
.then(function (result) {
|
||||
// Empty result means to re-use existing resolution
|
||||
if (!result) {
|
||||
return;
|
||||
} else {
|
||||
if (!result.tempPath) {
|
||||
throw createError('Resolver did not provide path to extracted contents of package.');
|
||||
}
|
||||
|
||||
that._tempDir = result.tempPath;
|
||||
|
||||
return that._readJson(that._tempDir).then(function (meta) {
|
||||
return that._applyPkgMeta(meta, result)
|
||||
.then(that._savePkgMeta.bind(that, meta, result))
|
||||
.then(function () {
|
||||
return that._tempDir;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype._readJson = function (dir) {
|
||||
var that = this;
|
||||
|
||||
return readJson(dir, {
|
||||
assume: { name: that.getName() }
|
||||
})
|
||||
.spread(function (json, deprecated) {
|
||||
if (deprecated) {
|
||||
bower.logger.warn('deprecated', 'Package ' + that.getName() + ' is using the deprecated ' + deprecated);
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype._applyPkgMeta = function (meta, result) {
|
||||
// Check if name defined in the json is different
|
||||
// If so and if the name was "guessed", assume the json name
|
||||
if (meta.name !== this._name) {
|
||||
this._name = meta.name;
|
||||
}
|
||||
|
||||
// Handle ignore property, deleting all files from the temporary directory
|
||||
// If no ignores were specified, simply resolve
|
||||
if (result.removeIgnores === false || !meta.ignore || !meta.ignore.length) {
|
||||
return Q.resolve(meta);
|
||||
}
|
||||
|
||||
// Otherwise remove them from the temp dir
|
||||
return removeIgnores(this._tempDir, meta).then(function () {
|
||||
return meta;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype._savePkgMeta = function (meta, result) {
|
||||
var that = this;
|
||||
|
||||
meta._source = that.getSource();
|
||||
meta._target = that.getTarget();
|
||||
|
||||
if (result.resolution) {
|
||||
meta._resolution = result.resolution;
|
||||
}
|
||||
|
||||
if (that._release) {
|
||||
meta._release = that._release;
|
||||
}
|
||||
|
||||
if (that._version) {
|
||||
meta.version = that._version;
|
||||
} else {
|
||||
delete meta.version;
|
||||
}
|
||||
|
||||
// Stringify contents
|
||||
var contents = JSON.stringify(meta, null, 2);
|
||||
|
||||
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
|
||||
.then(function () {
|
||||
return that._pkgMeta = meta;
|
||||
});
|
||||
};
|
||||
|
||||
// It is used only by "bower info". It returns all semver versions.
|
||||
PluginResolver.versions = function (source) {
|
||||
return Q.fcall(resolver.releases.bind(resolver), source).then(function (result) {
|
||||
if (!result) {
|
||||
throw createError('Resolver did not provide releases of package.');
|
||||
}
|
||||
|
||||
var releases = this._releases = result;
|
||||
|
||||
var versions = releases.map(function (version) {
|
||||
return semver.clean(version.version);
|
||||
});
|
||||
|
||||
versions = versions.filter(function (version) {
|
||||
return version;
|
||||
});
|
||||
|
||||
versions.sort(function (a, b) {
|
||||
return semver.rcompare(a, b);
|
||||
});
|
||||
|
||||
return versions;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.isTargetable = function() {
|
||||
// If resolver doesn't define versions function, it's not targetable..
|
||||
return typeof resolver.releases === 'function';
|
||||
};
|
||||
|
||||
PluginResolver.clearRuntimeCache = function () {
|
||||
resolver = resolverFactory(bower);
|
||||
};
|
||||
|
||||
PluginResolver.match = function (source) {
|
||||
if (typeof resolver.match !== 'function') {
|
||||
throw createError('Resolver is missing "match" method.', 'ERESOLVERAPI');
|
||||
}
|
||||
|
||||
var match = resolver.match.bind(resolver);
|
||||
|
||||
return Q.fcall(match, source).then(function (result) {
|
||||
if (typeof result !== 'boolean') {
|
||||
throw createError('Resolver\'s "match" method should return a boolean', 'ERESOLVERAPI');
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.locate = function (source) {
|
||||
if (typeof resolver.locate !== 'function') {
|
||||
return source;
|
||||
}
|
||||
|
||||
return Q.fcall(resolver.locate.bind(resolver), source).then(function (result) {
|
||||
if (typeof result !== 'string') {
|
||||
throw createError('Resolver\'s "locate" method should return a string', 'ERESOLVERAPI');
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
return PluginResolver;
|
||||
}
|
||||
|
||||
|
||||
module.exports = pluginResolverFactory;
|
||||
|
||||
@@ -93,4 +93,4 @@ module.exports = {
|
||||
postinstall: mout.function.partial(hook, 'postinstall', true),
|
||||
//only exposed for test
|
||||
_orderByDependencies: orderByDependencies
|
||||
};
|
||||
};
|
||||
|
||||
31
package.json
31
package.json
@@ -1,14 +1,9 @@
|
||||
{
|
||||
"name": "bower",
|
||||
"version": "1.3.12",
|
||||
"version": "1.5.2",
|
||||
"description": "The browser package manager",
|
||||
"author": "Twitter",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/bower/bower/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": "bower/bower",
|
||||
"main": "lib",
|
||||
"homepage": "http://bower.io",
|
||||
@@ -18,17 +13,20 @@
|
||||
"dependencies": {
|
||||
"abbrev": "^1.0.5",
|
||||
"archy": "1.0.0",
|
||||
"bower-config": "^0.5.2",
|
||||
"bower-config": "^0.6.1",
|
||||
"bower-endpoint-parser": "^0.2.2",
|
||||
"bower-json": "^0.4.0",
|
||||
"bower-logger": "^0.2.2",
|
||||
"bower-registry-client": "^0.2.1",
|
||||
"bower-registry-client": "^0.3.0",
|
||||
"cardinal": "0.4.4",
|
||||
"chalk": "^1.0.0",
|
||||
"chmodr": "0.1.0",
|
||||
"configstore": "^0.3.2",
|
||||
"decompress-zip": "^0.1.0",
|
||||
"deep-sort-object": "~0.1.1",
|
||||
"fstream": "^1.0.3",
|
||||
"fstream-ignore": "^1.0.2",
|
||||
"github": "^0.2.3",
|
||||
"glob": "^4.3.2",
|
||||
"graceful-fs": "^3.0.5",
|
||||
"handlebars": "^2.0.0",
|
||||
@@ -45,7 +43,7 @@
|
||||
"p-throttler": "0.1.1",
|
||||
"promptly": "0.2.0",
|
||||
"q": "^1.1.2",
|
||||
"request": "^2.51.0",
|
||||
"request": "2.53.0",
|
||||
"request-progress": "0.3.1",
|
||||
"retry": "0.6.1",
|
||||
"rimraf": "^2.2.8",
|
||||
@@ -74,13 +72,16 @@
|
||||
"multiline": "^1.0.2",
|
||||
"nock": "^0.56.0",
|
||||
"node-uuid": "^1.4.2",
|
||||
"proxyquire": "^1.3.0"
|
||||
"proxyquire": "^1.3.0",
|
||||
"spawn-sync": "^1.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"bin": {
|
||||
"bower": "bin/bower"
|
||||
},
|
||||
"preferGlobal": true
|
||||
"bin": "bin/bower",
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"templates"
|
||||
]
|
||||
}
|
||||
|
||||
19
templates/json/help-login.json
Normal file
19
templates/json/help-login.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"command": "login",
|
||||
"description": "Authenticate with GitHub and store credentials to be used later.",
|
||||
"usage": [
|
||||
"login [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
},
|
||||
{
|
||||
"shorthand": "-t",
|
||||
"flag": "--token",
|
||||
"description": "Pass an existing GitHub auth token rather than prompting for username and password."
|
||||
}
|
||||
]
|
||||
}
|
||||
14
templates/json/help-unregister.json
Normal file
14
templates/json/help-unregister.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "unregister",
|
||||
"description": "Unregisters a package.",
|
||||
"usage": [
|
||||
"unregister <name> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,12 +11,14 @@
|
||||
"install": "Install a package locally",
|
||||
"link": "Symlink a package folder",
|
||||
"list": "List local packages - and possible updates",
|
||||
"login": "Authenticate with GitHub and store credentials",
|
||||
"lookup": "Look up a package URL by name",
|
||||
"prune": "Removes local extraneous packages",
|
||||
"register": "Register a package",
|
||||
"search": "Search for a package by name",
|
||||
"update": "Update a local package",
|
||||
"uninstall": "Remove a local package",
|
||||
"unregister": "Remove a package from the registry",
|
||||
"version": "Bump a package version"
|
||||
},
|
||||
"options": [
|
||||
@@ -60,6 +62,7 @@
|
||||
"description": "Allows running commands as root"
|
||||
},
|
||||
{
|
||||
"shorthand": "-v",
|
||||
"flag": "--version",
|
||||
"description": "Output Bower version"
|
||||
},
|
||||
|
||||
@@ -36,7 +36,8 @@ describe('bower info', function () {
|
||||
});
|
||||
|
||||
it('shows info about given package', function () {
|
||||
return package.prepareGit({}).then(function() {
|
||||
package.prepareGit({});
|
||||
|
||||
return helpers.run(info, [package.path]).spread(function(results) {
|
||||
expect(results).to.eql({
|
||||
'latest': meta2,
|
||||
@@ -47,6 +48,5 @@ describe('bower info', function () {
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -245,7 +245,7 @@ describe('bower install', function () {
|
||||
});
|
||||
|
||||
it('works for git repositories', function () {
|
||||
return gitPackage.prepareGit({
|
||||
gitPackage.prepareGit({
|
||||
'1.0.0': {
|
||||
'bower.json': {
|
||||
name: 'package'
|
||||
@@ -258,19 +258,19 @@ describe('bower install', function () {
|
||||
},
|
||||
'version.txt': '1.0.1'
|
||||
}
|
||||
}).then(function() {
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return helpers.run(install).then(function() {
|
||||
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
|
||||
});
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install).then(function() {
|
||||
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ describe('bower list', function () {
|
||||
});
|
||||
|
||||
it('lists 1 dependency when 1 git package installed', function () {
|
||||
return gitPackage.prepareGit({
|
||||
gitPackage.prepareGit({
|
||||
'1.0.0': {
|
||||
'bower.json': {
|
||||
name: 'package',
|
||||
@@ -180,41 +180,42 @@ describe('bower list', function () {
|
||||
},
|
||||
'version.txt': '1.0.1'
|
||||
}
|
||||
}).then(function() {
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
});
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
});
|
||||
return install().then(function() {
|
||||
return list().spread(function(results) {
|
||||
expect(results).to.be.an(Object);
|
||||
expect(results.canonicalDir).to.equal(tempDir.path);
|
||||
expect(results.pkgMeta.dependencies).to.eql({
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
});
|
||||
expect(results.pkgMeta.devDependencies).to.eql({});
|
||||
expect(results.dependencies.package).to.be.an(Object);
|
||||
expect(results.dependencies.package.pkgMeta).to.be.an(Object);
|
||||
expect(results.dependencies.package.pkgMeta.main).to.equal('test.txt');
|
||||
expect(results.dependencies.package.canonicalDir).to.equal(
|
||||
path.join(tempDir.path, 'bower_components/package')
|
||||
);
|
||||
expect(results.dependencies.package.dependencies).to.eql({});
|
||||
expect(results.dependencies.package.nrDependants).to.equal(1);
|
||||
expect(results.dependencies.package.versions).to.eql(['1.0.1', '1.0.0']);
|
||||
expect(results.nrDependants).to.equal(0);
|
||||
expect(results.versions).to.eql([]);
|
||||
}
|
||||
});
|
||||
|
||||
return install().then(function() {
|
||||
return list().spread(function(results) {
|
||||
expect(results).to.be.an(Object);
|
||||
expect(results.canonicalDir).to.equal(tempDir.path);
|
||||
expect(results.pkgMeta.dependencies).to.eql({
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
});
|
||||
expect(results.pkgMeta.devDependencies).to.eql({});
|
||||
expect(results.dependencies.package).to.be.an(Object);
|
||||
expect(results.dependencies.package.pkgMeta).to.be.an(Object);
|
||||
expect(results.dependencies.package.pkgMeta.main).to.equal('test.txt');
|
||||
expect(results.dependencies.package.canonicalDir).to.equal(
|
||||
path.join(tempDir.path, 'bower_components/package')
|
||||
);
|
||||
expect(results.dependencies.package.dependencies).to.eql({});
|
||||
expect(results.dependencies.package.nrDependants).to.equal(1);
|
||||
expect(results.dependencies.package.versions).to.eql(['1.0.1', '1.0.0']);
|
||||
expect(results.nrDependants).to.equal(0);
|
||||
expect(results.versions).to.eql([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists 1 dependency with relative paths when 1 git package installed', function () {
|
||||
return gitPackage.prepareGit({
|
||||
gitPackage.prepareGit({
|
||||
'1.0.0': {
|
||||
'bower.json': {
|
||||
name: 'package',
|
||||
@@ -229,25 +230,26 @@ describe('bower list', function () {
|
||||
},
|
||||
'version.txt': '1.0.1'
|
||||
}
|
||||
}).then(function() {
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
});
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
});
|
||||
return install().then(function() {
|
||||
return list({relative: true}).spread(function(results) {
|
||||
expect(results.canonicalDir).to.equal(tempDir.path);
|
||||
expect(results.pkgMeta.dependencies).to.eql({
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
});
|
||||
expect(results.dependencies.package.canonicalDir).to.equal(
|
||||
path.normalize('bower_components/package')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return install().then(function() {
|
||||
return list({relative: true}).spread(function(results) {
|
||||
expect(results.canonicalDir).to.equal(tempDir.path);
|
||||
expect(results.pkgMeta.dependencies).to.eql({
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
});
|
||||
expect(results.dependencies.package.canonicalDir).to.equal(
|
||||
path.normalize('bower_components/package')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
187
test/commands/login.js
Normal file
187
test/commands/login.js
Normal file
@@ -0,0 +1,187 @@
|
||||
var expect = require('expect.js');
|
||||
var helpers = require('../helpers');
|
||||
|
||||
var fakeGitHub = function (authenticate) {
|
||||
function FakeGitHub() { }
|
||||
|
||||
var _creds;
|
||||
|
||||
FakeGitHub.prototype.authenticate = function (creds) {
|
||||
_creds = creds;
|
||||
};
|
||||
|
||||
FakeGitHub.prototype.authorization = {
|
||||
create: function (options, cb) {
|
||||
if (_creds.password === 'validpassword') {
|
||||
cb(null, { token: 'faketoken' });
|
||||
} else if (_creds.password === 'withtwofactor') {
|
||||
if (options.headers && options.headers['X-GitHub-OTP'] === '123456') {
|
||||
cb(null, { token: 'faketwoauthtoken' });
|
||||
} else {
|
||||
cb({ code: 401, message: '{ "message": "Must specify two-factor authentication OTP code." }' });
|
||||
}
|
||||
} else {
|
||||
cb({ code: 401, message: 'Bad credentials' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return FakeGitHub;
|
||||
};
|
||||
|
||||
var fakeConfigstore = function (set, get) {
|
||||
function FakeConfigstore() { }
|
||||
|
||||
FakeConfigstore.prototype.set = set;
|
||||
FakeConfigstore.prototype.get = get;
|
||||
|
||||
return FakeConfigstore;
|
||||
};
|
||||
|
||||
var login = helpers.command('login');
|
||||
|
||||
var loginFactory = function (options) {
|
||||
return helpers.command('login', {
|
||||
'github': fakeGitHub(),
|
||||
'configstore': fakeConfigstore(
|
||||
options.set || function () { return true; },
|
||||
options.get || function () { return true; }
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
describe('bower login', function () {
|
||||
|
||||
it('correctly reads arguments', function() {
|
||||
expect(login.readOptions(['--token', 'foobar']))
|
||||
.to.eql([{ token: 'foobar' }]);
|
||||
});
|
||||
|
||||
it('fails if run in non-interactive shell without token passed', function () {
|
||||
return helpers.run(login, []).fail(function(reason) {
|
||||
expect(reason.message).to.be('Login requires an interactive shell');
|
||||
expect(reason.code).to.be('ENOINT');
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds if run in non-interactive shell with token passed', function () {
|
||||
return helpers.run(login, [{ token: 'foobar' }]);
|
||||
});
|
||||
|
||||
it('succeeds if provided password is valid', function () {
|
||||
var login = loginFactory({});
|
||||
|
||||
var logger = login({}, { interactive: true });
|
||||
|
||||
logger.once('prompt', function (prompt, answer) {
|
||||
answer({
|
||||
username: 'user',
|
||||
password: 'validpassword'
|
||||
});
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'end')
|
||||
.spread(function(options) {
|
||||
expect(options.token).to.be('faketoken');
|
||||
});
|
||||
});
|
||||
|
||||
it('supports two-factor authorization', function () {
|
||||
var login = loginFactory({});
|
||||
|
||||
var logger = login({}, { interactive: true });
|
||||
|
||||
logger.once('prompt', function (prompt, answer) {
|
||||
logger.once('prompt', function (prompt, answer) {
|
||||
answer({
|
||||
otpcode: '123456'
|
||||
});
|
||||
});
|
||||
|
||||
answer({
|
||||
username: 'user',
|
||||
password: 'withtwofactor'
|
||||
});
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'end')
|
||||
.spread(function(options) {
|
||||
expect(options.token).to.be('faketwoauthtoken');
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if provided password is invalid', function () {
|
||||
var login = loginFactory({});
|
||||
|
||||
var logger = login({}, { interactive: true });
|
||||
|
||||
logger.once('prompt', function (prompt, answer) {
|
||||
answer({
|
||||
username: 'user',
|
||||
password: 'invalidpassword'
|
||||
});
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'error').spread(function (error) {
|
||||
expect(error.code).to.be('EAUTH');
|
||||
expect(error.message).to.be('Authorization failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('uses username stored in config as default username', function () {
|
||||
var login = loginFactory({
|
||||
get: function (key) {
|
||||
if (key === 'username') {
|
||||
return 'savedusername';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var logger = login({}, { interactive: true });
|
||||
|
||||
return helpers.expectEvent(logger, 'prompt')
|
||||
.spread(function (prompt, answer) {
|
||||
expect(prompt[0].default).to.be('savedusername');
|
||||
});
|
||||
});
|
||||
|
||||
it('saves username in config', function (done) {
|
||||
var login = loginFactory({
|
||||
set: function (key, value) {
|
||||
if(key === 'username') {
|
||||
expect(value).to.be('user');
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var logger = login({}, { interactive: true });
|
||||
|
||||
logger.once('prompt', function (prompt, answer) {
|
||||
answer({
|
||||
username: 'user',
|
||||
password: 'validpassword'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('saves received token in accessToken config', function (done) {
|
||||
var login = loginFactory({
|
||||
set: function (key, value) {
|
||||
if(key === 'accessToken') {
|
||||
expect(value).to.be('faketoken');
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var logger = login({}, { interactive: true });
|
||||
|
||||
logger.once('prompt', function (prompt, answer) {
|
||||
answer({
|
||||
username: 'user',
|
||||
password: 'validpassword'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
79
test/commands/unregister.js
Normal file
79
test/commands/unregister.js
Normal file
@@ -0,0 +1,79 @@
|
||||
var expect = require('expect.js');
|
||||
var helpers = require('../helpers');
|
||||
|
||||
var fakeRepositoryFactory = function () {
|
||||
function FakeRepository() { }
|
||||
|
||||
FakeRepository.prototype.getRegistryClient = function() {
|
||||
return {
|
||||
unregister: function (name, cb) {
|
||||
cb(null, { name: name });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return FakeRepository;
|
||||
};
|
||||
|
||||
var unregister = helpers.command('unregister');
|
||||
|
||||
var unregisterFactory = function () {
|
||||
return helpers.command('unregister', {
|
||||
'../core/PackageRepository': fakeRepositoryFactory()
|
||||
});
|
||||
};
|
||||
|
||||
describe('bower unregister', function () {
|
||||
|
||||
it('correctly reads arguments', function() {
|
||||
expect(unregister.readOptions(['jquery']))
|
||||
.to.eql(['jquery']);
|
||||
});
|
||||
|
||||
it('errors if name is not provided', function () {
|
||||
return helpers.run(unregister).fail(function(reason) {
|
||||
expect(reason.message).to.be('Usage: bower unregister <name> <url>');
|
||||
expect(reason.code).to.be('EINVFORMAT');
|
||||
});
|
||||
});
|
||||
|
||||
it('should call registry client with name', function () {
|
||||
var unregister = unregisterFactory();
|
||||
|
||||
return helpers.run(unregister, ['some-name'])
|
||||
.spread(function(result) {
|
||||
expect(result).to.eql({
|
||||
// Result from register action on stub
|
||||
name: 'some-name'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm in interactive mode', function () {
|
||||
var register = unregisterFactory();
|
||||
|
||||
var promise = helpers.run(register,
|
||||
['some-name', {
|
||||
interactive: true,
|
||||
registry: { register: 'http://localhost' }
|
||||
}]
|
||||
);
|
||||
|
||||
return helpers.expectEvent(promise.logger, 'confirm')
|
||||
.spread(function(e) {
|
||||
expect(e.type).to.be('confirm');
|
||||
expect(e.message).to.be('You are about to remove component "some-name" from the bower registry (http://localhost). It is generally considered bad behavior to remove versions of a library that others are depending on. Are you really sure?');
|
||||
expect(e.default).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip confirming when forcing', function () {
|
||||
var register = unregisterFactory();
|
||||
|
||||
return helpers.run(register,
|
||||
['some-name',
|
||||
{ interactive: true, force: true }
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ describe('bower update', function () {
|
||||
var tempDir = new helpers.TempDir();
|
||||
|
||||
var gitPackage = new helpers.TempDir();
|
||||
|
||||
gitPackage.prepareGit({
|
||||
'1.0.0': {
|
||||
'bower.json': {
|
||||
|
||||
@@ -53,39 +53,37 @@ describe('bower list', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the new version', function() {
|
||||
package.prepare();
|
||||
|
||||
return helpers.run(version, ['major', {}, { cwd: package.path }]).then(function(results) {
|
||||
expect(results[0]).to.be('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('bumps patch version, create commit, and tag', function() {
|
||||
return gitPackage.prepareGit().then(function() {
|
||||
gitPackage.prepareGit();
|
||||
|
||||
return helpers.run(version, ['patch', {}, { cwd: gitPackage.path }]).then(function() {
|
||||
expect(gitPackage.readJson('bower.json').version).to.be('0.0.1');
|
||||
|
||||
return gitPackage.git('tag').spread(function(result) {
|
||||
expect(result).to.be('v0.0.0\nv0.0.1\n');
|
||||
|
||||
return gitPackage.git('log', '--pretty=format:%s', '-n1').spread(function(result) {
|
||||
expect(result).to.be('v0.0.1');
|
||||
});
|
||||
});
|
||||
});
|
||||
return helpers.run(version, ['patch', {}, { cwd: gitPackage.path }]).then(function() {
|
||||
expect(gitPackage.readJson('bower.json').version).to.be('0.0.1');
|
||||
|
||||
var tags = gitPackage.git('tag');
|
||||
expect(tags).to.be('v0.0.0\nv0.0.1\n');
|
||||
var message = gitPackage.git('log', '--pretty=format:%s', '-n1');
|
||||
expect(message).to.be('v0.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
it('bumps with custom commit message', function() {
|
||||
return gitPackage.prepareGit().then(function() {
|
||||
gitPackage.prepareGit();
|
||||
|
||||
return helpers.run(version, ['patch', { message: 'Bumping %s, because what'}, { cwd: gitPackage.path }]).then(function() {
|
||||
expect(gitPackage.readJson('bower.json').version).to.be('0.0.1');
|
||||
|
||||
return gitPackage.git('tag').spread(function(result) {
|
||||
expect(result).to.be('v0.0.0\nv0.0.1\n');
|
||||
|
||||
return gitPackage.git('log', '--pretty=format:%s', '-n1').spread(function(result) {
|
||||
expect(result).to.be('Bumping 0.0.1, because what');
|
||||
});
|
||||
});
|
||||
});
|
||||
return helpers.run(version, ['patch', { message: 'Bumping %s, because what'}, { cwd: gitPackage.path }]).then(function() {
|
||||
expect(gitPackage.readJson('bower.json').version).to.be('0.0.1');
|
||||
|
||||
var tags = gitPackage.git('tag');
|
||||
expect(tags).to.be('v0.0.0\nv0.0.1\n');
|
||||
var message = gitPackage.git('log', '--pretty=format:%s', '-n1');
|
||||
expect(message).to.be('Bumping 0.0.1, because what');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,10 @@ describe('PackageRepository', function () {
|
||||
});
|
||||
|
||||
// Mock the resolver factory to always return a resolver for the test package
|
||||
function resolverFactory(decEndpoint, _config, _logger, _registryClient) {
|
||||
function resolverFactory(decEndpoint, options, _registryClient) {
|
||||
var _config = options.config;
|
||||
var _logger = options.logger;
|
||||
|
||||
expect(_config).to.eql(config);
|
||||
expect(_logger).to.be.an(Logger);
|
||||
expect(_registryClient).to.be.an(RegistryClient);
|
||||
@@ -67,7 +70,9 @@ describe('PackageRepository', function () {
|
||||
return Q.resolve(resolver);
|
||||
}
|
||||
resolverFactory.getConstructor = function () {
|
||||
return Q.resolve([resolvers.GitRemote, helpers.localSource(testPackage), false]);
|
||||
return Q.resolve([resolvers.GitRemote, {
|
||||
source: helpers.localSource(testPackage)
|
||||
}]);
|
||||
};
|
||||
resolverFactory.clearRuntimeCache = function () {
|
||||
resolverFactoryClearHook();
|
||||
@@ -239,8 +244,7 @@ describe('PackageRepository', function () {
|
||||
resolverFactoryHook = function (resolver) {
|
||||
var originalHasNew = resolver.hasNew;
|
||||
|
||||
resolver.hasNew = function (canonicalDir, pkgMeta) {
|
||||
expect(canonicalDir).to.equal(tempPackage);
|
||||
resolver.hasNew = function (pkgMeta) {
|
||||
expect(pkgMeta).to.eql(json);
|
||||
called = true;
|
||||
return originalHasNew.apply(this, arguments);
|
||||
@@ -283,8 +287,7 @@ describe('PackageRepository', function () {
|
||||
return originalResolve.apply(this, arguments);
|
||||
};
|
||||
|
||||
resolver.hasNew = function (canonicalDir, pkgMeta) {
|
||||
expect(canonicalDir).to.equal(tempPackage);
|
||||
resolver.hasNew = function (pkgMeta) {
|
||||
expect(pkgMeta).to.eql(json);
|
||||
called.push('hasNew');
|
||||
return Q.resolve(true);
|
||||
@@ -327,8 +330,7 @@ describe('PackageRepository', function () {
|
||||
return originalResolve.apply(this, arguments);
|
||||
};
|
||||
|
||||
resolver.hasNew = function (canonicalDir, pkgMeta) {
|
||||
expect(canonicalDir).to.equal(tempPackage);
|
||||
resolver.hasNew = function (pkgMeta) {
|
||||
expect(pkgMeta).to.eql(json);
|
||||
called.push('hasNew');
|
||||
return Q.resolve(false);
|
||||
@@ -364,8 +366,7 @@ describe('PackageRepository', function () {
|
||||
resolverFactoryHook = function (resolver) {
|
||||
var originalResolve = resolver.resolve;
|
||||
|
||||
resolver.hasNew = function (canonicalDir, pkgMeta) {
|
||||
expect(canonicalDir).to.equal(tempPackage);
|
||||
resolver.hasNew = function (pkgMeta) {
|
||||
expect(pkgMeta).to.eql(json);
|
||||
called.push('resolve');
|
||||
return originalResolve.apply(this, arguments);
|
||||
|
||||
@@ -34,8 +34,8 @@ describe('resolverFactory', function () {
|
||||
rimraf('pure', next);
|
||||
});
|
||||
|
||||
function callFactory(decEndpoint, config) {
|
||||
return resolverFactory(decEndpoint, defaultConfig(config), logger, registryClient);
|
||||
function callFactory(decEndpoint, config, skipRegistry) {
|
||||
return resolverFactory(decEndpoint, { config: defaultConfig(config), logger: logger }, skipRegistry ? undefined : registryClient);
|
||||
}
|
||||
|
||||
it('should recognize git remote endpoints correctly', function (next) {
|
||||
@@ -526,6 +526,41 @@ describe('resolverFactory', function () {
|
||||
.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 'pure' file at the root to prevent regressions of #666
|
||||
fs.writeFileSync('pure', 'foo');
|
||||
@@ -573,16 +608,15 @@ describe('resolverFactory', function () {
|
||||
.done();
|
||||
});
|
||||
|
||||
it('should set registry to true on the decomposed endpoint if fetched from the registry', function (next) {
|
||||
var decEndpoint = { source: 'pure' };
|
||||
// it('should set registry to true on the decomposed endpoint if fetched from the registry', function (next) {
|
||||
// var decEndpoint = { source: 'pure' };
|
||||
|
||||
callFactory(decEndpoint)
|
||||
.then(function () {
|
||||
expect(decEndpoint.registry).to.be(true);
|
||||
next();
|
||||
})
|
||||
.done();
|
||||
});
|
||||
// return callFactory(decEndpoint, { resolvers: ['sample-custom-resolver'] })
|
||||
// .then(function () {
|
||||
// next();
|
||||
// })
|
||||
// .done();
|
||||
// });
|
||||
|
||||
it('should use the configured shorthand resolver', function (next) {
|
||||
callFactory({ source: 'bower/bower' })
|
||||
@@ -617,7 +651,7 @@ describe('resolverFactory', function () {
|
||||
|
||||
|
||||
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)
|
||||
callFactory({ source: 'some-package-that-will-never-exist' }, undefined, true)
|
||||
.then(function () {
|
||||
throw new Error('Should have failed');
|
||||
}, function (err) {
|
||||
|
||||
@@ -82,13 +82,11 @@ describe('FsResolver', function () {
|
||||
it('should resolve always to true (for now..)', function (next) {
|
||||
var resolver = create(testPackage);
|
||||
|
||||
tempSource = path.resolve(__dirname, '../../tmp/tmp');
|
||||
mkdirp.sync(tempSource);
|
||||
fs.writeFileSync(path.join(tempSource, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'test'
|
||||
}));
|
||||
};
|
||||
|
||||
resolver.hasNew(tempSource)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
|
||||
@@ -70,6 +70,8 @@ describe('GitHub', function () {
|
||||
});
|
||||
|
||||
it('should retry using the GitRemoteResolver mechanism if download failed', function (next) {
|
||||
this.timeout(20000);
|
||||
|
||||
var resolver;
|
||||
var retried;
|
||||
|
||||
@@ -100,6 +102,8 @@ describe('GitHub', function () {
|
||||
});
|
||||
|
||||
it('should retry using the GitRemoteResolver mechanism if extraction failed', function (next) {
|
||||
this.timeout(20000);
|
||||
|
||||
var resolver;
|
||||
var retried;
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ var expect = require('expect.js');
|
||||
var path = require('path');
|
||||
var fs = require('graceful-fs');
|
||||
var Logger = require('bower-logger');
|
||||
var helpers = require('../../helpers');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var multiline = require('multiline').stripIndent;
|
||||
var GitRemoteResolver = require('../../../lib/core/resolvers/GitRemoteResolver');
|
||||
var defaultConfig = require('../../../lib/config');
|
||||
|
||||
@@ -106,6 +110,92 @@ describe('GitRemoteResolver', function () {
|
||||
.done();
|
||||
});
|
||||
|
||||
describe('shallow cloning', function () {
|
||||
var gitRemoteResolverFactory;
|
||||
|
||||
beforeEach(function () {
|
||||
gitRemoteResolverFactory = function (handler) {
|
||||
return helpers.require('lib/core/resolvers/GitRemoteResolver', {
|
||||
'../../util/cmd': handler
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
it('should add --depth=1 when shallow cloning is supported', function (next) {
|
||||
var testSource = 'http://foo/bar.git';
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(function (cmd, args) {
|
||||
// The first git call fetches the tags for the provided source
|
||||
if (mout.array.equals(args, ['ls-remote', '--tags', '--heads', testSource])) {
|
||||
// Return list of commits, including one tag.
|
||||
// The tag will be used for the clone call.
|
||||
return Q.all([multiline(function () {/*
|
||||
e4655d250f2a3f64ef2d712f25dafa60652bb93e refs/heads/some-branch
|
||||
0a7daf646d4fd743b6ef701d63bdbe20eee422de refs/tags/0.0.1
|
||||
*/
|
||||
})]);
|
||||
}
|
||||
else if (args[0] === 'clone') {
|
||||
// Verify parameters of the clone call.
|
||||
// In this case, the arguments need to contain "--depth 1".
|
||||
expect(args).to.eql(['clone', 'http://foo/bar.git', '-b', '0.0.1', '--progress', '.', '--depth', 1]);
|
||||
|
||||
// In this case, only the stderr content is evaluated. Everything's fine as long as it
|
||||
// does not contain any error description.
|
||||
return Q.all(['stdout', 'stderr']);
|
||||
}
|
||||
});
|
||||
|
||||
// Mock the call, return true for this test.
|
||||
MyGitRemoteResolver.prototype._supportsShallowCloning = function () {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig(), logger);
|
||||
|
||||
resolver.resolve().then(function () {
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add --depth=1 when shallow cloning is not supported', function (next) {
|
||||
var testSource = 'http://foo/bar.git';
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(function (cmd, args) {
|
||||
// The first git call fetches the tags for the provided source
|
||||
if (mout.array.equals(args, ['ls-remote', '--tags', '--heads', testSource])) {
|
||||
// Return list of commits, including one tag.
|
||||
// The tag will be used for the clone call.
|
||||
return Q.all([multiline(function () {/*
|
||||
e4655d250f2a3f64ef2d712f25dafa60652bb93e refs/heads/some-branch
|
||||
0a7daf646d4fd743b6ef701d63bdbe20eee422de refs/tags/0.0.1
|
||||
*/
|
||||
})]);
|
||||
}
|
||||
else if (args[0] === 'clone') {
|
||||
// Verify parameters of the clone call.
|
||||
// In this case, the arguments should not contain "--depth 1".
|
||||
expect(args).to.eql(['clone', 'http://foo/bar.git', '-b', '0.0.1', '--progress', '.']);
|
||||
|
||||
// In this case, only the stderr content is evaluated. Everything's fine as long as it
|
||||
// does not contain any error description.
|
||||
return Q.all(['stdout', 'stderr']);
|
||||
}
|
||||
});
|
||||
|
||||
// Mock the call, return false for this test.
|
||||
MyGitRemoteResolver.prototype._supportsShallowCloning = function () {
|
||||
return Q.resolve(false);
|
||||
};
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig(), logger);
|
||||
|
||||
resolver.resolve().then(function () {
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should handle gracefully servers that do not support --depth=1');
|
||||
it.skip('should report progress when it takes too long to clone');
|
||||
});
|
||||
@@ -162,4 +252,251 @@ describe('GitRemoteResolver', function () {
|
||||
.done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_supportsShallowCloning', function () {
|
||||
var gitRemoteResolverFactory;
|
||||
|
||||
beforeEach(function () {
|
||||
gitRemoteResolverFactory = function (handler) {
|
||||
return helpers.require('lib/core/resolvers/GitRemoteResolver', {
|
||||
'../../util/cmd': handler
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
function createCmdHandlerFn (testSource, stderr) {
|
||||
return function (cmd, args, options) {
|
||||
expect(cmd).to.be('git');
|
||||
expect(args).to.eql([ 'ls-remote', '--heads', testSource ]);
|
||||
expect(options.env.GIT_CURL_VERBOSE).to.be(2);
|
||||
|
||||
return Q.all(['stdout', stderr]);
|
||||
};
|
||||
}
|
||||
|
||||
it('should call ls-remote when using http protocol', function (next) {
|
||||
var testSource = 'http://foo/bar.git';
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
createCmdHandlerFn(testSource, multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: none
|
||||
1234: 5678
|
||||
*/}))
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(false);
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call ls-remote when using https protocol', function (next) {
|
||||
var testSource = 'https://foo/bar.git';
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
createCmdHandlerFn(testSource, multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: none
|
||||
1234: 5678
|
||||
*/}))
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(false);
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate to false when the URL can not be parsed', function (next) {
|
||||
var testSource = 'grmblfjx///:::.git';
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
createCmdHandlerFn(testSource, multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: none
|
||||
1234: 5678
|
||||
*/}))
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig(), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(false);
|
||||
|
||||
next();
|
||||
}, function (err) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate to true when the smart content type is returned', function (next) {
|
||||
var testSource = 'https://foo/bar.git';
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
createCmdHandlerFn(testSource, multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: application/x-git-upload-pack-advertisement
|
||||
1234: 5678
|
||||
*/}))
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
it('should cache hosts that support shallow cloning', function (next) {
|
||||
var testSource = 'https://foo/bar.git';
|
||||
|
||||
var counter = 0;
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
function (cmd, args, options) {
|
||||
counter++;
|
||||
|
||||
if (counter === 1) {
|
||||
expect(cmd).to.be('git');
|
||||
expect(args).to.eql([ 'ls-remote', '--heads', testSource ]);
|
||||
expect(options.env.GIT_CURL_VERBOSE).to.be(2);
|
||||
|
||||
return Q.all(['stdout', multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: application/x-git-upload-pack-advertisement
|
||||
1234: 5678
|
||||
*/
|
||||
})]);
|
||||
}
|
||||
else {
|
||||
return Q.reject(new Error('More calls than expected'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
var resolver2 = new MyGitRemoteResolver({ source: testSource }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver2._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
next();
|
||||
}, function(err) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should cache hosts that support shallow cloning across multiple repos', function (next) {
|
||||
var testSource1 = 'https://foo/bar.git';
|
||||
var testSource2 = 'https://foo/barbaz.git';
|
||||
|
||||
var counter = 0;
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
function (cmd, args, options) {
|
||||
counter++;
|
||||
|
||||
if (counter === 1) {
|
||||
expect(cmd).to.be('git');
|
||||
expect(args).to.eql([ 'ls-remote', '--heads', testSource1 ]);
|
||||
expect(options.env.GIT_CURL_VERBOSE).to.be(2);
|
||||
|
||||
return Q.all(['stdout', multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: application/x-git-upload-pack-advertisement
|
||||
1234: 5678
|
||||
*/
|
||||
})]);
|
||||
}
|
||||
else {
|
||||
return Q.reject(new Error('More calls than expected'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource1 }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
var resolver2 = new MyGitRemoteResolver({ source: testSource2 }, defaultConfig({ shallowCloneHosts: ['foo'] }), logger);
|
||||
|
||||
resolver2._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
next();
|
||||
}, function(err) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should run separate checks for separate hosts ', function (next) {
|
||||
var testSource1 = 'https://foo/bar.git';
|
||||
var testSource2 = 'https://foo.bar.baz/barbaz.git';
|
||||
|
||||
var counter = 0;
|
||||
|
||||
var MyGitRemoteResolver = gitRemoteResolverFactory(
|
||||
function (cmd, args, options) {
|
||||
counter++;
|
||||
|
||||
if (counter === 1) {
|
||||
expect(cmd).to.be('git');
|
||||
expect(args).to.eql([ 'ls-remote', '--heads', testSource1 ]);
|
||||
expect(options.env.GIT_CURL_VERBOSE).to.be(2);
|
||||
|
||||
return Q.all(['stdout', multiline(function () {/*
|
||||
foo: bar
|
||||
Content-Type: application/x-git-upload-pack-advertisement
|
||||
1234: 5678
|
||||
*/
|
||||
})]);
|
||||
}
|
||||
else {
|
||||
expect(cmd).to.be('git');
|
||||
expect(args).to.eql([ 'ls-remote', '--heads', testSource2 ]);
|
||||
expect(options.env.GIT_CURL_VERBOSE).to.be(2);
|
||||
|
||||
return Q.all(['stdout', multiline(function () {/*
|
||||
foo: barbaz
|
||||
Content-Type: application/x-git-upload-pack-advertisement
|
||||
1234: 5678
|
||||
*/
|
||||
})]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
var resolver = new MyGitRemoteResolver({ source: testSource1 }, defaultConfig({ shallowCloneHosts: ['foo', 'foo.bar.baz'] }), logger);
|
||||
|
||||
resolver._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
var resolver2 = new MyGitRemoteResolver({ source: testSource2 }, defaultConfig({ shallowCloneHosts: ['foo', 'foo.bar.baz'] }), logger);
|
||||
|
||||
resolver2._shallowClone().then(function (shallowCloningSupported) {
|
||||
expect(shallowCloningSupported).to.be(true);
|
||||
|
||||
next();
|
||||
}, function(err) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('GitResolver', function () {
|
||||
it('should be true when the resolution type is different', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0',
|
||||
_resolution: {
|
||||
@@ -69,7 +69,8 @@ describe('GitResolver', function () {
|
||||
tag: '0.0.0',
|
||||
commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/master' // same commit hash on purpose
|
||||
@@ -77,7 +78,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -88,7 +89,7 @@ describe('GitResolver', function () {
|
||||
it('should be true when a higher version for a range is available', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.0',
|
||||
_resolution: {
|
||||
@@ -96,7 +97,8 @@ describe('GitResolver', function () {
|
||||
tag: '1.0.0',
|
||||
commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
|
||||
@@ -106,7 +108,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -117,7 +119,7 @@ describe('GitResolver', function () {
|
||||
it('should be true when a resolved to a lower version of a range', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.1',
|
||||
_resolution: {
|
||||
@@ -125,7 +127,8 @@ describe('GitResolver', function () {
|
||||
tag: '1.0.1',
|
||||
commit: 'cccccccccccccccccccccccccccccccccccccccc'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
|
||||
@@ -134,7 +137,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -145,7 +148,7 @@ describe('GitResolver', function () {
|
||||
it('should be false when resolved to the same tag (with same commit hash) for a given range', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.1',
|
||||
_resolution: {
|
||||
@@ -153,7 +156,8 @@ describe('GitResolver', function () {
|
||||
tag: '1.0.1',
|
||||
commit: 'cccccccccccccccccccccccccccccccccccccccc'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
|
||||
@@ -163,7 +167,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(false);
|
||||
next();
|
||||
@@ -174,7 +178,7 @@ describe('GitResolver', function () {
|
||||
it('should be true when resolved to the same tag (with different commit hash) for a given range', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.1',
|
||||
_resolution: {
|
||||
@@ -182,7 +186,8 @@ describe('GitResolver', function () {
|
||||
tag: '1.0.1',
|
||||
commit: 'cccccccccccccccccccccccccccccccccccccccc'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
|
||||
@@ -192,7 +197,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -203,14 +208,15 @@ describe('GitResolver', function () {
|
||||
it('should be true when a different commit hash for a given branch is available', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
_resolution: {
|
||||
type: 'branch',
|
||||
branch: 'master',
|
||||
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/master'
|
||||
@@ -218,7 +224,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -229,14 +235,15 @@ describe('GitResolver', function () {
|
||||
it('should be false when resolved to the the same commit hash for a given branch', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
_resolution: {
|
||||
type: 'branch',
|
||||
branch: 'master',
|
||||
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master'
|
||||
@@ -244,7 +251,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(false);
|
||||
next();
|
||||
@@ -255,13 +262,14 @@ describe('GitResolver', function () {
|
||||
it('should be false when targeting commit hashes', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
_resolution: {
|
||||
type: 'commit',
|
||||
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
GitResolver.refs = function () {
|
||||
return Q.resolve([
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/master'
|
||||
@@ -269,7 +277,7 @@ describe('GitResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('Resolver', function () {
|
||||
})
|
||||
.done();
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function () {
|
||||
succeeded = true;
|
||||
}, function (err) {
|
||||
@@ -129,17 +129,17 @@ describe('Resolver', function () {
|
||||
var resolver = create('foo');
|
||||
var succeeded;
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function () {
|
||||
// Test if hasNew can be called again when done
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function () {
|
||||
next(succeeded ? new Error('Should have failed') : null);
|
||||
});
|
||||
})
|
||||
.done();
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function () {
|
||||
succeeded = true;
|
||||
}, function (err) {
|
||||
@@ -152,7 +152,7 @@ describe('Resolver', function () {
|
||||
it('should resolve to true by default', function (next) {
|
||||
var resolver = create('foo');
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.equal(true);
|
||||
next();
|
||||
@@ -160,32 +160,17 @@ describe('Resolver', function () {
|
||||
.done();
|
||||
});
|
||||
|
||||
it('should resolve to true if the there\'s an error reading the package meta', function (next) {
|
||||
it('should call _hasNew with the package meta', function (next) {
|
||||
var resolver = create('foo');
|
||||
|
||||
rimraf.sync(path.join(tempDir, '.bower.json'));
|
||||
resolver.hasNew(tempDir)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.equal(true);
|
||||
next();
|
||||
})
|
||||
.done();
|
||||
});
|
||||
|
||||
it('should call _hasNew with the canonical dir and the package meta', function (next) {
|
||||
var resolver = create('foo');
|
||||
var canonical;
|
||||
var meta;
|
||||
|
||||
resolver._hasNew = function (canonicalDir, pkgMeta) {
|
||||
canonical = canonicalDir;
|
||||
resolver._hasNew = function (pkgMeta) {
|
||||
meta = pkgMeta;
|
||||
return Q.resolve(true);
|
||||
};
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({ name: 'test' })
|
||||
.then(function () {
|
||||
expect(canonical).to.equal(tempDir);
|
||||
expect(meta).to.be.an('object');
|
||||
expect(meta.name).to.equal('test');
|
||||
next();
|
||||
@@ -197,12 +182,12 @@ describe('Resolver', function () {
|
||||
var resolver = create('foo');
|
||||
var meta;
|
||||
|
||||
resolver._hasNew = function (canonicalDir, pkgMeta) {
|
||||
resolver._hasNew = function (pkgMeta) {
|
||||
meta = pkgMeta;
|
||||
return Q.resolve(true);
|
||||
};
|
||||
|
||||
resolver.hasNew(tempDir, {
|
||||
resolver.hasNew({
|
||||
name: 'foo'
|
||||
})
|
||||
.then(function () {
|
||||
@@ -261,10 +246,10 @@ describe('Resolver', function () {
|
||||
|
||||
resolver._resolve = function () {};
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function () {
|
||||
// Test if hasNew can be called again when done
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew({})
|
||||
.then(function () {
|
||||
next(succeeded ? new Error('Should have failed') : null);
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ else describe('SvnResolver', function () {
|
||||
it('should be true when the resolution type is different', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0',
|
||||
_resolution: {
|
||||
@@ -71,7 +71,7 @@ else describe('SvnResolver', function () {
|
||||
tag: '0.0.0',
|
||||
commit: 123
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
SvnResolver.tags = function () {
|
||||
return Q.resolve({
|
||||
@@ -86,7 +86,7 @@ else describe('SvnResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -97,7 +97,7 @@ else describe('SvnResolver', function () {
|
||||
it('should be true when a higher version for a range is available', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.0',
|
||||
_resolution: {
|
||||
@@ -105,7 +105,7 @@ else describe('SvnResolver', function () {
|
||||
tag: '1.0.0',
|
||||
commit: 3
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
SvnResolver.tags = function () {
|
||||
return Q.resolve({
|
||||
@@ -115,7 +115,7 @@ else describe('SvnResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -126,7 +126,7 @@ else describe('SvnResolver', function () {
|
||||
it('should be true when a resolved to a lower version of a range', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.1',
|
||||
_resolution: {
|
||||
@@ -134,7 +134,8 @@ else describe('SvnResolver', function () {
|
||||
tag: '1.0.1',
|
||||
commit: 3
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
SvnResolver.tags = function () {
|
||||
return Q.resolve({
|
||||
'1.0.0': 2
|
||||
@@ -142,7 +143,7 @@ else describe('SvnResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -153,7 +154,7 @@ else describe('SvnResolver', function () {
|
||||
it('should be false when resolved to the same tag (with same commit hash) for a given range', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.1',
|
||||
_resolution: {
|
||||
@@ -161,7 +162,8 @@ else describe('SvnResolver', function () {
|
||||
tag: '1.0.1',
|
||||
commit: 2
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
SvnResolver.tags = function () {
|
||||
return Q.resolve({
|
||||
'1.0.0': 1,
|
||||
@@ -170,7 +172,7 @@ else describe('SvnResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(false);
|
||||
next();
|
||||
@@ -181,7 +183,7 @@ else describe('SvnResolver', function () {
|
||||
it('should be true when resolved to the same tag (with different commit hash) for a given range', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '1.0.1',
|
||||
_resolution: {
|
||||
@@ -189,7 +191,8 @@ else describe('SvnResolver', function () {
|
||||
tag: '1.0.1',
|
||||
commit: 3
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
SvnResolver.tags = function () {
|
||||
return Q.resolve({
|
||||
'1.0.0': 2,
|
||||
@@ -198,7 +201,7 @@ else describe('SvnResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -210,13 +213,14 @@ else describe('SvnResolver', function () {
|
||||
it('should be false when targeting commit hashes', function (next) {
|
||||
var resolver;
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
_resolution: {
|
||||
type: 'commit',
|
||||
commit: 1
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
SvnResolver.tags = function () {
|
||||
return Q.resolve({
|
||||
'1.0.0': 2
|
||||
@@ -224,7 +228,7 @@ else describe('SvnResolver', function () {
|
||||
};
|
||||
|
||||
resolver = create('foo');
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
|
||||
@@ -93,12 +93,12 @@ describe('UrlResolver', function () {
|
||||
.head('/foo.js')
|
||||
.reply(500);
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0'
|
||||
}));
|
||||
};
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -116,16 +116,16 @@ describe('UrlResolver', function () {
|
||||
'Last-Modified': 'Tue, 15 Nov 2012 12:45:26 GMT'
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0',
|
||||
_cacheHeaders: {
|
||||
'ETag': 'fk3454fdmmlw20i9nf',
|
||||
'Last-Modified': 'Tue, 16 Nov 2012 13:35:29 GMT'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(true);
|
||||
next();
|
||||
@@ -143,16 +143,16 @@ describe('UrlResolver', function () {
|
||||
'Last-Modified': 'Tue, 15 Nov 2012 12:45:26 GMT'
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0',
|
||||
_cacheHeaders: {
|
||||
'ETag': '686897696a7c876b7e',
|
||||
'Last-Modified': 'Tue, 15 Nov 2012 12:45:26 GMT'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(false);
|
||||
next();
|
||||
@@ -171,16 +171,16 @@ describe('UrlResolver', function () {
|
||||
'Last-Modified': 'Tue, 15 Nov 2012 12:45:26 GMT'
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0',
|
||||
_cacheHeaders: {
|
||||
'ETag': '686897696a7c876b7e',
|
||||
'Last-Modified': 'Tue, 15 Nov 2012 12:45:26 GMT'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(false);
|
||||
next();
|
||||
@@ -205,18 +205,18 @@ describe('UrlResolver', function () {
|
||||
});
|
||||
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, '.bower.json'), JSON.stringify({
|
||||
var pkgMeta = {
|
||||
name: 'foo',
|
||||
version: '0.0.0',
|
||||
_cacheHeaders: {
|
||||
'ETag': '686897696a7c876b7e',
|
||||
'Last-Modified': 'Tue, 15 Nov 2012 12:45:26 GMT'
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
resolver = create(redirectingUrl + '/foo.js');
|
||||
|
||||
resolver.hasNew(tempDir)
|
||||
resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
expect(hasNew).to.be(false);
|
||||
next();
|
||||
|
||||
@@ -12,7 +12,7 @@ var os = require('os');
|
||||
var which = require('which');
|
||||
var path = require('path');
|
||||
var proxyquire = require('proxyquire').noCallThru().noPreserveCache();
|
||||
var cmd = require('../lib/util/cmd');
|
||||
var spawnSync = require('spawn-sync');
|
||||
var config = require('../lib/config');
|
||||
|
||||
// For better promise errors
|
||||
@@ -106,29 +106,22 @@ exports.TempDir = (function() {
|
||||
|
||||
mkdirp.sync(that.path);
|
||||
|
||||
var promise = new Q();
|
||||
this.git('init');
|
||||
|
||||
object.forOwn(revisions, function (files, tag) {
|
||||
promise = promise.then(function () {
|
||||
return that.git('init');
|
||||
}).then(function () {
|
||||
that.glob('./!(.git)').map(function (removePath) {
|
||||
var fullPath = path.join(that.path, removePath);
|
||||
this.glob('./!(.git)').map(function (removePath) {
|
||||
var fullPath = path.join(that.path, removePath);
|
||||
|
||||
rimraf.sync(fullPath);
|
||||
});
|
||||
|
||||
that.create(files, {});
|
||||
}).then(function () {
|
||||
return that.git('add', '-A');
|
||||
}).then(function () {
|
||||
return that.git('commit', '-m"commit"');
|
||||
}).then(function () {
|
||||
return that.git('tag', tag);
|
||||
});
|
||||
rimraf.sync(fullPath);
|
||||
});
|
||||
|
||||
return promise;
|
||||
object.forOwn(revisions, function (files, tag) {
|
||||
this.create(files, {});
|
||||
this.git('add', '-A');
|
||||
this.git('commit', '-m"commit"');
|
||||
this.git('tag', tag);
|
||||
}.bind(this));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
TempDir.prototype.glob = function (pattern) {
|
||||
@@ -152,8 +145,13 @@ exports.TempDir = (function() {
|
||||
|
||||
TempDir.prototype.git = function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var result = spawnSync('git', args, { cwd: this.path });
|
||||
|
||||
return cmd('git', args, { cwd: this.path, env: env });
|
||||
if (result.status !== 0) {
|
||||
throw new Error(result.stderr);
|
||||
} else {
|
||||
return result.stdout.toString();
|
||||
}
|
||||
};
|
||||
|
||||
TempDir.prototype.exists = function (name) {
|
||||
|
||||
Reference in New Issue
Block a user