Compare commits

...

66 Commits

Author SHA1 Message Date
Adam Stankiewicz
64d990ba10 Add changelog for 1.5.2 2015-08-25 22:37:23 +02:00
Adam Stankiewicz
cb019c405b Bump version to 1.5.2 2015-08-25 22:31:13 +02:00
Adam Stankiewicz
26f80d25be Put smart host detection behind a configuration, fixes #1764 2015-08-25 22:26:26 +02:00
Adam Stankiewicz
fe9a1bb5fc Revert "upgrade to newer semver, fixes #1817,#1845,#1851"
This reverts commit 8744449016.
2015-08-25 17:12:51 +02:00
Adam Stankiewicz
79679f9b08 Revert "Make bower commands work from subdirectories"
This reverts commit 7acafc26d6.
2015-08-25 17:11:48 +02:00
Adam Stankiewicz
5ef5403d69 Update changelog 2015-08-25 11:20:57 +02:00
Adam Stankiewicz
23afb3a129 Bump version to 1.5.1 2015-08-23 16:37:07 +02:00
Adam Stankiewicz
fac08cf835 [test] Recover process.cwd() after running one test 2015-08-23 16:32:45 +02:00
Adam Stankiewicz
dd67cc7de0 If cwd provided explicitly, force using it, fixes #1866 2015-08-23 16:24:36 +02:00
Adam Stankiewicz
8531534241 Merge pull request #1885 from bower/rwjblue-patch-1
Remove `it.only`.
2015-08-23 15:30:38 +02:00
Robert Jackson
0993621bb8 Remove it.only. 2015-08-23 09:26:48 -04:00
Adam Stankiewicz
45fe5c37cc Bump version to 1.5.0 2015-08-23 14:34:30 +02:00
Adam Stankiewicz
35b60d8d89 Merge pull request #1865 from bower/sheerun-patch-1
No longer prefer installing bower as global
2015-08-23 14:30:16 +02:00
Adam Stankiewicz
63b4d37207 Merge pull request #1866 from bower/feature/cwd
Make bower commands work from subdirectories
2015-08-23 14:30:05 +02:00
Adam Stankiewicz
793268ed54 Merge pull request #1871 from bower/1.5.0-preview
Pluggable resolvers
2015-08-23 14:28:25 +02:00
Adam Stankiewicz
a4a05a5413 Make custom resolvers implementation pass through linter 2015-08-23 14:20:09 +02:00
Adam Stankiewicz
821979bab1 Rename options to bower and add "version" to resolvers options 2015-08-23 14:16:23 +02:00
Adam Stankiewicz
69be742619 Improve API for pluggable resolvers 2015-08-23 13:44:00 +02:00
Sindre Sorhus
298982b522 Merge pull request #1879 from bower/sheerun-patch-2
Remove core team section from README
2015-08-18 01:10:43 +07:00
Adam Stankiewicz
9f2b3d1cd4 Merge pull request #1872 from insanehong/add-shorthand-version
Add shorthand for version of options in help.json
2015-08-13 23:14:37 +02:00
Adam Stankiewicz
800119fc92 Remove core team section from README
Instead, use link to contributors page on GitHub
2015-08-13 23:11:44 +02:00
insanehong
725fc26880 Add shorthand for version of options in help.json
"-v" shorthand aleady supported of cli opeion for output version
2015-08-04 13:58:23 +09:00
Adam Stankiewicz
ed27e87540 Fix the tests 2015-08-04 06:40:03 +02:00
Adam Stankiewicz
4fc2b5cf76 Add tests and improve resolver interface 2015-08-04 06:21:06 +02:00
Adam Stankiewicz
749d46930d Mention about issue with node path on Ubuntu 2015-07-29 00:02:12 +02:00
Adam Stankiewicz
7acafc26d6 Make bower commands work from subdirectories 2015-07-28 23:22:44 +02:00
Adam Stankiewicz
2817936b87 No longer prefer installing bower as global
Installing bower locally if perfectly good. It allows to use different version of bower locally.

