Compare commits

...

61 Commits

Author SHA1 Message Date
Adam Stankiewicz
e0a204d806 Try to fix the tests 2014-10-22 14:05:52 +02:00
Adam Stankiewicz
202e52739e Test script in travis.yml 2014-10-22 13:30:17 +02:00
Hans Christian Reinl
db7e9f54f0 Add BitBucket to Travis' known_hosts file
This is needed for the build process to work correctly
2014-10-21 23:25:21 +02:00
Hans Christian Reinl
8c63554e02 Add special Git resolver for BitBucket
This commit adds a BitBucket resolver for packages hosted on BitBucket
with shallow cloning. It includes everything the GitHub resolver
includes but for BitBucket.

Ref.: #1564
2014-10-21 16:40:47 +02:00
Adam Stankiewicz
1a2b5a4718 Bump version to 1.3.12 and update changelog 2014-10-20 16:32:45 +02:00
Adam Stankiewicz
636aae971e [doc] Explain why env in test/helpers.js is needed 2014-10-20 16:32:45 +02:00
Ray Shan
e7556b0e90 fix broken analytics tracking introduced in #1507 2014-10-20 16:32:45 +02:00
Adam Stankiewicz
7a35c0e050 Downgrade mout to 0.9.0, closes #1525 2014-10-20 16:32:45 +02:00
Adam Stankiewicz
37815a624d Update tar-fs version, fixes #1537 2014-10-20 16:32:45 +02:00
zhiyelee
f0fe4ab56b add linebreak 2014-10-20 16:32:45 +02:00
Rodolphe Gohard
e5d7c241bd Fix 0.x dependencies to prevent breaking API updates
Semver states
> Major version zero (0.y.z) is for initial development.
> Anything may change at any time. The public API should
> not be considered stable.
We already had a case where a tmp package update (0.0.24)
broke bower install. This can happen again with  0.x
dependencies that can introduce breaking API changes
anytime.
2014-10-20 16:32:45 +02:00
Laurent Goderre
bfa76a3862 Bump and update changelog 2014-10-20 16:32:45 +02:00
Laurent Goderre
3f60ffbc4c Added update test cases 2014-10-20 16:32:45 +02:00
Laurent Goderre
65b536f374 Added the missing install command to the update task
Fixes #1518
2014-10-20 16:32:45 +02:00
Adam Stankiewicz
6f3109d8a1 Bump and update changelog 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
1b147abfef [test] Make sure install ends before running next test 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
a954ca22ad [test] Use new temp location per process 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
e88a3f6729 Add tests to analytics and fix them 2014-10-20 16:32:44 +02:00
John Schulz
5a223f7f90 "analytics" value in \.bowerrc\ removes need to prompt user 2014-10-20 16:32:44 +02:00
Laurent Goderre
f05bf58c43 Update p-throttle to allow displaying the output of hooks
Fixes #1484
2014-10-20 16:32:44 +02:00
Adam Stankiewicz
5b9665587a [test] Show coverage status for master branch 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
1bd451f613 [test] Provide git credentials for tests on CI 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
1bb0d8f823 [test] Add tests for git repository install 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
ec4538eaf3 Warn about unnecessary resolution, #1061 2014-10-20 16:32:44 +02:00
Adam Stankiewicz
cd22a2d0c9 Prevent error when piping bower output to head, fixes #1396 2014-10-20 16:32:43 +02:00
Burak Yigit Kaya
138d846ecc Use tar-fs instead of tar for faster TAR extraction 2014-10-20 16:32:43 +02:00
Adam Stankiewicz
eae912b8db Run posthook after saving bower.json, fixes #1471 2014-10-20 16:32:43 +02:00
Sindre Sorhus
afec75972c make bower list --paths a bit prettier 2014-10-20 16:32:43 +02:00
Sindre Sorhus
60a45edccf bump dependencies 2014-10-20 16:32:43 +02:00
Adam Stankiewicz
95c89dbcc5 Read .bowerrc from specified cwd, fixes #1301 2014-10-20 16:32:43 +02:00
Crisoforo Gaspar Hernandez
84393d1c26 Update the references to the issues and pulls in the CHANGELOG.md file 2014-10-20 16:32:43 +02:00
Ben Schwarz
b0db186ac6 Back concurrency down to 5 (from 50!) 2014-10-20 16:32:43 +02:00
Manuel Wiedenmann
3b2a571efe expose bower version 2014-10-20 16:32:43 +02:00
Ray Shan
b9f7303151 Further simplify readme.md 2014-10-20 16:32:42 +02:00
thorn0
09e8e3f51d Mention updates in the main help
See #955. The same was done for `bower list -h`, but not for `bower help` AKA just `bower`.
2014-10-20 16:32:42 +02:00
Sindre Sorhus
71a17ddf4f Update changelog 2014-10-20 16:32:42 +02:00
Sindre Sorhus
fffcf3e4f2 1.3.9 2014-10-20 16:32:42 +02:00
Kornel Lesiński
8b78888f67 When tmp.js returns cleanup callback along with the tmp dir path, nfcall changes return type to an array 2014-10-20 16:32:42 +02:00
MadLux
be1a0d121b Added .zip MIME type (e.g. the default served by Windows/IIS) 2014-10-20 16:32:42 +02:00
Ray Shan
cc0d261c4c analytics - bump insight to 0.4.1, supports OS/node/CLI tool version tracking 2014-10-20 16:32:42 +02:00
Adam Stankiewicz
dd0bded77a [doc] Encourage to always use --save option 2014-10-20 16:32:42 +02:00
Sindre Sorhus
466f2f6c5c lock down 0.0.x versions
as `~` causes unexpected and non-semver behaviour on them.

https://github.com/isaacs/node-semver
2014-10-20 16:32:42 +02:00
David DeSandro
66894f5654 Remove content that can be now be found on bower.io
+ bower.json
+ custom install directory
+ bower search
+ using bower cache
+ .bowerrc
+ continuous integration server
+ interactive configuration
+ Defining a package
+ maintaining deps
+ consuming a package
+ programmatic api
+ assistance & contribution list

