mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ef5a1dd2 | ||
|
|
29ec793386 | ||
|
|
187457df13 | ||
|
|
2eea214a42 | ||
|
|
639c2290b7 | ||
|
|
3044dcd3af | ||
|
|
3d64b16227 | ||
|
|
7bd22a5103 | ||
|
|
dcdd8cb3db | ||
|
|
e93c5f8265 | ||
|
|
77be4520dc | ||
|
|
cf76bcb2aa | ||
|
|
b272a61eac | ||
|
|
4ed5f278f8 | ||
|
|
6a16850960 | ||
|
|
0eac8bb96e | ||
|
|
46ae1e759c | ||
|
|
e0c1308c2a | ||
|
|
430dc04b09 | ||
|
|
39209f3bda | ||
|
|
0eaa43c05d | ||
|
|
5eadd36107 | ||
|
|
c6a370309d | ||
|
|
13e2514830 | ||
|
|
0b8be97fb5 | ||
|
|
c327dcb255 | ||
|
|
1b505cc0e6 | ||
|
|
35d83d2306 | ||
|
|
dd23feb5ea | ||
|
|
e21d7bd000 | ||
|
|
d18cfa7b59 | ||
|
|
eca956cf80 | ||
|
|
587cc5acdd | ||
|
|
b5e9a4c191 | ||
|
|
3ced03ca7f | ||
|
|
9fa08fee99 | ||
|
|
12baabcc89 | ||
|
|
13839e9384 | ||
|
|
d42a564de8 | ||
|
|
71d083a552 | ||
|
|
cf802c368d | ||
|
|
72e6e61970 | ||
|
|
4402b80ec9 | ||
|
|
3f0dbef7ea | ||
|
|
1ee8abf098 | ||
|
|
55eb1e2290 | ||
|
|
906990d9b5 | ||
|
|
518f3d2a8f | ||
|
|
6ba6ea0084 | ||
|
|
01c499d73f | ||
|
|
7621a71359 | ||
|
|
17f72f0ae2 | ||
|
|
47094ef046 | ||
|
|
6e28e79b29 | ||
|
|
463b258409 | ||
|
|
49d4c96ddf | ||
|
|
197d3e9d36 | ||
|
|
adcc368238 | ||
|
|
eff97c4d28 | ||
|
|
d2aeed7a0c | ||
|
|
bf4266c2e3 | ||
|
|
814a0f9016 | ||
|
|
098753520f | ||
|
|
43ae12f63b | ||
|
|
46937bbb87 | ||
|
|
5bf0c768f7 | ||
|
|
f124fd4c23 | ||
|
|
ff49d24743 | ||
|
|
99ca255aad | ||
|
|
d1a63b6fe0 | ||
|
|
3530a33fe7 | ||
|
|
d08efcfe3c | ||
|
|
337c0f2d0a | ||
|
|
1acf4a53d3 | ||
|
|
a2b0fb7d82 | ||
|
|
088c488e2d | ||
|
|
64ef934a3c | ||
|
|
39b690e96a | ||
|
|
08fc86698d | ||
|
|
7bacf8c425 | ||
|
|
896672c4f4 | ||
|
|
a6552d7d54 | ||
|
|
cacef7b316 | ||
|
|
7a8642db94 | ||
|
|
2b8e0fdf06 | ||
|
|
abe4264a13 | ||
|
|
60d9bfb2b8 | ||
|
|
9141b3020f | ||
|
|
367c3c3e95 | ||
|
|
41a3903ac9 | ||
|
|
146d6ac538 | ||
|
|
fce93e16ae | ||
|
|
fb12fc03bf | ||
|
|
9c49b097c8 | ||
|
|
524e83fca4 | ||
|
|
f5999e84a2 | ||
|
|
ee2640bc43 | ||
|
|
2b93aa000c |
52
CHANGELOG.md
52
CHANGELOG.md
@@ -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))
|
||||
|
||||
22
README.md
22
README.md
@@ -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),
|
||||
|
||||
15
bin/bower
15
bin/bower
@@ -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
|
||||
|
||||
63
lib/commands/cache/clean.js
vendored
63
lib/commands/cache/clean.js
vendored
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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 || '*';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () {};
|
||||
|
||||
// -------------------------
|
||||
|
||||
|
||||
@@ -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
50
lib/util/createLink.js
Normal 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;
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
74
lib/util/semver.js
Normal 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
|
||||
});
|
||||
@@ -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) {
|
||||
|
||||
14
package.json
14
package.json
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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, ' ');
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user