The next step is to create bower-cli package that can run local bower installation.
2015-07-28 22:22:56 +02:00
Adam Stankiewicz
022c5a8401 Merge pull request #1864 from jodytate/patch-1
change to use a standard three dot ellipsis
2015-07-28 18:16:43 +02:00
jody tate
50fd944a62 change to use a standard three dot ellipsis 2015-07-28 08:19:05 -07:00
Adam Stankiewicz
9c9cd8164b Merge pull request #1852 from ZephyrHealth/fix/update-semver
upgrade to newer semver, fixes #1817,#1845,#1851
2015-07-28 17:00:18 +02:00
Peter Chanthamynavong
8744449016 upgrade to newer semver, fixes #1817,#1845,#1851
Upgrading to latest version of semver to fix (#1817, #1845, #1851).

Possible breaking changes in regards to the way semvers handles pre-releases.

If a version has a prerelease tags (for example, 1.2.3-alpha.3) then it will only be allowed to satisfy comparator sets if at least one comparator with the same [major, minor, patch] tuple also has a prerelease tag.

For example, the range >1.2.3-alpha.0 would be allowed to match the version 1.2.3-alpha.7, but it would not be satisfied by * or 1.2.3. See [semvers](https://github.com/npm/node-semver#prerelease-tags) additional details.
2015-07-23 07:52:14 -05:00
Adam Stankiewicz
ada6fc18d9 Implement initial version of pluggable resolvers 2015-07-09 18:30:26 +03:00
Adam Stankiewicz
2c9d847a1d Merge pull request #1836 from blueyed/doc-remove-completion-section
doc: remove section about completion
2015-06-29 23:01:18 +03:00
Daniel Hahler
816cdda6bb doc: remove section about completion
The completion command appears to have been lost during the rewrite
(#1066).  So to avoid confusion it should not be mentioned in the
README.
2015-06-29 12:34:14 +02:00
Sindre Sorhus
833f97198e minor package.json tweak 2015-05-19 20:55:20 +02:00
Adam Stankiewicz
293d5cc02b Merge pull request #1802 from pgilad/patch-1
update license attribute
2015-05-19 16:04:24 +02:00
Gilad Peleg
dbb302f22a update license attribute
specifying the type and URL is deprecated:

https://docs.npmjs.com/files/package.json#license
http://npm1k.org/
2015-05-19 13:05:23 +03:00
Sindre Sorhus
87d21e7968 use package.json files object to reduce the package size
https://docs.npmjs.com/files/package.json#files
2015-04-28 12:34:44 +07:00
insanehong
9dd79a8061 Auto-sort bower.json dependencies alphabetically, fixes #1373 2015-04-14 00:06:13 +02:00
Adam Stankiewicz
a1287416d4 Update changelog for 1.4.1 2015-04-01 00:38:21 -07:00
Adam Stankiewicz
00dc877f0a 1.4.1 2015-04-01 00:35:54 -07:00
Adam Stankiewicz
335081053d Bump bower-config and bower-registry-client versions, fixes #1763 2015-04-01 00:35:00 -07:00
Adam Stankiewicz
4af22cfbc0 [test] Replace sync-exec with spawn-sync
sync-exec has performance issue on Windows
2015-03-31 23:39:13 -07:00
Adam Stankiewicz
ab4f8a0e39 [test] Make synchronous exec work on Windows 2015-03-31 22:00:02 -07:00
Adam Stankiewicz
7e110603b5 [test] Make git helper in tests synchronous 2015-03-31 19:08:37 -07:00
Adam Stankiewicz
94455192ad Use -y flag for Chocolatey as AppVeyor suggests 2015-03-31 18:42:21 -07:00
Adam Stankiewicz
c4ca24a537 Add changelog for 1.4.0 2015-03-31 08:52:55 -07:00
Adam Stankiewicz
ea1f5d1ff0 Bump to 1.4.0 2015-03-30 15:46:30 -07:00
Adam Stankiewicz
77b7355433 Bump npm-config, fixes #1689, fixes #1711 2015-03-30 01:23:20 -07:00
Adam Stankiewicz
e727566741 Revert SvnResolver changes (windows paths in URLs) 2015-03-30 00:49:06 -07:00
Adam Stankiewicz
0f68da4eb8 Add support for two-factor authentication for login 2015-03-30 00:19:05 -07:00
Adam Stankiewicz
1a7abfd3b7 Add tests for login command 2015-03-29 23:31:20 -07:00
Adam Stankiewicz
905c2775d2 Add tests for unregister command 2015-03-29 17:56:04 -07:00
Mat Scales
126da9ee12 Unregister and login commands 2015-03-29 17:56:04 -07:00
Adam Stankiewicz
1080cb71c4 Merge pull request #1759 from nwinkler/Request-dep
Request dep
2015-03-29 14:36:24 -07:00
Nils Winkler
39a295901a Set request version to 2.53.0
This fixes the failing unit tests
2015-03-29 16:38:07 +02:00
Nils Winkler
7e55b0b099 Merge pull request #1 from bower/master
Update
2015-03-29 16:36:27 +02:00
Adam Stankiewicz
b4aa90b402 Merge pull request #1755 from dancrumb/feature/1754
Fixes #1754: The version command in the programmatic API now returns the new version
2015-03-26 18:59:23 -07:00
Dan Rumney
a1ecf8a413 Fixes #1754: The version command in the programmatic API now returns the new version
When users of the programmatic API update the version, they may do so without having to inspect the new contents of the bower.json file to find out the new version.
This is useful in scripts where the tag needs to be pushed programatically; the user can push the new tag explicitly.
2015-03-26 10:03:52 -05:00
Adam Stankiewicz
4d59d266c1 Merge pull request #1628 from nwinkler/detect-smart-git
Automatically detecting smart Git hosts
2015-03-25 19:56:57 -07:00
Nils Winkler
7e0a2ea4cb Added check for empty remote or no protocol set. 2015-03-24 20:36:15 +01:00
Nils Winkler
912808b672 Added support for caching hosts that support shallow cloning. 2015-03-24 20:22:15 +01:00
Nils Winkler
a352d51711 Using a function reference instead of calling directly from constructor 2015-03-24 17:14:28 +01:00
Nils Winkler
4ad5ed64d7 Automatically detecting _smart Git hosts_.
Added logic to automatically detect smart Git hosts that allow shallow
cloning. This is done by sending an `ls-remote` request to the server
and then evaluating the returned HTTP header fields. For this, Curl
verbose logging is enabled for the `ls-remote` request, since Curl
verbose logging sends the returned HTTP headers to `stderr`.

If the `stderr` output contains the desired header

  Content-Type: application/x-git-upload-pack-advertisement

then the server supports shallow cloning.

This approach uses Git and Curl for the heavy lifting. Instead of
implementing the request to the server using a simple HTTP client, Git
is used, since it takes care of authentication using stored credentials.

The used approach should also work for BitBucket, which only sends the
Content-Type header when a specific user agent is used. Using Git to
make the request enables this behavior.

The function to detect the smart Git host
(`GitRemoteResolver.prototype._supportsShallowCloning`) returns a
promise that is resolved when the server's request is evaluated. The
promise handling required an addition to `GitHubResolver.js` - to always
resolve the promise to `true`, since GitHub supports shallow cloning.

Added test cases to verify the new functionality.
2015-03-24 17:13:18 +01:00
Sam Saccone
3838a3b4fb Merge pull request #1740 from kytwb/master
Fix broken link to npm completion doc
2015-03-13 09:57:33 -04:00
Amine Mouafik
7d748ae15e Fix broken link to npm completion doc 2015-03-13 11:41:36 +07:00
44 changed files with 1833 additions and 449 deletions

View File

@@ -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))

View File

@@ -20,7 +20,7 @@ module.exports = function (grunt) {
simplemocha: {
options: {
reporter: 'spec',
timeout: '10000'
timeout: '15000'
},
full: {
src: ['test/test.js']

View File

@@ -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

View File

@@ -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

View File

@@ -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')
};

View File

@@ -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

View File

@@ -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
View 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;

View 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;

View File

@@ -40,6 +40,7 @@ function bump(project, versionArg, message) {
})
.then(function () {
console.log('v' + newVersion);
return newVersion;
});
}

View File

@@ -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

View File

@@ -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 &&

View File

@@ -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;

View File

@@ -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));
}
});
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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()

View File

@@ -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);
};

View File

@@ -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());

View File

@@ -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 = {};

View 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;

View File

@@ -93,4 +93,4 @@ module.exports = {
postinstall: mout.function.partial(hook, 'postinstall', true),
//only exposed for test
_orderByDependencies: orderByDependencies
};
};

View File

@@ -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"
]
}

View 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."
}
]
}

View 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"
}
]
}

View File

@@ -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"
},

View File

@@ -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 () {
]
});
});
});
});
});

View File

@@ -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');
});
});
});

View File

@@ -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
View 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'
});
});
});
});

View 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 }
]
);
});
});

View File

@@ -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': {

View File

@@ -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');
});
});
});

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);
});
});
});
});
});

View File

@@ -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();

View File

@@ -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);
});

View File

@@ -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();

View File

@@ -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();

View File

@@ -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) {