Ref #1371
2014-10-20 16:32:42 +02:00
Shu Uesugi
ca612dafc6 Fix typo on help-uninstall.json 2014-10-20 16:32:41 +02:00
Paul Irish
e1c68201a5 Update changelog 2014-10-20 16:32:41 +02:00
Paul Irish
12ebb505d9 Bump to 1.3.8 2014-10-20 16:32:41 +02:00
vladikoff
3facaa95bd Fixes node-tmp update issue 2014-10-20 16:32:41 +02:00
Sindre Sorhus
91042387eb bump update-notifier and simplify 2014-10-20 16:32:41 +02:00
Steve Kenworthy
2dd301657c Fixed typo 2014-10-20 16:32:41 +02:00
Lucas, Justin
b9e6eff899 Disable shallow clones for all repos except github.com 2014-10-20 16:32:41 +02:00
Sindre Sorhus
d118151c89 bump deps 2014-10-20 16:32:41 +02:00
Adam Stankiewicz
f90e36a60a Update changelog 2014-10-20 16:32:41 +02:00
Adam Stankiewicz
1818bc9a10 Bump to 1.3.7 2014-10-20 16:32:41 +02:00
Vladimir Alaev
3ff14aa939 Fix callstack error when processing installed packages with circular dependencies 2014-10-20 16:32:41 +02:00
insanehong
23cbaddb5f It should shows an error when bower install failed, fixes #922
- reason no bower.json in current directory
2014-10-20 16:32:40 +02:00
insanehong
850de576b9 Fixed bower list --paths` fails with TypeError, fixes #1383 2014-10-20 16:32:40 +02:00
Adam Stankiewicz
978d8f2727 Update the changelog 2014-10-20 16:32:40 +02:00
Adam Stankiewicz
2a6bd40c58 Bump version to 1.3.6 2014-10-20 16:32:40 +02:00
Adam Stankiewicz
0ebda41fa8 Disable removing unnecessary resolutions, #1061 2014-07-02 11:38:14 +01:00
Adam Stankiewicz
48f7af12a6 There is no need for unlinking, reducing moving parts 2014-07-02 11:38:14 +01:00
Sergey Chikuyonok
98e4847732 Provide symlinks for nested dependencies 2014-07-02 11:38:13 +01:00
68 changed files with 1486 additions and 600 deletions

View File

@@ -5,7 +5,5 @@ node_js:
matrix:
allow_failures:
- node_js: '0.11'
before_script:
- npm install -g grunt-cli
script:
- grunt travis

View File

@@ -1,5 +1,56 @@
# Changelog
## 1.3.12 - 2014-09-28
- [stability] Fix versions for unstable dependencies ([#1532](https://github.com/bower/bower/pull/1532))
- [fix] Update tar-fs to support old tar format ([#1537](https://github.com/bower/bower/issues/1537))
- [fix] Make analytics work again ([#1529](https://github.com/bower/bower/pull/1529))
- [fix] Always disable analytics for non-interactive mode ([#1529](https://github.com/bower/bower/pull/1529))
- [fix] Bower init can create private packages again ([#1522](https://github.com/bower/bower/issues/1522))
- [fix] Show again missing newline for bower search output ([#1538](https://github.com/bower/bower/issues/1538))
## 1.3.11 - 2014-09-17
- [fix] Restore install missing dependencies on update ([1519](https://github.com/bower/bower/pull/1519))
## 1.3.10 - 2014-09-13
- [fix] Back down concurrency from 50 to 5 ([#1483](https://github.com/bower/bower/pull/1483))
- [fix] Read .bowerrc from specified cwd ([#1301](https://github.com/bower/bower/pull/1301))
- [fix] Disable shallow clones except those from GitHub ([#1393](https://github.com/bower/bower/pull/1393))
- [fix] Expose bower version ([#1478](https://github.com/bower/bower/pull/1478))
- [fix] Bump dependencies, including "request" ([#1467](https://github.com/bower/bower/pull/1467))
- [fix] Prevent an error when piping bower output to head ([#1508](https://github.com/bower/bower/pull/1508))
- [fix] Disable removing unnecessary resolutions ([#1061](https://github.com/bower/bower/pull/1061))
- [fix] Display the output of hooks again ([#1484](https://github.com/bower/bower/issues/1484))
- [fix] analytics: true in .bowerrc prevents user prompt ([#1470](https://github.com/bower/bower/pull/1470))
- [perf] Use `tar-fs` instead of `tar` for faster TAR extraction ([#1490](https://github.com/bower/bower/pull/1490))
## 1.3.9 - 2014-08-06
- [fix] Handle `tmp` sometimes returning an array ([#1434](https://github.com/bower/bower/pull/1434))
## 1.3.8 - 2014-7-11
- [fix] Lock down `tmp` package dep ([#1403](https://github.com/bower/bower/pull/1403), [#1407](https://github.com/bower/bower/pull/1407))
## 1.3.7 - 2014-07-04
- [fix] callstack error when processing installed packages with circular dependencies ([#1349](https://github.com/bower/bower/issues/1349))
- [fix] Prevent bower list --paths` failing with TypeError ([#1383](https://github.com/bower/bower/issues/1383))
- "bower install" fails if there's no bower.json in current directory ([#922](https://github.com/bower/bower/issues/922))
## 1.3.6 - 2014-07-02
- [fix] Make --force always re-run installation ([#931](https://github.com/bower/bower/issues/931))
- [fix] Disable caching for local resources ([#1356](https://github.com/bower/bower/issues/1356))
- [fix] Emit errors instead throwing them when using bower.commands API ([#1297](https://github.com/bower/bower/issues/1297))
- [fix] Main files and bower.json are never ignored ([#547](https://github.com/bower/bower/issues/547))
- [fix] Check if pkgMeta is undefined during uninstall command ([#1329](https://github.com/bower/bower/issues/1329))
- [fix] Make custom tmp dir and ignores play well with each other ([#1299](https://github.com/bower/bower/issues/1299))
- Warn users when installing package with missing properties ([#694](https://github.com/bower/bower/issues/694))
## 1.3.5 - 2014-06-06
- Search compatible versions in fetching packages ([#1147](https://github.com/bower/bower/issues/1147))

337
README.md
View File

@@ -1,17 +1,18 @@
# Bower
[![Build Status](https://travis-ci.org/bower/bower.svg?branch=master)](https://travis-ci.org/bower/bower) [![Coverage Status](https://coveralls.io/repos/bower/bower/badge.png?branch=feature%2Fintegration)](https://coveralls.io/r/bower/bower?branch=feature%2Fintegration)
[![Build Status](https://travis-ci.org/bower/bower.svg?branch=master)](https://travis-ci.org/bower/bower) [![Coverage Status](https://coveralls.io/repos/bower/bower/badge.png?branch=master)](https://coveralls.io/r/bower/bower?branch=master)
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
> A package manager for the web
It offers a generic, unopinionated solution to the problem of **front-end package management**, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.
Bower offers a generic, unopinionated solution to the problem of **front-end package management**, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.
Bower runs over Git, and is package-agnostic. A packaged component can be made up of any type of asset, and use any type of transport (e.g., AMD, CommonJS, etc.).
[View all packages available through Bower's registry](http://bower.io/search/).
**View complete docs on [bower.io](http://bower.io)**
[View all packages available through Bower's registry](http://bower.io/search/).
## Install
@@ -25,92 +26,21 @@ packages require it to be fetched and installed.
## Usage
Much more information is available via `bower help` once it's installed. This
is just enough to get you started.
See complete command line reference at [bower.io/docs/api/](http://bower.io/docs/api/)
### Installing packages and dependencies
Bower offers several ways to install packages:
##### Using the dependencies listed in the current directory's bower.json
```sh
# install dependencies listed in bower.json
$ bower install
# install a package and add it to bower.json
$ bower install <package> --save
# install specific version of a package and add it to bower.json
$ bower install <package>#<version> --save
```
##### Using a local or remote package
```sh
$ bower install <package>
```
##### Using a specific version of a package
```sh
$ bower install <package>#<version>
```
##### Using a different name and a specific version of a package
```sh
$ bower install <name>=<package>#<version>
```
Where `<package>` can be any one of the following:
* A name that maps to a package registered with Bower, e.g, `jquery`. ‡
* A public remote Git endpoint, e.g., `git://github.com/someone/some-package.git`. ‡
* A private Git repository, e.g., `https://github.com/someone/some-package.git`. If the protocol is https, a prompt will ask for the credentials. ssh can also be used, e.g., `git@github.com:someone/some-package.git` and can authenticate with the user's ssh public/private keys. ‡
* A local endpoint, i.e., a folder that's a Git repository. ‡
* A public remote Subversion endpoint, e.g., `svn+http://package.googlecode.com/svn/`. ‡
* A private Subversion repository, e.g., `svn+ssh://package.googlecode.com/svn/` or `svn+https://package.googlecode.com/svn/`. ‡
* A local endpoint, i.e., a folder that's an Subversion repository, e.g., `svn+file:///path/to/svn/`. ‡
* A shorthand endpoint, e.g., `someone/some-package` (defaults to GitHub). ‡
* A URL to a file, including `zip` and `tar` files. Its contents will be
extracted.
‡ These types of `<package>` might have versions available. You can specify a
[semver](http://semver.org/) compatible version to fetch a specific release, and lock the
package to that version. You can also specify a [range](https://github.com/isaacs/node-semver#ranges) of versions.
If you are using a package that is a git endpoint, you may use any tag, commit SHA,
or branch name as a version. For example: `<package>#<sha>`. Using branches is not
recommended because the HEAD does not reference a fixed commit SHA.
If you are using a package that is a subversion endpoint, you may use any tag, revision number,
or branch name as a version. For example: `<package>#<revision>`.
All package contents are installed in the `bower_components` directory by default.
You should **never** directly modify the contents of this directory.
Using `bower list` will show all the packages that are installed locally.
**N.B.** If you aren't authoring a package that is intended to be consumed by
others (e.g., you're building a web app), you should always [check installed
packages into source control](http://addyosmani.com/blog/checking-in-front-end-dependencies/).
### Custom install directory
A custom install location can be set in a `.bowerrc` file using the `directory` property. The .bowerrc file should be a sibling of your project's bower.json.
```json
{
"directory": "app/components"
}
```
### Finding packages
To search for packages registered with Bower:
```sh
$ bower search [<name>]
```
Using just `bower search` will list all packages in the registry.
### Using packages
We discourage using bower components statically for performance and security reasons (if component has an `upload.php` file that is not ignored, that can be easily exploited to do malicious stuff).
@@ -125,17 +55,16 @@ To uninstall a locally installed package:
$ bower uninstall <package-name>
```
#### Warning
### prezto and oh-my-zsh users
On `prezto` or `oh-my-zsh`, do not forget to `alias bower='noglob bower'` or `bower install jquery\#1.9.1`
#### Running commands with sudo
### Running commands with sudo
Bower is a user command, there is no need to execute it with superuser permissions.
However, if you still want to run commands with sudo, use `--allow-root` option.
#### A note for Windows users
### Windows users
To use Bower on Windows, you must install
[msysgit](http://msysgit.github.io/) correctly. Be sure to check the
@@ -148,196 +77,9 @@ password, you should add the following environment variable: `GIT_SSH -
C:\Program Files\TortoiseGit\bin\TortoisePlink.exe`. Adjust the `TortoisePlink`
path if needed.
### Using bower's cache
Bower supports installing packages from its local cache (without internet connection), if the packages were installed before.
```sh
$ bower install <package-name> --offline
```
The content of the cache can be listed with:
```sh
$ bower cache list
```
The cache can be cleaned with:
```sh
$ bower cache clean
```
## Configuration
Bower can be configured using JSON in a `.bowerrc` file.
The current spec can be read
[here](https://docs.google.com/document/d/1APq7oA9tNao1UYWyOm8dKqlRP2blVkROYLZ2fLIjtWc/edit#heading=h.4pzytc1f9j8k)
in the `Configuration` section.
## Running on a continuous integration server
Bower will skip some interactive and analytics operations if it finds a `CI` environmental variable set to `true`. You will find that the `CI` variable is already set for you on many continuous integration servers, e.g., [CircleCI](https://circleci.com/docs/environment-variables#basics) and [Travis-CI](http://docs.travis-ci.com/user/ci-environment/#Environment-variables).
You may try to set the `CI` variable manually before running your Bower commands. On Mac or Linux, `export CI=true` and on Windows `set CI=true`
### Interactive configuration
If for some reason you are unable to set the `CI` environment variable, you can alternately use the `--config.interactive=false` flag. (`bower install --config.interactive=false`)
## Defining a package
You must create a `bower.json` in your project's root, and specify all of its
dependencies. This is similar to Node's `package.json`, or Ruby's `Gemfile`,
and is useful for locking down a project's dependencies.
*NOTE:* In versions of Bower before 0.9.0 the package metadata file was called
`component.json` rather than `bower.json`. This has changed to avoid a name
clash with another tool. You can still use `component.json` for now but it is
deprecated and the automatic fallback is likely to be removed in an upcoming
release.
You can interactively create a `bower.json` with the following command:
```sh
$ bower init
```
The `bower.json` ([spec](https://github.com/bower/bower.json-spec)) defines several options, including:
* `name` (required): The name of your package.
* `version`: A semantic version number (see [semver](http://semver.org/)).
* `main` [string|array]: The primary endpoints of your package.
* `ignore` [array]: An array of paths not needed in production that you want
Bower to ignore when installing your package.
* `dependencies` [hash]: Packages your package depends upon in production.
Note that you can specify [ranges](https://github.com/isaacs/node-semver#ranges)
of versions for your dependencies.
* `devDependencies` [hash]: Development dependencies.
* `private` [boolean]: Set to true if you want to keep the package private and
do not want to register the package in future.
```json
{
"name": "my-project",
"description": "My project does XYZ...",
"version": "1.0.0",
"main": "path/to/main.css",
"ignore": [
".jshintrc",
"**/*.txt"
],
"dependencies": {
"<name>": "<version>",
"<name>": "<folder>",
"<name>": "<package>"
},
"devDependencies": {
"<test-framework-name>": "<version>"
}
}
```
### Registering packages
To register a new package:
* There **must** be a valid manifest JSON in the current working directory.
* Your package should use [semver](http://semver.org/) Git tags.
* Your package **must** be available at a Git endpoint (e.g., GitHub); remember
to push your Git tags!
Then use the following command:
```sh
$ bower register <my-package-name> <git-endpoint>
```
The Bower registry does not have authentication or user management at this point
in time. It's on a first come, first served basis. Think of it like a URL
shortener. Now anyone can run `bower install <my-package-name>`, and get your
library installed.
There is no direct way to unregister a package yet. For now, you can [request a
package be unregistered](https://github.com/bower/bower/issues/120).
### Maintaining dependencies
Using `bower install pkgName --save` will add `pkgName` to your project's
bower.json `"depenencies"` array.
Similarly, using `bower install pkgName --save-dev` will add `pkgName` to your
project's bower.json `"devDependencies"` array.
## Consuming a package
Bower also makes available a source mapping. This can be used by build tools to
easily consume Bower packages.
If you pass the `--paths` option to Bower's `list` command, you will get a
simple name-to-path mapping:
```json
{
"backbone": "bower_components/backbone/index.js",
"jquery": "bower_components/jquery/index.js",
"underscore": "bower_components/underscore/index.js"
}
```
Alternatively, every command supports the `--json` option that makes bower
output JSON. Command result is outputted to `stdout` and error/logs to
`stderr`.
## Programmatic API
Bower provides a powerful, programmatic API. All commands can be accessed
through the `bower.commands` object.
```js
var bower = require('bower');
bower.commands
.install(['jquery'], { save: true }, { /* custom config */ })
.on('end', function (installed) {
console.log(installed);
});
bower.commands
.search('jquery', {})
.on('end', function (results) {
console.log(results);
});
```
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);
});
```
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
## Completion (experimental)
@@ -354,19 +96,6 @@ This command will output a Bash / ZSH script to put into your `~/.bashrc`,
$ bower completion >> ~/.bash_profile
```
## Analytics
Bower can collect anonymous usage statistics. This allows the community to improve Bower and publicly display insights into CLI usage and packages at [stats.bower.io](http://stats.bower.io).
Data is tracked using Google Analytics and instrumented via [Insight](https://github.com/yeoman/insight). It is made available to all bower team members. Tracking is opt-in upon initial usage. If you'd prefer to disable analytics altogether, you can manually opt-out, or create either a local, or global `.bowerrc` file with:
```json
{
"analytics": false
}
```
## Support
@@ -387,6 +116,8 @@ review the [guidelines for contributing](CONTRIBUTING.md).
## Bower Team
Bower is made by lots of people across the globe, contributions large and small. Our thanks to everyone who has played a part.
### Core team
* [@satazor](https://github.com/satazor)
@@ -397,38 +128,6 @@ review the [guidelines for contributing](CONTRIBUTING.md).
* [@svnlto](https://github.com/svnlto)
* [@sheerun](https://github.com/sheerun)
Thanks for assistance and contributions:
[@addyosmani](https://github.com/addyosmani),
[@ahmadnassri](https://github.com/ahmadnassri),
[@angus-c](https://github.com/angus-c),
[@borismus](https://github.com/borismus),
[@carsonmcdonald](https://github.com/carsonmcdonald),
[@chriseppstein](https://github.com/chriseppstein),
[@danwrong](https://github.com/danwrong),
[@davidmaxwaterman](https://github.com/davidmaxwaterman),
[@desandro](https://github.com/desandro),
[@hemanth](https://github.com/hemanth),
[@isaacs](https://github.com/isaacs),
[@josh](https://github.com/josh),
[@jrburke](https://github.com/jrburke),
[@kennethklee](https://github.com/kennethklee),
[@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),
[@richo](https://github.com/richo),
[@rvagg](https://github.com/rvagg),
[@ryanflorence](https://github.com/ryanflorence),
[@SlexAxton](https://github.com/SlexAxton),
[@sstephenson](https://github.com/sstephenson),
[@tomdale](https://github.com/tomdale),
[@uzquiano](https://github.com/uzquiano),
[@visionmedia](https://github.com/visionmedia),
[@wagenet](https://github.com/wagenet),
[@wycats](https://github.com/wycats)
### Bower Alumni
* [@fat](https://github.com/fat)

View File

@@ -19,7 +19,6 @@ var loglevel;
var command;
var commandFunc;
var logger;
var notifier;
var levels = Logger.LEVELS;
options = cli.readOptions({
@@ -134,13 +133,13 @@ analytics.setup(bower.config).then(function () {
var updateNotifier = require('update-notifier');
// Check for newer version of Bower
notifier = updateNotifier({
var notifier = updateNotifier({
packageName: pkg.name,
packageVersion: pkg.version
});
if (notifier.update && levels.info >= loglevel) {
renderer.updateNotice(notifier.update);
notifier.notify();
}
}
});

View File

@@ -14,7 +14,7 @@ function clean(logger, endpoints, options, config) {
var names;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
// If endpoints is an empty array, null them
if (endpoints && !endpoints.length) {

View File

@@ -6,7 +6,7 @@ var defaultConfig = require('../../config');
function list(logger, packages, options, config) {
var repository;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
repository = new PackageRepository(config, logger);
// If packages is an empty array, null them

View File

@@ -1,4 +1,3 @@
var mout = require('mout');
var Project = require('../core/Project');
var open = require('opn');
var endpointParser = require('bower-endpoint-parser');
@@ -11,7 +10,7 @@ function home(logger, name, config) {
var promise;
var decEndpoint;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
// Get the package meta

View File

@@ -11,7 +11,7 @@ function info(logger, endpoint, property, config) {
var decEndpoint;
var tracker;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
repository = new PackageRepository(config, logger);
tracker = new Tracker(config);

View File

@@ -6,6 +6,7 @@ var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var BitBucketResolver = require('../core/resolvers/BitBucketResolver');
var GitFsResolver = require('../core/resolvers/GitFsResolver');
var cli = require('../util/cli');
var cmd = require('../util/cmd');
@@ -14,7 +15,7 @@ var createError = require('../util/createError');
function init(logger, config) {
var project;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
// This command requires interactive to be enabled
if (!config.interactive) {
@@ -115,7 +116,7 @@ function setDefaults(config, json) {
// Homepage
if (!json.homepage) {
// Set as GitHub homepage if it's a GitHub repository
// Set as GitHub/Bitbucket homepage if it's a GitHub/Bitbucket repository
promise = promise.then(function () {
return cmd('git', ['config', '--get', 'remote.origin.url'])
.spread(function (stdout) {
@@ -130,6 +131,11 @@ function setDefaults(config, json) {
if (pair) {
json.homepage = 'https://github.com/' + pair.org + '/' + pair.repo;
}
pair = BitBucketResolver.getOrgRepoPair(stdout);
if (pair) {
json.homepage = 'https://bitbucket.org/' + pair.org + '/' + pair.repo;
}
})
.fail(function () { });
});

View File

@@ -1,4 +1,3 @@
var mout = require('mout');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var cli = require('../util/cli');
@@ -11,7 +10,7 @@ function install(logger, endpoints, options, config) {
var tracker;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
if (options.save === undefined) {
options.save = config.defaultSave;
}

View File

@@ -1,6 +1,5 @@
var path = require('path');
var rimraf = require('rimraf');
var mout = require('mout');
var Q = require('q');
var Project = require('../core/Project');
var createLink = require('../util/createLink');
@@ -18,7 +17,7 @@ function link(logger, name, localName) {
function linkSelf(logger, config) {
var project;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
return project.getJson()
@@ -44,9 +43,10 @@ function linkSelf(logger, config) {
function linkTo(logger, name, localName, config) {
var src;
var dst;
var project = new Project(config, logger);
var project;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
localName = localName || name;
src = path.join(config.storage.links, name);

View File

@@ -16,11 +16,9 @@ function list(logger, options, config) {
options.relative = true;
}
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
logger.json = !!options.paths;
return project.getTree(options)
.spread(function (tree, flattened) {
// Relativize paths

View File

@@ -1,4 +1,3 @@
var mout = require('mout');
var Q = require('q');
var RegistryClient = require('bower-registry-client');
var cli = require('../util/cli');
@@ -7,7 +6,7 @@ var defaultConfig = require('../config');
function lookup(logger, name, config) {
var registryClient;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
config.cache = config.storage.registry;
registryClient = new RegistryClient(config, logger);

View File

@@ -7,7 +7,7 @@ function prune(logger, options, config) {
var project;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
return clean(project, options);

View File

@@ -8,6 +8,7 @@ var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var BitBucketResolver = require('../core/resolvers/BitBucketResolver');
function register(logger, name, url, config) {
var repository;
@@ -15,7 +16,7 @@ function register(logger, name, url, config) {
var tracker;
var force;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
force = config.force;
tracker = new Tracker(config);
@@ -91,12 +92,18 @@ function convertUrl(url, logger) {
var newUrl;
if (!mout.string.startsWith(url, 'git://')) {
// Convert GitHub ssh & https to git://
// Convert GitHub/Bitbucket ssh & https to git://
pair = GitHubResolver.getOrgRepoPair(url);
if (pair) {
newUrl = 'git://github.com/' + pair.org + '/' + pair.repo + '.git';
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl);
}
pair = BitBucketResolver.getOrgRepoPair(url);
if (pair) {
newUrl = 'git://bitbucket.org/' + pair.org + '/' + pair.repo + '.git';
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl);
}
}
return newUrl || url;

View File

@@ -1,4 +1,3 @@
var mout = require('mout');
var Q = require('q');
var RegistryClient = require('bower-registry-client');
var cli = require('../util/cli');
@@ -9,7 +8,7 @@ function search(logger, name, config) {
var registryClient;
var tracker;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
config.cache = config.storage.registry;
registryClient = new RegistryClient(config, logger);

View File

@@ -10,7 +10,7 @@ function uninstall(logger, names, options, config) {
var tracker;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
tracker = new Tracker(config);

View File

@@ -1,4 +1,3 @@
var mout = require('mout');
var Project = require('../core/Project');
var cli = require('../util/cli');
var defaultConfig = require('../config');
@@ -7,7 +6,7 @@ function update(logger, names, options, config) {
var project;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
// If names is an empty array, null them

View File

@@ -1,4 +1,3 @@
var mout = require('mout');
var semver = require('semver');
var which = require('which');
var fs = require('fs');
@@ -13,7 +12,7 @@ var createError = require('../util/createError');
function version(logger, versionArg, options, config) {
var project;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
return bump(project, versionArg, options.message);

View File

@@ -1,37 +1,55 @@
var tty = require('tty');
var mout = require('mout');
var config = require('bower-config').read();
var object = require('mout').object;
var bowerConfig = require('bower-config');
var cli = require('./util/cli');
// Delete the json attribute because it is no longer supported
// and conflicts with --json
delete config.json;
var cachedConfigs = {};
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = (
process.bin === 'bower' &&
tty.isatty(1) &&
!process.env.CI
);
function defaultConfig(config) {
config = config || {};
var cachedConfig = readCachedConfig(config.cwd || process.cwd());
return object.merge(cachedConfig, config);
}
// If `analytics` hasn't been explicitly set, we disable
// it when ran programatically.
if (config.analytics == null) {
// Don't enable analytics on CI server unless explicitly configured.
config.analytics = config.interactive;
function readCachedConfig(cwd) {
if (cachedConfigs[cwd]) {
return cachedConfigs[cwd];
}
var config = cachedConfigs[cwd] = bowerConfig.read(cwd);
// Delete the json attribute because it is no longer supported
// 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) &&
!process.env.CI
);
}
// Merge common CLI options into the config
object.mixIn(config, cli.readOptions({
force: { type: Boolean, shorthand: 'f' },
offline: { type: Boolean, shorthand: 'o' },
verbose: { type: Boolean, shorthand: 'V' },
quiet: { type: Boolean, shorthand: 'q' },
loglevel: { type: String, shorthand: 'l' },
json: { type: Boolean, shorthand: 'j' },
silent: { type: Boolean, shorthand: 's' }
}));
return config;
}
// Merge common CLI options into the config
mout.object.mixIn(config, cli.readOptions({
force: { type: Boolean, shorthand: 'f' },
offline: { type: Boolean, shorthand: 'o' },
verbose: { type: Boolean, shorthand: 'V' },
quiet: { type: Boolean, shorthand: 'q' },
loglevel: { type: String, shorthand: 'l' },
json: { type: Boolean, shorthand: 'j' },
silent: { type: Boolean, shorthand: 's' }
}));
function resetCache () {
cachedConfigs = {};
}
module.exports = config;
module.exports = defaultConfig;
module.exports.reset = resetCache;

View File

@@ -9,6 +9,7 @@ var PackageRepository = require('./PackageRepository');
var semver = require('../util/semver');
var copy = require('../util/copy');
var createError = require('../util/createError');
var dependencyLinker = require('../util/dependencyLinker');
var scripts = require('./scripts');
function Manager(config, logger) {
@@ -109,6 +110,40 @@ Manager.prototype.resolve = function () {
}.bind(this));
};
Manager.prototype.preinstall = function (json) {
var that = this;
var componentsDir = path.join(this._config.cwd, this._config.directory);
// If nothing to install, skip the code bellow
if (mout.lang.isEmpty(that._dissected)) {
return Q.resolve({});
}
return Q.nfcall(mkdirp, componentsDir)
.then(function () {
return scripts.preinstall(
that._config, that._logger, that._dissected, that._installed, json
);
});
};
Manager.prototype.postinstall = function (json) {
var that = this;
var componentsDir = path.join(this._config.cwd, this._config.directory);
// If nothing to install, skip the code bellow
if (mout.lang.isEmpty(that._dissected)) {
return Q.resolve({});
}
return Q.nfcall(mkdirp, componentsDir)
.then(function () {
return scripts.postinstall(
that._config, that._logger, that._dissected, that._installed, json
);
});
};
Manager.prototype.install = function (json) {
var componentsDir;
var that = this;
@@ -125,9 +160,6 @@ Manager.prototype.install = function (json) {
componentsDir = path.join(this._config.cwd, this._config.directory);
return Q.nfcall(mkdirp, componentsDir)
.then(function () {
return scripts.preinstall(that._config, that._logger, that._dissected, that._installed, json);
})
.then(function () {
var promises = [];
@@ -169,9 +201,6 @@ Manager.prototype.install = function (json) {
return Q.all(promises);
})
.then(function () {
return scripts.postinstall(that._config, that._logger, that._dissected, that._installed, json);
})
.then(function () {
// Sync up dissected dependencies and dependants
// See: https://github.com/bower/bower/issues/879
@@ -190,7 +219,13 @@ Manager.prototype.install = function (json) {
return dissected;
}, this);
}, that);
})
.then(function () {
// Create symlinks for sub-dependencies
var flattened = mout.object.mixIn({}, that._installed, that._dissected);
return dependencyLinker.link(flattened, that._config, that._logger);
})
.then(function () {
// Resolve with meaningful data
return mout.object.map(that._dissected, function (decEndpoint) {
return this.toData(decEndpoint);
@@ -543,13 +578,11 @@ Manager.prototype._dissect = function () {
return;
}
this._logger.info('resolution', 'Removed unnecessary ' + name + '#' + resolution + ' resolution', {
this._logger.warn('extra-resolution', 'Unnecessary resolution: ' + name + '#' + resolution, {
name: name,
resolution: resolution,
action: 'delete'
});
delete this._resolutions[name];
}, this);
// Filter only packages that need to be installed

View File

@@ -20,7 +20,7 @@ function Project(config, logger) {
// on config and logger
// The reason behind it is that users can likely use this component
// directly if commands do not fulfil their needs
this._config = config || defaultConfig;
this._config = defaultConfig(config);
this._logger = logger || new Logger();
this._manager = new Manager(this._config, this._logger);
@@ -47,6 +47,12 @@ Project.prototype.install = function (decEndpoints, options, config) {
// Analyse the project
return this._analyse()
.spread(function (json, tree) {
// It shows an error when issuing `bower install`
// and no bower.json is present in current directory
if(!that._jsonFile && decEndpoints.length === 0 ) {
throw createError('No bower.json present', 'ENOENT');
}
// Recover tree
that.walkTree(tree, function (node, name) {
if (node.incompatible) {
@@ -72,6 +78,12 @@ Project.prototype.install = function (decEndpoints, options, config) {
// Bootstrap the process
return that._bootstrap(targets, resolved, incompatibles);
})
.then(function () {
return that._manager.preinstall(that._json);
})
.then(function () {
return that._manager.install(that._json);
})
.then(function (installed) {
// Handle save and saveDev options
if (that._options.save || that._options.saveDev) {
@@ -94,7 +106,9 @@ Project.prototype.install = function (decEndpoints, options, config) {
// Save JSON, might contain changes to dependencies and resolutions
return that.saveJson()
.then(function () {
return installed;
return that._manager.postinstall(that._json).then(function () {
return installed;
});
});
})
.fin(function () {
@@ -175,11 +189,19 @@ Project.prototype.update = function (names, options) {
// Bootstrap the process
return that._bootstrap(targets, resolved, incompatibles)
.then(function () {
return that._manager.preinstall(that._json);
})
.then(function () {
return that._manager.install(that._json);
})
.then(function (installed) {
// Save JSON, might contain changes to resolutions
return that.saveJson()
.then(function () {
return installed;
return that._manager.postinstall(that._json).then(function () {
return installed;
});
});
});
})
@@ -523,9 +545,7 @@ Project.prototype._bootstrap = function (targets, resolved, incompatibles) {
if (!mout.object.size(this._json.resolutions)) {
delete this._json.resolutions;
}
}.bind(this))
// Install resolved ones
.then(this._manager.install.bind(this._manager, this._json));
}.bind(this));
};
Project.prototype._readJson = function () {
@@ -730,7 +750,7 @@ Project.prototype._removePackages = function (packages) {
});
};
Project.prototype._restoreNode = function (node, flattened, jsonKey) {
Project.prototype._restoreNode = function (node, flattened, jsonKey, processed) {
var deps;
// Do not restore if the node is missing
@@ -740,10 +760,11 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
node.dependencies = node.dependencies || {};
node.dependants = node.dependants || {};
processed = processed || {};
// Only process deps that are not yet processed
deps = mout.object.filter(node.pkgMeta[jsonKey], function (value, key) {
return !node.dependencies[key];
return !processed[node.name + ':' + key];
});
mout.object.forOwn(deps, function (value, key) {
@@ -790,15 +811,17 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
// Cross reference
node.dependencies[key] = restored;
processed[node.name + ':' + key] = true;
restored.dependants = restored.dependants || {};
restored.dependants[node.name] = mout.object.mixIn({}, node); // We need to clone due to shared objects in the manager!
// Call restore for this dependency
this._restoreNode(restored, flattened, 'dependencies');
this._restoreNode(restored, flattened, 'dependencies', processed);
// Do the same for the incompatible local package
if (local && restored !== local) {
this._restoreNode(local, flattened, 'dependencies');
this._restoreNode(local, flattened, 'dependencies', processed);
}
}, this);
};

View File

@@ -41,6 +41,11 @@ function getConstructor(source, config, registryClient) {
return [resolvers.GitHub, source];
}
// If it's a BitBucket repository, return the specialized resolver
if (resolvers.BitBucket.getOrgRepoPair(source)) {
return [resolvers.BitBucket, source];
}
return [resolvers.GitRemote, source];
});
}

View File

@@ -0,0 +1,150 @@
var util = require('util');
var path = require('path');
var mout = require('mout');
var GitRemoteResolver = require('./GitRemoteResolver');
var download = require('../../util/download');
var extract = require('../../util/extract');
var createError = require('../../util/createError');
function BitBucketResolver(decEndpoint, config, logger) {
var pair;
GitRemoteResolver.call(this, decEndpoint, config, logger);
// Grab the org/repo
// /xxxxx/yyyyy.git or :xxxxx/yyyyy.git (.git is optional)
pair = BitBucketResolver.getOrgRepoPair(this._source);
if (!pair) {
throw createError('Invalid BitBucket URL', 'EINVEND', {
details: this._source + ' does not seem to be a valid BitBucket 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://');
// Use https:// rather than git:// if on a proxy
if (this._config.proxy || this._config.httpsProxy) {
this._source = this._source.replace('git://', 'https://');
}
// Enable shallow clones for BitBucket repos
this._shallowClone = true;
}
util.inherits(BitBucketResolver, GitRemoteResolver);
mout.object.mixIn(BitBucketResolver, GitRemoteResolver);
// -----------------
BitBucketResolver.prototype._checkout = function () {
// Only fully works with public repositories and tags
// Could work with https/ssh protocol but not with 100% certainty
if (!this._public || !this._resolution.tag) {
return GitRemoteResolver.prototype._checkout.call(this);
}
var msg;
var tarballUrl = 'https://bitbucket.org/' + this._org + '/' + this._repo + '/get/' + this._resolution.tag + '.tar.gz';
var file = path.join(this._tempDir, 'archive.tar.gz');
var reqHeaders = {};
var that = this;
if (this._config.userAgent) {
reqHeaders['User-Agent'] = this._config.userAgent;
}
this._logger.action('download', tarballUrl, {
url: that._source,
to: file
});
// Download tarball
return download(tarballUrl, file, {
proxy: this._config.httpsProxy,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders
})
.progress(function (state) {
// 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);
}
// Progress
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
if (state.total) {
msg += ' of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
msg += state.percent + '%';
}
that._logger.info('progress', msg);
})
.then(function () {
// Extract archive
that._logger.action('extract', path.basename(file), {
archive: file,
to: 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));
});
};
BitBucketResolver.prototype._savePkgMeta = function (meta) {
// Set homepage if not defined
if (!meta.homepage) {
meta.homepage = 'https://bitbucket.org/' + this._org + '/' + this._repo;
}
return GitRemoteResolver.prototype._savePkgMeta.call(this, meta);
};
// ----------------
BitBucketResolver.getOrgRepoPair = function (url) {
var match;
match = url.match(/(?:@|:\/\/)bitbucket.org[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i);
if (!match) {
return null;
}
return {
org: match[1],
repo: match[2]
};
};
module.exports = BitBucketResolver;

View File

@@ -35,6 +35,9 @@ function GitHubResolver(decEndpoint, config, logger) {
if (this._config.proxy || this._config.httpsProxy) {
this._source = this._source.replace('git://', 'https://');
}
// Enable shallow clones for GitHub repos
this._shallowClone = true;
}
util.inherits(GitHubResolver, GitRemoteResolver);

View File

@@ -25,6 +25,9 @@ function GitRemoteResolver(decEndpoint, config, logger) {
} else {
this._host = url.parse(this._source).host;
}
// Disable shallow clones
this._shallowClone = false;
}
util.inherits(GitRemoteResolver, GitResolver);
@@ -113,7 +116,7 @@ GitRemoteResolver.prototype._fastClone = function (resolution) {
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)) {
if (this._shallowClone && !GitRemoteResolver._noShallow.get(this._host)) {
args.push('--depth', 1);
}

View File

@@ -10,7 +10,6 @@ var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var defaultConfig = require('../../config');
var hasGit;
@@ -22,13 +21,13 @@ 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) {
// 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(config.storage.empty);
process.env.GIT_TEMPLATE_DIR = config.storage.empty;
Resolver.call(this, decEndpoint, config, logger);
if (!hasGit) {

View File

@@ -169,8 +169,8 @@ Resolver.prototype._createTempDir = function () {
});
}.bind(this))
.then(function (dir) {
this._tempDir = dir;
return dir;
// nfcall may return multiple callback arguments as an array
return this._tempDir = Array.isArray(dir) ? dir[0] : dir;
}.bind(this));
};

View File

@@ -2,6 +2,7 @@ module.exports = {
GitFs: require('./GitFsResolver'),
GitRemote: require('./GitRemoteResolver'),
GitHub: require('./GitHubResolver'),
BitBucket: require('./BitBucketResolver'),
Svn: require('./SvnResolver'),
Fs: require('./FsResolver'),
Url: require('./UrlResolver')

View File

@@ -1,6 +1,7 @@
var abbrev = require('abbrev');
var mout = require('mout');
var commands = require('./commands');
var pkg = require('../package.json');
var abbreviations = abbrev(expandNames(commands));
abbreviations.i = 'install';
@@ -34,8 +35,9 @@ function clearRuntimeCache() {
}
module.exports = {
version: pkg.version,
commands: commands,
config: require('./config'),
config: require('./config')(),
abbreviations: abbreviations,
reset: clearRuntimeCache
};

View File

@@ -121,8 +121,6 @@ JsonRenderer.prototype.prompt = function (prompts) {
});
};
JsonRenderer.prototype.updateNotice = function () {};
// -------------------------
JsonRenderer.prototype._stringify = function (log) {

View File

@@ -30,6 +30,16 @@ function StandardRenderer(command, config) {
} else {
this._compact = process.stdout.columns < 120;
}
var exitOnPipeError = function (err) {
if (err.code === 'EPIPE') {
process.exit(0);
}
};
// It happens when piping command to "head" util
process.stdout.on('error', exitOnPipeError);
process.stderr.on('error', exitOnPipeError);
}
StandardRenderer.prototype.end = function (data) {
@@ -110,11 +120,6 @@ StandardRenderer.prototype.prompt = function (prompts) {
return deferred.promise;
};
StandardRenderer.prototype.updateNotice = function (data) {
var str = template.render('std/update-notice.std', data);
this._write(process.stderr, str);
};
// -------------------------
StandardRenderer.prototype._help = function (data) {
@@ -170,10 +175,14 @@ StandardRenderer.prototype._update = function (packages) {
StandardRenderer.prototype._list = function (tree) {
var cliTree;
tree.root = true;
cliTree = this._tree2archy(tree);
if (tree.pkgMeta) {
tree.root = true;
cliTree = archy(this._tree2archy(tree));
} else {
cliTree = stringifyObject(tree, { indent: ' ' }).replace(/[{}]/g, '') + '\n';
}
this._write(process.stdout, archy(cliTree));
this._write(process.stdout, cliTree);
};
StandardRenderer.prototype._search = function (results) {

View File

@@ -2,47 +2,96 @@ var Q = require('q');
var mout = require('mout');
var analytics = module.exports;
var insight;
// Initializes the application-wide insight singleton and asks for the
// permission on the CLI during the first run.
analytics.setup = function setup(config) {
var deferred = Q.defer();
var enableAnalytics = false;
// Display the ask prompt only if it hasn't been answered before
// and the current session is looking to configure the analytics.
if (config.analytics) {
// Insight takes long to load, and often causes problems
// in non-interactive environment, so we load it lazily
//
// Insight is used in two cases:
//
// 1. Read insight configuration (whether track user actions)
// 2. Track user actions (Tracker.track method)
//
// We don't want to instantiate Insight in non-interactive mode
// because it takes time to read config and configstore has concurrency issues:
//
// https://github.com/yeoman/configstore/issues/20
function ensureInsight () {
if (!insight) {
var Insight = require('insight');
var pkg = require('../../package.json');
insight = new Insight({
trackingCode: 'UA-43531210-1',
packageName: pkg.name,
packageVersion: pkg.version
});
}
}
if (insight.optOut === undefined) {
insight.askPermission(null, deferred.resolve);
// Initializes the application-wide insight singleton and asks for the
// permission on the CLI during the first run.
//
// This method is called only from bin/bower. Programmatic API skips it.
analytics.setup = function setup (config) {
var deferred = Q.defer();
// No need for asking if analytics is set in bower config
if (config.analytics === undefined) {
ensureInsight();
// For non-interactive call from bin/bower we disable analytics
if (config.interactive) {
if (insight.optOut !== undefined) {
deferred.resolve(!insight.optOut);
} else {
insight.askPermission(null, function(err, optIn) {
// optIn callback param was exactly opposite before 0.4.3
// so we force at least insight@0.4.3 in package.json
deferred.resolve(optIn);
});
}
} else {
deferred.resolve();
// no specified value, no stored value, and can't prompt for one
// most likely CI environment; defaults to false to reduce data noise
deferred.resolve(false);
}
} else {
deferred.resolve();
// use the specified value
deferred.resolve(config.analytics);
}
return deferred.promise;
return deferred.promise.then(function (enabled) {
enableAnalytics = enabled;
return enabled;
});
};
var Tracker = analytics.Tracker = function Tracker(config) {
if (!config.analytics) {
this.track = function noop() {};
function analyticsEnabled () {
// Allow for overriding analytics default
if (config && config.analytics !== undefined) {
return config.analytics;
}
// TODO: let bower pass this variable from bin/bower instead closure
return enableAnalytics;
}
if (analyticsEnabled()) {
ensureInsight();
} else {
this.track = function noop () {};
this.trackDecomposedEndpoints = function noop () {};
this.trackPackages = function noop () {};
this.trackNames = function noop () {};
}
};
Tracker.prototype.track = function track() {
if (!insight) {
throw new Error('You must call analytics.setup() prior to tracking.');
}
insight.track.apply(insight, arguments);
};

View File

@@ -8,9 +8,12 @@ var createError = require('./createError');
// The concurrency limit here is kind of magic. You don't really gain a lot from
// having a large number of commands spawned at once, so it isn't super
// important for this number to be large. However, it would still be nice to
// *know* how high this number can be, rather than having to guess low.
var throttler = new PThrottler(50);
// important for this number to be large. Reports have shown that much more than 5
// or 10 cause issues for corporate networks, private repos or situations where
// internet bandwidth is limited. We're running with a concurrency of 5 until
// 1.4.X is released, at which time we'll move to what was discussed in #1262
// https://github.com/bower/bower/pull/1262
var throttler = new PThrottler(5);
var winBatchExtensions;
var winWhichCache;

View File

@@ -6,7 +6,9 @@ var createError = require('./createError');
var isWin = process.platform === 'win32';
function createLink(src, dst, type) {
function createLink(src, dst, options) {
options = options || {};
var dstDir = path.dirname(dst);
// Create directory
@@ -26,7 +28,12 @@ function createLink(src, dst, type) {
})
// Create symlink
.then(function (stat) {
type = type || (stat.isDirectory() ? 'dir' : 'file');
var type = options.type || (stat.isDirectory() ? 'dir' : 'file');
if (options.relative && !isWin) {
src = path.relative(path.dirname(dst), src);
} else {
src = path.resolve(src);
}
return Q.nfcall(fs.symlink, src, dst, type)
.fail(function (err) {

View File

@@ -0,0 +1,39 @@
var Q = require('q');
var Config = require('bower-config');
var createLink = require('../util/createLink');
var readJson = require('../util/readJson');
var path = require('path');
var fs = require('graceful-fs');
module.exports.link = function (packages, config, logger) {
var rootComponentsDir = path.join(config.cwd, config.directory);
// create links for new packages and make sure that existing
// ones has all dependencies linked
var promises = Object.keys(packages).map(function (depName) {
var dep = packages[depName];
var depPath = dep.canonicalDir || path.join(rootComponentsDir, dep.name);
var conf = Config.read(depPath);
var componentsDir = path.join(conf.cwd, conf.directory);
return readJson(depPath)
.spread(function (json, deprecated, assumed) {
if (json.dependencies) {
return Q.all(Object.keys(json.dependencies || {}).map(function (d) {
var dst = path.join(componentsDir, d);
var src = path.join(rootComponentsDir, d);
return Q.nfcall(fs.stat, dst)
.fail(function () {
logger.info('dep-link', d, {
name: json.name + '#' + d
});
return createLink(src, dst, {relative: true});
});
}));
}
});
});
return Q.all(promises);
};

View File

@@ -2,7 +2,7 @@ var path = require('path');
var fs = require('graceful-fs');
var zlib = require('zlib');
var DecompressZip = require('decompress-zip');
var tar = require('tar');
var tar = require('tar-fs');
var Q = require('q');
var mout = require('mout');
var junk = require('junk');
@@ -23,6 +23,7 @@ extractors = {
'.gz': extractGz,
'application/zip': extractZip,
'application/x-zip': extractZip,
'application/x-zip-compressed': extractZip,
'application/x-tar': extractTar,
'application/x-tgz': extractTarGz,
'application/x-gzip': extractGz
@@ -50,13 +51,11 @@ function extractTar(archive, dst) {
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
.pipe(tar.extract(dst, {
ignore: isSymlink // Filter symlink files
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('finish', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -68,13 +67,11 @@ function extractTarGz(archive, dst) {
.on('error', deferred.reject)
.pipe(zlib.createGunzip())
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
.pipe(tar.extract(dst, {
ignore: isSymlink // Filter symlink files
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('finish', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -93,6 +90,10 @@ function extractGz(archive, dst) {
return deferred.promise;
}
function isSymlink(entry) {
return entry.type === 'SymbolicLink';
}
function filterSymlinks(entry) {
return entry.type !== 'SymbolicLink';
}

View File

@@ -1,6 +1,6 @@
{
"name": "bower",
"version": "1.3.5",
"version": "1.3.12",
"description": "The browser package manager",
"author": "Twitter",
"licenses": [
@@ -17,61 +17,62 @@
},
"dependencies": {
"abbrev": "~1.0.4",
"archy": "~0.0.2",
"archy": "0.0.2",
"bower-config": "~0.5.2",
"bower-endpoint-parser": "~0.2.2",
"bower-json": "~0.4.0",
"bower-logger": "~0.2.2",
"bower-registry-client": "~0.2.0",
"cardinal": "~0.4.0",
"chalk": "~0.4.0",
"chmodr": "~0.1.0",
"decompress-zip": "~0.0.6",
"fstream": "~0.1.22",
"fstream-ignore": "~0.0.6",
"cardinal": "0.4.0",
"chalk": "0.5.0",
"chmodr": "0.1.0",
"decompress-zip": "0.0.8",
"fstream": "~1.0.2",
"fstream-ignore": "~1.0.1",
"glob": "~4.0.2",
"graceful-fs": "~3.0.1",
"handlebars": "~1.3.0",
"inquirer": "~0.5.1",
"insight": "~0.3.0",
"is-root": "~0.1.0",
"junk": "~0.3.0",
"lockfile": "~0.4.2",
"handlebars": "~2.0.0",
"inquirer": "0.7.1",
"insight": "0.4.3",
"is-root": "~1.0.0",
"junk": "~1.0.0",
"lockfile": "~1.0.0",
"lru-cache": "~2.5.0",
"mkdirp": "~0.5.0",
"mout": "~0.9.1",
"mkdirp": "0.5.0",
"mout": "~0.9.0",
"nopt": "~3.0.0",
"opn": "~0.1.1",
"osenv": "~0.1.0",
"p-throttler": "~0.0.1",
"promptly": "~0.2.0",
"opn": "~1.0.0",
"osenv": "0.1.0",
"p-throttler": "0.1.0",
"promptly": "0.2.0",
"q": "~1.0.1",
"request": "~2.36.0",
"request-progress": "~0.3.0",
"retry": "~0.6.0",
"request": "~2.42.0",
"request-progress": "0.3.0",
"retry": "0.6.0",
"rimraf": "~2.2.0",
"semver": "~2.3.0",
"shell-quote": "~1.4.1",
"stringify-object": "~0.2.0",
"tar": "~0.1.17",
"tmp": "~0.0.23",
"update-notifier": "~0.1.8",
"stringify-object": "~1.0.0",
"tar-fs": "0.5.2",
"tmp": "0.0.23",
"update-notifier": "0.2.0",
"which": "~1.0.5"
},
"devDependencies": {
"coveralls": "~2.11.0",
"expect.js": "~0.3.1",
"grunt": "~0.4.4",
"grunt-cli": "^0.1.13",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-exec": "~0.4.2",
"grunt-simple-mocha": "~0.4.0",
"istanbul": "~0.2.4",
"load-grunt-tasks": "~0.4.0",
"mocha": "~1.20.1",
"nock": "~0.34.1",
"proxyquire": "~1.0.1",
"coveralls": "~2.10.0",
"node-uuid": "~1.4.1"
"istanbul": "~0.3.2",
"load-grunt-tasks": "~0.6.0",
"mocha": "~1.21.4",
"nock": "~0.46.0",
"node-uuid": "~1.4.1",
"proxyquire": "~1.0.1"
},
"scripts": {
"test": "grunt test"

View File

@@ -13,12 +13,12 @@
{
"shorthand": "-S",
"flag": "--save",
"description": "Save installed packages into the project's bower.json dependencies"
"description": "Remove uninstalled packages from the project's bower.json dependencies"
},
{
"shorthand": "-D",
"flag": "--save-dev",
"description": "Save installed packages into the project's bower.json devDependencies"
"description": "Remove uninstalled packages from the project's bower.json devDependencies"
}
]
}

View File

@@ -10,7 +10,7 @@
"init": "Interactively create a bower.json file",
"install": "Install a package locally",
"link": "Symlink a package folder",
"list": "List local packages",
"list": "List local packages - and possible updates",
"lookup": "Look up a package URL by name",
"prune": "Removes local extraneous packages",
"register": "Register a package",

View File

@@ -5,3 +5,4 @@
Package not found.
{{/if}}
{{/condense}}

View File

@@ -5,5 +5,6 @@
{{#cyan}}{{{name}}}{{/cyan}} {{{url}}}
{{/.}}
{{/condense}}
{{else}}No results.
{{/if}}
{{/if}}

View File

@@ -1,5 +0,0 @@
{{#red}}-----------------------------------------{{/red}}
Update available: {{#yellow}}{{latest}}{{/yellow}} {{#cyan}}(current: {{current}}){{/cyan}}
Run {{#yellow}}npm update -g {{name}}{{/yellow}} to update
{{#red}}-----------------------------------------{{/red}}

View File

@@ -4,7 +4,7 @@ var Logger = require('bower-logger');
var Resolver = require('../../../lib/core/resolvers/Resolver');
var defaultConfig = require('../../../lib/config');
var resolver = new Resolver({ source: 'foo' }, defaultConfig, new Logger());
var resolver = new Resolver({ source: 'foo' }, defaultConfig(), new Logger());
resolver._createTempDir()
.then(function (dir) {
// Need to write something to prevent tmp to automatically

View File

@@ -4,7 +4,7 @@ var Logger = require('bower-logger');
var Resolver = require('../../../lib/core/resolvers/Resolver');
var defaultConfig = require('../../../lib/config');
var resolver = new Resolver({ source: 'foo' }, defaultConfig, new Logger());
var resolver = new Resolver({ source: 'foo' }, defaultConfig(), new Logger());
resolver._createTempDir()
.then(function (dir) {
// Need to write something to prevent tmp to automatically

View File

@@ -2,4 +2,5 @@ describe('integration tests', function () {
require('./init');
require('./install');
require('./uninstall');
require('./update');
});

View File

@@ -7,15 +7,17 @@ var bower = helpers.require('lib/index');
describe('bower init', function () {
var tempDir = helpers.createTmpDir();
var bowerJsonPath = path.join(tempDir, 'bower.json');
var tempDir = new helpers.TempDir();
var bowerJsonPath = path.join(tempDir.path, 'bower.json');
var config = {
cwd: tempDir,
cwd: tempDir.path,
interactive: true
};
it('generates bower.json file', function () {
tempDir.prepare();
var logger = bower.commands.init(config);
return helpers.expectEvent(logger, 'prompt')

View File

@@ -1,47 +1,205 @@
var path = require('path');
var expect = require('expect.js');
var fs = require('fs');
var object = require('mout').object;
var helpers = require('../helpers');
var bower = helpers.require('lib/index');
var commands = helpers.require('lib/index').commands;
describe('bower install', function () {
var tempDir = helpers.createTmpDir();
var bowerJsonPath = path.join(tempDir, 'bower_components', 'underscore', 'bower.json');
var tempDir = new helpers.TempDir();
function bowerJson() {
return JSON.parse(fs.readFileSync(bowerJsonPath));
}
var package = new helpers.TempDir({
'bower.json': {
name: 'package'
}
}).prepare();
var config = {
cwd: tempDir,
interactive: true
var gitPackage = new helpers.TempDir();
var installLogger = function(packages, options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
return commands.install(packages, options, config);
};
it('does nothing if no bower.json is present', function () {
var logger = bower.commands.install([], undefined, config);
var install = function(packages, options, config) {
var logger = installLogger(packages, options, config);
return helpers.expectEvent(logger, 'end');
});
};
it.skip('installs a package', function () {
this.timeout(10000);
var logger = bower.commands.install(['underscore'], undefined, config);
it('writes to bower.json if --save flag is used', function () {
package.prepare();
return helpers.expectEvent(logger, 'end')
.then(function () {
expect(bowerJson()).to.have.key('name');
tempDir.prepare({
'bower.json': {
name: 'test'
}
});
return install([package.path], { save: true }).then(function() {
expect(tempDir.read('bower.json')).to.contain('dependencies');
});
});
it.skip('installs package with --save flag', function () {
var logger = bower.commands.install(['underscore'], {save: true}, config);
it('reads .bowerrc from cwd', function () {
package.prepare({ foo: 'bar' });
return helpers.expectEvent(logger, 'end')
.then(function () {
expect(bowerJson()).to.have.key('name');
tempDir.prepare({
'.bowerrc': { directory: 'assets' },
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
}
});
return install().then(function() {
expect(tempDir.read('assets/package/foo')).to.be('bar');
});
});
it('runs preinstall hook', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
preinstall: 'bash -c "echo -n % > preinstall.txt"'
}
}
});
return install().then(function() {
expect(tempDir.read('preinstall.txt')).to.be('package');
});
});
it('runs preinstall hook', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "echo -n % > postinstall.txt"'
}
}
});
return install().then(function() {
expect(tempDir.read('postinstall.txt')).to.be('package');
});
});
// To be discussed, but that's the implementation now
it('does not run hooks if nothing is installed', function () {
tempDir.prepare({
'bower.json': {
name: 'test'
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "echo -n % > hooks.txt"',
preinstall: 'bash -c "echo -n % > hooks.txt"'
}
}
});
return install().then(function() {
expect(tempDir.exists('hooks.txt')).to.be(false);
});
});
it('runs postinstall after bower.json is written', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test'
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "cat bower.json > hook.txt"',
}
}
});
return install([package.path], { save: true }).then(function() {
expect(tempDir.read('hook.txt')).to.contain('dependencies');
});
});
it('display the output of hook scripts', function (next) {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "echo foobar"'
}
}
});
var lastAction = null;
installLogger().intercept(function (log) {
if (log.level === 'action') {
lastAction = log;
}
}).on('end', function () {
expect(lastAction.message).to.be('foobar');
next();
});
});
it('works for git repositories', function () {
return gitPackage.prepareGit({
'1.0.0': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.0'
},
'1.0.1': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.1'
}
}).then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return install().then(function() {
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
});
});
});
});

View File

@@ -7,7 +7,7 @@ var bower = helpers.require('lib/index');
describe('bower uninstall', function () {
var tempDir = helpers.createTmpDir({
var tempDir = new helpers.TempDir({
'bower.json': {
name: 'hello-world',
dependencies: {
@@ -16,14 +16,18 @@ describe('bower uninstall', function () {
}
});
var bowerJsonPath = path.join(tempDir, 'bower.json');
beforeEach(function() {
tempDir.prepare();
});
var bowerJsonPath = path.join(tempDir.path, 'bower.json');
function bowerJson() {
return JSON.parse(fs.readFileSync(bowerJsonPath));
}
var config = {
cwd: tempDir,
cwd: tempDir.path,
interactive: true
};

239
test/commands/update.js Normal file
View File

@@ -0,0 +1,239 @@
var expect = require('expect.js');
var object = require('mout').object;
var helpers = require('../helpers');
var commands = helpers.require('lib/index').commands;
describe('bower update', function () {
var tempDir = new helpers.TempDir();
var gitPackage = new helpers.TempDir();
gitPackage.prepareGit({
'1.0.0': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.0'
},
'1.0.1': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.1'
}
});
var package = new helpers.TempDir({
'bower.json': {
name: 'package'
}
}).prepare();
var updateLogger = function(packages, options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
return commands.update(packages, options, config);
};
var update = function(packages, options, config) {
var logger = updateLogger(packages, options, config);
return helpers.expectEvent(logger, 'end');
};
var install = function(packages, options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
var logger = commands.install(
packages, options, config
);
return helpers.expectEvent(logger, 'end');
};
it('install missing packages', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
}
});
return update().then(function() {
expect(tempDir.exists('bower_components/package/bower.json')).to.equal(true);
expect(tempDir.read('bower_components/package/bower.json')).to.contain('"name": "package"');
});
});
it('runs preinstall hook when installing missing package', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
preinstall: 'bash -c "echo -n % > preinstall.txt"'
}
}
});
return update().then(function() {
expect(tempDir.read('preinstall.txt')).to.be('package');
});
});
it('runs postinstall hook when installing missing package', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "echo -n % > postinstall.txt"'
}
}
});
return update().then(function() {
expect(tempDir.read('postinstall.txt')).to.be('package');
});
});
it('doesn\'t runs postinstall when no package is update', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "echo -n % > postinstall.txt"'
}
}
});
return install().then(function() {
tempDir.prepare();
return update().then(function() {
expect(tempDir.exists('postinstall.txt')).to.be(false);
});
});
});
it('updates a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return install().then(function() {
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
}
});
return update().then(function() {
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.1');
});
});
});
it('runs preinstall hook when updating a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
},
'.bowerrc': {
scripts: {
preinstall: 'bash -c "echo -n % > preinstall.txt"'
}
}
});
return install().then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
}
});
expect(tempDir.exists('preinstall.txt')).to.be(false);
return update().then(function() {
expect(tempDir.read('preinstall.txt')).to.be('package');
});
});
});
it('runs postinstall hook when updating a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
},
'.bowerrc': {
scripts: {
postinstall: 'bash -c "echo -n % > postinstall.txt"'
}
}
});
return install().then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
}
});
expect(tempDir.exists('postinstall.txt')).to.be(false);
return update().then(function() {
expect(tempDir.read('postinstall.txt')).to.be('package');
});
});
});
});

View File

@@ -1,6 +1,5 @@
var expect = require('expect.js');
var path = require('path');
var mout = require('mout');
var rimraf = require('rimraf');
var Logger = require('bower-logger');
var Manager = require('../../lib/core/Manager');
@@ -23,7 +22,7 @@ describe('Manager', function () {
beforeEach(function (next) {
var logger = new Logger();
var config = mout.object.deepMixIn({}, defaultConfig, {
var config = defaultConfig({
storage: {
packages: packagesCacheDir,
registry: registryCacheDir

View File

@@ -35,7 +35,7 @@ describe('PackageRepository', function () {
var logger = new Logger();
// Config
config = mout.object.deepMixIn({}, defaultConfig, {
config = defaultConfig({
storage: {
packages: packagesCacheDir,
registry: registryCacheDir

View File

@@ -23,7 +23,7 @@ describe('ResolveCache', function () {
rimraf.sync(cacheDir);
// Instantiate resolver cache
resolveCache = new ResolveCache(mout.object.deepMixIn(defaultConfig, {
resolveCache = new ResolveCache(defaultConfig({
storage: {
packages: cacheDir
}
@@ -55,7 +55,7 @@ describe('ResolveCache', function () {
});
function initialize(cacheDir) {
return new ResolveCache(mout.object.deepMixIn(defaultConfig, {
return new ResolveCache(defaultConfig({
storage: {
packages: cacheDir
}

View File

@@ -14,9 +14,9 @@ var defaultConfig = require('../../lib/config');
describe('resolverFactory', function () {
var tempSource;
var logger = new Logger();
var registryClient = new RegistryClient(mout.object.fillIn({
cache: defaultConfig._registry
}, defaultConfig));
var registryClient = new RegistryClient(defaultConfig({
cache: defaultConfig()._registry
}));
afterEach(function (next) {
logger.removeAllListeners();
@@ -34,7 +34,7 @@ describe('resolverFactory', function () {
});
function callFactory(decEndpoint, config) {
return resolverFactory(decEndpoint, config || defaultConfig, logger, registryClient);
return resolverFactory(decEndpoint, defaultConfig(config), logger, registryClient);
}
it('should recognize git remote endpoints correctly', function (next) {
@@ -584,14 +584,12 @@ describe('resolverFactory', function () {
it('should use the configured shorthand resolver', function (next) {
callFactory({ source: 'bower/bower' })
.then(function (resolver) {
var config;
var config = {
shorthandResolver: 'git://bower.io/{{owner}}/{{package}}/{{shorthand}}'
};
expect(resolver.getSource()).to.equal('git://github.com/bower/bower.git');
config = mout.object.fillIn({
shorthandResolver: 'git://bower.io/{{owner}}/{{package}}/{{shorthand}}'
}, defaultConfig);
return callFactory({ source: 'IndigoUnited/promptly' }, config);
})
.then(function (resolver) {
@@ -616,7 +614,7 @@ describe('resolverFactory', function () {
it('should error out if there\'s no suitable resolver for a given source', function (next) {
resolverFactory({ source: 'some-package-that-will-never-exist' }, defaultConfig, logger)
resolverFactory({ source: 'some-package-that-will-never-exist' }, defaultConfig(), logger)
.then(function () {
throw new Error('Should have failed');
}, function (err) {

View File

@@ -0,0 +1,171 @@
var path = require('path');
var nock = require('nock');
var fs = require('graceful-fs');
var expect = require('expect.js');
var Logger = require('bower-logger');
var GitRemoteResolver = require('../../../lib/core/resolvers/GitRemoteResolver');
var BitBucketResolver = require('../../../lib/core/resolvers/BitBucketResolver');
var defaultConfig = require('../../../lib/config');
describe('BitBucket', function () {
var logger;
var testPackage = path.resolve(__dirname, '../../assets/package-a');
before(function () {
logger = new Logger();
});
afterEach(function () {
// Clean nocks
nock.cleanAll();
logger.removeAllListeners();
});
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new BitBucketResolver(decEndpoint, defaultConfig({ strictSsl: false }), logger);
}
describe('.constructor', function () {
it.skip('should throw an error on invalid BitBucket URLs');
it('should ensure .git in the source', function () {
var resolver;
resolver = create('git://bitbucket.org/drublic/bower-test');
expect(resolver.getSource()).to.equal('git://bitbucket.org/drublic/bower-test.git');
resolver = create('git://bitbucket.org/drublic/bower-test.git');
expect(resolver.getSource()).to.equal('git://bitbucket.org/drublic/bower-test.git');
resolver = create('git://bitbucket.org/drublic/bower-test.git/');
expect(resolver.getSource()).to.equal('git://bitbucket.org/drublic/bower-test.git');
});
});
describe('.resolve', function () {
this.timeout(10000); // Give some time to execute
it('should download and extract the .tar.gz archive from bitbucket.org', function (next) {
var resolver;
nock('https://bitbucket.org')
.get('/drublic/bower-test/get/0.1.0.tar.gz')
.replyWithFile(200, path.resolve(__dirname, '../../assets/package-tar.tar.gz'));
resolver = create({ source: 'https://bitbucket.org/drublic/bower-test.git', target: '0.1.0' });
resolver.resolve()
.then(function (dir) {
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, 'bar'))).to.be(true);
expect(fs.existsSync(path.join(dir, '.bower.json'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'README.md'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'package-tar.tar.gz'))).to.be(false);
expect(fs.existsSync(path.join(dir, 'package-tar.tar'))).to.be(false);
next();
})
.done();
});
it('should retry using the GitRemoteResolver mechanism if download failed', function (next) {
var resolver;
var retried;
nock('https://bitbucket.org')
.get('/drublic/bower-test/get/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://bitbucket.org/drublic/bower-test.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://bitbucket.org')
.get('/drublic/bower-test/get/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://bitbucket.org/drublic/bower-test.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: 'git://bitbucket.org/drublic/bower-test.git', target: '2af02ac6ddeaac1c2f4bead8d6287ce54269c039' });
var originalCheckout = GitRemoteResolver.prototype._checkout;
var called;
GitRemoteResolver.prototype._checkout = function () {
called = true;
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);
expect(fs.existsSync(path.join(dir, 'bar'))).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
expect(called).to.be(true);
next();
})
.fin(function () {
GitRemoteResolver.prototype._checkout = originalCheckout;
})
.done();
});
it.skip('it should error out if the status code is not within 200-299');
it.skip('should report progress if it takes too long to download');
});
describe('._savePkgMeta', function () {
it.skip('should guess the homepage if not already set');
});
});

View File

@@ -35,12 +35,12 @@ describe('FsResolver', function () {
}
});
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new FsResolver(decEndpoint, config || defaultConfig, logger);
return new FsResolver(decEndpoint, defaultConfig(), logger);
}
describe('.constructor', function () {

View File

@@ -33,12 +33,12 @@ describe('GitFsResolver', function () {
GitFsResolver.clearRuntimeCache();
}
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new GitFsResolver(decEndpoint, config || defaultConfig, logger);
return new GitFsResolver(decEndpoint, defaultConfig(), logger);
}
describe('.constructor', function () {

View File

@@ -15,27 +15,19 @@ describe('GitHub', function () {
logger = new Logger();
});
beforeEach(function () {
// Turn off strict ssl because it gives problems with nock
defaultConfig.strictSsl = false;
});
afterEach(function () {
// Clean nocks
nock.cleanAll();
logger.removeAllListeners();
// Enable strict ssl back again
defaultConfig.strictSsl = true;
});
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new GitHubResolver(decEndpoint, config || defaultConfig, logger);
return new GitHubResolver(decEndpoint, defaultConfig({ strictSsl: false }), logger);
}
describe('.constructor', function () {

View File

@@ -21,12 +21,12 @@ describe('GitRemoteResolver', function () {
GitRemoteResolver.clearRuntimeCache();
}
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new GitRemoteResolver(decEndpoint, config || defaultConfig, logger);
return new GitRemoteResolver(decEndpoint, defaultConfig(), logger);
}
describe('.constructor', function () {

View File

@@ -30,12 +30,12 @@ describe('GitResolver', function () {
GitResolver.clearRuntimeCache();
}
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new GitResolver(decEndpoint, config || defaultConfig, logger);
return new GitResolver(decEndpoint, defaultConfig(), logger);
}
describe('misc', function () {
@@ -334,7 +334,7 @@ describe('GitResolver', function () {
}.bind(this));
};
resolver = new DummyResolver({ source: 'foo', target: 'master' }, defaultConfig, logger);
resolver = new DummyResolver({ source: 'foo', target: 'master' }, defaultConfig(), logger);
resolver.resolve()
.then(function () {

View File

@@ -17,6 +17,7 @@ describe('Resolver', function () {
var testPackage = path.resolve(__dirname, '../../assets/package-a');
var logger;
var dirMode0777;
var config = defaultConfig();
before(function () {
var stat;
@@ -33,12 +34,12 @@ describe('Resolver', function () {
logger.removeAllListeners();
});
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new Resolver(decEndpoint, config || defaultConfig, logger);
return new Resolver(decEndpoint, config, logger);
}
describe('.getSource', function () {
@@ -333,7 +334,7 @@ describe('Resolver', function () {
}.bind(this));
};
resolver = new DummyResolver({ source: 'foo'}, defaultConfig, logger);
resolver = new DummyResolver({ source: 'foo'}, config, logger);
resolver.resolve()
.then(function () {
@@ -461,7 +462,7 @@ describe('Resolver', function () {
osTempDir = path.resolve(tmp.tmpdir);
expect(dir.indexOf(osTempDir)).to.be(0);
expect(dir.indexOf(defaultConfig.tmp)).to.be(0);
expect(dir.indexOf(config.tmp)).to.be(0);
expect(path.basename(dirname)).to.equal('bower');
expect(path.dirname(path.dirname(dirname))).to.equal(osTempDir);
@@ -487,13 +488,13 @@ describe('Resolver', function () {
it('should remove the folder after execution', function (next) {
this.timeout(15000); // Give some time to execute
rimraf(defaultConfig.tmp, function (err) {
rimraf(config.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(defaultConfig.tmp)).to.be(true);
expect(fs.readdirSync(defaultConfig.tmp)).to.eql([]);
expect(fs.existsSync(config.tmp)).to.be(true);
expect(fs.readdirSync(config.tmp)).to.eql([]);
next();
}, function (err) {
next(new Error(err.details));
@@ -503,15 +504,15 @@ describe('Resolver', function () {
});
it('should remove the folder on an uncaught exception', function (next) {
rimraf(defaultConfig.tmp, function (err) {
rimraf(config.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(defaultConfig.tmp)).to.be(true);
expect(fs.readdirSync(defaultConfig.tmp)).to.eql([]);
expect(fs.existsSync(config.tmp)).to.be(true);
expect(fs.readdirSync(config.tmp)).to.eql([]);
next();
})
.done();

View File

@@ -30,12 +30,12 @@ describe('SvnResolver', function () {
SvnResolver.clearRuntimeCache();
}
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new SvnResolver(decEndpoint, config || defaultConfig, logger);
return new SvnResolver(decEndpoint, defaultConfig(), logger);
}
describe('misc', function () {
@@ -278,7 +278,7 @@ describe('SvnResolver', function () {
}.bind(this));
};
resolver = new DummyResolver({ source: 'foo', target: '1.0.0' }, defaultConfig, logger);
resolver = new DummyResolver({ source: 'foo', target: '1.0.0' }, defaultConfig(), logger);
resolver.resolve()
.then(function () {

View File

@@ -1,7 +1,6 @@
var expect = require('expect.js');
var path = require('path');
var fs = require('graceful-fs');
var path = require('path');
var nock = require('nock');
var Q = require('q');
var rimraf = require('rimraf');
@@ -31,12 +30,12 @@ describe('UrlResolver', function () {
nock.cleanAll();
});
function create(decEndpoint, config) {
function create(decEndpoint) {
if (typeof decEndpoint === 'string') {
decEndpoint = { source: decEndpoint };
}
return new UrlResolver(decEndpoint, config || defaultConfig, logger);
return new UrlResolver(decEndpoint, defaultConfig(), logger);
}
describe('.constructor', function () {

View File

@@ -5,41 +5,142 @@ var rimraf = require('rimraf');
var uuid = require('node-uuid');
var object = require('mout/object');
var fs = require('fs');
var glob = require('glob');
var os = require('os');
var cmd = require('../lib/util/cmd');
var config = require('../lib/config');
// Those are needed for Travis or not configured git environment
var env = {
'GIT_AUTHOR_DATE': 'Sun Apr 7 22:13:13 2013 +0000',
'GIT_AUTHOR_NAME': 'André Cruz',
'GIT_AUTHOR_EMAIL': 'amdfcruz@gmail.com',
'GIT_COMMITTER_DATE': 'Sun Apr 7 22:13:13 2013 +0000',
'GIT_COMMITTER_NAME': 'André Cruz',
'GIT_COMMITTER_EMAIL': 'amdfcruz@gmail.com'
};
// Preserve the original environment
object.mixIn(env, process.env);
var tmpLocation = path.join(
os.tmpdir ? os.tmpdir() : os.tmpDir(),
'bower-tests',
uuid.v4().slice(0, 8)
);
exports.require = function (name) {
return require(path.join(__dirname, '../', name));
};
exports.createTmpDir = function (files) {
var tempDir = path.join(__dirname, 'tmp/' + uuid.v4());
// We need to reset cache because tests are reusing temp directories
beforeEach(function () {
config.reset();
});
beforeEach(function () {
mkdirp.sync(tempDir);
after(function () {
rimraf.sync(tmpLocation);
});
exports.TempDir = (function() {
function TempDir (defaults) {
this.path = path.join(tmpLocation, uuid.v4());
this.defaults = defaults;
}
TempDir.prototype.create = function (files) {
var that = this;
files = object.merge(files || {}, this.defaults);
if (files) {
object.forOwn(files, function (contents, filepath) {
if (typeof contents === 'object') {
contents = JSON.stringify(contents, null, ' ');
contents = JSON.stringify(contents, null, ' ') + '\n';
}
var fullPath = path.join(tempDir, filepath);
var fullPath = path.join(that.path, filepath);
mkdirp.sync(path.dirname(fullPath));
fs.writeFileSync(fullPath, contents);
});
}
});
afterEach(function () {
rimraf.sync(tempDir);
});
return this;
};
return tempDir;
};
TempDir.prototype.prepare = function (files) {
rimraf.sync(this.path);
mkdirp.sync(this.path);
this.create(files);
return this;
};
// TODO: Rewrite to synchronous form
TempDir.prototype.prepareGit = function (revisions) {
var that = this;
revisions = object.merge(revisions || {}, this.defaults);
rimraf.sync(that.path);
mkdirp.sync(that.path);
var promise = new Q();
object.forOwn(revisions, function (files, tag) {
promise = promise.then(function () {
return that.git('init');
}).then(function () {
that.glob('./!(.git)').map(function (removePath) {
var fullPath = path.join(that.path, removePath);
rimraf.sync(fullPath);
});
that.create(files);
}).then(function () {
return that.git('add', '-A');
}).then(function () {
return that.git('commit', '-m"commit"');
}).then(function () {
return that.git('tag', tag);
});
});
return promise;
};
TempDir.prototype.glob = function (pattern) {
return glob.sync(pattern, {
cwd: this.path,
dot: true
});
};
TempDir.prototype.read = function (name) {
return fs.readFileSync(path.join(this.path, name), 'utf8');
};
TempDir.prototype.git = function () {
var args = Array.prototype.slice.call(arguments);
return cmd('git', args, { cwd: this.path, env: env });
};
TempDir.prototype.exists = function (name) {
return fs.existsSync(path.join(this.path, name));
};
return TempDir;
})();
exports.expectEvent = function (emitter, eventName) {
var deferred = Q.defer();
emitter.once(eventName, function () {
deferred.resolve(arguments);
});
return deferred.promise;
};

View File

@@ -12,6 +12,7 @@ require('./core/resolvers/gitResolver');
require('./core/resolvers/gitFsResolver');
require('./core/resolvers/gitRemoteResolver');
require('./core/resolvers/gitHubResolver');
require('./core/resolvers/bitBucketResolver');
require('./core/resolvers/svnResolver');
require('./core/resolverFactory');
require('./core/resolveCache');

124
test/util/analytics.js Normal file
View File

@@ -0,0 +1,124 @@
var expect = require('expect.js');
var proxyquire = require('proxyquire');
var object = require('mout').object;
describe('analytics', function () {
var mockAnalytics = function(stubs, promptResponse) {
return proxyquire('../../lib/util/analytics', {
insight: function () {
return object.merge(stubs || {}, {
askPermission: function (message, callback) {
callback(undefined, promptResponse);
}
});
}
});
};
describe('#setup', function () {
it('leaves analytics enabled if provided', function () {
return mockAnalytics()
.setup({ analytics: true })
.then(function (enabled) {
expect(enabled).to.be(true);
});
});
it('leaves analytics disabled if provided', function () {
return mockAnalytics()
.setup({ analytics: false })
.then(function (enabled) {
expect(enabled).to.be(false);
});
});
it('disables analytics for non-interactive mode', function () {
return mockAnalytics()
.setup({ interactive: false })
.then(function (enabled) {
expect(enabled).to.be(false);
});
});
it('disables if insight.optOut is true and interactive', function () {
return mockAnalytics({ optOut: true })
.setup({ interactive: true })
.then(function (enabled) {
expect(enabled).to.be(false);
});
});
it('enables if insight.optOut is false and interactive', function () {
return mockAnalytics({ optOut: false })
.setup({ interactive: true })
.then(function (enabled) {
expect(enabled).to.be(true);
});
});
it('disables if insight.optOut is false and non-interactive', function () {
return mockAnalytics({ optOut: false })
.setup({ interactive: false })
.then(function (enabled) {
expect(enabled).to.be(false);
});
});
it('enables if interactive insights return true from prompt', function () {
return mockAnalytics({ optOut: undefined }, true)
.setup({ interactive: true })
.then(function (enabled) {
expect(enabled).to.be(true);
});
});
it('disables if interactive insights return false from prompt', function () {
return mockAnalytics({ optOut: undefined }, false)
.setup({ interactive: true })
.then(function (enabled) {
expect(enabled).to.be(false);
});
});
});
describe('Tracker', function (next) {
it('tracks if analytics = true', function(next) {
var analytics = mockAnalytics({
track: function (arg) {
expect(arg).to.be('foo');
next();
}
});
new analytics.Tracker({ analytics: true }).track('foo');
});
it('does not track if analytics = false', function () {
var analytics = mockAnalytics({
track: function (arg) {
throw new Error();
}
});
expect(function () {
new analytics.Tracker({ analytics: false }).track('foo');
}).to.not.throwError();
});
it('tracks if analytics = undefined and setup returns true', function(next) {
var analytics = mockAnalytics({
track: function (arg) {
expect(arg).to.be('foo');
next();
}
});
analytics
.setup({ analytics: true })
.then(function () {
new analytics.Tracker({}).track('foo');
});
});
});
});

View File

@@ -1,3 +1,4 @@
describe('util', function () {
require('./removeIgnores');
require('./analytics');
});

View File

@@ -7,13 +7,15 @@ var removeIgnores = require('../../lib/util/removeIgnores');
describe('removeIgnores', function () {
var tempDir = helpers.createTmpDir({
var tempDir = new helpers.TempDir({
'bower.json': {},
'index.js': 'Not to ignore',
'node_modules/underscore/index.js': 'Should be ignored'
});
var ignoreTest = function(dir, meta, leftovers) {
tempDir.prepare();
var deferred = Q.defer();
removeIgnores(dir, meta).then(function() {
@@ -27,42 +29,42 @@ describe('removeIgnores', function () {
};
it('removes all files in directory', function () {
return ignoreTest(tempDir,
return ignoreTest(tempDir.path,
{ ignore: [ 'node_modules/**/*' ] },
[ 'bower.json', 'index.js' ]
);
});
it('removes whole directory', function () {
return ignoreTest(tempDir,
return ignoreTest(tempDir.path,
{ ignore: [ 'node_modules/' ] },
[ 'bower.json', 'index.js' ]
);
});
it('removes whole directory (no ending slash)', function () {
return ignoreTest(tempDir,
return ignoreTest(tempDir.path,
{ ignore: [ 'node_modules' ] },
[ 'bower.json', 'index.js' ]
);
});
it('removes all but one file', function() {
return ignoreTest(tempDir,
return ignoreTest(tempDir.path,
{ ignore: [ '**/*', '!bower.json' ] },
[ 'bower.json' ]
);
});
it('refuses to ignore bower.json', function() {
return ignoreTest(tempDir,
return ignoreTest(tempDir.path,
{ ignore: [ '**/*', '!index.js' ] },
[ 'bower.json', 'index.js' ]
);
});
it('removes all but one file deep down the tree', function() {
return ignoreTest(tempDir,
return ignoreTest(tempDir.path,
{ ignore: [ '**/*', '!node_modules/underscore/index.js' ] },
[
'bower.json',