Compare commits

...

98 Commits

Author SHA1 Message Date
André Cruz
81ef5a1dd2 Bump version. 2013-08-22 19:07:34 +01:00
André Cruz
29ec793386 Update changelog. 2013-08-22 19:07:13 +01:00
André Cruz
187457df13 Fix interactive not being set correctly on node 0.8.x, fixes #802. 2013-08-22 18:52:42 +01:00
André Cruz
2eea214a42 Fix linked dependencies in windows, fixes #813. 2013-08-22 18:45:34 +01:00
André Cruz
639c2290b7 Do not list versions if a property was requested, #684. 2013-08-22 18:27:41 +01:00
André Cruz
3044dcd3af Merge branch 'master' of github.com:bower/bower 2013-08-22 13:07:22 +01:00
André Cruz
3d64b16227 Fix extraneous being incorrectly set for saved dev dependencies in some cases. 2013-08-22 13:07:03 +01:00
André Cruz
7bd22a5103 Fix update notice when using --json. 2013-08-21 19:30:47 +01:00
André Cruz
dcdd8cb3db Typo. 2013-08-20 23:39:43 +01:00
André Cruz
e93c5f8265 Forgot date. 2013-08-20 23:34:41 +01:00
André Cruz
77be4520dc Bump version. 2013-08-20 23:32:58 +01:00
André Cruz
cf76bcb2aa Update changelog. 2013-08-20 23:31:33 +01:00
André Cruz
b272a61eac Merge branch 'master' of github.com:bower/bower 2013-08-20 22:58:06 +01:00
André Cruz
4ed5f278f8 Ignore remote tags. 2013-08-20 22:57:42 +01:00
André Cruz
6a16850960 Merge pull request #805 from tschaub/shallow-failure
Try without shallow clone if fatal error
2013-08-20 13:50:35 -07:00
Tim Schaub
0eac8bb96e Try without shallow clone if fatal error
$ git --version
    git version 1.7.12.4

    $ git clone https://code.google.com/p/closure-library -b master --progress . --depth 1
    Cloning into '.'...
    fatal: expected shallow/unshallow, got Error: internal server error
2013-08-20 12:43:01 -04:00
André Cruz
46ae1e759c Improve non-interactive shell error message. 2013-08-20 09:26:01 +01:00
André Cruz
e0c1308c2a Update deps. 2013-08-20 01:04:26 +01:00
André Cruz
430dc04b09 Add another missing default value for confirm prompt. 2013-08-20 00:53:10 +01:00
André Cruz
39209f3bda Standardise prompting behaviour between standard/json renderers. 2013-08-20 00:51:01 +01:00
André Cruz
0eaa43c05d Missing default answer for the "Looks good?" question. 2013-08-20 00:50:21 +01:00
André Cruz
5eadd36107 Merge branch 'master' of github.com:bower/bower 2013-08-19 19:38:42 +01:00
André Cruz
c6a370309d Project install now accepts decomposed endpoints instead of endpoints. 2013-08-19 19:38:30 +01:00
André Cruz
13e2514830 Simplify. 2013-08-19 19:26:44 +01:00
André Cruz
0b8be97fb5 Typo. 2013-08-19 19:24:11 +01:00
André Cruz
c327dcb255 Bump version. 2013-08-19 19:22:44 +01:00
André Cruz
1b505cc0e6 Fix maxSatisfying throwing on invalid versions, fixes #800 2013-08-19 19:20:29 +01:00
André Cruz
35d83d2306 Merge branch 'master' of github.com:bower/bower 2013-08-19 09:01:30 +01:00
André Cruz
dd23feb5ea Simplify. 2013-08-19 09:01:20 +01:00
André Cruz
e21d7bd000 Another typo. 2013-08-19 08:53:08 +01:00
André Cruz
d18cfa7b59 Typo. 2013-08-19 08:52:30 +01:00
André Cruz
eca956cf80 Bump version. 2013-08-19 08:39:17 +01:00
André Cruz
587cc5acdd Typo in changelog. 2013-08-19 01:55:17 +01:00
André Cruz
b5e9a4c191 Update change log. 2013-08-19 01:53:22 +01:00
Andre Cruz
3ced03ca7f Unnecessary var. 2013-08-19 01:18:37 +01:00
Andre Cruz
9fa08fee99 Close GH-797: Decoupled prompting from the inner architecture.. Fixes #645 2013-08-19 01:16:19 +01:00
Andre Cruz
12baabcc89 Improve error logs on the json renderer. 2013-08-18 19:45:55 +01:00
Andre Cruz
13839e9384 Normalize paths on windows only if --paths. 2013-08-18 18:37:20 +01:00
Andre Cruz
d42a564de8 Small tweaks to util/semver. 2013-08-18 13:16:35 +01:00
Andre Cruz
71d083a552 Use graceful-fs. 2013-08-18 13:15:12 +01:00
Andre Cruz
cf802c368d Unnecessary check. 2013-08-18 13:14:49 +01:00
Andre Cruz
72e6e61970 Close GH-795: Ignore file symlinks when reading project, fixes #791 and #783.. Fixes #791, Fixes #783 2013-08-17 22:39:59 +01:00
André Cruz
4402b80ec9 Close GH-794: Fallback to standard git clone if download/untar fails . 2013-08-17 22:37:03 +01:00
Andre Cruz
3f0dbef7ea Minor change to util/readJson (consistency). 2013-08-17 14:01:01 +01:00
Andre Cruz
1ee8abf098 Fix tests in windows. 2013-08-17 13:49:04 +01:00
Andre Cruz
55eb1e2290 Only check for difference sources if _originalSource is set. 2013-08-17 13:17:49 +01:00
André Cruz
906990d9b5 Close GH-789: Install dependencies that have different sources, fixes #788.. Fixes #788 2013-08-17 11:50:39 +01:00
Trask Stalnaker
518f3d2a8f Close GH-773: Order tags by semver build metadata. Fixes #0, Fixes #0, Fixes #0, Fixes #0, Fixes #0, Fixes #0, Fixes #0, Fixes #0 2013-08-17 11:39:51 +01:00
Andre Cruz
6ba6ea0084 Prevent deferred from being resolved because of a failed request. 2013-08-17 11:09:58 +01:00
André Cruz
01c499d73f Update bower-config 0.4.0 that suffixes the tmp dir with "user/bower". 2013-08-16 09:44:58 +01:00
André Cruz
7621a71359 Merge pull request #756 from bower/meaningful-proxy-error
Add additional message for proxy users about https//.insteadOf git://, fixes #250.
2013-08-16 01:19:05 -07:00
André Cruz
17f72f0ae2 Merge branch 'master' of github.com:bower/bower into meaningful-proxy-error 2013-08-16 09:13:59 +01:00
André Cruz
47094ef046 Merge pull request #790 from mokkabonna/main-string-split
, is a valid character in a path, just wrap in array if string
2013-08-16 01:11:37 -07:00
Martin Hansen
6e28e79b29 , is a valid character in a path, just wrap in array if string 2013-08-16 09:33:27 +02:00
André Cruz
463b258409 Forgot to decorate flattened tree with additional keys. 2013-08-15 22:43:55 +01:00
André Cruz
49d4c96ddf Fix list command when there is a missing dependency. 2013-08-15 22:42:41 +01:00
André Cruz
197d3e9d36 Merge pull request #786 from bower/git-semver-branch
Match against a branch even if it's a semver version/range that does not exist, fixes #771.
2013-08-15 01:08:42 -07:00
André Cruz
adcc368238 Close GH-784: Several main files are outputted as an array with --paths, fixes #781.. Fixes #781 2013-08-15 09:05:44 +01:00
André Cruz
eff97c4d28 Match against a branch even if it's a semver version/range that does not exist, fixes #771. 2013-08-15 01:14:54 +01:00
André Cruz
d2aeed7a0c Enable relative by default when used with --paths, fixes #785. 2013-08-15 00:55:33 +01:00
Sindre Sorhus
bf4266c2e3 Merge pull request #770 from matthewmichihara/patch-1
Remove bash language identifier from README
2013-08-13 00:35:54 -07:00
Matthew Michihara
814a0f9016 Remove bash language identifier from README
It's messing up the generated project page `http://bower.io/`.
2013-08-12 22:12:35 -07:00
André Cruz
098753520f Use the latest tag as the version by default for the init command.
Also fix some bugs.
2013-08-12 21:25:34 +01:00
André Cruz
43ae12f63b Merge pull request #754 from bower/link-meaningful-error
Try to create a junction when a regular symlink fails, fixes #472
2013-08-12 06:34:52 -07:00
André Cruz
46937bbb87 Remove check. 2013-08-12 08:44:29 +01:00
André Cruz
5bf0c768f7 Merge branch 'master' of github.com:bower/bower 2013-08-12 00:33:21 +01:00
André Cruz
f124fd4c23 Parse array items correctly in bower init. 2013-08-12 00:33:01 +01:00
André Cruz
ff49d24743 Add @MrDHat to the contributors. 2013-08-12 00:14:52 +01:00
André Cruz
99ca255aad Tweaks to PR #763.
#693.
2013-08-12 00:11:03 +01:00
André Cruz
d1a63b6fe0 Merge pull request #763 from MrDHat/improve-init
Improve init. Fixes #693
2013-08-11 15:53:17 -07:00
MrDHat
3530a33fe7 Improve init. Fixes #693 2013-08-12 04:18:46 +05:30
André Cruz
d08efcfe3c Fix wrong tests. 2013-08-11 20:57:35 +01:00
André Cruz
337c0f2d0a Refactor GitHub url matching to a static function on the GitHubResolver. 2013-08-11 20:53:28 +01:00
André Cruz
1acf4a53d3 Minor cache clean fix related to last PR. 2013-08-11 20:52:42 +01:00
André Cruz
a2b0fb7d82 Create the empty dir if not yet created. 2013-08-11 19:09:23 +01:00
André Cruz
088c488e2d CS. 2013-08-11 18:44:53 +01:00
André Cruz
64ef934a3c Increase timeouts slightly of two tests. 2013-08-11 18:43:29 +01:00
André Cruz
39b690e96a Add missing tests. 2013-08-11 18:32:26 +01:00
André Cruz
08fc86698d Use an empty folder for git templates, fixes #761.
This is to prevent user template/hooks from being run.
2013-08-11 18:30:33 +01:00
André Cruz
7bacf8c425 Close GH-762: Support semver targets in cache clean command, fixes #688.. Fixes #688 2013-08-11 18:12:18 +01:00
André Cruz
896672c4f4 Print empty strings in bower info. 2013-08-11 18:08:22 +01:00
André Cruz
a6552d7d54 Merge branch 'master' of github.com:bower/bower 2013-08-11 15:18:10 +01:00
André Cruz
cacef7b316 Better var name. 2013-08-11 15:18:00 +01:00
André Cruz
7a8642db94 Merge pull request #755 from bower/win-paths-normalize
Normalise windows paths, closes #279.
2013-08-11 06:49:37 -07:00
André Cruz
2b8e0fdf06 Upgrade to bower-json@0.4.0 that ignores component(1) files, closes #556. 2013-08-11 14:48:57 +01:00
André Cruz
abe4264a13 Close GH-760: Info command now shows the latest version package meta always, fixes #759. 2013-08-11 14:44:58 +01:00
André Cruz
60d9bfb2b8 Try junctions on windows before erroring out. 2013-08-11 10:05:40 +01:00
André Cruz
9141b3020f Add additional message for proxy users about https//.insteadOf git://, fixes #250. 2013-08-10 23:46:26 +01:00
André Cruz
367c3c3e95 Fix host not correctly being set. 2013-08-10 23:36:48 +01:00
André Cruz
41a3903ac9 Fix typo. 2013-08-10 23:31:39 +01:00
André Cruz
146d6ac538 Normalise windows paths, closes #279. 2013-08-10 21:59:24 +01:00
André Cruz
fce93e16ae Close GH-726: Add relative option to the list command, closes #714.. 2013-08-10 21:43:04 +01:00
André Cruz
fb12fc03bf Add meaningful error when executing bower link on windows when user is not elevated.
#472
2013-08-10 21:37:13 +01:00
André Cruz
9c49b097c8 Bump version. 2013-08-10 16:07:53 +01:00
André Cruz
524e83fca4 Update change log. 2013-08-10 16:07:44 +01:00
André Cruz
f5999e84a2 Add missing test. 2013-08-10 15:45:24 +01:00
André Cruz
ee2640bc43 Close GH-747: Do not use --depth=1 if the server does not support it, fixes #744. 2013-08-10 15:43:54 +01:00
Sindre Sorhus
2b93aa000c Simplify 9c4b30b65c 2013-08-08 21:01:30 +02:00
37 changed files with 1416 additions and 408 deletions

View File

@@ -1,5 +1,57 @@
# Changelog
## 1.2.3 - 2013-08-22
- Fix read of environment variables with dashes and also support nested ones ([#8@bower-config](https://github.com/bower/config/issues/8))
- Fix `bower info <package> <property>` printing the available versions (it shouldn't!)
- Fix interactive shell not being correctly detected in node `0.8.x`
- Fix `extraneous` flag in the `list` command being incorrectly set for saved dev dependencies in some cases
- Fix linked dependencies not being read listed in `bower list` on Windows
- Fix update notice not working with `--json`
## 1.2.2 - 2013-08-20
- Standardize prompt behaviour with and without `--json`
- Improve detection of `git` servers that do not support shallow clones ([#805](https://github.com/bower/bower/issues/805))
- Ignore remote tags (tags ending with ^{})
- Fix bower not saving the correct endpoint in some edge cases ([#806](https://github.com/bower/bower/issues/806))
## 1.2.1 - 2013-08-19
- Fix bower throwing on non-semver targets ([#800](https://github.com/bower/bower/issues/800))
## 1.2.0 - 2013-08-19
- __Bower no longer installs a pre-release version by default, that is, if no version/range is specified__ ([#782](https://github.com/bower/bower/issues/782))
- __`bower info <package>` will now show the latest `<package>` information along with the available versions__ ([#759](https://github.com/bower/bower/issues/759))
- __`bower link` no longer requires an elevated user on Windows in most cases__ ([#472](https://github.com/bower/bower/issues/472))
- __Init command now prompts for the whole `bower.json` spec properties, filling in default values for `author` and `homepage` based on `git` settings__ ([#693](https://github.com/bower/bower/issues/693))
- Changes to endpoint sources in `bower.json` are now catched up by `bower install` and `bower update` ([#788](https://github.com/bower/bower/issues/788))
- Allow semver ranges in `bower cache clean`, e.g. `bower cache clean jquery#<2.0.0` ([#688](https://github.com/bower/bower/issues/688))
- Normalize `bower list --paths` on Windows ([#279](https://github.com/bower/bower/issues/279))
- Multiple mains are now correctly outputted as an array in `bower list --paths` ([#784](https://github.com/bower/bower/issues/784))
- Add `--relative` option to `bower list --json` so that Bower outputs relative paths instead of absolute ([#714](https://github.com/bower/bower/issues/714))
- `bower list --paths` now outputs relative paths by default; can be turned off with `--no-relative` ([#785](https://github.com/bower/bower/issues/785))
- Bower no longer fails if `symlinks` to files are present in the `bower_components` folder ([#783](https://github.com/bower/bower/issues/783) and [#791](https://github.com/bower/bower/issues/791))
- Disable git templates/hooks when running `git` ([#761](https://github.com/bower/bower/issues/761))
- Add instructions to setup git workaround for proxies when execution of `git` fails ([#250](https://github.com/bower/bower/issues/250))
- Ignore `component.json` if it looks like a component(1) file ([#556](https://github.com/bower/bower/issues/556))
- Fix multi-user usage on bower when it creates temporary directories to hold some files
- Fix prompting causing an invalid JSON output when running commands with `--json`
- When running Bower commands programmatically, prompting is now disabled by default (see the updated progammatic [usage](https://github.com/bower/bower#programmatic-api) for more info)
- Other minor improvements and fixes
Fix for `#788` requires installed components to be re-installed.
## 1.1.2 - 2013-08-10
- Detect and fallback if the git server does not support `--depth=1` when cloning ([#747](https://github.com/bower/bower/issues/747))
## 1.1.1 - 2013-08-08
- Fix silent fail when spawning child processes in some edge cases ([#722](https://github.com/bower/bower/issues/722))

View File

@@ -44,7 +44,7 @@ However, if you still want to run commands with sudo, use `--allow-root` option.
Bower offers several ways to install packages:
```bash
```
# Using the dependencies listed in the current directory's bower.json
bower install
# Using a local or remote package
@@ -224,7 +224,7 @@ through the `bower.commands` object.
var bower = require('bower');
bower.commands
.install(paths, options)
.install(['jquery'], { save: true }, { /* custom config */ })
.on('end', function (installed) {
console.log(installed);
});
@@ -236,15 +236,30 @@ bower.commands
});
```
Commands emit three types of events: `log`, `end`, and `error`.
Commands emit four types of events: `log`, `prompt`, `end`, `error`.
* `log` is emitted to report the state/progress of the command.
* `prompt` is emitted whenever the user needs to be prompted.
* `error` will only be emitted if something goes wrong.
* `end` is emitted when the command successfully ends.
For a better of idea how this works, you may want to check out [our bin
file](https://github.com/bower/bower/blob/master/bin/bower).
When using bower programmatically, prompting is disabled by default. Though you can enable it when calling commands with `interactive: true` in the config.
This requires you to listen for the `prompt` event and handle the prompting yourself. The easiest way is to use the [inquirer](https://npmjs.org/package/inquirer) npm module like so:
```js
var inquirer = require('inquirer');
bower.commands
.install(['jquery'], { save: true }, { interactive: true })
// ..
.on('prompt', function (prompts, callback) {
inquirer.prompt(prompts, callback);
});
```
## Completion (experimental)
@@ -318,6 +333,7 @@ Thanks for assistance and contributions:
[@marcelombc](https://github.com/marcelombc),
[@marcooliveira](https://github.com/marcooliveira),
[@mklabs](https://github.com/mklabs),
[@MrDHat](https://github.com/MrDHat),
[@necolas](https://github.com/necolas),
[@paulirish](https://github.com/paulirish),
[@richo](https://github.com/richo),

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env node
process.bin = process.title = 'bower';
var path = require('path');
var tty = require('tty');
var mout = require('mout');
var updateNotifier = require('update-notifier');
var Logger = require('bower-logger');
@@ -21,8 +22,6 @@ var emitter;
var notifier;
var levels = Logger.LEVELS;
process.title = 'bower';
options = cli.readOptions({
version: { type: Boolean, shorthand: 'v' },
help: { type: Boolean, shorthand: 'h' },
@@ -49,10 +48,6 @@ if (bower.config.silent) {
loglevel = levels[bower.config.loglevel] || levels.info;
}
// Enable interactive if terminal is TTY,
// loglevel is equal or lower then conflict
bower.config.interactive = tty.isatty(1) && loglevel <= levels.conflict;
// Get the command to execute
while (options.argv.remain.length) {
command = options.argv.remain.join(' ');
@@ -119,6 +114,12 @@ emitter
if (levels[log.level] >= loglevel) {
renderer.log(log);
}
})
.on('prompt', function (prompt, callback) {
renderer.prompt(prompt)
.then(function (answer) {
callback(answer);
});
});
// Check for newer version of Bower

View File

@@ -4,36 +4,37 @@ var mout = require('mout');
var Q = require('q');
var rimraf = require('rimraf');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../../core/PackageRepository');
var semver = require('../../util/semver');
var cli = require('../../util/cli');
var defaultConfig = require('../../config');
function clean(packages, options, config) {
function clean(endpoints, options, config) {
var logger = new Logger();
var decEndpoints;
var names;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
// If packages wasn't provided or is an empty array, null them
if (!packages || !packages.length) {
packages = names = null;
// Otherwise parse them
} else {
packages = packages.map(function (pkg) {
var split = pkg.split('#');
return {
name: split[0],
version: split[1]
};
// If endpoints is an empty array, null them
if (endpoints && !endpoints.length) {
endpoints = null;
}
// Generate decomposed endpoints and names based on the endpoints
if (endpoints) {
decEndpoints = endpoints.map(function (endpoint) {
return endpointParser.decompose(endpoint);
});
names = packages.map(function (pkg) {
return pkg.name;
names = decEndpoints.map(function (decEndpoint) {
return decEndpoint.name || decEndpoint.source;
});
}
Q.all([
clearPackages(packages, config, logger),
clearPackages(decEndpoints, config, logger),
clearLinks(names, config, logger),
!names ? clearCompletion(config, logger) : null
])
@@ -47,7 +48,7 @@ function clean(packages, options, config) {
return logger;
}
function clearPackages(packages, config, logger) {
function clearPackages(decEndpoints, config, logger) {
var repository = new PackageRepository(config, logger);
return repository.list()
@@ -55,24 +56,32 @@ function clearPackages(packages, config, logger) {
var promises;
// Filter entries according to the specified packages
if (packages) {
if (decEndpoints) {
entries = entries.filter(function (entry) {
return !!mout.array.find(packages, function (pkg) {
return !!mout.array.find(decEndpoints, function (decEndpoint) {
var entryPkgMeta = entry.pkgMeta;
// Check if names are different
if (pkg.name !== entryPkgMeta.name) {
// Check if name or source match the entry
if (decEndpoint.name !== entryPkgMeta.name &&
decEndpoint.source !== entryPkgMeta.name &&
decEndpoint.source !== entryPkgMeta._source
) {
return false;
}
// If version was specified, check if they are different
if (pkg.version) {
return pkg.version === entryPkgMeta.version ||
pkg.version === entryPkgMeta._target ||
pkg.version === entryPkgMeta._release;
// If target is a wildcard, simply return true
if (decEndpoint.target === '*') {
return true;
}
return true;
// If it's a semver target, compare using semver spec
if (semver.validRange(decEndpoint.target)) {
return semver.satisfies(entryPkgMeta.version, decEndpoint.target);
}
// Otherwise, compare against target/release
return decEndpoint.target === entryPkgMeta._target ||
decEndpoint.target === entryPkgMeta._release;
});
});
}
@@ -88,7 +97,7 @@ function clearPackages(packages, config, logger) {
return Q.all(promises)
.then(function () {
if (!packages) {
if (!decEndpoints) {
// Ensure that everything is cleaned,
// even invalid packages in the cache
return repository.clear();

View File

@@ -1,11 +1,12 @@
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../core/PackageRepository');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function info(pkg, property, config) {
function info(endpoint, property, config) {
var repository;
var decEndpoint;
var logger = new Logger();
@@ -13,43 +14,46 @@ function info(pkg, property, config) {
config = mout.object.deepFillIn(config || {}, defaultConfig);
repository = new PackageRepository(config, logger);
decEndpoint = endpointParser.decompose(pkg);
decEndpoint = endpointParser.decompose(endpoint);
// If no target and property were specified, retrieve whole package info
if (pkg.split('#').length === 1 && !property) {
repository.versions(decEndpoint.source)
.then(function (versions) {
logger.emit('end', {
Q.all([
getPkgMeta(repository, decEndpoint, property),
decEndpoint.target === '*' && !property ? repository.versions(decEndpoint.source) : null
])
.spread(function (pkgMeta, versions) {
if (versions) {
return logger.emit('end', {
name: decEndpoint.source,
versions: versions
});
})
.fail(function (error) {
logger.emit('error', error);
});
// Otherwise fetch version and retrieve package meta
} else {
repository.fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function (value, key) {
return key.charAt(0) !== '_';
versions: versions,
latest: pkgMeta
});
}
// Retrieve specific property
if (property) {
pkgMeta = mout.object.get(pkgMeta, property);
}
logger.emit('end', pkgMeta);
})
.fail(function (error) {
logger.emit('error', error);
});
}
logger.emit('end', pkgMeta);
})
.fail(function (error) {
logger.emit('error', error);
});
return logger;
}
function getPkgMeta(repository, decEndpoint, property) {
return repository.fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function (value, key) {
return key.charAt(0) !== '_';
});
// Retrieve specific property
if (property) {
pkgMeta = mout.object.get(pkgMeta, property);
}
return pkgMeta;
});
}
// -------------------
info.line = function (argv) {

View File

@@ -2,18 +2,32 @@ var mout = require('mout');
var fs = require('graceful-fs');
var path = require('path');
var Q = require('q');
var inquirer = require('inquirer');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var cli = require('../util/cli');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var GitFsResolver = require('../core/resolvers/GitFsResolver');
var cli = require('../util/cli');
var cmd = require('../util/cmd');
var createError = require('../util/createError');
function init(config) {
var project;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
// This command requires interactive to be enabled
if (!config.interactive) {
process.nextTick(function () {
logger.emit('error', createError('Register requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
}));
});
return logger;
}
project = new Project(config, logger);
// Start with existing JSON details
@@ -21,13 +35,13 @@ function init(config) {
// Fill in defaults
.then(setDefaults.bind(null, config))
// Now prompt user to make changes
.then(promptUser)
.then(promptUser.bind(null, logger))
// Set ignore based on the response
.spread(setIgnore)
// Set dependencies based on the response
.spread(setDependencies.bind(null, project))
// All done!
.spread(saveJson.bind(null, project))
.spread(saveJson.bind(null, project, logger))
.then(function (json) {
logger.emit('end', json);
})
@@ -49,20 +63,35 @@ function readJson(project, logger) {
});
}
function saveJson(project, json) {
// Cleanup empty props, including objects and arrays
function saveJson(project, logger, json) {
// Cleanup empty props (null values, empty strings, objects and arrays)
mout.object.forOwn(json, function (value, key) {
if (mout.lang.isEmpty(value)) {
if (value == null || mout.lang.isEmpty(value)) {
delete json[key];
}
});
// Save json (true forces file creation)
return project.saveJson(true);
logger.info('json', 'Generated json', { json: json });
// Confirm the json with the user
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Looks good?',
default: true
})
.then(function (good) {
if (!good) {
return null;
}
// Save json (true forces file creation)
return project.saveJson(true);
});
}
function setDefaults(config, json) {
var name;
var promise = Q.resolve();
// Name
if (!json.name) {
@@ -71,7 +100,15 @@ function setDefaults(config, json) {
// Version
if (!json.version) {
json.version = '0.0.0';
// Assume latest semver tag if it's a git repo
promise = promise.then(function () {
return GitFsResolver.versions(config.cwd)
.then(function (versions) {
json.version = versions[0] || '0.0.0';
}, function () {
json.version = '0.0.0';
});
});
}
// Main
@@ -86,12 +123,60 @@ function setDefaults(config, json) {
}
}
return json;
// Homepage
if (!json.homepage) {
// Set as GitHub homepage if it's a GitHub repository
promise = promise.then(function () {
return cmd('git', ['config', '--get', 'remote.origin.url'])
.spread(function (stdout) {
var pair;
stdout = stdout.trim();
if (!stdout) {
return;
}
pair = GitHubResolver.getOrgRepoPair(stdout);
if (pair) {
json.homepage = 'https://github.com/' + pair.org + '/' + pair.repo;
}
})
.fail(function () { });
});
}
if (!json.authors) {
promise = promise.then(function () {
// Get the user name configured in git
return cmd('git', ['config', '--get', '--global', 'user.name'])
.spread(function (stdout) {
var gitEmail;
var gitName = stdout.trim();
// Abort if no name specified
if (!gitName) {
return;
}
// Get the user email configured in git
return cmd('git', ['config', '--get', '--global', 'user.email'])
.spread(function (stdout) {
gitEmail = stdout.trim();
}, function () {})
.then(function () {
json.authors = gitName;
json.authors += gitEmail ? ' <' + gitEmail + '>' : '';
});
}, function () {});
});
}
return promise.then(function () {
return json;
});
}
function promptUser(json) {
var deferred = Q.defer();
function promptUser(logger, json) {
var questions = [
{
'name': 'name',
@@ -105,12 +190,42 @@ function promptUser(json) {
'default': json.version,
'type': 'input'
},
{
'name': 'description',
'message': 'description',
'default': json.description,
'type': 'input'
},
{
'name': 'main',
'message': 'main file',
'default': json.main,
'type': 'input'
},
{
'name': 'keywords',
'message': 'keywords',
'default': json.keywords ? json.keywords.toString() : null,
'type': 'input'
},
{
'name': 'authors',
'message': 'authors',
'default': json.authors ? json.authors.toString() : null,
'type': 'input'
},
{
'name': 'license',
'message': 'license',
'default': json.license || 'MIT',
'type': 'input'
},
{
'name': 'homepage',
'message': 'homepage',
'default': json.homepage,
'type': 'input'
},
{
'name': 'dependencies',
'message': 'set currently installed components as dependencies?',
@@ -122,18 +237,45 @@ function promptUser(json) {
'message': 'add commonly ignored files to ignore list?',
'default': true,
'type': 'confirm'
},
{
'name': 'private',
'message': 'would you like to mark this package as private which prevents it from being accidentally published to the registry?',
'default': !!json.private,
'type': 'confirm'
}
];
inquirer.prompt(questions, function (answers) {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
json.name = answers.name;
json.version = answers.version;
json.description = answers.description;
json.main = answers.main;
json.keywords = toArray(answers.keywords);
json.authors = toArray(answers.authors, ',');
json.license = answers.license;
json.homepage = answers.homepage;
json.private = answers.private || null;
return deferred.resolve([json, answers]);
return [json, answers];
});
}
function toArray(value, splitter) {
var arr = value.split(splitter || /[\s,]/);
// Trim values
arr = arr.map(function (item) {
return item.trim();
});
return deferred.promise;
// Filter empty values
arr = arr.filter(function (item) {
return !!item;
});
return arr.length ? arr : null;
}
function setIgnore(json, answers) {
@@ -158,8 +300,6 @@ function setDependencies(project, json, answers) {
json.dependencies = {};
// Add extraneous as dependencies
// TODO: The final expanded source is used instead of the original source
// While this the most correct it might be confusing to users
extraneous.forEach(function (extra) {
var jsonEndpoint;

View File

@@ -1,23 +1,26 @@
var mout = require('mout');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function install(endpoints, options, config) {
var project;
var decEndpoints;
var logger = new Logger();
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
// If endpoints is an empty array, null them
if (endpoints && !endpoints.length) {
endpoints = null;
}
// Convert endpoints to decomposed endpoints
endpoints = endpoints || [];
decEndpoints = endpoints.map(function (endpoint) {
return endpointParser.decompose(endpoint);
});
project.install(endpoints, options)
project.install(decEndpoints, options)
.then(function (installed) {
logger.emit('end', installed);
})

View File

@@ -1,12 +1,10 @@
var fs = require('graceful-fs');
var path = require('path');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var createError = require('../util/createError');
var createLink = require('../util/createLink');
var cli = require('../util/cli');
var defaultConfig = require('../config');
@@ -72,30 +70,6 @@ function linkTo(name, localName, config) {
return logger;
}
function createLink(src, dst) {
var dstDir = path.dirname(dst);
// Create directory
return Q.nfcall(mkdirp, dstDir)
// Check if source exists
.then(function () {
return Q.nfcall(fs.lstat, src)
.fail(function (error) {
if (error.code === 'ENOENT') {
throw createError('Failed to create link to ' + path.basename(src), 'ENOENT', {
details: src + ' doest not exists or points to a non-existent package'
});
}
throw error;
});
})
// Create symlink
.then(function () {
return Q.nfcall(fs.symlink, src, dst, 'dir');
});
}
// -------------------
var link = {

View File

@@ -1,9 +1,9 @@
var path = require('path');
var mout = require('mout');
var semver = require('semver');
var Q = require('q');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var semver = require('../util/semver');
var cli = require('../util/cli');
var defaultConfig = require('../config');
@@ -12,19 +12,60 @@ function list(options, config) {
var logger = new Logger();
options = options || {};
// Make relative option true by default when used with paths
if (options.paths && options.relative == null) {
options.relative = true;
}
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
project.getTree()
.spread(function (tree, flattened) {
var baseDir = path.dirname(path.join(config.cwd, config.directory));
// Relativize paths
// Also normalize paths on windows
project.walkTree(tree, function (node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(baseDir, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
}
}, true);
// Note that we need to to parse the flattened tree because it might
// contain additional packages
mout.object.forOwn(flattened, function (node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(baseDir, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
}
});
// Render paths?
if (options.paths) {
return logger.emit('end', paths(flattened));
}
// Do not check for new versions?
if (config.offline) {
return logger.emit('end', tree);
}
// Check for new versions
return checkVersions(project, tree, logger)
.then(function () {
logger.emit('end', tree);
@@ -89,6 +130,7 @@ function paths(flattened) {
main = pkg.pkgMeta.main;
// If no main was specified, fallback to canonical dir
if (!main) {
ret[name] = pkg.canonicalDir;
return;
@@ -96,20 +138,26 @@ function paths(flattened) {
// Normalize main
if (typeof main === 'string') {
main = main.split(',');
main = [main];
}
// Concatenate each main entry with the canonical dir
main = main.map(function (part) {
return path.join(pkg.canonicalDir, part).trim();
}).join(',');
return normalize(path.join(pkg.canonicalDir, part).trim());
});
ret[name] = main;
// If only one main file, use a string
// Otherwise use an array
ret[name] = main.length === 1 ? main[0] : main;
});
return ret;
}
function normalize(src) {
return src.replace(/\\/g, '/');
}
// -------------------
list.line = function (argv) {
@@ -119,7 +167,8 @@ list.line = function (argv) {
list.options = function (argv) {
return cli.readOptions({
'paths': { type: Boolean, shorthand: 'p' }
'paths': { type: Boolean, shorthand: 'p' },
'relative': { type: Boolean, shorthand: 'r' }
}, argv);
};

View File

@@ -1,6 +1,5 @@
var mout = require('mout');
var Q = require('q');
var promptly = require('promptly');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('bower-logger');
@@ -55,8 +54,12 @@ function register(name, url, config) {
}
// Confirm if the user really wants to register
return Q.nfcall(promptly.confirm, 'Registering a package will make it visible and installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue? (y/n)');
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue?',
default: true
});
})
.then(function (result) {
// If user response was negative, abort

View File

@@ -1,3 +1,4 @@
var tty = require('tty');
var mout = require('mout');
var config = require('bower-config').read();
var cli = require('./util/cli');
@@ -6,6 +7,11 @@ var cli = require('./util/cli');
// and conflicts with --json
delete config.json;
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = process.bin === 'bower' && tty.isatty(1);
}
// Merge common CLI options into the config
mout.object.mixIn(config, cli.readOptions({
force: { type: Boolean, shorthand: 'f' },

View File

@@ -1,13 +1,12 @@
var Q = require('q');
var mout = require('mout');
var semver = require('semver');
var path = require('path');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var fs = require('graceful-fs');
var promptly = require('promptly');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('./PackageRepository');
var semver = require('../util/semver');
var copy = require('../util/copy');
var createError = require('../util/createError');
@@ -146,6 +145,7 @@ Manager.prototype.install = function () {
var json = JSON.parse(contents.toString());
json._target = decEndpoint.target;
json._originalSource = decEndpoint.source;
if (decEndpoint.newly) {
json._direct = true;
}
@@ -177,7 +177,11 @@ Manager.prototype.toData = function (decEndpoint, extraKeys) {
var data = {};
data.endpoint = mout.object.pick(decEndpoint, ['name', 'source', 'target']);
mout.object.mixIn(data, mout.object.pick(decEndpoint, ['canonicalDir', 'pkgMeta']));
if (decEndpoint.canonicalDir) {
data.canonicalDir = decEndpoint.canonicalDir;
data.pkgMeta = decEndpoint.pkgMeta;
}
if (extraKeys) {
extra = mout.object.pick(decEndpoint, extraKeys);
@@ -507,12 +511,12 @@ Manager.prototype._dissect = function () {
componentsDir = path.resolve(that._config.cwd, that._config.directory);
this._dissected = mout.object.filter(suitables, function (decEndpoint, name) {
var installedMeta = this._installed[name];
var target = decEndpoint.target;
var dst;
// Analyse a few props
if (installedMeta &&
installedMeta._target === target &&
installedMeta._target === decEndpoint.target &&
installedMeta._originalSource === decEndpoint.source &&
installedMeta._release === decEndpoint.pkgMeta._release
) {
return false;
@@ -687,20 +691,29 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
});
choices = picks.map(function (pick, index) { return index + 1; });
return Q.nfcall(promptly.choose, 'Choice:', choices, {
validator: function (choice) {
if (choice.charAt(0) === '!') {
choice = choice.substr(1);
save = true;
return Q.nfcall(this._logger.prompt.bind(this._logger), {
type: 'input',
message: 'Answer:',
validate: function (choice) {
choice = Number(mout.string.trim(choice.trim(), '!'));
if (!choice || choice < 1 || choice > picks.length) {
return 'Invalid choice';
}
return choice;
return true;
}
})
.then(function (choice) {
var pick = picks[choice - 1];
var pick;
var resolution;
// Sanitize choice
choice = choice.trim();
save = /^!/.test(choice) || /!$/.test(choice); // Save if prefixed or suffixed with !
choice = Number(mout.string.trim(choice, '!'));
pick = picks[choice - 1];
// Store choice into resolutions
if (pick.target === '*') {
resolution = pick.pkgMeta._release || '*';

View File

@@ -2,14 +2,13 @@ var glob = require('glob');
var path = require('path');
var fs = require('graceful-fs');
var Q = require('q');
var semver = require('semver');
var mout = require('mout');
var rimraf = require('rimraf');
var promptly = require('promptly');
var endpointParser = require('bower-endpoint-parser');
var Logger = require('bower-logger');
var Manager = require('./Manager');
var defaultConfig = require('../config');
var semver = require('../util/semver');
var md5 = require('../util/md5');
var createError = require('../util/createError');
var readJson = require('../util/readJson');
@@ -29,8 +28,7 @@ function Project(config, logger) {
// -----------------
Project.prototype.install = function (endpoints, options) {
var decEndpoints;
Project.prototype.install = function (decEndpoints, options) {
var that = this;
var targets = [];
var resolved = {};
@@ -49,7 +47,7 @@ Project.prototype.install = function (endpoints, options) {
.spread(function (json, tree) {
// Recover tree
that.walkTree(tree, function (node, name) {
if (node.missing) {
if (node.missing || node.different) {
targets.push(node);
} else if (node.incompatible) {
incompatibles.push(node);
@@ -64,23 +62,16 @@ Project.prototype.install = function (endpoints, options) {
}
});
// Add endpoints as targets
if (endpoints) {
decEndpoints = endpoints.map(function (endpoint) {
var decEndpoint = endpointParser.decompose(endpoint);
// Mark as new so that a conflict for this target
// always require a choice
// Also allows for the target to be converted in case
// of being *
decEndpoint.newly = true;
targets.push(decEndpoint);
return decEndpoint;
});
} else {
decEndpoints = [];
}
// Add decomposed endpoints as targets
decEndpoints = decEndpoints || [];
decEndpoints.forEach(function (decEndpoint) {
// Mark as new so that a conflict for this target
// always require a choice
// Also allows for the target to be converted in case
// of being *
decEndpoint.newly = true;
targets.push(decEndpoint);
});
// Bootstrap the process
return that._bootstrap(targets, resolved, incompatibles);
@@ -174,7 +165,7 @@ Project.prototype.update = function (names, options) {
// Recover tree
that.walkTree(tree, function (node, name) {
if (node.missing) {
if (node.missing || node.different) {
targets.push(node);
} else if (node.incompatible) {
incompatibles.push(node);
@@ -266,15 +257,10 @@ Project.prototype.uninstall = function (names, options) {
// Otherwise we need to figure it out if the user really wants to remove it,
// even with dependants
// As such we need to prompt the user with a meaningful message
dependantsNames = dependants
.map(function (dep) {
return dep.name;
})
.sort(function (name1, name2) {
return name1.localeCompare(name2);
});
dependantsNames = dependants.map(function (dep) { return dep.name; });
dependantsNames.sort(function (name1, name2) { return name1.localeCompare(name2); });
dependantsNames = mout.array.unique(dependantsNames);
dependants = dependants.map(function (dependant) { return that._manager.toData(dependant); });
message = dependantsNames.join(', ') + ' depends on ' + decEndpoint.name;
data = {
name: decEndpoint.name,
@@ -290,8 +276,12 @@ Project.prototype.uninstall = function (names, options) {
that._logger.conflict('mutual', message, data);
// Question the user
return Q.nfcall(promptly.confirm, 'Continue anyway? (y/n)')
// Prompt the user
return Q.nfcall(that._logger.prompt.bind(that._logger), {
type: 'confirm',
message: 'Continue anyway?',
default: true
})
.then(function (confirmed) {
// If the user decided to skip it, remove from the array so that it won't
// influence subsequent dependants
@@ -320,7 +310,7 @@ Project.prototype.getTree = function () {
return this._analyse()
.spread(function (json, tree, flattened) {
var extraneous = [];
var additionalKeys = ['missing', 'extraneous', 'linked'];
var additionalKeys = ['missing', 'extraneous', 'different', 'linked'];
// Convert tree
tree = this._manager.toData(tree, additionalKeys);
@@ -353,7 +343,7 @@ Project.prototype.getTree = function () {
// Convert flattened
flattened = mout.object.map(flattened, function (node) {
return this._manager.toData(node);
return this._manager.toData(node, additionalKeys);
}, this);
return [tree, flattened, extraneous];
@@ -472,13 +462,15 @@ Project.prototype._analyse = function () {
// Mix direct extraneous as dependencies
// (dependencies installed without --save/--save-dev)
jsonCopy.dependencies = jsonCopy.dependencies || {};
jsonCopy.devDependencies = jsonCopy.devDependencies || {};
mout.object.forOwn(installed, function (decEndpoint, key) {
var pkgMeta = decEndpoint.pkgMeta;
var isSaved = jsonCopy.dependencies[key] || jsonCopy.devDependencies[key];
// The _direct propery is saved by the manager when .newly is specified
if (!jsonCopy.dependencies[key] && pkgMeta._direct) {
if (!isSaved && pkgMeta._direct) {
decEndpoint.extraneous = true;
jsonCopy.dependencies[key] = pkgMeta._source + '#' + pkgMeta._target;
jsonCopy.dependencies[key] = (pkgMeta._originalSource || pkgMeta._source) + '#' + pkgMeta._target;
}
});
@@ -544,7 +536,7 @@ Project.prototype._readJson = function () {
// Read local json
return this._json = readJson(this._config.cwd, {
name: path.basename(this._config.cwd) || 'root'
assume: { name: path.basename(this._config.cwd) || 'root' }
})
.spread(function (json, deprecated, assumed) {
var jsonStr;
@@ -592,7 +584,7 @@ Project.prototype._readInstalled = function () {
.spread(function (pkgMeta) {
decEndpoints[name] = {
name: name,
source: pkgMeta._source,
source: pkgMeta._originalSource || pkgMeta._source,
target: pkgMeta._target,
canonicalDir: path.dirname(metaFile),
pkgMeta: pkgMeta
@@ -638,8 +630,15 @@ Project.prototype._readLinks = function () {
return;
}
// Skip links to files (see #783)
if (!valid.isDirectory()) {
return;
}
name = path.basename(dir);
return readJson(dir, { name: name })
return readJson(dir, {
assume: { name: name }
})
.spread(function (json, deprecated) {
if (deprecated) {
that._logger.warn('deprecated', 'Package ' + name + ' is using the deprecated ' + deprecated);
@@ -747,6 +746,7 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
var json = endpointParser.json2decomposed(key, value);
var restored;
var compatible;
var originalSource;
// Check if the dependency is not installed
if (!local) {
@@ -772,6 +772,10 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
restored = local;
mout.object.mixIn(local, json);
}
// Check if source changed, marking as different if it did
originalSource = mout.object.get(local, 'pkgMeta._originalSource');
restored.different = originalSource && originalSource !== json.source;
}
// Cross reference

View File

@@ -1,11 +1,11 @@
var fs = require('graceful-fs');
var path = require('path');
var semver = require('semver');
var mout = require('mout');
var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var LRU = require('lru-cache');
var semver = require('../util/semver');
var readJson = require('../util/readJson');
var copy = require('../util/copy');
var md5 = require('../util/md5');
@@ -49,10 +49,7 @@ ResolveCache.prototype.retrieve = function (source, target) {
// If target is a semver, find a suitable version
if (semver.validRange(target)) {
suitable = mout.array.find(versions, function (version) {
return semver.valid(version) &&
semver.satisfies(version, target);
});
suitable = semver.maxSatisfying(versions, target, true);
if (suitable) {
return suitable;

View File

@@ -31,12 +31,13 @@ function getConstructor(source, config, registryClient) {
// Git case: git git+ssh, git+http, git+https
// .git at the end (probably ssh shorthand)
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source)) {
// 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 () {
// If it's a GitHub repository, return the specialized resolver
if (/(@|:\/\/)github.com[:\/]/i.test(source)) {
if (resolvers.GitHub.getOrgRepoPair(source)) {
return [resolvers.GitHub, source];
}
@@ -86,8 +87,15 @@ function getConstructor(source, config, registryClient) {
return promise
// Check if is a shorthand and expand it
.fail(function (err) {
var parts = source.split('/');
var parts;
// Skip ssh and/or URL with auth
if (/[:@]/.test(source)) {
throw err;
}
// Ensure exactly only one "/"
parts = source.split('/');
if (parts.length === 2) {
source = mout.string.interpolate(config.shorthandResolver, {
shorthand: source,

View File

@@ -7,10 +7,27 @@ var extract = require('../../util/extract');
var createError = require('../../util/createError');
function GitHubResolver(decEndpoint, config, logger) {
var match;
var pair;
GitRemoteResolver.call(this, decEndpoint, config, logger);
// Grab the org/repo
// /xxxxx/yyyyy.git or :xxxxx/yyyyy.git (.git is optional)
pair = GitHubResolver.getOrgRepoPair(this._source);
if (!pair) {
throw createError('Invalid GitHub URL', 'EINVEND', {
details: this._source + ' does not seem to be a valid GitHub URL'
});
}
this._org = pair.org;
this._repo = pair.repo;
// Ensure trailing for all protocols
if (!mout.string.endsWith(this._source, '.git')) {
this._source += '.git';
}
// Check if it's public
this._public = mout.string.startsWith(this._source, 'git://');
@@ -18,18 +35,6 @@ function GitHubResolver(decEndpoint, config, logger) {
if (this._config.proxy || this._config.httpsProxy) {
this._source = this._source.replace('git://', 'https://');
}
// Grab the org/repo
// /xxxxx/yyyyy.git or :xxxxx/yyyyy.git (.git is optional)
match = this._source.match(/[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?$/i);
if (!match) {
throw createError('Invalid GitHub URL', 'EINVEND', {
details: this._source + ' does not seem to be a valid GitHub URL'
});
}
this._org = match[1];
this._repo = match[2];
}
util.inherits(GitHubResolver, GitRemoteResolver);
@@ -44,6 +49,7 @@ GitHubResolver.prototype._checkout = function () {
return GitRemoteResolver.prototype._checkout.call(this);
}
var msg;
var tarballUrl = 'https://github.com/' + this._org + '/' + this._repo + '/archive/' + this._resolution.tag + '.tar.gz';
var file = path.join(this._tempDir, 'archive.tar.gz');
var reqHeaders = {};
@@ -66,12 +72,11 @@ GitHubResolver.prototype._checkout = function () {
headers: reqHeaders
})
.progress(function (state) {
var msg;
// Retry?
if (state.retry) {
msg = 'Download of ' + tarballUrl + ' failed with ' + state.error.code + ', ';
msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
that._logger.debug('error', state.error.message, { error: state.error });
return that._logger.warn('retry', msg);
}
@@ -88,7 +93,27 @@ GitHubResolver.prototype._checkout = function () {
to: that._tempDir
});
return extract(file, that._tempDir);
return extract(file, that._tempDir)
// Fallback to standard git clone if extraction failed
.fail(function (err) {
msg = 'Decompression of ' + path.basename(file) + ' failed' + (err.code ? ' with ' + err.code : '') + ', ';
msg += 'trying with git..';
that._logger.debug('error', err.message, { error: err });
that._logger.warn('retry', msg);
return that._cleanTempDir()
.then(GitRemoteResolver.prototype._checkout.bind(that));
});
// Fallback to standard git clone if download failed
}, function (err) {
msg = 'Download of ' + tarballUrl + ' failed' + (err.code ? ' with ' + err.code : '') + ', ';
msg += 'trying with git..';
that._logger.debug('error', err.message, { error: err });
that._logger.warn('retry', msg);
return that._cleanTempDir()
.then(GitRemoteResolver.prototype._checkout.bind(that));
});
};
@@ -101,4 +126,20 @@ GitHubResolver.prototype._savePkgMeta = function (meta) {
return GitRemoteResolver.prototype._savePkgMeta.call(this, meta);
};
// ----------------
GitHubResolver.getOrgRepoPair = function (url) {
var match;
match = url.match(/(?:@|:\/\/)github.com[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i);
if (!match) {
return null;
}
return {
org: match[1],
repo: match[2]
};
};
module.exports = GitHubResolver;

View File

@@ -1,26 +1,30 @@
var util = require('util');
var url = require('url');
var Q = require('q');
var mout = require('mout');
var LRU = require('lru-cache');
var GitResolver = require('./GitResolver');
var cmd = require('../../util/cmd');
function GitRemoteResolver(decEndpoint, config, logger) {
if (!mout.string.startsWith(decEndpoint.source, 'file://')) {
// Trim trailing slashes
decEndpoint.source = decEndpoint.source.replace(/\/+$/, '');
// Ensure trailing .git
if (!mout.string.endsWith(decEndpoint.source, '.git')) {
decEndpoint.source += '.git';
}
}
GitResolver.call(this, decEndpoint, config, logger);
if (!mout.string.startsWith(this._source, 'file://')) {
// Trim trailing slashes
this._source = this._source.replace(/\/+$/, '');
}
// If the name was guessed, remove the trailing .git
if (this._guessedName && mout.string.endsWith(this._name, '.git')) {
this._name = this._name.slice(0, -4);
}
// Get the host of this source
if (!/:\/\//.test(this._source)) {
this._host = url.parse('ssh://' + this._source).host;
} else {
this._host = url.parse(this._source).host;
}
}
util.inherits(GitRemoteResolver, GitResolver);
@@ -29,7 +33,6 @@ mout.object.mixIn(GitRemoteResolver, GitResolver);
// -----------------
GitRemoteResolver.prototype._checkout = function () {
var branch;
var promise;
var timer;
var reporter;
@@ -44,23 +47,10 @@ GitRemoteResolver.prototype._checkout = function () {
// If resolution is a commit, we need to clone the entire repo and check it out
// Because a commit is not a named ref, there's no better solution
if (resolution.type === 'commit') {
promise = cmd('git', ['clone', this._source, this._tempDir, '--progress'])
.then(cmd.bind(cmd, 'git', ['checkout', resolution.commit], { cwd: this._tempDir }));
promise = this._slowClone(resolution);
// Otherwise we are checking out a named ref so we can optimize it
} else {
branch = resolution.tag || resolution.branch;
promise = cmd('git', ['clone', this._source, '-b', branch, '--depth', 1, '--progress', '.'], { 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;
}
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 });
});
promise = this._fastClone(resolution);
}
// Throttle the progress reporter to 1 time each sec
@@ -82,12 +72,89 @@ GitRemoteResolver.prototype._checkout = function () {
}, 8000);
return promise
// Add additional proxy information to the error if necessary
.fail(function (err) {
that._suggestProxyWorkaround(err);
throw err;
})
// Clear timer at the end
.fin(function () {
clearTimeout(timer);
});
};
GitRemoteResolver.prototype._findResolution = function (target) {
var that = this;
// Override this function to include a meaningful message related to proxies
// if necessary
return GitResolver.prototype._findResolution.call(this, target)
.fail(function (err) {
that._suggestProxyWorkaround(err);
throw err;
});
};
// ------------------------------
GitRemoteResolver.prototype._slowClone = function (resolution) {
return cmd('git', ['clone', this._source, this._tempDir, '--progress'])
.then(cmd.bind(cmd, 'git', ['checkout', resolution.commit], { cwd: this._tempDir }));
};
GitRemoteResolver.prototype._fastClone = function (resolution) {
var branch,
args,
that = this;
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 (!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;
}
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)/i.test(err.details)
) {
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
}
throw err;
});
};
GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
if ((this._config.proxy || this._config.httpsProxy) &&
mout.string.startsWith(this._source, 'git://') &&
err.code === 'ECMDERR' && err.details
) {
err.details = err.details.trim();
err.details += '\n\nWhen under a proxy, you must configure git to use https:// instead of git://.';
err.details += '\nYou can configure it for every endpoint or for this specific host as follows:';
err.details += '\ngit config --global url."https://".insteadOf git://';
err.details += '\ngit config --global url."https://' + this._host + '".insteadOf git://' + this._host;
err.details += 'Ignore this suggestion if you already have this configured.';
}
};
// ------------------------------
// Grab refs remotely
@@ -123,4 +190,7 @@ GitRemoteResolver.refs = function (source) {
return value;
};
// Store hosts that do not support shallow clones here
GitRemoteResolver._noShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
module.exports = GitRemoteResolver;

View File

@@ -1,14 +1,16 @@
var util = require('util');
var path = require('path');
var Q = require('q');
var semver = require('semver');
var chmodr = require('chmodr');
var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var defaultConfig = require('../../config');
var hasGit;
@@ -20,6 +22,12 @@ try {
hasGit = false;
}
// Set template dir to the empty directory so that user templates are not run
// This environment variable is not multiple config aware but it's not documented
// anyway
mkdirp.sync(defaultConfig.storage.empty);
process.env.GIT_TEMPLATE_DIR = defaultConfig.storage.empty;
function GitResolver(decEndpoint, config, logger) {
Resolver.call(this, decEndpoint, config, logger);
@@ -84,6 +92,7 @@ GitResolver.refs = function (source) {
GitResolver.prototype._findResolution = function (target) {
var err;
var self = this.constructor;
var that = this;
target = target || this._target || '*';
@@ -98,27 +107,42 @@ GitResolver.prototype._findResolution = function (target) {
if (semver.validRange(target)) {
return self.versions(this._source, true)
.then(function (versions) {
var versionsArr,
version,
index;
versionsArr = versions.map(function (obj) { return obj.version; });
// If there are no tags and target is *,
// fallback to the latest commit on master
if (!versions.length && target === '*') {
return this._findResolution('master');
return that._findResolution('master');
}
// Find the highest one that satisfies the target
var version = mout.array.find(versions, function (version) {
return semver.satisfies(version.version, target);
}, this);
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) {
version = versions[index];
return that._resolution = { type: 'version', tag: version.tag, commit: version.commit };
}
// Check if there's an exact branch with this name as last resort
return self.branches(that._source)
.then(function (branches) {
// Use hasOwn because a branch could have a name like "hasOwnProperty"
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
}
if (!version) {
throw createError('No tag found that was able to satisfy ' + target, 'ENORESTARGET', {
details: !versions.length ?
'No versions found in ' + this._source :
'No versions found in ' + that._source :
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
});
}
return this._resolution = { type: 'version', tag: version.tag, commit: version.commit };
}.bind(this));
});
});
}
// Otherwise, target is either a tag or a branch
@@ -126,32 +150,32 @@ GitResolver.prototype._findResolution = function (target) {
return self.tags(this._source)
.then(function (tags) {
if (mout.object.hasOwn(tags, target)) {
return this._resolution = { type: 'tag', tag: target, commit: tags[target] };
return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
}
// Finally check if is a valid branch
return self.branches(this._source)
return self.branches(that._source)
.then(function (branches) {
// Use hasOwn because a branch could have a name like "hasOwnProperty"
if (!mout.object.hasOwn(branches, target)) {
branches = Object.keys(branches);
tags = Object.keys(tags);
err = createError('Tag/branch ' + target + ' does not exist', 'ENORESTARGET');
err.details = !tags.length ?
'No tags found in ' + this._source :
'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length ?
'No branches found in ' + this._source :
'Available branches: ' + branches.join(', ');
throw err;
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
}
return this._resolution = { type: 'branch', branch: target, commit: branches[target] };
}.bind(this));
}.bind(this));
branches = Object.keys(branches);
tags = Object.keys(tags);
err = createError('Tag/branch ' + target + ' does not exist', 'ENORESTARGET');
err.details = !tags.length ?
'No tags found in ' + that._source :
'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length ?
'No branches found in ' + that._source :
'Available branches: ' + branches.join(', ');
throw err;
});
});
};
GitResolver.prototype._cleanup = function () {
@@ -278,7 +302,7 @@ GitResolver.tags = function (source) {
var match = line.match(/^([a-f0-9]{40})\s+refs\/tags\/(\S+)/);
var tag;
if (match) {
if (match && !mout.string.endsWith(match[2], '^{}')) {
tag = match[2];
tags[match[2]] = match[1];
}

View File

@@ -3,6 +3,7 @@ var path = require('path');
var Q = require('q');
var tmp = require('tmp');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var readJson = require('../../util/readJson');
var createError = require('../../util/createError');
var removeIgnores = require('../../util/removeIgnores');
@@ -139,12 +140,10 @@ Resolver.clearRuntimeCache = function () {};
// -----------------
Resolver.prototype._createTempDir = function () {
var baseDir = path.join(this._config.tmp, 'bower');
return Q.nfcall(mkdirp, baseDir)
return Q.nfcall(mkdirp, this._config.tmp)
.then(function () {
return Q.nfcall(tmp.dir, {
template: path.join(baseDir, this._name + '-' + process.pid + '-XXXXXX'),
template: path.join(this._config.tmp, this._name + '-' + process.pid + '-XXXXXX'),
mode: 0777 & ~process.umask(),
unsafeCleanup: true
});
@@ -155,11 +154,30 @@ Resolver.prototype._createTempDir = function () {
}.bind(this));
};
Resolver.prototype._cleanTempDir = function () {
var tempDir = this._tempDir;
if (!tempDir) {
return Q.resolve();
}
// Delete and create folder
return Q.nfcall(rimraf, tempDir)
.then(function () {
return Q.nfcall(mkdirp, tempDir, 0777 & ~process.umask());
})
.then(function () {
return tempDir;
});
};
Resolver.prototype._readJson = function (dir) {
var that = this;
dir = dir || this._tempDir;
return readJson(dir, { name: this._name })
return readJson(dir, {
assume: { name: this._name }
})
.spread(function (json, deprecated) {
if (deprecated) {
that._logger.warn('deprecated', 'Package ' + that._name + ' is using the deprecated ' + deprecated);

View File

@@ -132,8 +132,9 @@ UrlResolver.prototype._download = function () {
// Retry?
if (state.retry) {
msg = 'Download of ' + that._source + ' failed with ' + state.error.code + ', ';
msg = 'Download of ' + that._source + ' failed' + (state.error.code ? ' with ' + state.error.code : '') + ', ';
msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
that._logger.debug('error', state.error.message, { error: state.error });
return that._logger.warn('retry', msg);
}

View File

@@ -1,4 +1,7 @@
var chalk = require('chalk');
var Q = require('q');
var promptly = require('promptly');
var createError = require('../util/createError');
function JsonRenderer() {
this._nrLogs = 0;
@@ -16,6 +19,7 @@ JsonRenderer.prototype.end = function (data) {
JsonRenderer.prototype.error = function (err) {
var message = err.message;
var stack;
err.id = err.code || 'error';
err.level = 'error';
@@ -26,6 +30,12 @@ JsonRenderer.prototype.error = function (err) {
delete err.message;
err.message = message;
// Stack
/*jshint camelcase:false*/
stack = err.fstream_stack || err.stack || 'N/A';
err.stacktrace = (Array.isArray(stack) ? stack.join('\n') : stack);
/*jshint camelcase:true*/
this.log(err);
this.end();
};
@@ -41,7 +51,68 @@ JsonRenderer.prototype.log = function (log) {
this._nrLogs++;
};
JsonRenderer.prototype.updateAvailable = function () {};
JsonRenderer.prototype.prompt = function (prompts) {
var promise = Q.resolve();
var answers = {};
var that = this;
prompts.forEach(function (prompt) {
var opts;
var funcName;
// Strip colors
prompt.message = chalk.stripColor(prompt.message);
// Prompt
opts = {
silent: true, // To not mess with JSON output
trim: false, // To allow " " to not assume the default value
default: prompt.default == null ? '' : prompt.default, // If default is null, make it '' so that it does not retry
validator: !prompt.validate ? null : function (value) {
var ret = prompt.validate(value);
if (typeof ret === 'string') {
throw ret;
}
return value;
},
};
// For now only "input", "confirm" and "password" are supported
switch (prompt.type) {
case 'input':
funcName = 'prompt';
break;
case 'confirm':
case 'password':
funcName = prompt.type;
break;
default:
promise = promise.then(function () {
throw createError('Unknown prompt type', 'ENOTSUP');
});
return;
}
promise = promise.then(function () {
// Log
prompt.level = 'prompt';
that.log(prompt);
return Q.nfcall(promptly[funcName], '', opts)
.then(function (answer) {
answers[prompt.name] = answer;
});
});
});
return promise.then(function () {
return answers;
});
};
JsonRenderer.prototype.updateNotice = function () {};
// -------------------------

View File

@@ -3,6 +3,8 @@ var chalk = require('chalk');
var path = require('path');
var mout = require('mout');
var archy = require('archy');
var Q = require('q');
var inquirer = require('inquirer');
var stringifyObject = require('stringify-object');
var os = require('os');
var pkg = require(path.join(__dirname, '../..', 'package.json'));
@@ -42,6 +44,7 @@ StandardRenderer.prototype.end = function (data) {
StandardRenderer.prototype.error = function (err) {
var str;
var stack;
this._guessOrigin(err);
@@ -61,8 +64,9 @@ StandardRenderer.prototype.error = function (err) {
// or if the error is a node error
if (this._config.verbose || !err.code || err.errno) {
/*jshint camelcase:false*/
stack = err.fstream_stack || err.stack || 'N/A';
str = chalk.yellow('\nStack trace:\n');
str += (err.fstream_stack ? err.fstream_stack.join('\n') : err.stack || 'N/A') + '\n';
str += (Array.isArray(stack) ? stack.join('\n') : stack) + '\n';
str += chalk.yellow('\nConsole trace:\n');
/*jshint camelcase:true*/
@@ -90,6 +94,23 @@ StandardRenderer.prototype.log = function (log) {
}
};
StandardRenderer.prototype.prompt = function (prompts) {
var deferred;
// Strip colors from the prompt if color is disabled
if (!this._config.color) {
prompts.forEach(function (prompt) {
prompt.message = chalk.stripColor(prompt.message);
});
}
// Prompt
deferred = Q.defer();
inquirer.prompt(prompts, deferred.resolve);
return deferred.promise;
};
StandardRenderer.prototype.updateNotice = function (data) {
var str = template.render('std/update-notice.std', data);
this._write(process.stderr, str);
@@ -162,27 +183,25 @@ StandardRenderer.prototype._search = function (results) {
};
StandardRenderer.prototype._info = function (data) {
var str;
var highlightedJson;
var str = '';
var pkgMeta = data;
var includeVersions = false;
// If the response is the whole package info
// render the appropriate template
if (typeof data === 'object' && data.name && data.versions) {
str = template.render('std/info.std', data);
} else {
highlightedJson = cardinal.highlight(stringifyObject(data, { indent: ' ' }), {
theme: {
String: {
_default: function (s) { return chalk.styles.cyan[0] + s + chalk.styles.cyan[1]; }
},
Identifier: {
_default: function (s) { return chalk.styles.green[0] + s + chalk.styles.green[1]; }
}
},
json: true
});
// If the response is the whole package info, the package meta
// is under the "latest" property
if (typeof data === 'object' && data.versions) {
pkgMeta = data.latest;
includeVersions = true;
}
str = '\n' + highlightedJson + '\n';
// Render package meta
if (pkgMeta != null) {
str += '\n' + this._highlightJson(pkgMeta) + '\n';
}
// Render the versions at the end
if (includeVersions) {
str += '\n' + template.render('std/info.std', data);
}
this._write(process.stdout, str);
@@ -288,6 +307,10 @@ StandardRenderer.prototype._solvedLog = function (log) {
this._incompatibleLog(log);
};
StandardRenderer.prototype._jsonLog = function (log) {
this._write(process.stdout, '\n' + this._highlightJson(log.data.json) + '\n\n');
};
StandardRenderer.prototype._cachedEntryLog = function (log) {
if (this._compact) {
log.message = log.origin;
@@ -365,6 +388,20 @@ StandardRenderer.prototype._write = function (stream, str) {
stream.write(str);
};
StandardRenderer.prototype._highlightJson = function (json) {
return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
theme: {
String: {
_default: chalk.cyan
},
Identifier: {
_default: chalk.green
}
},
json: true
});
};
StandardRenderer.prototype._tree2archy = function (node) {
var dependencies = mout.object.values(node.dependencies);
var version = !node.missing ? node.pkgMeta._release || node.pkgMeta.version : null;
@@ -375,12 +412,16 @@ StandardRenderer.prototype._tree2archy = function (node) {
label += ' ' + node.canonicalDir;
}
// State lables
// State labels
if (node.missing) {
label += chalk.red(' missing');
return label;
}
if (node.different) {
label += chalk.red(' different');
}
if (node.linked) {
label += chalk.magenta(' linked');
}

50
lib/util/createLink.js Normal file
View File

@@ -0,0 +1,50 @@
var fs = require('graceful-fs');
var path = require('path');
var Q = require('q');
var mkdirp = require('mkdirp');
var createError = require('./createError');
var isWin = process.platform === 'win32';
function createLink(src, dst, type) {
var dstDir = path.dirname(dst);
// Create directory
return Q.nfcall(mkdirp, dstDir)
// Check if source exists
.then(function () {
return Q.nfcall(fs.stat, src)
.fail(function (error) {
if (error.code === 'ENOENT') {
throw createError('Failed to create link to ' + path.basename(src), 'ENOENT', {
details: src + ' doest not exists or points to a non-existent file'
});
}
throw error;
});
})
// Create symlink
.then(function (stat) {
type = type || (stat.isDirectory() ? 'dir' : 'file');
return Q.nfcall(fs.symlink, src, dst, type)
.fail(function (err) {
if (!isWin || err.code !== 'EPERM') {
throw err;
}
// Try with type "junction" on Windows
// Junctions behave equally to true symlinks and can be created in
// non elevated terminal (well, not always..)
return Q.nfcall(fs.symlink, src, dst, 'junction')
.fail(function (err) {
throw createError('Unable to create link to ' + path.basename(src), err.code, {
details: err.message.trim() + '\n\nTry running this command in an elevated terminal (run as root/administrator).'
});
});
});
});
}
module.exports = createLink;

View File

@@ -30,7 +30,10 @@ function download(url, file, options) {
// Retry on network errors
operation = retry.operation(options);
operation.attempt(function () {
var req = progress(request(url, options), {
var req;
var writeStream;
req = progress(request(url, options), {
delay: progressDelay
})
.on('response', function (res) {
@@ -58,10 +61,12 @@ function download(url, file, options) {
// Check if there are more retries
if (operation.retry(error)) {
// Ensure that there are no more "error" events from this request
req.removeAllListeners('error');
req.removeAllListeners('progress');
// Ensure that there are no more events from this request
req.removeAllListeners();
req.on('error', function () {});
// Ensure that there are no more events from the write stream
writeStream.removeAllListeners();
writeStream.on('error', function () {});
return deferred.notify({
retry: true,
@@ -75,7 +80,7 @@ function download(url, file, options) {
});
// Pipe read stream to write stream
req
writeStream = req
.pipe(fs.createWriteStream(file))
.on('error', deferred.reject)
.on('close', function () {

View File

@@ -2,10 +2,13 @@ var path = require('path');
var bowerJson = require('bower-json');
var Q = require('q');
// The valid options are the same as bower-json#readFile.
// If the "assume" option is passed, it will be used if no json file was found
// This promise is resolved with [json, deprecated, assumed]
// - json: The read json
// - deprecated: The deprecated filename being used or false otherwise
// - assumed: True if a dummy json was created if there is not json, false otherwise
// - assumed: True if a dummy json was returned if no json file was found, false otherwise
function readJson(file, options) {
options = options || {};
@@ -20,8 +23,8 @@ function readJson(file, options) {
return [json, deprecated, false];
}, function (err) {
// No json file was found, assume one
if (err.code === 'ENOENT' && options.name) {
return [bowerJson.parse({ name: options.name }), false, true];
if (err.code === 'ENOENT' && options.assume) {
return [bowerJson.parse(options.assume, options), false, true];
}
err.details = err.message;

74
lib/util/semver.js Normal file
View File

@@ -0,0 +1,74 @@
var semver = require('semver');
var mout = require('mout');
function maxSatisfying(versions, range, strictMatch) {
var version;
var filteredVersions;
// Filter only valid versions, since semver.maxSatisfying() throws an error
versions = versions.filter(function (version) {
return semver.valid(version);
});
// Exact version & range match
if (semver.valid(range)) {
version = mout.array.find(versions, function (version) {
return version === range;
});
if (version) {
return version;
}
}
// When strict match is enabled and range is *,
// give priority to non-pre-releases
// We do this by filtering every pre-release version
range = typeof range === 'string' ? range.trim() : range;
if (strictMatch && (!range || range === '*')) {
filteredVersions = versions.map(function (version) {
return !isPreRelease(version) ? version : null;
});
version = semver.maxSatisfying(filteredVersions, range);
if (version) {
return version;
}
}
// Fallback to regular semver max satisfies
return semver.maxSatisfying(versions, range);
}
function maxSatisfyingIndex(versions, range, strictMatch) {
var version = maxSatisfying(versions, range, strictMatch);
if (!version) {
return -1;
}
return versions.indexOf(version);
}
function clean(version) {
var parsed = semver.parse(version);
if (!parsed) {
return null;
}
// Keep builds!
return parsed.version + (parsed.build.length ? '+' + parsed.build.join('.') : '');
}
function isPreRelease(version) {
var parsed = semver.parse(version);
return parsed && parsed.prerelease && parsed.prerelease.length;
}
// Export a semver like object but with our custom functions
mout.object.mixIn(module.exports, semver, {
maxSatisfying: maxSatisfying,
maxSatisfyingIndex: maxSatisfyingIndex,
clean: clean,
valid: clean
});

View File

@@ -2,16 +2,17 @@ var Q = require('q');
var fs = require('graceful-fs');
function validLink(file) {
// Filter only those that are valid links
// Ensures that a file is a symlink that points
// to a valid file
return Q.nfcall(fs.lstat, file)
.then(function (stat) {
if (!stat.isSymbolicLink()) {
return [false, null];
.then(function (lstat) {
if (!lstat.isSymbolicLink()) {
return [false];
}
return Q.nfcall(fs.stat, file)
.then(function () {
return [true, null];
.then(function (stat) {
return [stat];
});
})
.fail(function (err) {

View File

@@ -1,6 +1,6 @@
{
"name": "bower",
"version": "1.1.1",
"version": "1.2.3",
"description": "The browser package manager.",
"author": "Twitter",
"licenses": [
@@ -21,11 +21,11 @@
"dependencies": {
"abbrev": "~1.0.4",
"archy": "0.0.2",
"bower-config": "~0.3.0",
"bower-config": "~0.4.3",
"bower-endpoint-parser": "~0.2.0",
"bower-json": "~0.3.0",
"bower-logger": "~0.1.0",
"bower-registry-client": "~0.1.2",
"bower-json": "~0.4.0",
"bower-logger": "~0.2.1",
"bower-registry-client": "~0.1.4",
"cardinal": "~0.4.0",
"chalk": "~0.2.0",
"chmodr": "~0.1.0",
@@ -43,7 +43,7 @@
"open": "~0.0.3",
"promptly": "~0.2.0",
"q": "~0.9.2",
"request": "~2.25.0",
"request": "~2.27.0",
"request-progress": "~0.2.0",
"retry": "~0.6.0",
"rimraf": "~2.2.0",
@@ -66,7 +66,7 @@
"mocha": "~1.12.0",
"nock": "~0.22.0",
"istanbul": "~0.1.42",
"proxyquire": "~0.4.1"
"proxyquire": "~0.5.0"
},
"scripts": {
"test": "grunt test"

View File

@@ -14,6 +14,11 @@
"shorthand": "-p",
"flag": "--paths",
"description": "Generates a simple JSON source mapping"
},
{
"shorthand": "-r",
"flag": "--relative",
"description": "Make paths relative to the directory config property, which defaults to bower_components"
}
]
}

View File

@@ -1,10 +1,10 @@
{{#cyan}}{{name}}{{/cyan}}
{{#if versions}} Versions:
{{#if versions}}{{#cyan}}Available versions:{{/cyan}}
{{#condense}}
{{#versions}}
- {{.}}
- {{.}}
{{/versions}}
{{/condense}}
{{else}} No versions available.
You can request info for a specific version with 'bower info {{name}}#<version>'
{{else}}No versions available.
{{/if}}

View File

@@ -563,6 +563,18 @@ describe('PackageRepository', function () {
});
});
describe('.getRegistryClient', function () {
it('should return the underlying registry client', function () {
expect(packageRepository.getRegistryClient()).to.be.an(RegistryClient);
});
});
describe('.getResolveCache', function () {
it('should return the underlying resolve cache', function () {
expect(packageRepository.getResolveCache()).to.be.an(ResolveCache);
});
});
describe('#clearRuntimeCache', function () {
it('should clear the resolve cache runtime cache', function () {
var called;
@@ -605,16 +617,4 @@ describe('PackageRepository', function () {
expect(called).to.be(true);
});
});
describe('.getRegistryClient', function () {
it('should return the underlying registry client', function () {
expect(packageRepository.getRegistryClient()).to.be.an(RegistryClient);
});
});
describe('.getResolveCache', function () {
it('should return the underlying resolve cache', function () {
expect(packageRepository.getResolveCache()).to.be.an(ResolveCache);
});
});
});

View File

@@ -432,7 +432,7 @@ describe('ResolveCache', function () {
.done();
});
it('should resolve to the highest package that matches a range target', function (next) {
it('should resolve to the highest package that matches a range target, ignoring pre-releases', function (next) {
var source = String(Math.random());
var sourceId = md5(source);
var sourceDir = path.join(cacheDir, sourceId);
@@ -449,6 +449,10 @@ describe('ResolveCache', function () {
fs.mkdirSync(path.join(sourceDir, '0.1.0'));
fs.writeFileSync(path.join(sourceDir, '0.1.0', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0-rc.1';
fs.mkdirSync(path.join(sourceDir, '0.1.0-rc.1'));
fs.writeFileSync(path.join(sourceDir, '0.1.0-rc.1', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.9';
fs.mkdirSync(path.join(sourceDir, '0.1.9'));
fs.writeFileSync(path.join(sourceDir, '0.1.9', '.bower.json'), JSON.stringify(json, null, ' '));
@@ -475,7 +479,71 @@ describe('ResolveCache', function () {
.done();
});
it('should resolve to the _wildcard package if target is * and there are not semver versions', function (next) {
it('should resolve to the highest package that matches a range target, not ignoring pre-releases if they are the only versions', function (next) {
var source = String(Math.random());
var sourceId = md5(source);
var sourceDir = path.join(cacheDir, sourceId);
var json = { name: 'foo' };
// Create some versions
fs.mkdirSync(sourceDir);
json.version = '0.1.0-rc.1';
fs.mkdirSync(path.join(sourceDir, '0.1.0-rc.1'));
fs.writeFileSync(path.join(sourceDir, '0.1.0-rc.1', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0-rc.2';
fs.mkdirSync(path.join(sourceDir, '0.1.0-rc.2'));
fs.writeFileSync(path.join(sourceDir, '0.1.0-rc.2', '.bower.json'), JSON.stringify(json, null, ' '));
resolveCache.retrieve(source, '~0.1.0')
.spread(function (canonicalDir, pkgMeta) {
expect(pkgMeta).to.be.an('object');
expect(pkgMeta.version).to.equal('0.1.0-rc.2');
expect(canonicalDir).to.equal(path.join(sourceDir, '0.1.0-rc.2'));
next();
})
.done();
});
it('should resolve to exact match (including build metadata) if available', function (next) {
var source = String(Math.random());
var sourceId = md5(source);
var sourceDir = path.join(cacheDir, sourceId);
var json = { name: 'foo' };
// Create some versions
fs.mkdirSync(sourceDir);
json.version = '0.1.0';
fs.mkdirSync(path.join(sourceDir, '0.1.0'));
fs.writeFileSync(path.join(sourceDir, '0.1.0', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0+build.4';
fs.mkdirSync(path.join(sourceDir, '0.1.0+build.4'));
fs.writeFileSync(path.join(sourceDir, '0.1.0+build.4', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0+build.5';
fs.mkdirSync(path.join(sourceDir, '0.1.0+build.5'));
fs.writeFileSync(path.join(sourceDir, '0.1.0+build.5', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0+build.6';
fs.mkdirSync(path.join(sourceDir, '0.1.0+build.6'));
fs.writeFileSync(path.join(sourceDir, '0.1.0+build.6', '.bower.json'), JSON.stringify(json, null, ' '));
resolveCache.retrieve(source, '0.1.0+build.5')
.spread(function (canonicalDir, pkgMeta) {
expect(pkgMeta).to.be.an('object');
expect(pkgMeta.version).to.equal('0.1.0+build.5');
expect(canonicalDir).to.equal(path.join(sourceDir, '0.1.0+build.5'));
next();
})
.done();
});
it('should resolve to the _wildcard package if target is * and there are no semver versions', function (next) {
var source = String(Math.random());
var sourceId = md5(source);
var sourceDir = path.join(cacheDir, sourceId);
@@ -821,9 +889,11 @@ describe('ResolveCache', function () {
expectedJson = fs.readFileSync(path.join(__dirname, '../assets/resolve-cache/list-json-1.json'));
expectedJson = expectedJson.toString();
// Trim absolute bower path from json
mout.object.forOwn(entries, function (entry) {
// Trim absolute bower path from json
entry.canonicalDir = entry.canonicalDir.substr(bowerDir.length);
// Convert windows \ paths to /
entry.canonicalDir = entry.canonicalDir.replace(/\\/g, '/');
});
json = JSON.stringify(entries, null, ' ');

View File

@@ -43,40 +43,44 @@ describe('resolverFactory', function () {
endpoints = {
// git:
'git://hostname.com/user/project': 'git://hostname.com/user/project',
'git://hostname.com/user/project/': 'git://hostname.com/user/project',
'git://hostname.com/user/project.git': 'git://hostname.com/user/project.git',
'git://hostname.com/user/project.git/': 'git://hostname.com/user/project.git',
// git@:
'git@hostname.com:user/project': 'git@hostname.com:user/project',
'git@hostname.com:user/project/': 'git@hostname.com:user/project',
'git@hostname.com:user/project.git': 'git@hostname.com:user/project.git',
'git@hostname.com:user/project.git/': 'git@hostname.com:user/project.git',
// git+ssh:
'git+ssh://user@hostname.com:project': 'ssh://user@hostname.com:project.git',
'git+ssh://user@hostname.com:project/': 'ssh://user@hostname.com:project.git',
'git+ssh://user@hostname.com:project': 'ssh://user@hostname.com:project',
'git+ssh://user@hostname.com:project/': 'ssh://user@hostname.com:project',
'git+ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git',
'git+ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git',
'git+ssh://user@hostname.com/project': 'ssh://user@hostname.com/project.git',
'git+ssh://user@hostname.com/project/': 'ssh://user@hostname.com/project.git',
'git+ssh://user@hostname.com/project': 'ssh://user@hostname.com/project',
'git+ssh://user@hostname.com/project/': 'ssh://user@hostname.com/project',
'git+ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git',
'git+ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git',
// git+http
'git+http://hostname.com/project/blah': 'http://hostname.com/project/blah.git',
'git+http://hostname.com/project/blah/': 'http://hostname.com/project/blah.git',
'git+http://hostname.com/project/blah': 'http://hostname.com/project/blah',
'git+http://hostname.com/project/blah/': 'http://hostname.com/project/blah',
'git+http://hostname.com/project/blah.git': 'http://hostname.com/project/blah.git',
'git+http://hostname.com/project/blah.git/': 'http://hostname.com/project/blah.git',
'git+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah.git',
'git+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah.git',
'git+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah',
'git+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah',
'git+http://user@hostname.com/project/blah.git': 'http://user@hostname.com/project/blah.git',
'git+http://user@hostname.com/project/blah.git/': 'http://user@hostname.com/project/blah.git',
// git+https
'git+https://hostname.com/project/blah': 'https://hostname.com/project/blah.git',
'git+https://hostname.com/project/blah/': 'https://hostname.com/project/blah.git',
'git+https://hostname.com/project/blah': 'https://hostname.com/project/blah',
'git+https://hostname.com/project/blah/': 'https://hostname.com/project/blah',
'git+https://hostname.com/project/blah.git': 'https://hostname.com/project/blah.git',
'git+https://hostname.com/project/blah.git/': 'https://hostname.com/project/blah.git',
'git+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah.git',
'git+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah.git',
'git+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah',
'git+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah',
'git+https://user@hostname.com/project/blah.git': 'https://user@hostname.com/project/blah.git',
'git+https://user@hostname.com/project/blah.git/': 'https://user@hostname.com/project/blah.git',
@@ -92,7 +96,7 @@ describe('resolverFactory', function () {
'http://user@hostname.com/project.git': 'http://user@hostname.com/project.git',
'http://user@hostname.com/project.git/': 'http://user@hostname.com/project.git',
// https
// https .git$
'https://hostname.com/project.git': 'https://hostname.com/project.git',
'https://hostname.com/project.git/': 'https://hostname.com/project.git',
'https://user@hostname.com/project.git': 'https://user@hostname.com/project.git',
@@ -150,12 +154,16 @@ describe('resolverFactory', function () {
gitHub = {
// git:
'git://github.com/user/project/blah.git': 'git://github.com/user/project/blah.git',
'git://github.com/user/project/blah.git/': 'git://github.com/user/project/blah.git',
'git://github.com/user/project': 'git://github.com/user/project.git',
'git://github.com/user/project/': 'git://github.com/user/project.git',
'git://github.com/user/project.git': 'git://github.com/user/project.git',
'git://github.com/user/project.git/': 'git://github.com/user/project.git',
// git@:
'git@github.com:user/project/blah.git': 'git@github.com:user/project/blah.git',
'git@github.com:user/project/blah.git/': 'git@github.com:user/project/blah.git',
'git@github.com:user/project': 'git@github.com:user/project.git',
'git@github.com:user/project/': 'git@github.com:user/project.git',
'git@github.com:user/project.git': 'git@github.com:user/project.git',
'git@github.com:user/project.git/': 'git@github.com:user/project.git',
// git+ssh:
'git+ssh://git@github.com:project/blah': 'ssh://git@github.com:project/blah.git',
@@ -210,19 +218,20 @@ describe('resolverFactory', function () {
};
nonGitHub = [
'git://xxxxgithub.com/user/project/blah.git',
'git@xxxxgithub.com:user:project/blah.git',
'git@xxxxgithub.com:user/project/blah.git',
'git+ssh://git@xxxxgithub.com:project/blah',
'git+ssh://git@xxxxgithub.com/project/blah',
'git+http://user@xxxxgithub.com/project/blah',
'git+https://user@xxxxgithub.com/project/blah',
'ssh://git@xxxxgithub.com:project:blah.git',
'ssh://git@xxxxgithub.com:project/blah.git',
'http://xxxxgithub.com/project/blah.git',
'https://xxxxgithub.com/project/blah.git',
'http://user@xxxxgithub.com/project/blah.git',
'https://user@xxxxgithub.com/project/blah.git'
'git://github.com/user/project/bleh.git',
'git://xxxxgithub.com/user/project.git',
'git@xxxxgithub.com:user:project.git',
'git@xxxxgithub.com:user/project.git',
'git+ssh://git@xxxxgithub.com:user/project',
'git+ssh://git@xxxxgithub.com/user/project',
'git+http://user@xxxxgithub.com/user/project',
'git+https://user@xxxxgithub.com/user/project',
'ssh://git@xxxxgithub.com:user/project.git',
'ssh://git@xxxxgithub.com/user/project.git',
'http://xxxxgithub.com/user/project.git',
'https://xxxxgithub.com/user/project.git',
'http://user@xxxxgithub.com/user/project.git',
'https://user@xxxxgithub.com/user/project.git'
];
// Test GitHub ones
@@ -494,6 +503,7 @@ describe('resolverFactory', function () {
callFactory({ source: 'bower/bower' })
.then(function (resolver) {
var config;
expect(resolver.getSource()).to.equal('git://github.com/bower/bower.git');
config = mout.object.fillIn({
@@ -503,12 +513,26 @@ describe('resolverFactory', function () {
return callFactory({ source: 'IndigoUnited/promptly' }, config);
})
.then(function (resolver) {
expect(resolver.getSource()).to.equal('git://bower.io/IndigoUnited/promptly/IndigoUnited/promptly.git');
expect(resolver.getSource()).to.equal('git://bower.io/IndigoUnited/promptly/IndigoUnited/promptly');
next();
})
.done();
});
it('should not expand using the shorthand resolver if it looks like a SSH URL', function (next) {
callFactory({ source: 'bleh@xxx.com:foo/bar' })
.then(function (resolver) {
throw new Error('Should have failed');
}, function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOTFOUND');
expect(err.message).to.contain('bleh@xxx.com:foo/bar');
next();
})
.done();
});
it('should error out if there\'s no suitable resolver for a given source', function (next) {
resolverFactory({ source: 'some-package-that-will-never-exist' }, defaultConfig, logger)
.then(function () {

View File

@@ -13,7 +13,9 @@ describe('GitHub', function () {
before(function () {
logger = new Logger();
});
beforeEach(function () {
// Turn off strict ssl because it gives problems with nock
defaultConfig.strictSsl = false;
});
@@ -38,6 +40,19 @@ describe('GitHub', function () {
describe('.constructor', function () {
it.skip('should throw an error on invalid GitHub URLs');
it('should ensure .git in the source', function () {
var resolver;
resolver = create('git://github.com/twitter/bower');
expect(resolver.getSource()).to.equal('git://github.com/twitter/bower.git');
resolver = create('git://github.com/twitter/bower.git');
expect(resolver.getSource()).to.equal('git://github.com/twitter/bower.git');
resolver = create('git://github.com/twitter/bower.git/');
expect(resolver.getSource()).to.equal('git://github.com/twitter/bower.git');
});
});
describe('.resolve', function () {
@@ -62,8 +77,68 @@ describe('GitHub', function () {
.done();
});
it('should retry using the GitRemoteResolver mechanism if download failed', function (next) {
var resolver;
var retried;
nock('https://github.com')
.get('/IndigoUnited/events-emitter/archive/0.1.0.tar.gz')
.reply(200, 'this is not a valid tar');
logger.on('log', function (entry) {
if (entry.level === 'warn' && entry.id === 'retry') {
retried = true;
}
});
resolver = create({ source: 'git://github.com/IndigoUnited/events-emitter.git', target: '0.1.0' });
// Monkey patch source to file://
resolver._source = 'file://' + testPackage;
resolver.resolve()
.then(function (dir) {
expect(retried).to.be(true);
expect(fs.existsSync(path.join(dir, 'foo'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'bar'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
next();
})
.done();
});
it('should retry using the GitRemoteResolver mechanism if extraction failed', function (next) {
var resolver;
var retried;
nock('https://github.com')
.get('/IndigoUnited/events-emitter/archive/0.1.0.tar.gz')
.reply(500);
logger.on('log', function (entry) {
if (entry.level === 'warn' && entry.id === 'retry') {
retried = true;
}
});
resolver = create({ source: 'git://github.com/IndigoUnited/events-emitter.git', target: '0.1.0' });
// Monkey patch source to file://
resolver._source = 'file://' + testPackage;
resolver.resolve()
.then(function (dir) {
expect(retried).to.be(true);
expect(fs.existsSync(path.join(dir, 'foo'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'bar'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
next();
})
.done();
});
it('should fallback to the GitRemoteResolver mechanism if resolution is not a tag', function (next) {
var resolver = create({ source: 'file://' + testPackage, target: '2af02ac6ddeaac1c2f4bead8d6287ce54269c039' });
var resolver = create({ source: 'git://github.com/foo/bar.git', target: '2af02ac6ddeaac1c2f4bead8d6287ce54269c039' });
var originalCheckout = GitRemoteResolver.prototype._checkout;
var called;
@@ -72,6 +147,9 @@ describe('GitHub', function () {
return originalCheckout.apply(this, arguments);
};
// Monkey patch source to file://
resolver._source = 'file://' + testPackage;
resolver.resolve()
.then(function (dir) {
expect(fs.existsSync(path.join(dir, 'foo'))).to.be(true);

View File

@@ -45,22 +45,6 @@ describe('GitRemoteResolver', function () {
resolver = create('git://github.com');
expect(resolver.getName()).to.equal('github.com');
});
it('should ensure .git in the source (except if protocol is file://)', function () {
var resolver;
resolver = create('git://github.com/twitter/bower');
expect(resolver.getSource()).to.equal('git://github.com/twitter/bower.git');
resolver = create('git://github.com/twitter/bower.git');
expect(resolver.getSource()).to.equal('git://github.com/twitter/bower.git');
resolver = create('git://github.com/twitter/bower.git/');
expect(resolver.getSource()).to.equal('git://github.com/twitter/bower.git');
resolver = create('file://' + testPackage);
expect(resolver.getSource()).to.equal('file://' + testPackage);
});
});
describe('.resolve', function () {
@@ -122,6 +106,7 @@ describe('GitRemoteResolver', function () {
.done();
});
it.skip('should handle gracefully servers that do not support --depth=1');
it.skip('should report progress when it takes too long to clone');
});

View File

@@ -38,6 +38,11 @@ describe('GitResolver', function () {
return new GitResolver(decEndpoint, config || defaultConfig, logger);
}
describe('misc', function () {
it.skip('should error out if git is not installed');
it.skip('should setup git template dir to an empty folder');
});
describe('.hasNew', function () {
before(function () {
mkdirp.sync(tempDir);
@@ -451,14 +456,15 @@ describe('GitResolver', function () {
.done();
});
it('should resolve "*" to the latest version if a repository has valid semver tags', function (next) {
it('should resolve "*" to the latest version if a repository has valid semver tags, ignoring pre-releases', function (next) {
var resolver;
GitResolver.refs = function () {
return Q.resolve([
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.0',
'cccccccccccccccccccccccccccccccccccccccc refs/tags/v0.1.1'
'cccccccccccccccccccccccccccccccccccccccc refs/tags/v0.1.1',
'dddddddddddddddddddddddddddddddddddddddd refs/tags/0.2.0-rc.1' // Should ignore release candidates
]);
};
@@ -475,6 +481,30 @@ describe('GitResolver', function () {
.done();
});
it('should resolve "*" to the latest version if a repository has valid semver tags, not ignoring pre-releases if they are the only versions', function (next) {
var resolver;
GitResolver.refs = function () {
return Q.resolve([
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.0-rc.1',
'cccccccccccccccccccccccccccccccccccccccc refs/tags/0.1.0-rc.2'
]);
};
resolver = create('foo');
resolver._findResolution('*')
.then(function (resolution) {
expect(resolution).to.eql({
type: 'version',
tag: '0.1.0-rc.2',
commit: 'cccccccccccccccccccccccccccccccccccccccc'
});
next();
})
.done();
});
it('should resolve to the latest version that matches a range/version', function (next) {
var resolver;
@@ -501,6 +531,83 @@ describe('GitResolver', function () {
.done();
});
it('should resolve to a branch even if target is a range/version that does not exist', function (next) {
var resolver;
// See #771
GitResolver.refs = function () {
return Q.resolve([
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/3.0.0-wip',
'cccccccccccccccccccccccccccccccccccccccc refs/tags/v0.1.1'
]);
};
resolver = create('foo');
resolver._findResolution('3.0.0-wip')
.then(function (resolution) {
expect(resolution).to.eql({
type: 'branch',
branch: '3.0.0-wip',
commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
});
next();
})
.done();
});
it('should resolve to the latest pre-release version that matches a range/version', function (next) {
var resolver;
GitResolver.refs = function () {
return Q.resolve([
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.0',
'cccccccccccccccccccccccccccccccccccccccc refs/tags/v0.1.1',
'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee refs/tags/0.2.0',
'ffffffffffffffffffffffffffffffffffffffff refs/tags/v0.2.1-rc.1'
]);
};
resolver = create('foo');
resolver._findResolution('~0.2.1')
.then(function (resolution) {
expect(resolution).to.eql({
type: 'version',
tag: 'v0.2.1-rc.1',
commit: 'ffffffffffffffffffffffffffffffffffffffff'
});
next();
})
.done();
});
it('should resolve to the exact version if exists', function (next) {
var resolver;
GitResolver.refs = function () {
return Q.resolve([
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.8.1',
'cccccccccccccccccccccccccccccccccccccccc refs/tags/0.8.1+build.1',
'dddddddddddddddddddddddddddddddddddddddd refs/tags/0.8.1+build.2',
'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee refs/tags/0.8.1+build.3'
]);
};
resolver = create('foo');
resolver._findResolution('0.8.1+build.2')
.then(function (resolution) {
expect(resolution).to.eql({
type: 'version',
tag: '0.8.1+build.2',
commit: 'dddddddddddddddddddddddddddddddddddddddd'
});
next();
})
.done();
});
it('should fail to resolve if none of the versions matched a range/version', function (next) {
var resolver;
@@ -661,7 +768,7 @@ describe('GitResolver', function () {
var resolver = create('foo');
var dst = path.join(tempDir, '.git');
this.timeout(15000); // Give some time to copy
this.timeout(30000); // Give some time to copy
// Copy .git folder to the tempDir
copy.copyDir(path.resolve(__dirname, '../../../.git'), dst, {
@@ -772,6 +879,18 @@ describe('GitResolver', function () {
var json = JSON.parse(contents.toString());
expect(json._release).to.equal('0.0.1');
})
// Test with type 'version' + build metadata
.then(function () {
resolver._resolution = { type: 'version', tag: '0.0.1+build.5', commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' };
return resolver._savePkgMeta({ name: 'foo' });
})
.then(function () {
return Q.nfcall(fs.readFile, metaFile);
})
.then(function (contents) {
var json = JSON.parse(contents.toString());
expect(json._release).to.equal('0.0.1+build.5');
})
// Test with type 'tag'
.then(function () {
resolver._resolution = { type: 'tag', tag: '0.0.1', commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' };
@@ -1262,8 +1381,8 @@ describe('GitResolver', function () {
.then(function (versions) {
expect(versions).to.eql([
{ version: '0.2.1', tag: 'v0.2.1', commit: 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' },
{ version: '0.1.1', tag: '0.1.1+build.11', commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' },
{ version: '0.1.1', tag: '0.1.1+build.100', commit: 'cccccccccccccccccccccccccccccccccccccccc' },
{ version: '0.1.1+build.11', tag: '0.1.1+build.11', commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' },
{ version: '0.1.1+build.100', tag: '0.1.1+build.100', commit: 'cccccccccccccccccccccccccccccccccccccccc' },
{ version: '0.1.1', tag: '0.1.1', commit: 'ffffffffffffffffffffffffffffffffffffffff' },
{ version: '0.1.1-rc.200', tag: '0.1.1-rc.200', commit: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' },
{ version: '0.1.1-rc.22', tag: '0.1.1-rc.22', commit: 'dddddddddddddddddddddddddddddddddddddddd' },

View File

@@ -16,8 +16,16 @@ describe('Resolver', function () {
var tempDir = path.resolve(__dirname, '../../assets/tmp');
var testPackage = path.resolve(__dirname, '../../assets/package-a');
var logger;
var dirMode0777;
before(function () {
var stat;
mkdirp.sync(tempDir);
stat = fs.statSync(tempDir);
dirMode0777 = stat.mode;
rimraf.sync(tempDir);
logger = new Logger();
});
@@ -438,21 +446,7 @@ describe('Resolver', function () {
});
describe('._createTempDir', function () {
var dirMode0777;
before(function () {
var stat;
mkdirp.sync(tempDir);
stat = fs.statSync(tempDir);
dirMode0777 = stat.mode;
});
after(function (next) {
rimraf(tempDir, next);
});
it('should create a directory inside a bower folder, located within the OS temp folder', function (next) {
it('should create a directory inside a "username/bower" folder, located within the OS temp folder', function (next) {
var resolver = create('foo');
resolver._createTempDir()
@@ -466,8 +460,11 @@ describe('Resolver', function () {
dirname = path.dirname(dir);
osTempDir = path.resolve(tmp.tmpdir);
expect(dir.indexOf(osTempDir)).to.be(0);
expect(dir.indexOf(defaultConfig.tmp)).to.be(0);
expect(path.basename(dirname)).to.equal('bower');
expect(path.dirname(dirname)).to.equal(osTempDir);
expect(path.dirname(path.dirname(dirname))).to.equal(osTempDir);
next();
})
.done();
@@ -488,15 +485,15 @@ describe('Resolver', function () {
});
it('should remove the folder after execution', function (next) {
var bowerOsTempDir = path.join(tmp.tmpdir, 'bower');
this.timeout(15000); // Give some time to execute
rimraf(bowerOsTempDir, function (err) {
rimraf(defaultConfig.tmp, function (err) {
if (err) return next(err);
cmd('node', ['test/assets/test-temp-dir/test.js'], { cwd: path.resolve(__dirname, '../../..') })
.then(function () {
expect(fs.existsSync(bowerOsTempDir)).to.be(true);
expect(fs.readdirSync(bowerOsTempDir)).to.eql([]);
expect(fs.existsSync(defaultConfig.tmp)).to.be(true);
expect(fs.readdirSync(defaultConfig.tmp)).to.eql([]);
next();
}, function (err) {
next(new Error(err.details));
@@ -506,17 +503,15 @@ describe('Resolver', function () {
});
it('should remove the folder on an uncaught exception', function (next) {
var bowerOsTempDir = path.join(tmp.tmpdir, 'bower');
rimraf(bowerOsTempDir, function (err) {
rimraf(defaultConfig.tmp, function (err) {
if (err) return next(err);
cmd('node', ['test/assets/test-temp-dir/test-exception.js'], { cwd: path.resolve(__dirname, '../../..') })
.then(function () {
next(new Error('The command should have failed'));
}, function () {
expect(fs.existsSync(bowerOsTempDir)).to.be(true);
expect(fs.readdirSync(bowerOsTempDir)).to.eql([]);
expect(fs.existsSync(defaultConfig.tmp)).to.be(true);
expect(fs.readdirSync(defaultConfig.tmp)).to.eql([]);
next();
})
.done();
@@ -536,6 +531,60 @@ describe('Resolver', function () {
});
});
describe('._cleanTempDir', function () {
it('should not error out if temporary dir is not yet created', function (next) {
var resolver = create('foo');
resolver._cleanTempDir()
.then(next.bind(null))
.done();
});
it('should delete the temporary folder contents', function (next) {
var resolver = create('foo');
resolver._createTempDir()
.then(resolver._cleanTempDir.bind(resolver))
.then(function (dir) {
expect(dir).to.equal(resolver.getTempDir());
expect(fs.readdirSync(dir).length).to.be(0);
next();
})
.done();
});
it('should keep the mode', function (next) {
var resolver = create('foo');
resolver._createTempDir()
.then(resolver._cleanTempDir.bind(resolver))
.then(function (dir) {
var stat = fs.statSync(dir);
var expectedMode = dirMode0777 & ~process.umask();
expect(stat.mode).to.equal(expectedMode);
next();
})
.done();
});
it('should keep the dir path', function (next) {
var resolver = create('foo');
var tempDir;
resolver._createTempDir()
.then(function (dir) {
tempDir = dir;
return resolver._cleanTempDir();
})
.then(function (dir) {
expect(dir).to.equal(tempDir);
next();
})
.done();
});
});
describe('._readJson', function () {
afterEach(function (next) {
rimraf(tempDir, next);